├── .yo-rc.json ├── firebase-debug.log ├── .gitattributes ├── .idea ├── .name ├── misc.xml ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── jsLibraryMappings.xml ├── modules.xml ├── weighIn-project.iml ├── libraries │ └── weighIn_project_node_modules.xml └── workspace.xml ├── app ├── .buildignore ├── robots.txt ├── favicon.ico ├── images │ ├── yeoman.png │ ├── scale-icon.png │ ├── scale-icon-2.png │ ├── scale-icon-blue.png │ ├── scale-icon-yellow.png │ └── jumbotronbackground.jpg ├── scripts │ ├── angularfire │ │ ├── config.js │ │ ├── auth.js │ │ └── firebase.ref.js │ ├── filters │ │ └── reverse.js │ ├── controllers │ │ ├── adduser.js │ │ ├── user.js │ │ ├── main.js │ │ ├── headercontroller.js │ │ ├── chat.js │ │ ├── account.js │ │ ├── login.js │ │ └── userlist.js │ ├── app.js │ ├── directives │ │ ├── ngHideAuth.js │ │ └── ngShowAuth.js │ ├── services │ │ └── fbfactory.js │ └── routes.js ├── views │ ├── chat.html │ ├── main.html │ ├── login.html │ ├── userlist.html │ ├── account.html │ ├── user.html │ └── adduser.html ├── styles │ └── main.css ├── 404.html ├── index.html └── .htaccess ├── .bowerrc ├── .gitignore ├── sitemap.pdf ├── .travis.yml ├── test ├── spec │ ├── services │ │ └── fbfactory.js │ └── controllers │ │ ├── main.js │ │ ├── user.js │ │ ├── adduser.js │ │ ├── userlist.js │ │ └── headercontroller.js ├── .jshintrc └── karma.conf.js ├── .jshintrc ├── .editorconfig ├── bower.json ├── weigh-in.json ├── package.json ├── README.md └── Gruntfile.js /.yo-rc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /firebase-debug.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | weighIn-project -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | bower_components 6 | -------------------------------------------------------------------------------- /sitemap.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/sitemap.pdf -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/app/favicon.ico -------------------------------------------------------------------------------- /app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/app/images/yeoman.png -------------------------------------------------------------------------------- /app/images/scale-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/app/images/scale-icon.png -------------------------------------------------------------------------------- /app/images/scale-icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/app/images/scale-icon-2.png -------------------------------------------------------------------------------- /app/images/scale-icon-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/app/images/scale-icon-blue.png -------------------------------------------------------------------------------- /app/images/scale-icon-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/app/images/scale-icon-yellow.png -------------------------------------------------------------------------------- /app/images/jumbotronbackground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runtime/weighin-app/master/app/images/jumbotronbackground.jpg -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' 7 | before_script: 8 | - 'npm install -g bower grunt-cli' 9 | - 'bower install' 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/scripts/angularfire/config.js: -------------------------------------------------------------------------------- 1 | angular.module('firebase.config', []) 2 | .constant('FBURL', 'https://weighin-project.firebaseio.com') 3 | .constant('SIMPLE_LOGIN_PROVIDERS', ['password']) 4 | 5 | .constant('loginRedirectPath', '/login'); 6 | -------------------------------------------------------------------------------- /app/scripts/filters/reverse.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('weighInApp') 4 | .filter('reverse', function() { 5 | return function(items) { 6 | return angular.isArray(items)? items.slice().reverse() : []; 7 | }; 8 | }); 9 | -------------------------------------------------------------------------------- /app/scripts/angularfire/auth.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | angular.module('firebase.auth', ['firebase', 'firebase.ref']) 4 | 5 | .factory('Auth', function($firebaseAuth, Ref) { 6 | return $firebaseAuth(Ref); 7 | }); 8 | })(); 9 | -------------------------------------------------------------------------------- /app/scripts/angularfire/firebase.ref.js: -------------------------------------------------------------------------------- 1 | angular.module('firebase.ref', ['firebase', 'firebase.config']) 2 | .factory('Ref', ['$window', 'FBURL', function($window, FBURL) { 3 | 'use strict'; 4 | return new $window.Firebase(FBURL); 5 | }]); 6 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/weighIn-project.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/views/chat.html: -------------------------------------------------------------------------------- 1 | 2 |

Chat

3 | 4 |
5 | 6 | 7 |
8 | 9 | 12 | 13 |

{{err}}

14 | -------------------------------------------------------------------------------- /app/scripts/controllers/adduser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name weighInApp.controller:AdduserCtrl 6 | * @description 7 | * # AdduserCtrl 8 | * Controller of the weighInApp 9 | */ 10 | angular.module('weighInApp') 11 | .controller('AdduserCtrl', function ($scope) { 12 | $scope.awesomeThings = [ 13 | 'HTML5 Boilerplate', 14 | 'AngularJS', 15 | 'Karma' 16 | ]; 17 | }); 18 | -------------------------------------------------------------------------------- /app/scripts/controllers/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name weighInApp.controller:UserCtrl 6 | * @description 7 | * # UserCtrl 8 | * Controller of the weighInApp 9 | */ 10 | angular.module('weighInApp') 11 | .controller('UserCtrl', function ($scope) { 12 | $scope.awesomeThings = [ 13 | 'HTML5 Boilerplate', 14 | 'AngularJS', 15 | 'Karma' 16 | ]; 17 | }); 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/spec/services/fbfactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: FBFactory', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('weighInApp')); 7 | 8 | // instantiate service 9 | var FBFactory; 10 | beforeEach(inject(function (_FBFactory_) { 11 | FBFactory = _FBFactory_; 12 | })); 13 | 14 | it('should do something', function () { 15 | expect(!!FBFactory).toBe(true); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc overview 5 | * @name weighInApp 6 | * @description 7 | * # weighInApp 8 | * 9 | * Main module of the application. 10 | */ 11 | angular.module('weighInApp', [ 12 | 'ngAnimate', 13 | 'ngCookies', 14 | 'ngResource', 15 | 'ngRoute', 16 | 'ngSanitize', 17 | 'ngTouch', 18 | 'firebase', 19 | 'firebase.ref', 20 | 'firebase.auth', 21 | 'chart.js' 22 | ]); 23 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "esnext": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": true, 12 | "noarg": true, 13 | "node": true, 14 | "quotmark": "single", 15 | "strict": true, 16 | "undef": true, 17 | "unused": true, 18 | "globals": { 19 | "angular": false, 20 | "firebase": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "esnext": true, 8 | "immed": true, 9 | "jasmine": true, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "node": true, 14 | "quotmark": "single", 15 | "strict": true, 16 | "undef": true, 17 | "unused": true, 18 | "globals": { 19 | "angular": false, 20 | "inject": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name weighInApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the weighInApp 9 | */ 10 | angular.module('weighInApp') 11 | .controller('MainCtrl', ['$scope', 'users', 12 | function ($scope, users) { 13 | 14 | console.log("MainCtrl"); 15 | 16 | $scope.users = users; 17 | // console.log($scope.users); 18 | 19 | }]); 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.idea/libraries/weighIn_project_node_modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/scripts/controllers/headercontroller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name weighInApp.controller:HeadercontrollerCtrl 6 | * @description 7 | * # HeadercontrollerCtrl 8 | * Controller of the weighInApp 9 | */ 10 | angular.module('weighInApp') 11 | .controller('HeadercontrollerCtrl', function ($scope, $location) { 12 | $scope.isActive = function (viewLocation) { 13 | //console.log('HeadercontrollerCtrl:' + $location.path()); 14 | return viewLocation === $location.path(); 15 | }; 16 | }); 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Weighin

5 | 6 |

