<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="font-awesome@*" data-semver="4.5.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.css" />
<link data-require="bootstrap-css@3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
<link data-require="animate.css@*" data-semver="3.2.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.0/animate.min.css" />
<link data-require="ngToast@*" data-semver="1.5.0" rel="stylesheet" href="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast.css" />
<link data-require="ngToast@*" data-semver="1.5.0" rel="stylesheet" href="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast-animations.css" />
<script data-require="jquery@*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script data-require="angular.js@1.4.9" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular.js"></script>
<script data-require="ui-router@*" data-semver="0.2.18" src="//cdn.rawgit.com/angular-ui/ui-router/0.2.18/release/angular-ui-router.js"></script>
<script data-require="underscore.js@*" data-semver="1.8.3" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script data-require="ui-bootstrap@*" data-semver="1.3.2" src="https://cdn.rawgit.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-tpls-1.3.2.js"></script>
<script data-require="angular-sanitize@*" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular-sanitize.js"></script>
<script data-require="angular-animate@*" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular-animate.js"></script>
<script data-require="ngToast@*" data-semver="1.5.0" src="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast.js"></script>
<script src="angular-clipboard.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
<script src="MainController.js"></script>
</head>
<body ng-controller="mainController as vm">
<toast></toast>
<div ui-view=""></div>
<div class="container-fluid">
<form>
<div class="row">
<div class="col-md-12">
<h4>Kinobody Calories / Macros</h4>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<div class="form-group has-warning">
<div class="input-group input-select">
<span class="input-group-addon input-label">Kinobody Program</span>
<select class="form-control" ng-options="(option.name) for option in vm.programOptions" ng-model="vm.selectedProgram" title="{{vm.selectedProgram.description}}" ng-change="vm.onProgramChange()"></select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<div class="form-group has-warning">
<div class="input-group">
<span class="input-group-addon input-label">Weight in {{vm.inputUnitsWeight}}</span>
<input type="text" class="form-control" ng-model="vm.inputWeight" ng-model-options="{debounce: 500}" ng-change="vm.onWeightChange()" />
<span class="input-group-btn">
<button type="button" class="btn btn-toggle" ng-click="vm.toggleInputUnitsWeight()" tabindex="-1" title="Click to toggle kg/lbs">
{{vm.inputUnitsWeight}} <i class="fa fa-toggle-on" ng-class="{'fa-flip-horizontal': vm.inputUnitsWeight !== 'kg'}"></i>
</button>
</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<fieldset disabled="">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon input-label">KB maintenance (lbs x 15)</span>
<input type="text" ng-model="vm.kinobodyEstimateMaintenanceCalories_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
<span class="input-group-addon">kcal</span>
</div>
</div>
</fieldset>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<fieldset disabled="">
<div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
<div class="input-group">
<span class="input-group-addon input-label">KB daily calories (lbs x {{vm.selectedProgram.lbsFactor}}{{vm.programOffset}})</span>
<input type="text" ng-model="vm.kinobodyEstimateDailyCalories_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
<span class="input-group-addon">kcal</span>
</div>
</div>
</fieldset>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h4>Your macros</h4>
</div>
</div>
<fieldset disabled="">
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<div class="row">
<div class="col-xs-8">
<div class="form-group has-success">
<div class="input-group">
<span class="input-group-addon input-macros">Protein</span>
<input type="text" ng-model="vm.macrosProtein" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
<span class="input-group-addon">g</span>
</div>
</div>
</div>
<div class="col-xs-4">
<div class="form-group has-success">
<div class="input-group">
<input type="text" ng-model="vm.percentageProtein" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
<span class="input-group-addon">%</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<div class="row">
<div class="col-xs-8">
<div class="form-group has-success">
<div class="input-group">
<span class="input-group-addon input-macros">Fats</span>
<input type="text" ng-model="vm.macrosFats" id="macrosFat" class="form-control text-right" placeholder="" readonly="" />
<span class="input-group-addon">g</span>
</div>
</div>
</div>
<div class="col-xs-4">
<div class="form-group has-success">
<div class="input-group">
<input type="text" ng-model="vm.percentageFats" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
<span class="input-group-addon">%</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<div class="row">
<div class="col-xs-8">
<div class="form-group has-success">
<div class="input-group">
<span class="input-group-addon input-macros">Carbs</span>
<input type="text" ng-model="vm.macrosCarbs" id="macrosCarb" class="form-control text-right" placeholder="" readonly="" />
<span class="input-group-addon">g</span>
</div>
</div>
</div>
<div class="col-xs-4">
<div class="form-group has-success">
<div class="input-group">
<input type="text" ng-model="vm.percentageCarbs" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
<span class="input-group-addon">%</span>
</div>
</div>
</div>
</div>
</div>
</div>
</fieldset>
<hr />
<div class="row">
<div class="col-md-6 col-sm-9 col-xs-12">
<div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
<div class="input-group">
<span class="input-group-addon">Link: </span>
<input type="text" ng-model="vm.shareLink" class="form-control" />
<span class="input-group-btn">
<button clipboard="" supported="vm.clipboardSupported" text="vm.shareLink" on-copied="vm.onClipboardSuccess()" on-error="onClipboardFail(err)" type="button" class="btn btn-toggle" tabindex="-1" title="Click to copy to clipboard">
<i class="fa fa-copy"></i>
</button>
</span>
</div>
</div>
</div>
</div>
</form>
</div>
</body>
</html>
/* Styles go here */
body {
padding: 15px;
}
.input-label {
width: 150px;
text-align: left;
}
.input-macros {
width: 100px;
text-align: left;
}
.input-select {
width: 100%;
}
.btn-toggle {
color: #A66D3B;
background-color: #FCF8E3;
border-color: #8A6D3B;
}
(function() {
var app = angular.module('app', [
'ui.router',
'ui.bootstrap',
'angular-clipboard',
'ngAnimate',
'ngSanitize',
'ngToast'
]);
app.run(['$state', function($state) {
console.log("app run");
}]);
app.config(function() {
console.log("app config");
});
app.config(['ngToastProvider', function(ngToastProvider) {
ngToastProvider.configure({
animation: 'slide' // or 'fade'
});
}]);
app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', uiRouterConfigurator]);
function uiRouterConfigurator($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
// States
$stateProvider
.state('home', {
url: '/{programId}/{weight}_{weightUnits}',
//templateUrl: 'test.html',
})
// .state('status', {
// url: '/status/:statusName',
// templateUrl: 'hallo.html',
// })
;
}
})();
(function() {
'use strict';
angular.module('app')
.controller('mainController', MainController);
MainController.$inject = ['$scope', '$log', '$state', '$stateParams', '$location', '$timeout', 'ngToast'];
function MainController($scope, $log, $state, $stateParams, $location, $timeout, ngToast) {
var _kgToPoundRatio = 2.2046226218;
var _cmToInRatio = 0.39370;
var _kinobodyMaintenanceFactor = 15;
var _selectedProgram = '';
var _selectedActivity = '';
var _selectedDeficit = '';
var _selectedMacroSplit = '';
var _inputWeight = '';
var _inputHeight = '';
var initialized = false;
var _initTimeout = 3000;
var vm = this;
vm.title = 'ThinkEatLift Calories/Macros!';
vm.clipboardSupported = false;
vm.weightKg = '';
vm.selectedProgramId = 'kinobody_afl';
vm.inputUnitsWeight = 'kg';
vm.programOptions = [
{
id: 'kinobody_afl',
name: 'Kinobody AFL',
description: 'Aggressive Fat Loss',
lbsFactor: 11,
fatsDefaultPercentage: 0.30,
caloriesOffset: 0,
},
{
id: 'kinobody_wsp',
name: 'Kinobody WSP',
description: 'Warrior Shredding Program',
lbsFactor: 12,
fatsDefaultPercentage: 0.30,
caloriesOffset: 0,
},
{
id: 'kinobody_ggp_lb_rest',
name: 'Kinobody GGP, Lean Bulk, Rest Days',
description: 'Greek God Program - Lean Bulk - Rest Days',
lbsFactor: 15,
fatsDefaultPercentage: 0.25,
caloriesOffset: 100,
},
{
id: 'kinobody_ggp_lb_lift',
name: 'Kinobody GGP, Lean Bulk, Lifting Days',
description: 'Greek God Program - Lean Bulk - Lifting Days',
lbsFactor: 15,
fatsDefaultPercentage: 0.25,
caloriesOffset: 500,
},
{
id: 'kinobody_ggp_rc_rest',
name: 'Kinobody GGP, Recomp, Rest Days',
description: 'Greek God Program - Recomp - Rest Days',
lbsFactor: 15,
fatsDefaultPercentage: 0.25,
caloriesOffset: -300,
},
{
id: 'kinobody_ggp_rc_lift',
name: 'Kinobody GGP, Recomp, Lifting Days',
description: 'Greek God Program - Recomp - Lifting Days',
lbsFactor: 15,
fatsDefaultPercentage: 0.25,
caloriesOffset: 400,
},
{
id: 'kinobody_gtp',
name: 'Kinobody GTP',
description: 'Goddess Toning Program',
lbsFactor: 12,
fatsDefaultPercentage: 0.30,
caloriesOffset: 0,
},
{
id: 'kinobody_gtp_afl10',
name: 'Kinobody GTP / AFL 10',
description: 'Goddess Toning Program',
lbsFactor: 10,
fatsDefaultPercentage: 0.30,
caloriesOffset: 0,
},
{
id: 'kinobody_gtp_afl11',
name: 'Kinobody GTP / AFL 11',
description: 'Goddess Toning Program',
lbsFactor: 11,
fatsDefaultPercentage: 0.30,
caloriesOffset: 0,
},
];
function init() {
// process state params here
processStateParams($stateParams);
updateRoute();
$timeout(function() {
initialized = true;
}, _initTimeout);
}
// http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_afl/74_kg
function processStateParams(stateParams) {
// $log.log('processStateParams $location', $location);
// $log.log('processStateParams stateParams', stateParams);
// $log.log('processStateParams $state.params', $state.params);
if(stateParams) {
// restore program by id:
if (stateParams.programId) {
vm.selectedProgramId = stateParams.programId;
}
// weight / weight units
if (stateParams.weight && stateParams.weightUnits) {
var weight = stateParams.weight;
var units = stateParams.weightUnits;
vm.inputUnitsWeight = (units === 'lbs') ? 'lbs' : 'kg';
if (vm.inputUnitsWeight === 'kg') {
vm.weightKg = weight;
} else {
vm.weightKg = weight / _kgToPoundRatio;
}
vm.inputWeight = (vm.inputUnitsWeight === 'kg') ? vm.weightKg : (vm.weightKg * _kgToPoundRatio);
}
}
// initialize controls
_selectedProgram = _.find(vm.programOptions, function(item) { return item.id.toUpperCase() === vm.selectedProgramId.toUpperCase(); });
if(!_selectedProgram) {
// $log.log('invalid program name');
_selectedProgram = vm.programOptions[0];
vm.selectedProgramId = _selectedProgram.programId;
$timeout(function() {
updateRoute();
}, 10);
}
// INIT: vm.weightKg is always in kg!
vm.inputWeight = (vm.inputUnitsWeight === 'kg') ? vm.weightKg : (vm.weightKg * _kgToPoundRatio);
vm.inputHeight = (vm.inputUnitsHeight === 'cm') ? vm.heightCm : (vm.heightCm * _cmToInRatio);
updateRoute();
}
Object.defineProperty(vm, "shareLink", {
get: function() {
return $location.absUrl();
}
});
Object.defineProperty(vm, "programOffset", {
get: function() {
if (_selectedProgram) {
if(_selectedProgram.caloriesOffset && _selectedProgram.caloriesOffset !== 0) {
if(_selectedProgram.caloriesOffset > 0) {
return ' + ' + _selectedProgram.caloriesOffset;
} else {
return ' - ' + Math.abs(_selectedProgram.caloriesOffset);
}
}
}
return '';
}
});
Object.defineProperty(vm, "selectedProgram", {
get: function() {
return _selectedProgram;
},
set: function(value) {
_selectedProgram = value;
vm.selectedProgramId = _selectedProgram.id;
}
});
Object.defineProperty(vm, "inputWeight", {
get: function() {
return _inputWeight;
},
set: function(value) {
_inputWeight = value;
vm.weightKg = convertInputWeightToKg(_inputWeight);
}
});
Object.defineProperty(vm, "weightLb", {
get: function() {
if(vm.weightKg) {
return vm.weightKg * _kgToPoundRatio;
}
return '';
}
});
Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories", {
get: function() {
if(vm.weightLb) {
return _kinobodyMaintenanceFactor * vm.weightLb;
}
return '';
}
});
Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories_display", {
get: function() {
if(vm.weightLb) {
return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
}
return '';
}
});
Object.defineProperty(vm, "kinobodyEstimateDailyCalories", {
get: function() {
if(vm.weightLb) {
return (vm.selectedProgram.lbsFactor * vm.weightLb) + vm.selectedProgram.caloriesOffset;
}
return '';
}
});
Object.defineProperty(vm, "kinobodyEstimateDailyCalories_display", {
get: function() {
if(vm.weightLb) {
return formatCalNumber(vm.kinobodyEstimateDailyCalories);
}
return '';
}
});
Object.defineProperty(vm, "activeMaintenanceFormula", {
get: function() {
// => only use KB for now!
if (vm.kinobodyEstimateMaintenanceCalories) {
return 'KB';
}
return '';
}
});
Object.defineProperty(vm, "maintenanceCalories", {
get: function() {
if (vm.kinobodyEstimateMaintenanceCalories) {
return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
}
return '';
}
});
Object.defineProperty(vm, "deficitCalories", {
get: function() {
if(vm.kinobodyEstimateDailyCalories) {
return formatCalNumber(vm.kinobodyEstimateDailyCalories);
}
return '';
}
});
Object.defineProperty(vm, "macrosProtein", {
get: function() {
// 2g of protein pr kg / 0.9g per lb
if(vm.deficitCalories) {
var proteinGrams = roundToNearestX(vm.weightKg * 2, 5);
return formatCalNumber(proteinGrams);
}
return '';
}
});
function roundToNearestX(val, x) {
return Math.ceil(val/x)*x;
}
Object.defineProperty(vm, "macrosFats", {
get: function() {
// 25%/30% of calories from fat (program dependent)
if(vm.deficitCalories) {
var fatsFactor = vm.selectedProgram.fatsDefaultPercentage;
var fatGrams = vm.deficitCalories * fatsFactor / 9;
return formatCalNumber(fatGrams);
}
return '';
}
});
Object.defineProperty(vm, "macrosCarbs", {
get: function() {
// the rest from carbs
if(vm.deficitCalories) {
var carbGrams = 0;
var calsProtein = vm.macrosProtein * 4;
var calsFats = vm.macrosFats * 9;
var calsPF = calsProtein + calsFats;
var calsCarbs = vm.deficitCalories - calsPF;
carbGrams = calsCarbs / 4;
return formatCalNumber(carbGrams);
}
return '';
}
});
Object.defineProperty(vm, "percentageProtein", {
get: function() {
if(vm.macrosProtein) {
var calsFats = vm.macrosProtein * 4;
var p = calsFats / vm.deficitCalories * 100;
return formatCalNumber(p);
}
return '';
}
});
Object.defineProperty(vm, "percentageFats", {
get: function() {
if(vm.macrosFats) {
var calsFats = vm.macrosFats * 9;
var p = calsFats / vm.deficitCalories * 100;
return formatCalNumber(p);
}
return '';
}
});
Object.defineProperty(vm, "percentageCarbs", {
get: function() {
if(vm.macrosCarbs) {
var calsCarbs = vm.macrosCarbs * 4;
var p = calsCarbs / vm.deficitCalories * 100;
return formatCalNumber(p);
}
return '';
}
});
Object.defineProperty(vm, "crossCheckCalories", {
get: function() {
if(vm.macrosProtein && vm.macrosFats && vm.macrosCarbs) {
var calsProtein = vm.macrosProtein * 4;
var calsFats = vm.macrosFats * 9;
var calsCarbs = vm.macrosCarbs * 4;
return formatCalNumber(calsProtein + calsFats + calsCarbs);
}
return '';
}
});
vm.formatCalNumber = formatCalNumber;
vm.toggleInputUnitsWeight = toggleInputUnitsWeight;
vm.onWeightChange = onWeightChange;
vm.onProgramChange = onProgramChange;
vm.onClipboardSuccess = onClipboardSuccess;
vm.onClipboardFail = onClipboardFail;
function onClipboardSuccess() {
ngToast.create("Address copied to clipboard");
// $log.log('onClipboardSuccess');
}
function onClipboardFail(err) {
$log.log('onClipboardFail', err);
}
function onWeightChange() {
updateRoute();
}
function onProgramChange() {
updateRoute();
}
function updateRoute() {
if(!initialized || vm.inputWeight === '')
return;
// $log.log('updateRoute', vm.storage, $location, $location.absUrl());
var stateOptions = {
programId: vm.selectedProgram.id,
weight: vm.inputWeight,
weightUnits: vm.inputUnitsWeight,
};
$state.transitionTo('home', stateOptions, { notify: false });
}
function formatCalNumber(i) {
if(i === '') return '';
return i.toFixed(0);
}
function toggleInputUnitsWeight() {
// $log.log('toggleInputUnitsWeight');
switch(vm.inputUnitsWeight){
case 'kg':
// LBS
vm.inputUnitsWeight = 'lbs';
vm.inputWeight = vm.inputWeight * _kgToPoundRatio;
break;
case 'lbs':
// KG
vm.inputUnitsWeight = 'kg';
vm.inputWeight = vm.inputWeight / _kgToPoundRatio;
break;
}
updateRoute();
}
function convertInputWeightToKg(inputWeight) {
if(vm.inputUnitsWeight === 'kg') {
return inputWeight;
} else {
return inputWeight / _kgToPoundRatio;
}
}
// state change event handler
$scope.$on("$stateChangeStart",
function (event, toState, toParams, fromState, fromParams) {
// $log.log('$stateChangeStart', $location);
processStateParams(toParams);
}
);
init();
}
})();
Kinobody Calories / Macros
- Kinobody easy formulas for maintenance/deficit calculations
- Creates url that can be forum-posted:
http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_ggp_lb_rest/78_kg
http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_afl/165_lbs
http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_wsp/80_kg
Todo:
-
Useful links for this plunk:
https://github.com/omichelsen/angular-clipboard
http://tamerayd.in/ngToast/
https://www.niclassahlin.com/2014/05/31/kickstarting-angular-ui-router/
http://www.metric-conversions.org/length/centimeters-to-inches.htm
http://www.metric-conversions.org/weight/kilograms-to-pounds.htm
https://github.com/gsklee/ngStorage
https://scotch.io/tutorials/the-many-ways-to-use-ngclass
// fix enter key strange behaviour:
http://stackoverflow.com/a/11591419/54159 : Just add the type="button" attribute to the button element, some browsers interpret the type as submit by default.
http://stackoverflow.com/questions/10400149/avoid-modal-dismiss-on-enter-keypress
(function (root, factory) {
/* istanbul ignore next */
if (typeof define === 'function' && define.amd) {
define(['angular'], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory(require('angular'));
} else {
root.angularClipboard = factory(root.angular);
}
}(this, function (angular) {
return angular.module('angular-clipboard', [])
.factory('clipboard', ['$document', function ($document) {
function createNode(text) {
var node = $document[0].createElement('textarea');
node.style.position = 'absolute';
node.style.left = '-10000px';
node.textContent = text;
return node;
}
function copyNode(node) {
try {
// Set inline style to override css styles
$document[0].body.style.webkitUserSelect = 'initial';
var selection = $document[0].getSelection();
selection.removeAllRanges();
node.select();
if(!$document[0].execCommand('copy')) {
throw('failure copy');
}
selection.removeAllRanges();
} finally {
// Reset inline style
$document[0].body.style.webkitUserSelect = '';
}
}
function copyText(text) {
var node = createNode(text);
$document[0].body.appendChild(node);
copyNode(node);
$document[0].body.removeChild(node);
}
return {
copyText: copyText,
supported: 'queryCommandSupported' in document && document.queryCommandSupported('copy')
};
}])
.directive('clipboard', ['clipboard', function (clipboard) {
return {
restrict: 'A',
scope: {
onCopied: '&',
onError: '&',
text: '=',
supported: '='
},
link: function (scope, element) {
scope.supported = clipboard.supported;
element.on('click', function (event) {
try {
clipboard.copyText(scope.text);
if (angular.isFunction(scope.onCopied)) {
scope.$evalAsync(scope.onCopied());
}
} catch (err) {
if (angular.isFunction(scope.onError)) {
scope.$evalAsync(scope.onError({err: err}));
}
}
});
}
};
}]);
}));