7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('weighInApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: UserCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('weighInApp')); 7 | 8 | var UserCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | UserCtrl = $controller('UserCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/adduser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AdduserCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('weighInApp')); 7 | 8 | var AdduserCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AdduserCtrl = $controller('AdduserCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/userlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: UserlistCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('weighInApp')); 7 | 8 | var UserlistCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | UserlistCtrl = $controller('UserlistCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weigh-in", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "^1.3.0", 6 | "firebase": "2.2.2", 7 | "angularfire": "1.0.0", 8 | "bootstrap": "3.3.4", 9 | "angular-animate": "^1.3.0", 10 | "angular-cookies": "^1.3.0", 11 | "angular-resource": "^1.3.0", 12 | "angular-route": "^1.3.0", 13 | "angular-sanitize": "^1.3.0", 14 | "angular-touch": "^1.3.0", 15 | "chart.js": "^2.4.0", 16 | "angular-chart.js": "^1.1.1" 17 | }, 18 | "devDependencies": { 19 | "mockfirebase": "0.11.0", 20 | "angular-mocks": "^1.3.0" 21 | }, 22 | "appPath": "app", 23 | "moduleName": "weighInApp" 24 | } 25 | -------------------------------------------------------------------------------- /test/spec/controllers/headercontroller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: HeadercontrollerCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('weighInApp')); 7 | 8 | var HeadercontrollerCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | HeadercontrollerCtrl = $controller('HeadercontrollerCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/scripts/directives/ngHideAuth.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @ngdoc function 4 | * @name weighInApp.directive:ngHideAuth 5 | * @description 6 | * # ngHideAuthDirective 7 | * A directive that shows elements only when user is logged out. It also waits for Auth 8 | * to be initialized so there is no initial flashing of incorrect state. 9 | */ 10 | angular.module('weighInApp') 11 | .directive('ngHideAuth', ['Auth', '$timeout', function (Auth, $timeout) { 12 | 'use strict'; 13 | 14 | return { 15 | restrict: 'A', 16 | link: function(scope, el) { 17 | el.addClass('ng-cloak'); // hide until we process it 18 | function update() { 19 | // sometimes if ngCloak exists on same element, they argue, so make sure that 20 | // this one always runs last for reliability 21 | $timeout(function () { 22 | el.toggleClass('ng-cloak', !!Auth.$getAuth()); 23 | }, 0); 24 | } 25 | 26 | Auth.$onAuth(update); 27 | update(); 28 | } 29 | }; 30 | }]); 31 | -------------------------------------------------------------------------------- /app/scripts/directives/ngShowAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc function 3 | * @name weighInApp.directive:ngShowAuth 4 | * @description 5 | * # ngShowAuthDirective 6 | * A directive that shows elements only when user is logged in. It also waits for Auth 7 | * to be initialized so there is no initial flashing of incorrect state. 8 | */ 9 | angular.module('weighInApp') 10 | .directive('ngShowAuth', ['Auth', '$timeout', function (Auth, $timeout) { 11 | 'use strict'; 12 | 13 | return { 14 | restrict: 'A', 15 | link: function(scope, el) { 16 | el.addClass('ng-cloak'); // hide until we process it 17 | 18 | function update() { 19 | // sometimes if ngCloak exists on same element, they argue, so make sure that 20 | // this one always runs last for reliability 21 | $timeout(function () { 22 | el.toggleClass('ng-cloak', !Auth.$getAuth()); 23 | }, 0); 24 | } 25 | 26 | Auth.$onAuth(update); 27 | update(); 28 | } 29 | }; 30 | }]); 31 | -------------------------------------------------------------------------------- /app/scripts/controllers/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @ngdoc function 4 | * @name weighInApp.controller:ChatCtrl 5 | * @description 6 | * # ChatCtrl 7 | * A demo of using AngularFire to manage a synchronized list. 8 | */ 9 | angular.module('weighInApp') 10 | .controller('ChatCtrl', function ($scope, Ref, $firebaseArray, $timeout) { 11 | // synchronize a read-only, synchronized array of messages, limit to most recent 10 12 | $scope.messages = $firebaseArray(Ref.child('messages').limitToLast(10)); 13 | 14 | // display any errors 15 | $scope.messages.$loaded().catch(alert); 16 | 17 | // provide a method for adding a message 18 | $scope.addMessage = function(newMessage) { 19 | if( newMessage ) { 20 | // push a message to the end of the array 21 | $scope.messages.$add({text: newMessage}) 22 | // display any errors 23 | .catch(alert); 24 | } 25 | }; 26 | 27 | function alert(msg) { 28 | $scope.err = msg; 29 | $timeout(function() { 30 | $scope.err = null; 31 | }, 5000); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /app/scripts/services/fbfactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc service 5 | * @name weighInApp.FBFactory 6 | * @description 7 | * # FBFactory 8 | * Factory in the weighInApp. 9 | */ 10 | 11 | 12 | 13 | angular.module('weighInApp') 14 | .factory('users', ['$firebaseArray', 'FBURL', function ($firebaseArray, FBURL) { 15 | // Service logic 16 | // ... 17 | // FBURL was from config.js when we configured our FB url 18 | // var ref = new Firebase(FBURL); 19 | // instead we'll create a new one directly with users 20 | var ref = new Firebase('/https://weighin-project.firebaseio.com/users'); 21 | 22 | 23 | return $firebaseArray(ref); 24 | 25 | 26 | }]); 27 | 28 | 29 | 30 | //angular.module('weighInApp') 31 | // .factory('users', ["$firebaseArray", function ($firebaseArray) { 32 | // // Service logic 33 | // // ... 34 | // var ref = new Firebase('/https://weighin-project.firebaseio.com/users'); 35 | // // var ref = new Firebase('/https://mccann-case-studies.firebaseio.com/cases'); 36 | // 37 | // return $firebaseArray(ref); 38 | // 39 | // 40 | // 41 | // }]); 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /weigh-in.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WeighInAppData", 3 | "users": [ 4 | { "name": "Dad", 5 | "age": "50", 6 | "height": "6ft 2in", 7 | "avatar": "http://placekitten.com/g/300/300", 8 | "weighins": [{"date": "11/24/2016", "weight": "224.4"}, {"date": "11/26/2016", "weight": "226.4"}, {"date": "11/27/2016", "weight": "228.2"}] 9 | }, 10 | { "name": "Chloe", 11 | "age": "7", 12 | "height": "4ft 2in", 13 | "avatar": "http://placekitten.com/g/300/300", 14 | "weighins": [{"date": "11/24/2016", "weight": "70.6"}, {"date": "11/26/2016", "weight": "70.8"}, {"date": "11/27/2016", "weight": "70.4"}] 15 | }, 16 | { "name": "Kaya", 17 | "age": "6", 18 | "height": "3ft 10in", 19 | "avatar": "http://placekitten.com/g/300/300", 20 | "weighins": [{"date": "11/24/2016", "weight": "47.6"}, {"date": "11/25/2016", "weight": "43.6"}, {"date": "11/26/2016", "weight": "46.6"}, {"date": "11/27/2016", "weight": "46.8"}] 21 | }, 22 | { "name": "Mommy", 23 | "age": "46", 24 | "height": "5ft 10in", 25 | "avatar": "http://placekitten.com/g/300/300", 26 | "weighins": [{"date": "11/24/2016", "weight": "131.8"}, {"date": "11/26/2016", "weight": "131.1"}, {"date": "11/27/2016", "weight": "131.4"}] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /app/views/login.html: -------------------------------------------------------------------------------- 1 | 2 |

Login Page

3 | 4 |
5 |

6 | 7 | 8 |

9 |

10 | 11 | 12 |

13 |

14 | 15 | 16 |

17 | 18 |

19 | 20 | 21 | 23 | 24 |

25 |
26 |

{{err}}

27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weighin", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular-chart.js": "^1.1.1", 6 | "chart.js": "^2.4.0" 7 | }, 8 | "repository": {}, 9 | "devDependencies": { 10 | "chalk": "^0.4.0", 11 | "grunt": "^0.4.5", 12 | "grunt-autoprefixer": "^2.0.0", 13 | "grunt-concurrent": "^1.0.0", 14 | "grunt-contrib-clean": "^0.6.0", 15 | "grunt-contrib-concat": "^0.5.0", 16 | "grunt-contrib-connect": "^0.9.0", 17 | "grunt-contrib-copy": "^0.7.0", 18 | "grunt-contrib-cssmin": "^0.12.0", 19 | "grunt-contrib-htmlmin": "^0.4.0", 20 | "grunt-contrib-imagemin": "^0.9.2", 21 | "grunt-contrib-jshint": "^0.11.0", 22 | "grunt-contrib-uglify": "^0.7.0", 23 | "grunt-contrib-watch": "^0.6.1", 24 | "grunt-filerev": "^2.1.2", 25 | "grunt-google-cdn": "^0.4.3", 26 | "grunt-karma": "*", 27 | "grunt-newer": "^1.1.0", 28 | "grunt-ng-annotate": "^0.9.2", 29 | "grunt-svgmin": "^2.0.0", 30 | "grunt-usemin": "^3.0.0", 31 | "grunt-wiredep": "^2.0.0", 32 | "jshint-stylish": "^1.0.0", 33 | "karma-jasmine": "*", 34 | "karma-phantomjs-launcher": "*", 35 | "load-grunt-tasks": "^3.1.0", 36 | "time-grunt": "^1.0.0" 37 | }, 38 | "engines": { 39 | "node": ">=0.10.0" 40 | }, 41 | "scripts": { 42 | "test": "grunt test" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/views/userlist.html: -------------------------------------------------------------------------------- 1 |

Users

2 | 3 | 4 |
5 |
6 |
7 | 21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/scripts/controllers/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @ngdoc function 4 | * @name muck2App.controller:AccountCtrl 5 | * @description 6 | * # AccountCtrl 7 | * Provides rudimentary account management functions. 8 | */ 9 | angular.module('weighInApp') 10 | .controller('AccountCtrl', function ($scope, user, Auth, Ref, $firebaseObject, $timeout) { 11 | $scope.user = user; 12 | $scope.logout = function() { Auth.$unauth(); }; 13 | $scope.messages = []; 14 | var profile = $firebaseObject(Ref.child('users/'+user.uid)); 15 | profile.$bindTo($scope, 'profile'); 16 | 17 | 18 | $scope.changePassword = function(oldPass, newPass, confirm) { 19 | $scope.err = null; 20 | if( !oldPass || !newPass ) { 21 | error('Please enter all fields'); 22 | } 23 | else if( newPass !== confirm ) { 24 | error('Passwords do not match'); 25 | } 26 | else { 27 | Auth.$changePassword({email: profile.email, oldPassword: oldPass, newPassword: newPass}) 28 | .then(function() { 29 | success('Password changed'); 30 | }, error); 31 | } 32 | }; 33 | 34 | $scope.changeEmail = function(pass, newEmail) { 35 | $scope.err = null; 36 | Auth.$changeEmail({password: pass, newEmail: newEmail, oldEmail: profile.email}) 37 | .then(function() { 38 | profile.email = newEmail; 39 | profile.$save(); 40 | success('Email changed'); 41 | }) 42 | .catch(error); 43 | }; 44 | 45 | function error(err) { 46 | alert(err, 'danger'); 47 | } 48 | 49 | function success(msg) { 50 | alert(msg, 'success'); 51 | } 52 | 53 | function alert(msg, type) { 54 | var obj = {text: msg+'', type: type}; 55 | $scope.messages.unshift(obj); 56 | $timeout(function() { 57 | $scope.messages.splice($scope.messages.indexOf(obj), 1); 58 | }, 10000); 59 | } 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /app/views/account.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Profile

5 | 6 |
7 |

8 | 9 |
{{user.uid}} 10 |

11 | 12 |

13 | 14 | 15 |

16 | 17 |

18 | 19 |
{{profile.email}} 20 |

21 |
22 | 23 |

24 | 25 |

26 |
27 | 28 |
29 |
30 |

Change Password

31 | 32 |

33 | 34 | 35 |

36 | 37 |

38 | 39 | 40 |

41 | 42 |

43 | 44 | 45 |

46 | 47 | 48 | 49 |

{{err}}

50 |

{{msg}}

51 |
52 |
53 | 54 |
55 |
56 |

Change Email

57 | 58 |

59 | 60 | 61 |

62 | 63 |

64 | 65 | 66 |

67 | 68 | 69 |
70 |
71 | 72 |
73 |

74 |
{{message.text}}
75 | -------------------------------------------------------------------------------- /app/scripts/controllers/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @ngdoc function 4 | * @name weighInApp.controller:LoginCtrl 5 | * @description 6 | * # LoginCtrl 7 | * Manages authentication to any active providers. 8 | */ 9 | angular.module('weighInApp') 10 | .controller('LoginCtrl', function ($scope, Auth, $location, $q, Ref, $timeout) { 11 | $scope.passwordLogin = function(email, pass) { 12 | $scope.err = null; 13 | Auth.$authWithPassword({email: email, password: pass}, {rememberMe: true}).then( 14 | redirect, showError 15 | ); 16 | }; 17 | 18 | $scope.createAccount = function(email, pass, confirm) { 19 | $scope.err = null; 20 | if( !pass ) { 21 | $scope.err = 'Please enter a password'; 22 | } 23 | else if( pass !== confirm ) { 24 | $scope.err = 'Passwords do not match'; 25 | } 26 | else { 27 | Auth.$createUser({email: email, password: pass}) 28 | .then(function () { 29 | // authenticate so we have permission to write to Firebase 30 | return Auth.$authWithPassword({email: email, password: pass}, {rememberMe: true}); 31 | }) 32 | .then(createProfile) 33 | .then(redirect, showError); 34 | } 35 | 36 | function createProfile(user) { 37 | var ref = Ref.child('users', user.uid), def = $q.defer(); 38 | ref.set({email: email, name: firstPartOfEmail(email)}, function(err) { 39 | $timeout(function() { 40 | if( err ) { 41 | def.reject(err); 42 | } 43 | else { 44 | def.resolve(ref); 45 | } 46 | }); 47 | }); 48 | return def.promise; 49 | } 50 | }; 51 | 52 | function firstPartOfEmail(email) { 53 | return ucfirst(email.substr(0, email.indexOf('@'))||''); 54 | } 55 | 56 | function ucfirst (str) { 57 | // inspired by: http://kevin.vanzonneveld.net 58 | str += ''; 59 | var f = str.charAt(0).toUpperCase(); 60 | return f + str.substr(1); 61 | } 62 | 63 | 64 | 65 | function redirect() { 66 | $location.path('/account'); 67 | } 68 | 69 | function showError(err) { 70 | $scope.err = err; 71 | } 72 | 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # weigh-in 2 | 3 | App for family members to weigh themselves over time and track it. 4 | 5 | ##Tracks Users by weighins 6 | user object contains name and array of weighins 7 | 8 | This project is generated with [yo angular generator](https://github.com/yeoman/generator-angular) 9 | version 1.0.0. 10 | 11 | ## Build & development 12 | 13 | Run `grunt` for building and `grunt serve` for preview. 14 | 15 | ## Testing 16 | 17 | Running `grunt test` will run the unit tests with karma. 18 | 19 | 20 | ## Hurdles 11/26/16 21 | 22 | ## generator showing signs of age 23 | rolled back to bootstrap 3.3.4 due to bootstrap not seeing itself using bower to install. finally went with the CDN on the index 24 | 25 | 26 | # Steps 27 | ## created factory for FB called FBfactory 28 | .factory('users', ["$firebaseArray", function ($firebaseArray) {.. 29 | This injects the users (every weigh in is inside a user object) 30 | 31 | 32 | ## create route for user list and get a userlist controller to process our FB factory data 33 | 34 | yo angularfire:route userlist 35 | 36 | 37 | ## inject the user fb object into the userlist controller so that we can get the data 38 | .controller('UserlistCtrl', ['$scope', 'users', 39 | 40 | $scope.users = users; 41 | 42 | ## Nice Navigation class toggle state for Angular 43 | 44 | http://stackoverflow.com/questions/16199418/how-to-set-bootstrap-navbar-active-class-with-angular-js 45 | 46 | ## create header controller to control our nav 47 | yo angularfire:controller HeaderController 48 | 49 | 50 | ## direct route to user page by username 51 | 52 | yo angularfire:route user --uri=users/:user 53 | 54 | 55 | ##inject the routeParams into the controller 56 | 57 | function ($scope, users, $routeParams) { 58 | 59 | // will be undefined until you click one 60 | console.log("routeParams.userName: " + $routeParams.userName); 61 | 62 | // this is set in the userlist view 63 | 64 | 65 | 66 | ## and in the routes.js 67 | .when('/users/:userName', { 68 | templateUrl: 'views/user.html', 69 | controller: 'UserlistCtrl' 70 | }) 71 | 72 | // and consumed in the UserlistCtrl 73 | 74 | $scope.userName = $routeParams.userName; 75 | 76 | ## TODO 77 | Create Target Date 78 | Do we have All Time Low calculated? 79 | Update UI (sitemap.pdf) to match current App 80 | Front End CSS and Media Queries 81 | 82 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2016-11-26 using 4 | // generator-karma 1.0.0 5 | 6 | module.exports = function(config) { 7 | 'use strict'; 8 | 9 | config.set({ 10 | // enable / disable watching file and executing tests whenever any file changes 11 | autoWatch: true, 12 | 13 | // base path, that will be used to resolve files and exclude 14 | basePath: '../', 15 | 16 | // testing framework to use (jasmine/mocha/qunit/...) 17 | // as well as any additional frameworks (requirejs/chai/sinon/...) 18 | frameworks: [ 19 | "jasmine" 20 | ], 21 | 22 | // list of files / patterns to load in the browser 23 | files: [ 24 | // bower:js 25 | 'bower_components/jquery/dist/jquery.js', 26 | 'bower_components/angular/angular.js', 27 | 'bower_components/firebase/firebase.js', 28 | 'bower_components/angularfire/dist/angularfire.js', 29 | 'bower_components/bootstrap/dist/js/bootstrap.js', 30 | 'bower_components/angular-animate/angular-animate.js', 31 | 'bower_components/angular-cookies/angular-cookies.js', 32 | 'bower_components/angular-resource/angular-resource.js', 33 | 'bower_components/angular-route/angular-route.js', 34 | 'bower_components/angular-sanitize/angular-sanitize.js', 35 | 'bower_components/angular-touch/angular-touch.js', 36 | 'bower_components/chart.js/dist/Chart.js', 37 | 'bower_components/angular-chart.js/dist/angular-chart.js', 38 | 'bower_components/mockfirebase/browser/mockfirebase.js', 39 | 'bower_components/angular-mocks/angular-mocks.js', 40 | // endbower 41 | "app/scripts/**/*.js", 42 | "test/mock/**/*.js", 43 | "test/spec/**/*.js" 44 | ], 45 | 46 | // list of files / patterns to exclude 47 | exclude: [ 48 | ], 49 | 50 | // web server port 51 | port: 8080, 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera 58 | // - Safari (only Mac) 59 | // - PhantomJS 60 | // - IE (only Windows) 61 | browsers: [ 62 | "PhantomJS" 63 | ], 64 | 65 | // Which plugins to enable 66 | plugins: [ 67 | "karma-phantomjs-launcher", 68 | "karma-jasmine" 69 | ], 70 | 71 | // Continuous Integration mode 72 | // if true, it capture browsers, run tests and exit 73 | singleRun: false, 74 | 75 | colors: true, 76 | 77 | // level of logging 78 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 79 | logLevel: config.LOG_INFO, 80 | 81 | // Uncomment the following lines if you are using grunt's server to run the tests 82 | // proxies: { 83 | // '/': 'http://localhost:9000/' 84 | // }, 85 | // URL root prevent conflicts with the site root 86 | // urlRoot: '_karma_' 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | .browsehappy { 2 | margin: 0.2em 0; 3 | background: #ccc; 4 | color: #000; 5 | padding: 0.2em 0; 6 | } 7 | 8 | body { 9 | padding: 0; 10 | } 11 | 12 | /* Everything but the jumbotron gets side spacing for mobile first views */ 13 | .header, 14 | .marketing, 15 | .footer { 16 | padding-left: 15px; 17 | padding-right: 15px; 18 | } 19 | 20 | /* Custom page header */ 21 | .header { 22 | border-bottom: 1px solid #e5e5e5; 23 | margin-bottom: 10px; 24 | } 25 | /* Make the masthead heading the same height as the navigation */ 26 | .header h3 { 27 | margin-top: 0; 28 | margin-bottom: 0; 29 | line-height: 40px; 30 | padding-bottom: 19px; 31 | } 32 | 33 | /* Custom page footer */ 34 | .footer { 35 | padding-top: 19px; 36 | color: #777; 37 | border-top: 1px solid #e5e5e5; 38 | } 39 | 40 | .container-narrow > hr { 41 | margin: 30px 0; 42 | } 43 | 44 | /* Main marketing message and sign up button */ 45 | .jumbotron { 46 | text-align: center; 47 | border-bottom: 1px solid #e5e5e5; 48 | } 49 | .jumbotron .btn { 50 | font-size: 21px; 51 | padding: 14px 24px; 52 | } 53 | 54 | /* Supporting marketing content */ 55 | .marketing { 56 | margin: 40px 0; 57 | } 58 | .marketing p + h4 { 59 | margin-top: 28px; 60 | } 61 | 62 | /* Custom styles*/ 63 | .item-list-pad { 64 | padding: 6%; 65 | } 66 | 67 | /* Custom Colors from Kuler */ 68 | 69 | .purple { 70 | background-color: #A92A90; 71 | } 72 | 73 | .purple-text { 74 | color: #A92A90; 75 | } 76 | 77 | .blue { 78 | background-color: #001EA5; 79 | } 80 | 81 | .blue-text { 82 | color: #001EA5; 83 | } 84 | 85 | .light-blue { 86 | background-color: #169DFF; 87 | } 88 | 89 | .light-blue-text { 90 | color: #169DFF; 91 | } 92 | 93 | .yellow { 94 | background-color: #FFB900; 95 | } 96 | 97 | .yellow-text { 98 | background: #FFB900; 99 | } 100 | 101 | .red { 102 | background-color: #E13500; 103 | } 104 | 105 | .red-text { 106 | color: #E13500; 107 | } 108 | 109 | .grey { 110 | background-color: #ccc; 111 | } 112 | 113 | .grey-text { 114 | color: #cccccc; 115 | } 116 | 117 | .white-text { 118 | color: #ffffff; 119 | } 120 | 121 | .orange-text { 122 | color: #e35d0e; 123 | } 124 | 125 | /* bootstrap overrides */ 126 | .navbar-brand > img { 127 | margin-top: -7pt; 128 | } 129 | 130 | .navbar { 131 | font-family: 'Lobster', cursive; 132 | } 133 | 134 | .jumbotron > h1 { 135 | font-family: 'Lobster', cursive; 136 | } 137 | 138 | .jumbotron { 139 | margin-bottom: 0px; 140 | background-image: url(../images/jumbotronbackground.jpg); 141 | background-position: 0% 25%; 142 | background-size: cover; 143 | background-repeat: no-repeat; 144 | color: white; 145 | /*text-shadow: black 0.3em 0.3em 0.3em;*/ 146 | } 147 | 148 | 149 | 150 | 151 | /* Responsive: Portrait tablets and up */ 152 | @media screen and (min-width: 768px) { 153 | .container { 154 | max-width: 730px; 155 | } 156 | 157 | /* Remove the padding we set earlier */ 158 | .header, 159 | .marketing, 160 | .footer { 161 | padding-left: 0; 162 | padding-right: 0; 163 | } 164 | /* Space out the masthead */ 165 | .header { 166 | margin-bottom: 30px; 167 | } 168 | /* Remove the bottom border on the jumbotron for visual effect */ 169 | .jumbotron { 170 | border-bottom: 0; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/views/user.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |

{{currentUser.name}}

8 |

{{currentUser.weight}} lbs

9 |

{{currentUser.age}} years old | {{currentUser.height}} " tall

10 |

Start Date: {{currentUser.startdate}}

11 |

Start Weight: {{currentUser.startweight}}

12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |

target weight: {{currentUser.target}} lbs

20 |

pounds lost: {{currentUser.poundslost}} lbs

21 |

pounds from target: {{currentUser.pft}} lbs

22 |

progress: {{currentUser.progress}}%

23 |

target start date: {{currentUser.targetstartdate | date:'MM/dd/yyyy'}}

24 |

target end date: {{currentUser.targetenddate | date:'MM/dd/yyyy'}}

25 |

days to target : {{currentUser.targetdays}} days

26 |

all time high: {{currentUser.ath}} lbs

27 |

all time low: {{currentUser.atl}} lbs

28 |

bmi: {{currentUser.bmi}}

29 |

weight class: {{currentUser.weightclass}}

30 |
31 | 32 | 33 |
34 | 35 | 36 | 37 |
38 |
39 | 42 | 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 | 53 |
54 |
55 | 57 |
58 |
59 | 60 |
61 | 62 |
63 |
64 |
65 | 66 | 67 | 68 |
69 |
70 | 71 |

WeighIns:

72 |
    73 |
  • {{weighin.date | date:'small'}}, {{weighin.weight}}lbs
  • 74 |
75 | 76 |
77 |
78 | 79 | 80 | 81 | 82 |
83 |
84 | 85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /app/scripts/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @ngdoc overview 4 | * @name weighInApp:routes 5 | * @description 6 | * # routes.js 7 | * 8 | * Configure routes for use with Angular, and apply authentication security 9 | * Add new routes using `yo angularfire:route` with the optional --auth-required flag. 10 | * 11 | * Any controller can be secured so that it will only load if user is logged in by 12 | * using `whenAuthenticated()` in place of `when()`. This requires the user to 13 | * be logged in to view this route, and adds the current user into the dependencies 14 | * which can be injected into the controller. If user is not logged in, the promise is 15 | * rejected, which is handled below by $routeChangeError 16 | * 17 | * Any controller can be forced to wait for authentication to resolve, without necessarily 18 | * requiring the user to be logged in, by adding a `resolve` block similar to the one below. 19 | * It would then inject `user` as a dependency. This could also be done in the controller, 20 | * but abstracting it makes things cleaner (controllers don't need to worry about auth state 21 | * or timing of displaying its UI components; it can assume it is taken care of when it runs) 22 | * 23 | * resolve: { 24 | * user: ['Auth', function(Auth) { 25 | * return Auth.$getAuth(); 26 | * }] 27 | * } 28 | * 29 | */ 30 | angular.module('weighInApp') 31 | 32 | /** 33 | * Adds a special `whenAuthenticated` method onto $routeProvider. This special method, 34 | * when called, invokes Auth.$requireAuth() service (see Auth.js). 35 | * 36 | * The promise either resolves to the authenticated user object and makes it available to 37 | * dependency injection (see AccountCtrl), or rejects the promise if user is not logged in, 38 | * forcing a redirect to the /login page 39 | */ 40 | .config(['$routeProvider', 'SECURED_ROUTES', function($routeProvider, SECURED_ROUTES) { 41 | // credits for this idea: https://groups.google.com/forum/#!msg/angular/dPr9BpIZID0/MgWVluo_Tg8J 42 | // unfortunately, a decorator cannot be use here because they are not applied until after 43 | // the .config calls resolve, so they can't be used during route configuration, so we have 44 | // to hack it directly onto the $routeProvider object 45 | $routeProvider.whenAuthenticated = function(path, route) { 46 | route.resolve = route.resolve || {}; 47 | route.resolve.user = ['Auth', function(Auth) { 48 | return Auth.$requireAuth(); 49 | }]; 50 | $routeProvider.when(path, route); 51 | SECURED_ROUTES[path] = true; 52 | return $routeProvider; 53 | }; 54 | }]) 55 | 56 | // configure views; whenAuthenticated adds a resolve method to ensure users authenticate 57 | // before trying to access that route 58 | .config(['$routeProvider', function($routeProvider) { 59 | $routeProvider 60 | .when('/', { 61 | templateUrl: 'views/main.html', 62 | controller: 'MainCtrl' 63 | }) 64 | 65 | .when('/chat', { 66 | templateUrl: 'views/chat.html', 67 | controller: 'ChatCtrl' 68 | }) 69 | .when('/login', { 70 | templateUrl: 'views/login.html', 71 | controller: 'LoginCtrl' 72 | }) 73 | .whenAuthenticated('/account', { 74 | templateUrl: 'views/account.html', 75 | controller: 'AccountCtrl' 76 | }) 77 | .when('/userlist', { 78 | templateUrl: 'views/userlist.html', 79 | controller: 'UserlistCtrl' 80 | }) 81 | .when('/users/:userName', { 82 | templateUrl: 'views/user.html', 83 | controller: 'UserlistCtrl' 84 | }) 85 | .when('/adduser', { 86 | templateUrl: 'views/adduser.html', 87 | controller: 'UserlistCtrl' 88 | }) 89 | .otherwise({redirectTo: '/'}); 90 | }]) 91 | 92 | /** 93 | * Apply some route security. Any route's resolve method can reject the promise with 94 | * "AUTH_REQUIRED" to force a redirect. This method enforces that and also watches 95 | * for changes in auth status which might require us to navigate away from a path 96 | * that we can no longer view. 97 | */ 98 | .run(['$rootScope', '$location', 'Auth', 'SECURED_ROUTES', 'loginRedirectPath', 99 | function($rootScope, $location, Auth, SECURED_ROUTES, loginRedirectPath) { 100 | // watch for login status changes and redirect if appropriate 101 | Auth.$onAuth(check); 102 | 103 | // some of our routes may reject resolve promises with the special {authRequired: true} error 104 | // this redirects to the login page whenever that is encountered 105 | $rootScope.$on('$routeChangeError', function(e, next, prev, err) { 106 | if( err === 'AUTH_REQUIRED' ) { 107 | $location.path(loginRedirectPath); 108 | } 109 | }); 110 | 111 | function check(user) { 112 | if( !user && authRequired($location.path()) ) { 113 | $location.path(loginRedirectPath); 114 | } 115 | } 116 | 117 | function authRequired(path) { 118 | return SECURED_ROUTES.hasOwnProperty(path); 119 | } 120 | } 121 | ]) 122 | 123 | // used by route security 124 | .constant('SECURED_ROUTES', {}); 125 | -------------------------------------------------------------------------------- /app/views/adduser.html: -------------------------------------------------------------------------------- 1 |

Create User

2 | 3 |
4 |
5 | 6 |
7 | 9 |
10 |
11 | 12 |
13 | 14 |
15 | 17 |
18 |
19 | 20 | 21 | 22 |
23 | 24 |
25 | 27 |
28 |
29 | 30 |
31 | 32 |
33 | 35 |
36 |
37 | 38 |
39 | 40 |
41 | 43 |
44 |
45 | 46 |
47 | 48 |
49 | 51 |
52 |
53 | 54 |

Choose an avatar:

55 | 56 | 57 | 58 |
59 | 64 |
65 |
66 | 71 |
72 |
73 | 78 |
79 | 80 |
81 | 86 |
87 | 88 |
89 | 94 |
95 | 96 |
97 | 102 |
103 | 104 |
105 | 110 |
111 | 112 | 113 |
114 | 115 |
116 |
117 | 118 |
119 |
120 |
121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 |
27 | 65 |
66 | 67 |
68 |
69 |
70 | 71 | 76 | 77 | 78 | 79 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /app/scripts/controllers/userlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name weighInApp.controller:UserlistCtrl 6 | * @description 7 | * # UserlistCtrl 8 | * Controller of the weighInApp 9 | */ 10 | angular.module('weighInApp').config(['ChartJsProvider', function (ChartJsProvider) { 11 | // Configure all charts 12 | ChartJsProvider.setOptions({ 13 | chartColors: ['#e35d0e', '#169DFF', '#FFB900', '#001EA5', '#A92A90'], // orange, cyan, yellow, navy, purple 14 | responsive: true 15 | }); 16 | // Configure all line charts 17 | ChartJsProvider.setOptions('line', { 18 | showLines: true, 19 | fill: true, 20 | defaultFontColor: '#A92A90' 21 | }); 22 | }]) 23 | .controller('UserlistCtrl', ['$scope', 'users', '$routeParams', '$location', 24 | function ($scope, users, $routeParams, $location ) { 25 | 26 | //console.log("UserlistCtrl :: routeParams: " + $routeParams); 27 | $scope.users = users; 28 | 29 | //Current Page routeParams 30 | $scope.userName = $routeParams.userName; 31 | 32 | 33 | $scope.users.$loaded() 34 | .then(function(){ 35 | angular.forEach(users, function(users) { 36 | var theUser = users; 37 | var theUserID = users.$id; 38 | 39 | //console.log(">> theUserID: " + theUserID); 40 | // if there is a match in the route param 41 | if ( $scope.userName == users.name) { 42 | // we populate the currentUser with theUser data 43 | $scope.currentUser = theUser; 44 | // console.log('>> match ' + $scope.currentUser); 45 | 46 | $scope.labels = []; 47 | $scope.data = []; 48 | 49 | // Lets get Current Weight too 50 | angular.forEach(theUser.weighins, function (weighins) { 51 | theUser.currentWeight = weighins.weight; 52 | theUser.currentDate = new Date(weighins.date); 53 | console.log("cd: " + theUser.currentDate); 54 | theUser.bmi = $scope.getUserBMI(theUser.height, theUser.weight ); 55 | theUser.weightclass = $scope.getUserWeightClass(theUser.bmi); 56 | // get pounds from target 57 | theUser.pft = $scope.roundNum(theUser.currentWeight - theUser.target, 2), 58 | // get days to target 59 | theUser.targetdays = $scope.getUpdatedTargetDays($scope.currentUser.targetstartdate, $scope.currentUser.targetenddate); 60 | 61 | $scope.labels.push(theUser.currentDate); 62 | $scope.data.push(theUser.currentWeight); 63 | //$scope.colors = ["rgba(227,93,14,0.5)","rgba(22,157,255,0.7)","rgba(255,185,0,0.5)"]; 64 | 65 | $scope.colors = [ 66 | { 67 | backgroundColor: "rgba(227,93,14,0.5)", 68 | pointBackgroundColor: "rgba(255,185,0,0.7)", // only color that works 69 | pointHoverBackgroundColor: "rgba(255,185,0, 0.8)", 70 | borderColor: "rgba(0,30,165, 1)", 71 | pointBorderColor: '#A92A90', 72 | pointHoverBorderColor: "rgba(169,42,144, 1)" 73 | },"rgba(169,42,144,0.5)","#001EA5","rgb(233,177,69)" 74 | ], 75 | 76 | $scope.options = { 77 | borderJoinStyle: 'miter', 78 | pointBorderWidth: 3, 79 | scales: { 80 | yAxes: [{ 81 | ticks: { 82 | beginAtZero:true 83 | }, 84 | stacked: true, 85 | display: true, 86 | position: 'left' 87 | }], 88 | xAxes: [{ 89 | display: true, 90 | position: 'bottom', 91 | fontColor: '#A92A90' 92 | }] 93 | } 94 | }; 95 | 96 | //console.log("2.theUser.currentWeight = " + theUser.currentWeight + " " + theUser.currentDate); 97 | }); 98 | } 99 | }) 100 | }); 101 | 102 | //methods 103 | // ADD USER 104 | $scope.addUser = function() { 105 | // Get Date and Time 106 | 107 | /* var currentDate = new Date(); 108 | var dd = currentDate.getDate(); 109 | var mm = currentDate.getMonth()+1; //January is 0! 110 | var yyyy = currentDate.getFullYear(); 111 | if(dd<10) { 112 | dd='0'+dd 113 | } 114 | if(mm<10) { 115 | mm='0'+mm 116 | } 117 | currentDate = yyyy+'/'+mm+'/'+dd;*/ 118 | 119 | 120 | // $scope.date = currentDate.toLocaleDateString(); 121 | // $scope.time = currentDate.toLocaleTimeString(); 122 | // $scope.currentTime = String($scope.date + " " + $scope.time); 123 | 124 | 125 | //NEW DATE 126 | var currentDate = new Date() 127 | $scope.currentTime = currentDate; 128 | console.log($scope.currentTime); 129 | // Set the BMI 130 | 131 | $scope.user.bmi = $scope.getUserBMI($scope.user.height, $scope.user.weight ); 132 | $scope.user.weightclass = $scope.getUserWeightClass($scope.user.bmi); 133 | 134 | $scope.users.$add({ 135 | name: $scope.user.name, 136 | age: $scope.user.age, 137 | height: $scope.user.height, 138 | weight: $scope.user.weight, 139 | startweight: $scope.user.weight, 140 | ath: $scope.user.weight, 141 | atl: $scope.user.weight, 142 | target: $scope.user.target, 143 | progress: Number(0), 144 | poundslost: Number(0), 145 | pft: $scope.roundNum($scope.user.weight - $scope.user.target, 2), 146 | spft: $scope.roundNum($scope.user.weight - $scope.user.target, 2), 147 | startdate: String($scope.currentTime), 148 | targetstartdate: String($scope.currentTime), 149 | targetdays: $scope.user.daystotarget, 150 | targetenddate: String($scope.getTargetEndDate($scope.user.daystotarget)), 151 | bmi: $scope.user.bmi, 152 | weightclass: $scope.user.weightclass, 153 | avatar: $scope.user.avatar, 154 | weighins:[{date: String($scope.currentTime) , weight: $scope.user.weight}] 155 | }); 156 | 157 | $location.path('/userlist'); 158 | }; 159 | 160 | // REMOVE USER 161 | $scope.removeUser = function (obj) { 162 | // console.log("removeUser: " + obj.$id); 163 | $scope.users.$remove(obj); 164 | }; 165 | 166 | // SAVE USER 167 | $scope.saveUser = function (obj) { 168 | 169 | var currentDate = new Date(); 170 | $scope.currentTime = currentDate; 171 | //$scope.currentTime = String($scope.date + " " + $scope.time); 172 | //console.log($scope.currentTime); 173 | 174 | var currentWeighin = { 175 | date: String($scope.currentTime), weight: obj.weight 176 | }; 177 | 178 | if ($scope.currentUser.$id == obj.$id) { 179 | $scope.currentUser.weighins.push(currentWeighin); 180 | 181 | // update current weight bmi weightclass update pft 182 | $scope.currentUser.weight = obj.weight; 183 | if($scope.currentUser.weight > $scope.currentUser.ath) {$scope.currentUser.ath = $scope.currentUser.weight}; 184 | if($scope.currentUser.weight < $scope.currentUser.atl) {$scope.currentUser.atl = $scope.currentUser.weight}; 185 | $scope.currentUser.poundslost = $scope.roundNum($scope.currentUser.startweight - $scope.currentUser.weight, 2); 186 | if($scope.currentUser.poundslost < 0) {$scope.currentUser.poundslost = 0}; 187 | $scope.currentUser.pft = $scope.roundNum($scope.currentUser.weight - $scope.currentUser.target, 2); 188 | $scope.currentUser.progress = Number(Math.floor(100 - (($scope.currentUser.pft / $scope.currentUser.spft) * 100))); 189 | if($scope.currentUser.progress < 0) {$scope.currentUser.progress = 0}; 190 | $scope.currentUser.bmi = $scope.getUserBMI($scope.currentUser.height, $scope.currentUser.weight); 191 | $scope.currentUser.weightclass = $scope.getUserWeightClass($scope.currentUser.bmi); 192 | $scope.currentUser.targetdays = $scope.getUpdatedTargetDays($scope.currentUser.targetstartdate, $scope.currentUser.targetenddate); 193 | 194 | 195 | // update chart 196 | $scope.labels.push(String($scope.currentTime)); 197 | $scope.data.push(obj.weight); 198 | // Save to Firebase 199 | $scope.users.$save($scope.currentUser) 200 | console.log("saveuser: currentUser.weighins: " + $scope.currentUser.weighins); 201 | } else { 202 | console.log('error'); 203 | } 204 | 205 | }; 206 | 207 | // COMMON 208 | 209 | $scope.getUserBMI = function(h,w) { 210 | // metric conversion for weight 211 | var wik = w * 0.45; 212 | var him = h * 0.025; 213 | var him2 = him * him; 214 | //use roundnum func for custom decimals (val, decimals) 215 | $scope.bmi = $scope.roundNum(wik/him2, 2); 216 | 217 | return $scope.bmi; 218 | 219 | 220 | }; 221 | 222 | $scope.getUserWeightClass = function(n) { 223 | 224 | //18 and under is underweight 225 | if (n < 18.5) { 226 | $scope.weightclass = String("Underweight"); 227 | return $scope.weightclass; 228 | 229 | } else if (n > 18.5 && n <= 24.9) { 230 | //18.5 - 24.9 normal 231 | $scope.weightclass = String("Normal"); 232 | return $scope.weightclass; 233 | 234 | } else if (n > 24.9 && n <= 29.9) { 235 | // 25-29.9 overweight 236 | $scope.weightclass = String("Overweight"); 237 | return $scope.weightclass; 238 | 239 | } else if (n > 29.9 && n <= 39.9) { 240 | // 30 -39.9 obese 241 | $scope.weightclass = String("Obese"); 242 | return $scope.weightclass; 243 | 244 | } else if (n > 40) { 245 | //40+ morbidly obese 246 | $scope.weightclass = String("Morbidly obese"); 247 | return $scope.weightclass; 248 | 249 | } else { 250 | return; 251 | } 252 | 253 | }; 254 | 255 | $scope.roundNum = function (value, decimals) { 256 | return Number(Math.round(value+'e'+decimals)+'e-'+decimals); 257 | }; 258 | 259 | $scope.getTargetEndDate = function (num) { 260 | var days = num; 261 | var dat = new Date(); 262 | dat.setDate(dat.getDate() + days); 263 | return dat; 264 | } 265 | 266 | $scope.getUpdatedTargetDays = function (date1, date2) { 267 | var dat1 = new Date(date1); 268 | var dat2 = new Date(date2); 269 | var timeDiff = Math.abs(dat2.getTime() - dat1.getTime()); 270 | $scope.dayDifference = Math.ceil(timeDiff / (1000 * 3600 * 24)); 271 | return $scope.dayDifference; 272 | } 273 | 274 | 275 | 276 | }]); 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2016-11-26 using generator-angularfire 1.0.0 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Configurable paths for the application 19 | var appConfig = { 20 | app: require('./bower.json').appPath || 'app', 21 | dist: 'dist' 22 | }; 23 | 24 | // Define the configuration for all the tasks 25 | grunt.initConfig({ 26 | 27 | // Project settings 28 | yeoman: appConfig, 29 | 30 | // Watches files for changes and runs tasks based on the changed files 31 | watch: { 32 | bower: { 33 | files: ['bower.json'], 34 | tasks: ['wiredep'] 35 | }, 36 | js: { 37 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 38 | tasks: ['newer:jshint:all'], 39 | options: { 40 | livereload: '<%= connect.options.livereload %>' 41 | } 42 | }, 43 | jsTest: { 44 | files: ['test/spec/{,*/}*.js'], 45 | tasks: ['newer:jshint:test', 'karma'] 46 | }, 47 | styles: { 48 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 49 | tasks: ['newer:copy:styles', 'autoprefixer'] 50 | }, 51 | gruntfile: { 52 | files: ['Gruntfile.js'] 53 | }, 54 | livereload: { 55 | options: { 56 | livereload: '<%= connect.options.livereload %>' 57 | }, 58 | files: [ 59 | '<%= yeoman.app %>/{,*/}*.html', 60 | '.tmp/styles/{,*/}*.css', 61 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 62 | ] 63 | } 64 | }, 65 | 66 | // The actual grunt server settings 67 | connect: { 68 | options: { 69 | port: 9000, 70 | // Change this to '0.0.0.0' to access the server from outside. 71 | hostname: 'localhost', 72 | livereload: 35729 73 | }, 74 | livereload: { 75 | options: { 76 | open: true, 77 | middleware: function (connect) { 78 | return [ 79 | connect.static('.tmp'), 80 | connect().use( 81 | '/bower_components', 82 | connect.static('./bower_components') 83 | ), 84 | connect().use( 85 | '/app/styles', 86 | connect.static('./app/styles') 87 | ), 88 | connect.static(appConfig.app) 89 | ]; 90 | } 91 | } 92 | }, 93 | test: { 94 | options: { 95 | port: 9001, 96 | middleware: function (connect) { 97 | return [ 98 | connect.static('.tmp'), 99 | connect.static('test'), 100 | connect().use( 101 | '/bower_components', 102 | connect.static('./bower_components') 103 | ), 104 | connect.static(appConfig.app) 105 | ]; 106 | } 107 | } 108 | }, 109 | dist: { 110 | options: { 111 | open: true, 112 | base: '<%= yeoman.dist %>' 113 | } 114 | } 115 | }, 116 | 117 | // Make sure code styles are up to par and there are no obvious mistakes 118 | jshint: { 119 | options: { 120 | jshintrc: '.jshintrc', 121 | reporter: require('jshint-stylish') 122 | }, 123 | all: { 124 | src: [ 125 | 'Gruntfile.js', 126 | '<%= yeoman.app %>/scripts/{,*/}*.js' 127 | ] 128 | }, 129 | test: { 130 | options: { 131 | jshintrc: 'test/.jshintrc' 132 | }, 133 | src: ['test/spec/{,*/}*.js'] 134 | } 135 | }, 136 | 137 | // Empties folders to start fresh 138 | clean: { 139 | dist: { 140 | files: [{ 141 | dot: true, 142 | src: [ 143 | '.tmp', 144 | '<%= yeoman.dist %>/{,*/}*', 145 | '!<%= yeoman.dist %>/.git{,*/}*' 146 | ] 147 | }] 148 | }, 149 | server: '.tmp' 150 | }, 151 | 152 | // Add vendor prefixed styles 153 | autoprefixer: { 154 | options: { 155 | browsers: ['last 1 version'] 156 | }, 157 | server: { 158 | options: { 159 | map: true, 160 | }, 161 | files: [{ 162 | expand: true, 163 | cwd: '.tmp/styles/', 164 | src: '{,*/}*.css', 165 | dest: '.tmp/styles/' 166 | }] 167 | }, 168 | dist: { 169 | files: [{ 170 | expand: true, 171 | cwd: '.tmp/styles/', 172 | src: '{,*/}*.css', 173 | dest: '.tmp/styles/' 174 | }] 175 | } 176 | }, 177 | 178 | // Automatically inject Bower components into the app 179 | wiredep: { 180 | options: { 181 | cwd: '' 182 | }, 183 | app: { 184 | src: ['<%= yeoman.app %>/index.html'], 185 | ignorePath: /\.\.\// 186 | }, 187 | test: { 188 | devDependencies: true, 189 | src: '<%= karma.unit.configFile %>', 190 | ignorePath: /\.\.\//, 191 | fileTypes:{ 192 | js: { 193 | block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi, 194 | detect: { 195 | js: /'(.*\.js)'/gi 196 | }, 197 | replace: { 198 | js: '\'{{filePath}}\',' 199 | } 200 | } 201 | } 202 | } 203 | }, 204 | 205 | // Renames files for browser caching purposes 206 | filerev: { 207 | dist: { 208 | src: [ 209 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 210 | '<%= yeoman.dist %>/styles/{,*/}*.css', 211 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 212 | '<%= yeoman.dist %>/styles/fonts/*' 213 | ] 214 | } 215 | }, 216 | 217 | // Reads HTML for usemin blocks to enable smart builds that automatically 218 | // concat, minify and revision files. Creates configurations in memory so 219 | // additional tasks can operate on them 220 | useminPrepare: { 221 | html: '<%= yeoman.app %>/index.html', 222 | options: { 223 | dest: '<%= yeoman.dist %>', 224 | flow: { 225 | html: { 226 | steps: { 227 | js: ['concat', 'uglifyjs'], 228 | css: ['cssmin'] 229 | }, 230 | post: {} 231 | } 232 | } 233 | } 234 | }, 235 | 236 | // Performs rewrites based on filerev and the useminPrepare configuration 237 | usemin: { 238 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 239 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 240 | options: { 241 | assetsDirs: [ 242 | '<%= yeoman.dist %>', 243 | '<%= yeoman.dist %>/images', 244 | '<%= yeoman.dist %>/styles' 245 | ] 246 | } 247 | }, 248 | 249 | // The following *-min tasks will produce minified files in the dist folder 250 | // By default, your `index.html`'s will take care of 251 | // minification. These next options are pre-configured if you do not wish 252 | // to use the Usemin blocks. 253 | // cssmin: { 254 | // dist: { 255 | // files: { 256 | // '<%= yeoman.dist %>/styles/main.css': [ 257 | // '.tmp/styles/{,*/}*.css' 258 | // ] 259 | // } 260 | // } 261 | // }, 262 | // uglify: { 263 | // dist: { 264 | // files: { 265 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 266 | // '<%= yeoman.dist %>/scripts/scripts.js' 267 | // ] 268 | // } 269 | // } 270 | // }, 271 | // concat: { 272 | // dist: {} 273 | // }, 274 | 275 | imagemin: { 276 | dist: { 277 | files: [{ 278 | expand: true, 279 | cwd: '<%= yeoman.app %>/images', 280 | src: '{,*/}*.{png,jpg,jpeg,gif}', 281 | dest: '<%= yeoman.dist %>/images' 282 | }] 283 | } 284 | }, 285 | 286 | svgmin: { 287 | dist: { 288 | files: [{ 289 | expand: true, 290 | cwd: '<%= yeoman.app %>/images', 291 | src: '{,*/}*.svg', 292 | dest: '<%= yeoman.dist %>/images' 293 | }] 294 | } 295 | }, 296 | 297 | htmlmin: { 298 | dist: { 299 | options: { 300 | collapseWhitespace: true, 301 | conservativeCollapse: true, 302 | collapseBooleanAttributes: true, 303 | removeCommentsFromCDATA: true, 304 | removeOptionalTags: true 305 | }, 306 | files: [{ 307 | expand: true, 308 | cwd: '<%= yeoman.dist %>', 309 | src: ['*.html', 'views/{,*/}*.html'], 310 | dest: '<%= yeoman.dist %>' 311 | }] 312 | } 313 | }, 314 | 315 | // ng-annotate tries to make the code safe for minification automatically 316 | // by using the Angular long form for dependency injection. 317 | ngAnnotate: { 318 | dist: { 319 | files: [{ 320 | expand: true, 321 | cwd: '.tmp/concat/scripts', 322 | src: '*.js', 323 | dest: '.tmp/concat/scripts' 324 | }] 325 | } 326 | }, 327 | 328 | // Replace Google CDN references 329 | cdnify: { 330 | dist: { 331 | html: ['<%= yeoman.dist %>/*.html'] 332 | } 333 | }, 334 | 335 | // Copies remaining files to places other tasks can use 336 | copy: { 337 | dist: { 338 | files: [{ 339 | expand: true, 340 | dot: true, 341 | cwd: '<%= yeoman.app %>', 342 | dest: '<%= yeoman.dist %>', 343 | src: [ 344 | '*.{ico,png,txt}', 345 | '.htaccess', 346 | '*.html', 347 | 'views/{,*/}*.html', 348 | 'images/{,*/}*.{webp}', 349 | 'styles/fonts/{,*/}*.*' 350 | ] 351 | }, { 352 | expand: true, 353 | cwd: '.tmp/images', 354 | dest: '<%= yeoman.dist %>/images', 355 | src: ['generated/*'] 356 | }, { 357 | expand: true, 358 | cwd: 'bower_components/bootstrap/dist', 359 | src: 'fonts/*', 360 | dest: '<%= yeoman.dist %>' 361 | }] 362 | }, 363 | styles: { 364 | expand: true, 365 | cwd: '<%= yeoman.app %>/styles', 366 | dest: '.tmp/styles/', 367 | src: '{,*/}*.css' 368 | } 369 | }, 370 | 371 | // Run some tasks in parallel to speed up the build process 372 | concurrent: { 373 | server: [ 374 | 'copy:styles' 375 | ], 376 | test: [ 377 | 'copy:styles' 378 | ], 379 | dist: [ 380 | 'copy:styles', 381 | // 'imagemin', 382 | 'svgmin' 383 | ] 384 | }, 385 | 386 | // Test settings 387 | karma: { 388 | unit: { 389 | configFile: 'test/karma.conf.js', 390 | singleRun: true 391 | } 392 | } 393 | }); 394 | 395 | 396 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 397 | if (target === 'dist') { 398 | return grunt.task.run(['build', 'connect:dist:keepalive']); 399 | } 400 | 401 | grunt.task.run([ 402 | 'clean:server', 403 | 'wiredep', 404 | 'concurrent:server', 405 | 'autoprefixer:server', 406 | 'connect:livereload', 407 | 'watch' 408 | ]); 409 | }); 410 | 411 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { 412 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 413 | grunt.task.run(['serve:' + target]); 414 | }); 415 | 416 | grunt.registerTask('test', [ 417 | 'clean:server', 418 | 'wiredep', 419 | 'concurrent:test', 420 | 'autoprefixer', 421 | 'connect:test', 422 | 'karma' 423 | ]); 424 | 425 | grunt.registerTask('build', [ 426 | 'clean:dist', 427 | 'wiredep', 428 | 'useminPrepare', 429 | 'concurrent:dist', 430 | 'autoprefixer', 431 | 'concat', 432 | 'ngAnnotate', 433 | 'copy:dist', 434 | 'cdnify', 435 | 'cssmin', 436 | 'uglify', 437 | 'filerev', 438 | 'usemin', 439 | 'htmlmin' 440 | ]); 441 | 442 | grunt.registerTask('default', [ 443 | 'newer:jshint', 444 | 'test', 445 | 'build' 446 | ]); 447 | }; 448 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 131 | 132 | 133 | 134 | 135 | $PROJECT_DIR$/Gruntfile.js 136 | 137 | 138 | false 139 | 140 | 141 | true 142 | 143 | 144 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 180 | 181 | 182 | 183 | 186 | 187 | 190 | 191 | 192 | 193 | 196 | 197 | 200 | 201 | 204 | 205 | 206 | 207 | 210 | 211 | 214 | 215 | 218 | 219 | 220 | 221 | 224 | 225 | 228 | 229 | 232 | 233 | 236 | 237 | 238 | 239 | 242 | 243 | 246 | 247 | 250 | 251 | 254 | 255 | 256 | 257 | 260 | 261 | 264 | 265 | 268 | 269 | 272 | 273 | 274 | 275 | 278 | 279 | 282 | 283 | 286 | 287 | 290 | 291 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 336 | 337 | 338 | 339 | 340 | 341 | true 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 1480205155000 355 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 389 | 392 | 393 | 394 | 396 | 397 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | --------------------------------------------------------------------------------