├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .yo-rc.json ├── LICENSE ├── README.md ├── app ├── index.js └── templates │ ├── .bowerrc │ ├── .editorconfig │ ├── .gitignore │ ├── .jscsrc │ ├── .jshintrc │ ├── _README.md │ ├── _bower.json │ ├── _package.json │ ├── client │ └── source │ │ ├── app │ │ ├── app.module.js │ │ ├── core │ │ │ ├── _config.js │ │ │ ├── core.module.js │ │ │ ├── core.styl │ │ │ ├── error.constant.js │ │ │ ├── event.constant.js │ │ │ ├── focus-me.directive.js │ │ │ ├── loading-button.directive.js │ │ │ ├── production │ │ │ │ ├── _production.config.js │ │ │ │ └── exception-handler.provider.js │ │ │ ├── resolve.service.js │ │ │ ├── user.service.js │ │ │ ├── util.service.js │ │ │ └── validate-number.directive.js │ │ ├── dashboard │ │ │ ├── dashboard.controller.js │ │ │ ├── dashboard.jade │ │ │ ├── dashboard.module.js │ │ │ ├── dashboard.route.js │ │ │ └── dashboard.styl │ │ ├── helper │ │ │ ├── ajax-error-handler.service.js │ │ │ ├── helper.module.js │ │ │ ├── logger.service.js │ │ │ └── router-helper.provider.js │ │ ├── home │ │ │ ├── _home.jade │ │ │ ├── home.module.js │ │ │ ├── home.route.js │ │ │ └── home.styl │ │ ├── layout │ │ │ ├── 404.jade │ │ │ ├── _header.jade │ │ │ ├── breadcrumb.controller.js │ │ │ ├── breadcrumb.jade │ │ │ ├── footer.controller.js │ │ │ ├── footer.jade │ │ │ ├── header.controller.js │ │ │ ├── layout.module.js │ │ │ ├── layout.route.js │ │ │ ├── layout.styl │ │ │ ├── sidebar.controller.js │ │ │ └── sidebar.jade │ │ ├── login │ │ │ ├── login.controller.js │ │ │ ├── login.jade │ │ │ ├── login.module.js │ │ │ ├── login.route.js │ │ │ └── login.styl │ │ └── phone │ │ │ ├── phone.add.controller.js │ │ │ ├── phone.add.jade │ │ │ ├── phone.controller.js │ │ │ ├── phone.detail.controller.js │ │ │ ├── phone.detail.jade │ │ │ ├── phone.form.controller.js │ │ │ ├── phone.form.directive.js │ │ │ ├── phone.form.jade │ │ │ ├── phone.jade │ │ │ ├── phone.module.js │ │ │ ├── phone.route.js │ │ │ ├── phone.service.js │ │ │ └── phone.styl │ │ ├── images │ │ └── layout │ │ │ ├── bg.png │ │ │ └── logo.png │ │ ├── index.jade │ │ ├── styles │ │ ├── common.styl │ │ ├── core │ │ │ ├── animation.styl │ │ │ ├── base.styl │ │ │ └── fonts.styl │ │ ├── dashboard │ │ │ └── base.styl │ │ ├── home │ │ │ └── base.styl │ │ ├── layout │ │ │ ├── breadcrumb.styl │ │ │ ├── content.styl │ │ │ ├── footer.styl │ │ │ ├── header.styl │ │ │ ├── loading.styl │ │ │ └── sidebar.styl │ │ ├── login │ │ │ └── base.styl │ │ ├── mixin.styl │ │ ├── phone │ │ │ ├── base.styl │ │ │ └── phone.form.styl │ │ ├── responsive.styl │ │ └── variable.styl │ │ └── test │ │ ├── e2e │ │ ├── helper.js │ │ ├── mocks │ │ │ ├── e2e.config.js │ │ │ ├── e2e.data.js │ │ │ ├── e2e.module.js │ │ │ ├── e2e.phone.js │ │ │ └── e2e.user.js │ │ ├── screenshots │ │ │ └── .gitkeep │ │ └── specs │ │ │ ├── home.spec.js │ │ │ └── login.spec.js │ │ └── unit │ │ └── specs │ │ └── focus-me.directive.spec.js │ ├── gulp │ ├── gulp.config.js │ ├── index.js │ ├── karma.conf.js │ ├── protractor.conf.js │ └── tasks │ │ ├── _serve.task.js │ │ ├── build.task.js │ │ ├── clean.task.js │ │ ├── copy.task.js │ │ ├── css.task.js │ │ ├── js-style-check.task.js │ │ ├── template.task.js │ │ ├── test.task.js │ │ └── version.task.js │ ├── gulpfile.js │ └── server │ └── .gitkeep ├── change-env.js ├── html5-mode-patch.diff ├── package.json └── publish-to-gh-pages.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | .DS_Store 3 | # node module 4 | node_modules/ 5 | npm-debug.log 6 | # files need to be ignored in tempaltes folder 7 | app/templates/client/source/vendor 8 | app/templates/client/build 9 | app/templates/client/source/test/e2e/screenshots/* 10 | !app/templates/client/source/test/e2e/screenshots/.gitkeep 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "strict": true 17 | } 18 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-generator": {} 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 马斯特 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-aio-angular 2 | 3 | All In One [Yeoman](http://yeoman.io) generator for AngularJS 1.3, using `gulp` and `ui-router` with `material design`, based on John Papa's [generator-hottowel](https://github.com/johnpapa/generator-hottowel) and [angular-styleguide](https://github.com/johnpapa/angular-styleguide). 4 | 5 | Wanna use **Webpack + ES6**? Check the [angular1-webpack-starter](https://github.com/PinkyJie/angular1-webpack-starter) project. 6 | 7 | > Pure front-end implementation, all API interaction are mocked using [angular-mocks](https://docs.angularjs.org/api/ngMock). The [server folder](app/templates/server) is just a placeholder here, you can use any back-end technique. 8 | 9 | ## Preview 10 | 11 | Check out a [demo site](http://pinkyjie.com/generator-aio-angular/#/) generated by this generator. 12 | 13 | > The dome site is a pure front-end implementation, so you can use any email/password to login, see [mock file](app/templates/client/source/test/e2e/mocks/e2e.user.js#L23) for detail. It is hosted on Github pages, no back-end support, so we use `#` style URL. 14 | 15 | ## Feature 16 | 17 | * Material Design 18 | * Using [LumX](http://ui.lumapps.com/). 19 | * Why not [Angular Material](https://material.angularjs.org)? Many common used components are missing, for exmaple, dropdown menu. 20 | * Why not [Bootstrap Material](https://mdbootstrap.com/)? Many components are not implemented, not pure material design. 21 | * Flex Layout 22 | * Using flex layout for main layout and many other places. 23 | * Responsive 24 | * Support mutiple devices with different screen size. 25 | * Easy responsive implementation, very convenient to support small screen devices. (see [responsive.styl](app/templates/client/source/styles/responsive.styl)) 26 | * Animation 27 | * Using [animate.css](https://daneden.github.io/animate.css/). 28 | * All the animation defined by `animate.css` can be used directly as keyframe animation. (see [content.styl](app/templates/client/source/styles/layout/content.styl#L28)) 29 | * Splited Gulp Tasks 30 | * Gulp tasks are splited in different files by category. (see [gulp folder](app/templates/gulp)) 31 | * More understandable router design 32 | * Using [ui-router](https://github.com/angular-ui/ui-router) for main layout. (see [layout.route.js](app/templates/client/source/app/layout/layout.route.js)) 33 | * Easy implementation for Sidebar Navigation and Breadcrumb 34 | * See [sidebar.controller.js](app/templates/client/source/app/layout/sidebar.controller.js) and [breadcrumb.controller.js](app/templates/client/source/app/layout/breadcrumb.controller.js). 35 | 36 | ## Getting Started 37 | 38 | ```bash 39 | npm install -g yo 40 | npm install -g generator-aio-angular 41 | yo aio-angular 42 | ``` 43 | 44 | ## How to do development 45 | Many files(prefixed by `_`) under `app/templates` folder include `<%= appNmae %>` tag which needs to be replaced by Yeoman, it's not very convenient to do development under `app/templates` folder. That's the reason why I add a script called `change-env.js`. 46 | 47 | * make `app/templates` ready for developent: 48 | ```bash 49 | npm run-script env dev 50 | ``` 51 | > This will rename all files prefixed by `_` to normal name and replace the placeholder tag to normal content. 52 | 53 | * change back from development: 54 | ```bash 55 | npm run-script env prod 56 | ``` 57 | 58 | ## Blog Posts related(written in Chinese) 59 | * [相关博文](http://pinkyjie.com/tags/generator-aio-angular/) 60 | 61 | ## Future Plan 62 | Check the [issues](https://github.com/PinkyJie/generator-aio-angular/issues/created_by/PinkyJie) 63 | 64 | ## License 65 | 66 | MIT 67 | 68 | 69 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var generators = require('yeoman-generator'); 3 | var chalk = require('chalk'); 4 | var yosay = require('yosay'); 5 | var path = require('path'); 6 | var _ = require('underscore.string'); 7 | 8 | module.exports = generators.Base.extend({ 9 | constructor: function() { 10 | generators.Base.apply(this, arguments); 11 | this.argument('appName', { 12 | type: String, 13 | required: false 14 | }); 15 | }, 16 | 17 | prompting: function() { 18 | var done = this.async(); 19 | 20 | // Have Yeoman greet the user. 21 | this.log(yosay( 22 | 'Welcome to the excellent ' + chalk.red('AioAngular') + ' generator!' 23 | )); 24 | 25 | var prompts = [{ 26 | type: 'input', 27 | name: 'appName', 28 | message: 'What would you like to name your app?', 29 | default: this.name || path.basename(process.cwd()) 30 | }]; 31 | 32 | this.prompt(prompts, function(props) { 33 | this.appName = props.appName; 34 | done(); 35 | }.bind(this)); 36 | }, 37 | 38 | configuring: { 39 | enforceFolderName: function() { 40 | // If the appName dose not equal to the current folder name, 41 | // then change the destinationRoot to the `appName` folder. 42 | var currentFolderTree = this.destinationRoot().split(path.sep); 43 | if (this.appName !== currentFolderTree[currentFolderTree.length - 1]) { 44 | this.destinationRoot(this.appName); 45 | } 46 | this.config.save(); 47 | } 48 | }, 49 | 50 | writing: { 51 | templateFiles: function() { 52 | var context = { 53 | 'appName': this.appName, 54 | 'appDesc': _(this.appName).humanize().titleize().value() 55 | }; 56 | var fileList = [ 57 | '_bower.json', 58 | '_package.json', 59 | '_README.md', 60 | 'client/source/app/core/_config.js', 61 | 'client/source/app/layout/_header.jade', 62 | 'client/source/app/home/_home.jade', 63 | 'gulp/tasks/_serve.task.js' 64 | ]; 65 | var targetName; 66 | for (var i = 0; i < fileList.length; i++) { 67 | targetName = fileList[i].replace('_', ''); 68 | this.fs.copyTpl( 69 | this.templatePath(fileList[i]), 70 | this.destinationPath(targetName), 71 | context 72 | ); 73 | } 74 | }, 75 | 76 | projectFiles: function() { 77 | this.fs.copy( 78 | this.templatePath('**/*'), 79 | this.destinationPath(''), 80 | { 81 | globOptions: { 82 | // dot: true, 83 | ignore: [ 84 | this.templatePath('client/build/**/*'), 85 | this.templatePath('node_modules/**/*'), 86 | this.templatePath('client/source/vendor/**/*'), 87 | this.templatePath('**/_*'), 88 | ] 89 | } 90 | } 91 | ); 92 | this.fs.copy( 93 | this.templatePath('.*'), 94 | this.destinationRoot() 95 | ); 96 | this.fs.copy( 97 | this.templatePath('server/.*'), 98 | this.destinationPath('server') 99 | ); 100 | } 101 | }, 102 | 103 | install: function() { 104 | // this will run `bower install` automatically as configured in package.json 105 | this.log('Install npm and bower package...'); 106 | this.npmInstall(); 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /app/templates/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/source/vendor" 3 | } 4 | -------------------------------------------------------------------------------- /app/templates/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /app/templates/.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | .DS_Store 3 | # node dependencies 4 | node_modules 5 | npm-debug.log 6 | # bower dependencies 7 | client/source/vendor 8 | 9 | # build file 10 | client/build 11 | 12 | # test folder 13 | client/source/test/e2e/screenshots/* 14 | !client/source/test/e2e/screenshots/.gitkeep 15 | client/source/test/unit/results 16 | test-results.xml 17 | -------------------------------------------------------------------------------- /app/templates/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": ["node_modules/**", "bower_components/**"], 3 | 4 | "requireCurlyBraces": [ 5 | "if", 6 | "else", 7 | "for", 8 | "while", 9 | "do", 10 | "try", 11 | "catch" 12 | ], 13 | "requireOperatorBeforeLineBreak": true, 14 | "requireCamelCaseOrUpperCaseIdentifiers": true, 15 | "maximumLineLength": { 16 | "value": 100, 17 | "allowComments": true, 18 | "allowRegex": true 19 | }, 20 | "validateIndentation": 4, 21 | "validateQuoteMarks": "'", 22 | 23 | "disallowMultipleLineStrings": true, 24 | "disallowMixedSpacesAndTabs": true, 25 | "disallowTrailingWhitespace": true, 26 | "disallowSpaceAfterPrefixUnaryOperators": true, 27 | "disallowMultipleVarDecl": null, 28 | 29 | "requireSpaceAfterKeywords": [ 30 | "if", 31 | "else", 32 | "for", 33 | "while", 34 | "do", 35 | "switch", 36 | "return", 37 | "try", 38 | "catch" 39 | ], 40 | "requireSpaceBeforeBinaryOperators": [ 41 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 42 | "&=", "|=", "^=", "+=", 43 | 44 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 45 | "|", "^", "&&", "||", "===", "==", ">=", 46 | "<=", "<", ">", "!=", "!==" 47 | ], 48 | "requireSpaceAfterBinaryOperators": true, 49 | "requireSpacesInConditionalExpression": true, 50 | "requireSpaceBeforeBlockStatements": true, 51 | "requireLineFeedAtFileEnd": true, 52 | "disallowSpacesInsideObjectBrackets": "all", 53 | "disallowSpacesInsideArrayBrackets": "all", 54 | "disallowSpacesInsideParentheses": true, 55 | 56 | "validateJSDoc": { 57 | "checkParamNames": true, 58 | "requireParamTypes": true 59 | }, 60 | 61 | "disallowMultipleLineBreaks": true, 62 | 63 | "disallowCommaBeforeLineBreak": null, 64 | "disallowDanglingUnderscores": null, 65 | "disallowMultipleLineStrings": null, 66 | "disallowTrailingComma": null, 67 | "requireCommaBeforeLineBreak": null, 68 | "requireDotNotation": null, 69 | "requireMultipleVarDecl": null, 70 | "requireParenthesesAroundIIFE": true, 71 | "requireSpacesInFunctionDeclaration": { 72 | "beforeOpeningRoundBrace": true, 73 | "beforeOpeningCurlyBrace": true 74 | }, 75 | "requireSpacesInFunctionExpression": { 76 | "beforeOpeningRoundBrace": true, 77 | "beforeOpeningCurlyBrace": true 78 | }, 79 | "disallowMultipleSpaces": true, 80 | "disallowNewlineBeforeBlockStatements": true, 81 | "requireBlocksOnNewline": true, 82 | "requireCapitalizedConstructors": true, 83 | "disallowEmptyBlocks": true 84 | } 85 | -------------------------------------------------------------------------------- /app/templates/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "es3": false, 6 | "forin": true, 7 | "freeze": true, 8 | "latedef": "nofunc", 9 | "noarg": true, 10 | "nonbsp": true, 11 | "nonew": true, 12 | "plusplus": false, 13 | "undef": true, 14 | "unused": false, 15 | "strict": false, 16 | "maxparams": 10, 17 | 18 | "asi": false, 19 | "boss": false, 20 | "debug": false, 21 | "eqnull": true, 22 | "esnext": false, 23 | "evil": false, 24 | "expr": false, 25 | "funcscope": false, 26 | "globalstrict": false, 27 | "iterator": false, 28 | "lastsemic": false, 29 | "laxbreak": false, 30 | "laxcomma": false, 31 | "loopfunc": true, 32 | "maxerr": 50, 33 | "moz": false, 34 | "multistr": false, 35 | "notypeof": false, 36 | "proto": false, 37 | "scripturl": false, 38 | "shadow": false, 39 | "sub": true, 40 | "supernew": false, 41 | "validthis": false, 42 | "noyield": false, 43 | 44 | "browser": true, 45 | "node": true, 46 | "jasmine": true, 47 | 48 | "globals": { 49 | "angular": false, 50 | "browser": false, 51 | "module": false, 52 | "element": false, 53 | "by": false, 54 | "$$": false 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/templates/_README.md: -------------------------------------------------------------------------------- 1 | # <%= appName %> 2 | 3 | ## Prerequisites 4 | 5 | 1. Install [Node.js](http://nodejs.org) 6 | 7 | 2. Install these NPM packages globally 8 | 9 | ```bash 10 | npm install -g bower gulp 11 | ``` 12 | 13 | 3. Install the dependence packages 14 | 15 | ```bash 16 | npm install 17 | ``` 18 | 19 | > it will run `bower install` automatically 20 | 21 | ## Launch Mock Server 22 | 23 | Launch local mock server using `BrowserSync` in Chrome, it will automatically load your mock files under `client/source/test/e2e/mocks` folder, watch the files changes and reload the browser. 24 | 25 | ```bash 26 | npm start 27 | ``` 28 | 29 | You can also launch the server with `gulp`: 30 | 31 | * for development: `gulp serve:dev --mock` 32 | * for production: `gulp serve:prod --mock` 33 | 34 | > `--mock` will also include the API mock files 35 | 36 | ## Linting 37 | 38 | Use `JSHint` and `JSCS` to lint your javascript files. 39 | 40 | ```bash 41 | gulp lint 42 | ``` 43 | 44 | ## Tests 45 | 46 | * Unit Test: `gulp test:unit` 47 | * Unit Test with auto watch: `gulp test:tdd` 48 | * E2E Test: `gulp test:e2e` 49 | * run `./node_modules/protractor/bin/webdriver-manager update` first 50 | * make sure a local mock server is running 51 | 52 | ## Building 53 | 54 | * for development: `gulp build:dev --mock` 55 | * for production: `gulp build:prod --mock` 56 | 57 | All the build files will in the sub folder of `client/build/`, development environment will use the original source files, production environment has some optimizations: 58 | 59 | * All the Javascript/CSS files are minified and concated. 60 | * All the template files used in Angular are processed by `$templateCache`. 61 | * All the images used are optimized to smaller size. 62 | * The compressed files will be suffixed by random hash. 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/templates/_bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "0.0.1", 4 | "description": "<%= appDesc %>", 5 | "license": "MIT", 6 | "ignore": [ 7 | "**/.*", 8 | "node_modules", 9 | "bower_components", 10 | "test", 11 | "tests" 12 | ], 13 | "dependencies": { 14 | "angular": "~1.3.15", 15 | "angular-ui-router": "~0.2.14", 16 | "lumx": "0.3.31", 17 | "angular-animate": "~1.3.15", 18 | "animate.css": "~3.2.6", 19 | "angular-messages": "~1.3.15", 20 | "angular-loading-bar": "~0.7.1" 21 | }, 22 | "overrides": { 23 | "mdi": { 24 | "main": [ 25 | "css/materialdesignicons.css" 26 | ] 27 | } 28 | }, 29 | "devDependencies": { 30 | "angular-mocks": "~1.3.15", 31 | "bardjs": "~0.1.2" 32 | }, 33 | "resolutions": { 34 | "angular": "1.3.20" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "0.0.1", 4 | "description": "<%= appDesc %>", 5 | "repository": "", 6 | "scripts": { 7 | "install": "bower install", 8 | "start": "gulp serve:dev --mock", 9 | "test": "gulp test:unit" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "bower": "^1.4.1", 14 | "browser-sync": "^2.6.9", 15 | "connect-history-api-fallback": "^1.1.0", 16 | "del": "^1.1.1", 17 | "gulp": "^3.8.11", 18 | "gulp-angular-templatecache": "^1.6.0", 19 | "gulp-autoprefixer": "^2.2.0", 20 | "gulp-bump": "^0.3.0", 21 | "gulp-bytediff": "^0.2.1", 22 | "gulp-csso": "^1.0.0", 23 | "gulp-filter": "^2.0.2", 24 | "gulp-flatten": "0.0.4", 25 | "gulp-header": "^1.2.2", 26 | "gulp-if": "^1.2.5", 27 | "gulp-imagemin": "^2.2.1", 28 | "gulp-inject": "^1.2.0", 29 | "gulp-jade": "^1.0.0", 30 | "gulp-jscs": "^1.6.0", 31 | "gulp-jshint": "^1.10.0", 32 | "gulp-load-plugins": "^0.10.0", 33 | "gulp-minify-html": "^1.0.2", 34 | "gulp-ng-annotate": "^0.5.3", 35 | "gulp-order": "^1.1.1", 36 | "gulp-plumber": "^1.0.0", 37 | "gulp-print": "^1.1.0", 38 | "gulp-protractor": "^1.0.0", 39 | "gulp-rev": "^3.0.1", 40 | "gulp-rev-replace": "^0.4.0", 41 | "gulp-stylus": "^2.0.1", 42 | "gulp-task-listing": "^1.0.0", 43 | "gulp-uglify": "^1.2.0", 44 | "gulp-useref": "^1.1.2", 45 | "gulp-util": "^3.0.4", 46 | "jasmine-core": "^2.3.4", 47 | "jshint-stylish": "^1.0.1", 48 | "karma": "^0.12.31", 49 | "karma-chrome-launcher": "^0.1.8", 50 | "karma-coverage": "^0.3.1", 51 | "karma-firefox-launcher": "^0.1.4", 52 | "karma-jasmine": "^0.3.5", 53 | "karma-junit-reporter": "^0.2.2", 54 | "karma-mocha-reporter": "^1.0.2", 55 | "karma-phantomjs-launcher": "^0.1.4", 56 | "karma-safari-launcher": "^0.1.1", 57 | "merge-stream": "^0.1.7", 58 | "phantomjs": "^1.9.16", 59 | "protractor": "^2.0.0", 60 | "run-sequence": "^1.1.0", 61 | "wiredep": "^2.2.2", 62 | "yargs": "^3.8.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/templates/client/source/app/app.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app', [ 5 | 'app.core', 6 | 'app.helper', 7 | 'app.layout', 8 | 'app.home', 9 | 'app.login', 10 | 'app.dashboard', 11 | 'app.phone' 12 | ]); 13 | 14 | })(); 15 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/_config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .config(appConfig); 7 | 8 | var config = { 9 | appErrorPrefix: '[<%= appDesc %> Error] ', 10 | appTitle: '<%= appDesc %>' 11 | }; 12 | 13 | appConfig.$inject = ['routerHelperProvider']; 14 | /* @ngInject */ 15 | function appConfig (routerHelperProvider) { 16 | routerHelperProvider.configure({mainTitle: config.appTitle}); 17 | } 18 | 19 | })(); 20 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/core.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core', [ 6 | 'app.helper', 'ngAnimate', 'ngMessages', 7 | 'ui.router', 'angular-loading-bar', 'lumx' 8 | ]); 9 | })(); 10 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/core.styl: -------------------------------------------------------------------------------- 1 | @import '../../styles/common' 2 | 3 | @import '../../styles/core/*' 4 | 5 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/error.constant.js: -------------------------------------------------------------------------------- 1 | // all error messages 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.core') 7 | .constant('ErrorMessage', { 8 | '$SERVER': 'Server issue, please try later!', 9 | // login 10 | 'LOGIN_WRONG_EMAIL_PASSWORD_PAIR': 'Incorrect email or password, please try again!', 11 | 'LOGIN_USER_IN_LOCK': 'Your account is locked!', 12 | // phone 13 | 'PHONE_QUERY_NOT_FOUND': 'Sorry, the phone you queryed can not be found!', 14 | 'PHONE_UPDATE_NOT_FOUND': 'Sorry, the phone you updated can not be found!', 15 | 'PHONE_DELETE_NOT_FOUND': 'Sorry, the phone you deleted can not be found!' 16 | }); 17 | 18 | })(); 19 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/event.constant.js: -------------------------------------------------------------------------------- 1 | // all events 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.core') 7 | .constant('Event', { 8 | 'AUTH_LOGIN': 'auth_login_event', 9 | 'AUTH_LOGOUT': 'auth_logout_event', 10 | 'AUTH_SESSION_VALID': 'auth_session_valid_event' 11 | }); 12 | 13 | })(); 14 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/focus-me.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .directive('aioFocusMe', FocusMe); 7 | 8 | FocusMe.$inject = []; 9 | /* @ngInject */ 10 | function FocusMe () { 11 | var directive = { 12 | restrict: 'A', 13 | link: link 14 | }; 15 | return directive; 16 | 17 | ////////// 18 | 19 | function link (scope, element, attrs) { 20 | scope.$watch(attrs.aioFocusMe, function (val) { 21 | if (val) { 22 | scope.$evalAsync(function () { 23 | element[0].focus(); 24 | }); 25 | } 26 | }); 27 | } 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/loading-button.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .directive('aioLoadingButton', LoadingButton); 7 | 8 | LoadingButton.$inject = []; 9 | /* @ngInject */ 10 | function LoadingButton () { 11 | var directive = { 12 | restrict: 'A', 13 | link: link 14 | }; 15 | return directive; 16 | 17 | ////////// 18 | 19 | function link (scope, element, attrs) { 20 | var spinner = ''; 21 | scope.$watch(attrs.aioLoadingButton, function (val) { 22 | if (val) { 23 | element.prepend(spinner); 24 | } else { 25 | element.find('.icon-rotate-animation').remove(); 26 | } 27 | }); 28 | } 29 | } 30 | })(); 31 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/production/_production.config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .config(appProductionConfig); 7 | 8 | var config = { 9 | appErrorPrefix: '[<%= appDesc %> Error] ', 10 | }; 11 | 12 | appProductionConfig.$inject = ['$logProvider', '$compileProvider', 13 | 'exceptionHandlerProvider']; 14 | /* @ngInject */ 15 | function appProductionConfig ($logProvider, $compileProvider, exceptionHandlerProvider) { 16 | $logProvider.debugEnabled(false); 17 | $compileProvider.debugInfoEnabled(false); 18 | exceptionHandlerProvider.configure(config.appErrorPrefix); 19 | } 20 | 21 | })(); 22 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/production/exception-handler.provider.js: -------------------------------------------------------------------------------- 1 | // Handle app level exception 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.core') 7 | .provider('exceptionHandler', exceptionHandlerProvider) 8 | .config(config); 9 | 10 | // Configure log prefix for error handling 11 | function exceptionHandlerProvider () { 12 | /* jshint validthis:true */ 13 | this.config = { 14 | appErrorPrefix: undefined 15 | }; 16 | 17 | this.configure = function (appErrorPrefix) { 18 | this.config.appErrorPrefix = appErrorPrefix; 19 | }; 20 | 21 | this.$get = function () { 22 | return {config: this.config}; 23 | }; 24 | } 25 | 26 | config.$inject = ['$provide']; 27 | 28 | // Use decorator to extend the original $exceptionHandler: 29 | function config ($provide) { 30 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 31 | } 32 | 33 | extendExceptionHandler.$inject = ['$delegate', 'exceptionHandler', 'logger']; 34 | 35 | // Extend the original $exceptionHandler service. 36 | // * add error log prefix using exceptionHandlerProvider 37 | // * do other thing with error log 38 | function extendExceptionHandler ($delegate, exceptionHandler, logger) { 39 | $delegate = function (exception, cause) { 40 | var appErrorPrefix = exceptionHandler.config.appErrorPrefix || ''; 41 | var errorData = {exception: exception, cause: cause}; 42 | exception.message = appErrorPrefix + exception.message; 43 | // $delegate(exception, cause); 44 | /** 45 | * Could add the error to a service's collection, 46 | * add errors to $rootScope, log errors to remote web server, 47 | * or log locally. Or throw hard. It is entirely up to you. 48 | * throw exception; 49 | * 50 | * @example 51 | * throw { message: 'error message we added' }; 52 | */ 53 | logger.error(exception.message, errorData); 54 | }; 55 | return $delegate; 56 | } 57 | })(); 58 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/resolve.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .factory('resolve', resolve); 7 | 8 | resolve.$inject = ['userAPI', '$q']; 9 | /* @ngInject */ 10 | function resolve (userAPI, $q) { 11 | return { 12 | login: login 13 | }; 14 | 15 | /////////// 16 | 17 | function login () { 18 | return userAPI.checkLoggedInStatus() 19 | .catch(_error); 20 | 21 | function _error () { 22 | return $q.reject('requireLogin'); 23 | } 24 | } 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/user.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .factory('userAPI', userSerivce); 7 | 8 | userSerivce.$inject = ['$http', '$q', '$rootScope', 'Event', 'ajaxErrorHandler']; 9 | /* @ngInject */ 10 | function userSerivce ($http, $q, $rootScope, Event, ajaxError) { 11 | var _isLoggedIn; 12 | var _userInfo; 13 | var service = { 14 | isLoggedIn: isLoggedIn, 15 | checkLoggedInStatus: checkLoggedInStatus, 16 | login: login, 17 | logout: logout, 18 | getUserInfo: getUserInfo, 19 | getProductSummary: getProductSummary 20 | }; 21 | 22 | return service; 23 | 24 | ///////////// 25 | 26 | function isLoggedIn () { 27 | return _isLoggedIn; 28 | } 29 | 30 | function checkLoggedInStatus () { 31 | return $http.get('api/user/loginstatus', {ignoreLoadingBar: true}) 32 | .then(_success) 33 | .catch(_error); 34 | 35 | function _success (response) { 36 | var data = response.data; 37 | if (response.status === 200 && data.code === 0) { 38 | _setUser(data.result.user); 39 | $rootScope.$broadcast(Event.AUTH_SESSION_VALID, data.result.user); 40 | return data.result.user; 41 | } else { 42 | return $q.reject(data.message); 43 | } 44 | } 45 | 46 | function _error (reason) { 47 | _clearUser(); 48 | return ajaxError.catcher(reason); 49 | } 50 | } 51 | 52 | function login (email, password) { 53 | var req = { 54 | email: email, 55 | password: password 56 | }; 57 | return $http.post('api/user/login', req) 58 | .then(_success) 59 | .catch(_error); 60 | 61 | function _success (response) { 62 | var data = response.data; 63 | if (response.status === 200 && data.code === 0) { 64 | _setUser(data.result.user); 65 | $rootScope.$broadcast(Event.AUTH_LOGIN, data.result.user); 66 | return data.result.user; 67 | } else { 68 | return $q.reject(data.message); 69 | } 70 | } 71 | 72 | function _error (reason) { 73 | _clearUser(); 74 | return ajaxError.catcher(reason); 75 | } 76 | 77 | } 78 | 79 | function logout () { 80 | return $http.post('api/user/logout') 81 | .then(_success) 82 | .catch(_error); 83 | 84 | function _success (response) { 85 | var data = response.data; 86 | _clearUser(); 87 | if (response.status === 200 && data.code === 0) { 88 | $rootScope.$broadcast(Event.AUTH_LOGOUT); 89 | } else { 90 | return $q.reject(data.message); 91 | } 92 | } 93 | 94 | function _error (reason) { 95 | _clearUser(); 96 | return ajaxError.catcher(reason); 97 | } 98 | } 99 | 100 | function getUserInfo () { 101 | return _userInfo; 102 | } 103 | 104 | function getProductSummary () { 105 | return $http.get('api/user/products') 106 | .then(_success) 107 | .catch(ajaxError.catcher); 108 | 109 | function _success (response) { 110 | var data = response.data; 111 | if (response.status === 200 && data.code === 0) { 112 | return data.result.summary; 113 | } else { 114 | return $q.reject(data.message); 115 | } 116 | } 117 | } 118 | 119 | function _setUser (userData) { 120 | _isLoggedIn = true; 121 | _userInfo = userData; 122 | } 123 | 124 | function _clearUser () { 125 | _isLoggedIn = false; 126 | _userInfo = null; 127 | } 128 | 129 | } 130 | })(); 131 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/util.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .factory('util', utilSerivce); 7 | 8 | utilSerivce.$inject = []; 9 | /* @ngInject */ 10 | function utilSerivce () { 11 | var service = { 12 | preloadImage: preloadImage 13 | }; 14 | 15 | return service; 16 | 17 | ///////////// 18 | 19 | function preloadImage (url) { 20 | var img = new Image(); 21 | img.src = url; 22 | } 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /app/templates/client/source/app/core/validate-number.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .directive('aioValidateNumber', ValidateNumber); 7 | 8 | ValidateNumber.$inject = []; 9 | /* @ngInject */ 10 | function ValidateNumber () { 11 | var directive = { 12 | require: 'ngModel', 13 | restrict: 'A', 14 | link: link 15 | }; 16 | return directive; 17 | 18 | ////////// 19 | 20 | function link (scope, element, attrs, ctrl) { 21 | var pattern = /^\d+(\.\d{1,2})?$/; 22 | ctrl.$validators.number = function (modelValue, viewModel) { 23 | if (pattern.test(viewModel)) { 24 | return true; 25 | } 26 | return false; 27 | }; 28 | } 29 | } 30 | })(); 31 | -------------------------------------------------------------------------------- /app/templates/client/source/app/dashboard/dashboard.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .controller('DashboardController', DashboardController); 7 | 8 | DashboardController.$inject = ['userAPI']; 9 | /* @ngInject */ 10 | function DashboardController (userAPI) { 11 | var vm = this; 12 | 13 | vm.colors = [ 14 | 'bgc-indigo-500', 15 | 'bgc-red-500', 16 | 'bgc-pink-500' 17 | ]; 18 | 19 | init(); 20 | 21 | ////////////// 22 | 23 | function init () { 24 | vm.userInfo = userAPI.getUserInfo(); 25 | _getProductsSummary(); 26 | } 27 | 28 | function _getProductsSummary () { 29 | userAPI.getProductSummary() 30 | .then(function (data) { 31 | vm.products = data; 32 | vm.products.forEach(function (product) { 33 | product.link = 'root.' + product.name; 34 | }); 35 | }); 36 | } 37 | } 38 | })(); 39 | -------------------------------------------------------------------------------- /app/templates/client/source/app/dashboard/dashboard.jade: -------------------------------------------------------------------------------- 1 | .dashboard-view.full-width 2 | .card.bgc-blue-100(class="p+") 3 | p Welcome {{vm.userInfo.name}}! 4 | .boxes 5 | a.box.tc-white(ng-repeat="product in ::vm.products", 6 | ui-sref="{{::product.link}}", 7 | class="{{::vm.colors[$index % vm.colors.length]}}" 8 | ) 9 | i.icon.icon--xl.icon--flat.mdi.mdi-cellphone-android 10 | span.count {{::product.count}} 11 | p.name {{::product.name}} 12 | -------------------------------------------------------------------------------- /app/templates/client/source/app/dashboard/dashboard.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard', ['app.core']); 6 | })(); 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/dashboard/dashboard.route.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | 10 | /* @ngInject */ 11 | function appRun (routerHelper) { 12 | routerHelper.configureStates(getStates()); 13 | } 14 | 15 | function getStates () { 16 | return [ 17 | { 18 | state: 'root.dashboard', 19 | config: { 20 | url: '/dashboard', 21 | views: { 22 | 'main@': { 23 | templateUrl: 'static/dashboard/dashboard.html', 24 | controller: 'DashboardController as vm' 25 | } 26 | }, 27 | data: { 28 | title: 'Dashboard', 29 | _class: 'dashboard', 30 | requireLogin: true 31 | }, 32 | sidebar: { 33 | icon: 'mdi-view-dashboard', 34 | text: 'Dashboard' 35 | }, 36 | breadcrumb: 'Dashboard' 37 | } 38 | } 39 | ]; 40 | } 41 | })(); 42 | -------------------------------------------------------------------------------- /app/templates/client/source/app/dashboard/dashboard.styl: -------------------------------------------------------------------------------- 1 | @import '../../styles/common' 2 | 3 | @import '../../styles/dashboard/*' 4 | 5 | -------------------------------------------------------------------------------- /app/templates/client/source/app/helper/ajax-error-handler.service.js: -------------------------------------------------------------------------------- 1 | // Provide a unify handler to handle $http request failure 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.helper') 7 | .factory('ajaxErrorHandler', ajaxErrorHandlerService); 8 | 9 | ajaxErrorHandlerService.$inject = ['ErrorMessage', '$q']; 10 | 11 | /* @ngInject */ 12 | function ajaxErrorHandlerService (Error, $q) { 13 | var service = { 14 | catcher: catcher 15 | }; 16 | return service; 17 | 18 | // directly reject the human readable error message 19 | function catcher (reason) { 20 | // reason is: 21 | // 1. either an error $http response 22 | // 2. or an error message returned by _success 23 | var _type = typeof reason; 24 | var message = '$SERVER'; 25 | if (reason && _type === 'object') { 26 | message = reason.message; 27 | } else if (reason && _type === 'string') { 28 | message = reason; 29 | } 30 | return $q.reject(Error[message]); 31 | } 32 | } 33 | })(); 34 | -------------------------------------------------------------------------------- /app/templates/client/source/app/helper/helper.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.helper', ['ui.router']); 6 | })(); 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/helper/logger.service.js: -------------------------------------------------------------------------------- 1 | // A wrapper service for original $log 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.helper') 7 | .factory('logger', loggerService); 8 | 9 | loggerService.$inject = ['$log']; 10 | 11 | /* @ngInject */ 12 | function loggerService ($log) { 13 | var service = { 14 | error: error, 15 | info: info, 16 | success: success, 17 | warning: warning 18 | }; 19 | 20 | return service; 21 | 22 | ///////////////////// 23 | 24 | function error (message, data, title) { 25 | $log.error('Error: ' + message, data); 26 | } 27 | 28 | function info (message, data, title) { 29 | $log.info('Info: ' + message, data); 30 | } 31 | 32 | function success (message, data, title) { 33 | $log.info('Success: ' + message, data); 34 | } 35 | 36 | function warning (message, data, title) { 37 | $log.warn('Warning: ' + message, data); 38 | } 39 | } 40 | }()); 41 | -------------------------------------------------------------------------------- /app/templates/client/source/app/helper/router-helper.provider.js: -------------------------------------------------------------------------------- 1 | // Help configure the state-base ui.router 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.helper') 7 | .provider('routerHelper', routerHelperProvider); 8 | 9 | routerHelperProvider.$inject = ['$locationProvider', '$stateProvider', '$urlRouterProvider']; 10 | /* @ngInject */ 11 | function routerHelperProvider ($locationProvider, $stateProvider, $urlRouterProvider) { 12 | /* jshint validthis:true */ 13 | var config = { 14 | mainTitle: undefined, 15 | resolveAlways: {} 16 | }; 17 | 18 | $locationProvider.html5Mode(true); 19 | 20 | this.configure = function (cfg) { 21 | angular.extend(config, cfg); 22 | }; 23 | 24 | this.$get = RouterHelper; 25 | RouterHelper.$inject = ['$location', '$rootScope', '$state', 'logger', 'resolve']; 26 | /* @ngInject */ 27 | function RouterHelper ($location, $rootScope, $state, logger, resolve) { 28 | var handlingStateChangeError = false; 29 | var hasOtherwise = false; 30 | var stateCounts = { 31 | errors: 0, 32 | changes: 0 33 | }; 34 | 35 | var service = { 36 | configureStates: configureStates, 37 | getStates: getStates, 38 | stateCounts: stateCounts 39 | }; 40 | 41 | init(); 42 | 43 | return service; 44 | 45 | /////////////// 46 | 47 | function configureStates (states, otherwisePath) { 48 | states.forEach(function (state) { 49 | // add login check if requireLogin is true 50 | var data = state.config.data; 51 | if (data && data.requireLogin === true) { 52 | state.config.resolve = angular.extend( 53 | state.config.resolve || {}, 54 | {'loginResolve': resolve.login} 55 | ); 56 | } 57 | state.config.resolve = 58 | angular.extend(state.config.resolve || {}, config.resolveAlways); 59 | $stateProvider.state(state.state, state.config); 60 | }); 61 | if (otherwisePath && !hasOtherwise) { 62 | hasOtherwise = true; 63 | $urlRouterProvider.otherwise(otherwisePath); 64 | } 65 | } 66 | 67 | function handleRoutingErrors () { 68 | // Route cancellation: 69 | // On routing error, go to the dashboard. 70 | // Provide an exit clause if it tries to do it twice. 71 | $rootScope.$on('$stateChangeError', 72 | function (event, toState, toParams, fromState, fromParams, error) { 73 | if (handlingStateChangeError) { 74 | return; 75 | } 76 | stateCounts.errors++; 77 | handlingStateChangeError = true; 78 | var destination = (toState && 79 | (toState.title || toState.name || toState.loadedTemplateUrl)) || 80 | 'unknown target'; 81 | var msg = 'Error routing to ' + destination + '. ' + 82 | (error.data || '') + '.
' + (error.statusText || '') + 83 | ': ' + (error.status || ''); 84 | logger.warning(msg, [toState]); 85 | // handle requireLogin issue 86 | if (error === 'requireLogin') { 87 | $state.prev = { 88 | state: toState.name, 89 | params: toParams 90 | }; 91 | $state.go('root.login'); 92 | } else { 93 | $state.go('root.home'); 94 | } 95 | } 96 | ); 97 | } 98 | 99 | function init () { 100 | handleRoutingErrors(); 101 | updateDocTitle(); 102 | } 103 | 104 | function getStates () { 105 | return $state.get(); 106 | } 107 | 108 | function updateDocTitle () { 109 | $rootScope.$on('$stateChangeSuccess', 110 | function (event, toState, toParams, fromState, fromParams) { 111 | stateCounts.changes++; 112 | handlingStateChangeError = false; 113 | var title = (toState.data.title + ' - ' || '') + config.mainTitle; 114 | $rootScope.title = title; // data bind to 115 | $rootScope._class = toState.data._class; // data bind to <body> 116 | } 117 | ); 118 | } 119 | } 120 | } 121 | })(); 122 | -------------------------------------------------------------------------------- /app/templates/client/source/app/home/_home.jade: -------------------------------------------------------------------------------- 1 | .home-view 2 | .card.bgc-blue-800.text-center.tc-white(class="p+++") 3 | h2.title <%= appDesc %> 4 | h3.subtitle 5 | | Awesome web app built on AngularJS & Material Design. 6 | a.btn.btn--green.btn--l.btn--raised(class="mt+", ui-sref="root.login", lx-ripple="white") Get Started 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/home/home.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.home', ['app.core']); 6 | })(); 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/home/home.route.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.home') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | 10 | /* @ngInject */ 11 | function appRun (routerHelper) { 12 | routerHelper.configureStates(getStates()); 13 | } 14 | 15 | function getStates () { 16 | return [ 17 | { 18 | state: 'root.home', 19 | config: { 20 | url: '/', 21 | views: { 22 | 'main@': { 23 | templateUrl: 'static/home/home.html' 24 | }, 25 | 'sidebar@': {}, 26 | 'breadcrumb@': {} 27 | }, 28 | data: { 29 | title: 'Home', 30 | _class: 'home' 31 | } 32 | } 33 | } 34 | ]; 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /app/templates/client/source/app/home/home.styl: -------------------------------------------------------------------------------- 1 | @import '../../styles/common' 2 | 3 | @import '../../styles/home/*' 4 | 5 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/404.jade: -------------------------------------------------------------------------------- 1 | .not-found-view 2 | h2.text-center 404 Not Found 3 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/_header.jade: -------------------------------------------------------------------------------- 1 | .header 2 | .card.bgc-blue-500.tc-white 3 | .toolbar 4 | .toolbar__left.mr 5 | button.btn.btn--l.btn--white.btn--icon.hide-gt-sm(lx-ripple, ng-if="hasSidebar", ng-click="vm.switchSidebar()") 6 | i.mdi.mdi-menu 7 | img.logo(src="static/images/layout/logo.png") 8 | .toolbar__label.fs-title.header-title 9 | a.tc-white(ui-sref="root.home") <%= appDesc %> 10 | .toolbar__right 11 | a.btn.btn--l.btn--white.btn--icon.header-login(ui-sref="root.login", lx-ripple="white", ng-if="!vm.isLoggedIn") 12 | i.mdi.mdi-login 13 | span.header-user-name.hide-sm(ng-if="vm.isLoggedIn") {{vm.userInfo.name}} 14 | lx-dropdown.header-dropdown(position="right", from-top, ng-if="vm.isLoggedIn") 15 | button.btn.btn--xl.btn--white.btn--icon.dropdown-toggle(lx-ripple, lx-dropdown-toggle) 16 | i.mdi.mdi-dots-vertical 17 | lx-dropdown-menu 18 | ul.header-dropdown-menu 19 | li.hide-gt-sm.text-center.tc-grey-500(ng-if="vm.isLoggedIn") 20 | span {{vm.userInfo.name}} 21 | li.hide-gt-sm.dropdown-divider 22 | li 23 | a.dropdown-link.logout-link(ui-sref="root.login({action: 'logout'})") 24 | i.mdi.mdi-logout.mr 25 | | Logout 26 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/breadcrumb.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('BreadcrumbController', BreadcrumbController); 7 | 8 | BreadcrumbController.$inject = ['routerHelper', '$state', '$rootScope']; 9 | /* @ngInject */ 10 | function BreadcrumbController (routerHelper, $state, $rootScope) { 11 | var vm = this; 12 | 13 | init(); 14 | 15 | //////////// 16 | 17 | function init () { 18 | _applyNewBreadcrumb($state.current, $state.params); 19 | $rootScope.$on('$stateChangeSuccess', 20 | function (event, toState, toParams, fromState, fromParams) { 21 | _applyNewBreadcrumb(toState, toParams); 22 | }); 23 | } 24 | 25 | function _applyNewBreadcrumb (state, params) { 26 | vm.breadcrumbs = []; 27 | var name = state.name; 28 | var stateNames = _getAncestorStates(name); 29 | stateNames.forEach(function (name) { 30 | var stateConfig = $state.get(name); 31 | var breadcrumb = { 32 | link: name, 33 | text: stateConfig.breadcrumb 34 | }; 35 | if (params) { 36 | breadcrumb.link = name + '(' + JSON.stringify(params) + ')'; 37 | } 38 | vm.breadcrumbs.push(breadcrumb); 39 | }); 40 | } 41 | 42 | function _getAncestorStates (stateName) { 43 | var ancestors = []; 44 | var pieces = stateName.split('.'); 45 | if (pieces.length > 1) { 46 | for (var i = 1; i < pieces.length; i++) { 47 | var name = pieces.slice(0, i + 1); 48 | ancestors.push(name.join('.')); 49 | } 50 | } 51 | return ancestors; 52 | } 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/breadcrumb.jade: -------------------------------------------------------------------------------- 1 | ol.breadcrumb(class="p+") 2 | li.home-item 3 | a.btn.btn--xs.btn--white.btn--fab(ui-sref="root.home", lx-ripple) 4 | i.mdi.mdi-home 5 | li.breadcrumb-item(ng-repeat="nav in vm.breadcrumbs") 6 | i.icon.tc-white.icon--xs.mdi.mdi-chevron-right 7 | a.btn.btn--xs.btn--white.btn--raised(ng-if="!$last", ui-sref="{{nav.link}}") 8 | | {{nav.text}} 9 | span.tc-white(ng-if="$last") {{nav.text}} 10 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/footer.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('FooterController', FooterController); 7 | 8 | FooterController.$inject = []; 9 | /* @ngInject */ 10 | function FooterController () { 11 | var vm = this; 12 | 13 | vm.year = (new Date()).getFullYear(); 14 | } 15 | })(); 16 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/footer.jade: -------------------------------------------------------------------------------- 1 | .footer 2 | .toolbar.bgc-blue-grey-500 3 | p.copyright.tc-white.text-center 4 | | Copyright © {{::vm.year}}. 5 | | Powered by   6 | a.tc-white(href="https://github.com/PinkyJie/generator-aio-angular", target="_blank") AIO-Angular 7 | |   generator. 8 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/header.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('HeaderController', HeaderController); 7 | 8 | HeaderController.$inject = ['$rootScope', 'Event']; 9 | /* @ngInject */ 10 | function HeaderController ($rootScope, Event) { 11 | var vm = this; 12 | 13 | vm.switchSidebar = switchSidebar; 14 | 15 | init(); 16 | 17 | //////////// 18 | 19 | function init () { 20 | // udpate header based on auth event 21 | $rootScope.$on(Event.AUTH_LOGIN, _updateHeader); 22 | $rootScope.$on(Event.AUTH_LOGOUT, _updateHeader); 23 | $rootScope.$on(Event.AUTH_SESSION_VALID, _updateHeader); 24 | } 25 | 26 | function _updateHeader (e, userInfo) { 27 | if (userInfo) { 28 | vm.isLoggedIn = true; 29 | vm.userInfo = userInfo; 30 | } else { 31 | vm.isLoggedIn = false; 32 | vm.userInfo = null; 33 | } 34 | } 35 | 36 | function switchSidebar () { 37 | $rootScope.showSidebar = !$rootScope.showSidebar; 38 | } 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/layout.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout', ['app.core']); 6 | })(); 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/layout.route.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | 10 | /* @ngInject */ 11 | function appRun (routerHelper) { 12 | var otherwise = '/404'; 13 | routerHelper.configureStates(getStates(), otherwise); 14 | } 15 | 16 | function getStates () { 17 | return [ 18 | { 19 | state: 'root', 20 | config: { 21 | abstract: true, 22 | url: '', 23 | views: { 24 | 'header': { 25 | templateUrl: 'static/layout/header.html', 26 | controller: 'HeaderController as vm' 27 | }, 28 | 'sidebar': { 29 | templateUrl: 'static/layout/sidebar.html', 30 | controller: 'SidebarController as vm' 31 | }, 32 | 'breadcrumb': { 33 | templateUrl: 'static/layout/breadcrumb.html', 34 | controller: 'BreadcrumbController as vm' 35 | }, 36 | 'footer': { 37 | templateUrl: 'static/layout/footer.html', 38 | controller: 'FooterController as vm' 39 | } 40 | } 41 | } 42 | }, 43 | { 44 | state: 'root.notfound', 45 | config: { 46 | url: '/404', 47 | views: { 48 | 'main@': { 49 | templateUrl: 'static/layout/404.html' 50 | }, 51 | 'sidebar@': {} 52 | }, 53 | data: { 54 | title: '404', 55 | _class: 'notfound' 56 | }, 57 | breadcrumb: '404' 58 | } 59 | } 60 | ]; 61 | } 62 | })(); 63 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/layout.styl: -------------------------------------------------------------------------------- 1 | @import '../../styles/common' 2 | 3 | @import '../../styles/layout/*' 4 | 5 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/sidebar.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('SidebarController', SidebarController); 7 | 8 | SidebarController.$inject = ['routerHelper', '$scope', '$rootScope']; 9 | /* @ngInject */ 10 | function SidebarController (routerHelper, $scope, $rootScope) { 11 | var vm = this; 12 | 13 | vm.hideSidebar = hideSidebar; 14 | 15 | init(); 16 | 17 | /////////////// 18 | 19 | function init () { 20 | // generate sidebar nav menus 21 | vm.navs = _getNavMenus(); 22 | // tell others we have sidebar 23 | $rootScope.hasSidebar = true; 24 | $scope.$on('$destroy', function () { 25 | $rootScope.hasSidebar = false; 26 | }); 27 | } 28 | 29 | function hideSidebar () { 30 | $rootScope.showSidebar = false; 31 | } 32 | 33 | function _getNavMenus () { 34 | var navs = []; 35 | var allStates = routerHelper.getStates(); 36 | allStates.forEach(function (state) { 37 | if (state.sidebar) { 38 | var nav = state.sidebar; 39 | nav.link = state.name; 40 | navs.push(nav); 41 | } 42 | }); 43 | return navs; 44 | } 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /app/templates/client/source/app/layout/sidebar.jade: -------------------------------------------------------------------------------- 1 | .sidebar 2 | ul.sidebar-menu.p 3 | li.menu-item.p(ng-repeat="nav in ::vm.navs", ui-sref-active="active") 4 | a.tc-black.link(ui-sref="{{::nav.link}}", ng-click="vm.hideSidebar()") 5 | i.icon.icon--s.icon--grey.icon--flat.mdi(class="{{::nav.icon}}") 6 | span.ml.text {{::nav.text}} 7 | .sidebar-backdrop.hide-gt-sm(ng-show="showSidebar", ng-click="vm.hideSidebar()") 8 | 9 | -------------------------------------------------------------------------------- /app/templates/client/source/app/login/login.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.login') 6 | .controller('LoginController', LoginController); 7 | 8 | LoginController.$inject = ['userAPI', '$state', '$timeout']; 9 | /* @ngInject */ 10 | function LoginController (userAPI, $state, $timeout) { 11 | var vm = this; 12 | 13 | vm.login = login; 14 | 15 | var _routeAfterLogin = 'root.dashboard'; 16 | 17 | init(); 18 | 19 | //////////// 20 | 21 | function init () { 22 | // handle logout 23 | var action = $state.params.action; 24 | if (action === 'logout') { 25 | vm.needCheckLogin = false; 26 | userAPI.logout() 27 | .then(function () { 28 | _setError('success', 'You have been successfully logged out!'); 29 | }); 30 | } else { 31 | vm.userInfo = null; 32 | vm.needCheckLogin = true; 33 | // check login status firstly 34 | userAPI.checkLoggedInStatus() 35 | .then(function (data) { 36 | vm.userInfo = data; 37 | $timeout(function () { 38 | $state.go(_routeAfterLogin); 39 | }, 1000); 40 | }) 41 | .catch(function () { 42 | vm.needCheckLogin = false; 43 | }); 44 | } 45 | } 46 | 47 | function login (credential) { 48 | if (vm.loginForm.$invalid) { 49 | return; 50 | } 51 | vm.isRequest = true; 52 | userAPI.login(credential.email, credential.password) 53 | .then(_success) 54 | .catch(_error); 55 | 56 | function _success (data) { 57 | vm.loginError = null; 58 | // user was redirect to login page 59 | if ($state.prev) { 60 | $state.go($state.prev.state, $state.prev.params); 61 | $state.prev = null; 62 | } else { 63 | $state.go(_routeAfterLogin); 64 | } 65 | } 66 | 67 | function _error (message) { 68 | _setError('error', message); 69 | vm.isRequest = false; 70 | } 71 | } 72 | 73 | function _setError (type, text) { 74 | vm.loginError = { 75 | type: type, 76 | text: text 77 | }; 78 | } 79 | } 80 | })(); 81 | -------------------------------------------------------------------------------- /app/templates/client/source/app/login/login.jade: -------------------------------------------------------------------------------- 1 | .login-view 2 | .login-checking.tc-white(ng-if="vm.needCheckLogin") 3 | i.icon.icon--xl.icon--flat.mdi(ng-class="{'mdi-sync icon-rotate-animation': !vm.userInfo, 'mdi-account': vm.userInfo}") 4 | p.loading-text {{vm.userInfo ? vm.userInfo.name : 'Checking Login User...'}} 5 | form.card(name="vm.loginForm", ng-submit="vm.login(vm.credential)", novalidate) 6 | .card__header(class="p+") 7 | | Please sign in with your Email and Password 8 | .login-message(class="{{vm.loginError.type}}", ng-if="vm.loginError") 9 | p.tc-white {{vm.loginError.text}} 10 | .card__body(class="p++") 11 | lx-text-field(label="Email", fixed-label="true", icon="email") 12 | input(type="email", ng-model="vm.credential.email", required, aio-focus-me="true") 13 | lx-text-field(label="Password", fixed-label="true", icon="key") 14 | input(type="password", ng-model="vm.credential.password", required) 15 | .card__actions.text-center(class="p+") 16 | button.btn.btn--l.btn--blue.btn--raised.btn-login(type="submit", 17 | ng-disabled="vm.loginForm.$invalid || vm.isRequest", 18 | ng-class="{'btn--is-disabled': vm.loginForm.$invalid}", 19 | lx-ripple, 20 | aio-loading-button="vm.isRequest", 21 | ) Sign In 22 | -------------------------------------------------------------------------------- /app/templates/client/source/app/login/login.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.login', ['app.core']); 6 | })(); 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/login/login.route.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.login') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | 10 | /* @ngInject */ 11 | function appRun (routerHelper) { 12 | routerHelper.configureStates(getStates()); 13 | } 14 | 15 | function getStates () { 16 | return [ 17 | { 18 | state: 'root.login', 19 | config: { 20 | url: '/login?action', 21 | views: { 22 | 'main@': { 23 | templateUrl: 'static/login/login.html', 24 | controller: 'LoginController as vm' 25 | }, 26 | 'breadcrumb@': {}, 27 | 'sidebar@': {} 28 | }, 29 | data: { 30 | title: 'Login', 31 | _class: 'login' 32 | } 33 | } 34 | } 35 | ]; 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /app/templates/client/source/app/login/login.styl: -------------------------------------------------------------------------------- 1 | @import '../../styles/common' 2 | 3 | @import '../../styles/login/*' 4 | 5 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.add.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone') 6 | .controller('PhoneAddController', PhoneAddController); 7 | 8 | PhoneAddController.$inject = ['phoneAPI', '$state', 9 | 'LxNotificationService', '$q']; 10 | /* @ngInject */ 11 | function PhoneAddController (phoneAPI, $state, 12 | LxNotificationService, $q) { 13 | var vm = this; 14 | 15 | vm.addNewPhone = addNewPhone; 16 | 17 | init(); 18 | 19 | ///////////// 20 | 21 | function init () { 22 | vm.phone = {}; 23 | vm.state = 'add'; 24 | } 25 | 26 | function addNewPhone (phone) { 27 | // return promise here to let the phone form controller know the response status 28 | return phoneAPI.addNewPhone(phone) 29 | .then(_success) 30 | .catch(_error); 31 | 32 | function _success (data) { 33 | $state.go('root.phone'); 34 | } 35 | 36 | function _error (message) { 37 | LxNotificationService.alert('Add phone error', message, 'OK'); 38 | return $q.reject(); 39 | } 40 | } 41 | 42 | } 43 | })(); 44 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.add.jade: -------------------------------------------------------------------------------- 1 | .phone-add-view.full-width 2 | .card 3 | .card-header(class="p+") 4 | h3.title Add a new phone 5 | .card-content 6 | aio-phone-form(phone="vm.phone", submit="vm.addNewPhone", state="vm.state") 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone') 6 | .controller('PhoneController', PhoneController); 7 | 8 | PhoneController.$inject = ['phoneAPI', 'LxNotificationService']; 9 | /* @ngInject */ 10 | function PhoneController (phoneAPI, LxNotificationService) { 11 | var vm = this; 12 | 13 | vm.deletePhone = deletePhone; 14 | 15 | init(); 16 | 17 | ///////////// 18 | 19 | function init () { 20 | _getPhoneList(); 21 | } 22 | 23 | function _getPhoneList () { 24 | phoneAPI.getPhones() 25 | .then(function (data) { 26 | vm.phones = data; 27 | }); 28 | } 29 | 30 | function deletePhone (id, name) { 31 | LxNotificationService.confirm('Are your sure?', 32 | 'All information about [' + name + '] will be REMOVED!', 33 | {cancel:'cancel', ok:'delete'}, 34 | function (answer) { 35 | if (answer) { 36 | _doDelete(id); 37 | } 38 | } 39 | ); 40 | } 41 | 42 | function _doDelete (id) { 43 | phoneAPI.removePhone(id) 44 | .then(_success) 45 | .catch(_error); 46 | 47 | function _success (data) { 48 | _getPhoneList(); 49 | } 50 | 51 | function _error (message) { 52 | LxNotificationService.alert('Delete phone error', message, 'OK', function () { 53 | _getPhoneList(); 54 | }); 55 | } 56 | } 57 | 58 | } 59 | })(); 60 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.detail.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone') 6 | .controller('PhoneDetailController', PhoneDetailController); 7 | 8 | PhoneDetailController.$inject = ['phoneAPI', '$stateParams', 9 | 'LxNotificationService', '$q']; 10 | /* @ngInject */ 11 | function PhoneDetailController (phoneAPI, $stateParams, 12 | LxNotificationService, $q) { 13 | var vm = this; 14 | 15 | vm.state = 'view'; 16 | vm.beginEdit = beginEdit; 17 | vm.cancelUpdate = cancelUpdate; 18 | vm.updatePhone = updatePhone; 19 | 20 | var _originalPhone; 21 | 22 | init(); 23 | 24 | ///////////// 25 | 26 | function init () { 27 | var id = $stateParams.id; 28 | if (id) { 29 | _getPhoneDetail(id); 30 | } 31 | } 32 | 33 | function _getPhoneDetail (id) { 34 | phoneAPI.getPhoneDetail(id) 35 | .then(function (data) { 36 | vm.phone = data; 37 | }); 38 | } 39 | 40 | function beginEdit () { 41 | _originalPhone = angular.copy(vm.phone); 42 | vm.state = 'edit'; 43 | } 44 | 45 | function cancelUpdate () { 46 | vm.phone = angular.copy(_originalPhone); 47 | vm.state = 'view'; 48 | } 49 | 50 | function updatePhone (phone) { 51 | // return promise here to let the phone form controller know the response status 52 | return phoneAPI.updatePhone(phone.id, phone) 53 | .then(_success) 54 | .catch(_error); 55 | 56 | function _success (data) { 57 | vm.state = 'view'; 58 | vm.phone = data; 59 | } 60 | 61 | function _error (message) { 62 | LxNotificationService.alert('Update phone error', message, 'OK', function () { 63 | cancelUpdate(); 64 | }); 65 | return $q.reject(); 66 | } 67 | } 68 | 69 | } 70 | })(); 71 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.detail.jade: -------------------------------------------------------------------------------- 1 | .phone-detail-view.full-width 2 | .card 3 | .card-header(class="p+") 4 | h3.title {{::vm.phone.model}} 5 | button.btn.btn--m.btn--blue.btn--fab(ng-show="vm.state === 'view'", ng-click="vm.beginEdit()") 6 | i.mdi.mdi-pencil 7 | .card-content 8 | aio-phone-form(phone="vm.phone", state="vm.state", submit="vm.updatePhone", 9 | cancel="vm.cancelUpdate") 10 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.form.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone') 6 | .controller('PhoneFormController', PhoneFormController); 7 | 8 | PhoneFormController.$inject = ['phoneAPI']; 9 | /* @ngInject */ 10 | function PhoneFormController (phoneAPI) { 11 | var vm = this; 12 | 13 | vm.selects = { 14 | src: [ 15 | {'name': 'Android'}, 16 | {'name': 'iOS'}, 17 | {'name': 'Windows Phone'} 18 | ], 19 | toModel: _toModel, 20 | toSelection: _toSelection 21 | }; 22 | 23 | vm.submitForm = submitForm; 24 | 25 | init(); 26 | 27 | ///////////// 28 | 29 | function init () { 30 | 31 | } 32 | 33 | function _toModel (selection, callback) { 34 | if (selection) { 35 | callback(selection.name); 36 | } else { 37 | callback(); 38 | } 39 | } 40 | 41 | function _toSelection (model, callback) { 42 | var target; 43 | if (model) { 44 | target = vm.selects.src.filter(function (item) { 45 | return item.name === model; 46 | }); 47 | callback(target[0]); 48 | } else { 49 | callback(); 50 | } 51 | } 52 | 53 | function submitForm (phone) { 54 | if (vm.phoneForm.$invalid || !vm.phone.releaseDate) { 55 | return; 56 | } 57 | vm.isRequest = true; 58 | // call submit method passed in from outer scope 59 | vm.submit(phone) 60 | .then(function () { 61 | _endRequest(); 62 | vm.phoneForm.$setPristine(); 63 | }) 64 | .catch(_endRequest); 65 | } 66 | 67 | function _endRequest () { 68 | vm.isRequest = false; 69 | } 70 | 71 | } 72 | })(); 73 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.form.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone') 6 | .directive('aioPhoneForm', PhoneForm); 7 | 8 | PhoneForm.$inject = []; 9 | /* @ngInject */ 10 | function PhoneForm () { 11 | var directive = { 12 | restrict: 'AE', 13 | transclude: true, 14 | scope: { 15 | phone: '=', 16 | state: '=', 17 | submit: '=', 18 | cancel: '=' 19 | }, 20 | controller: 'PhoneFormController', 21 | controllerAs: 'vm', 22 | bindToController: true, 23 | templateUrl: 'static/phone/phone.form.html' 24 | }; 25 | return directive; 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.form.jade: -------------------------------------------------------------------------------- 1 | form.form(name="vm.phoneForm", 2 | ng-class="{'editing': vm.state === 'edit', 'adding': vm.state === 'add'}") 3 | .form-group 4 | .form-item 5 | .form-label 6 | span Model: 7 | span.form-value {{vm.phone.model}} 8 | .form-field 9 | .error-message(ng-messages="vm.phoneForm.modelInput.$error", 10 | ng-show="vm.phoneForm.modelInput.showError") 11 | p(ng-message="required") Model filed is required! 12 | lx-text-field.form-control(label="Model", error="vm.phoneForm.modelInput.$invalid && vm.phoneForm.modelInput.$dirty") 13 | input(name="modelInput", type="text", ng-model="vm.phone.model", required) 14 | .error-icon(ng-show="vm.phoneForm.modelInput.$invalid && vm.phoneForm.modelInput.$dirty") 15 | i.icon.icon--red.icon--circled.mdi.mdi-minus.icon-breath-animation( 16 | ng-mouseenter="vm.phoneForm.modelInput.showError = true", 17 | ng-mouseleave="vm.phoneForm.modelInput.showError = false" 18 | ) 19 | .form-item 20 | .form-label 21 | span OS: 22 | span.form-value {{vm.phone.os}} 23 | .form-field 24 | .error-message(ng-messages="vm.phoneForm.osSelect.$error", 25 | ng-show="vm.phoneForm.osSelect.showError") 26 | p(ng-message="required") OS Type filed is required! 27 | lx-select.form-control(name="osSelect" ng-model="vm.phone.os", 28 | placeholder="OS Type", choices="vm.selects.src", floating-label, 29 | selection-to-model="vm.selects.toModel(data, callback)", 30 | model-to-selection="vm.selects.toSelection(data, callback)", 31 | required 32 | ) 33 | lx-select-selected {{$selected.name}} 34 | lx-select-choices {{$choice.name}} 35 | .error-icon(ng-show="vm.phoneForm.osSelect.$invalid && vm.phoneForm.osSelect.$dirty") 36 | i.icon.icon--red.icon--circled.mdi.mdi-minus.icon-breath-animation( 37 | ng-mouseenter="vm.phoneForm.osSelect.showError = true", 38 | ng-mouseleave="vm.phoneForm.osSelect.showError = false" 39 | ) 40 | .form-item 41 | .form-label 42 | span Price: 43 | span.form-value {{vm.phone.price}} 44 | .form-field 45 | .error-message(ng-messages="vm.phoneForm.priceInput.$error", 46 | ng-show="vm.phoneForm.priceInput.showError") 47 | p(ng-message="required") Price filed is required! 48 | p(ng-message="number") Price filed must be a valid number! 49 | lx-text-field.form-control(label="Price", error="vm.phoneForm.priceInput.$invalid && vm.phoneForm.priceInput.$dirty") 50 | input(name="priceInput", type="text", ng-model="vm.phone.price", 51 | aio-validate-number, required) 52 | .error-icon(ng-show="vm.phoneForm.priceInput.$invalid && vm.phoneForm.priceInput.$dirty") 53 | i.icon.icon--red.icon--circled.mdi.mdi-minus.icon-breath-animation( 54 | ng-mouseenter="vm.phoneForm.priceInput.showError = true", 55 | ng-mouseleave="vm.phoneForm.priceInput.showError = false" 56 | ) 57 | .form-group 58 | .form-item 59 | .form-label 60 | span Screen Size: 61 | span.form-value {{vm.phone.size}} 62 | .form-field 63 | .error-message(ng-messages="vm.phoneForm.sizeInput.$error", 64 | ng-show="vm.phoneForm.sizeInput.showError") 65 | p(ng-message="required") Screen Size filed is required! 66 | p(ng-message="number") Screen Size must be a valid number! 67 | lx-text-field.form-control(label="Screen Size", error="vm.phoneForm.sizeInput.$invalid && vm.phoneForm.sizeInput.$dirty") 68 | input(name="sizeInput", type="text", ng-model="vm.phone.size", 69 | aio-validate-number, required) 70 | .error-icon(ng-show="vm.phoneForm.sizeInput.$invalid && vm.phoneForm.sizeInput.$dirty") 71 | i.icon.icon--red.icon--circled.mdi.mdi-minus.icon-breath-animation( 72 | ng-mouseenter="vm.phoneForm.sizeInput.showError = true", 73 | ng-mouseleave="vm.phoneForm.sizeInput.showError = false" 74 | ) 75 | .form-item 76 | .form-label 77 | span Manufacturer: 78 | span.form-value {{vm.phone.manufacturer}} 79 | .form-field 80 | .error-message(ng-messages="vm.phoneForm.manufacturerInput.$error", 81 | ng-show="vm.phoneForm.manufacturerInput.showError") 82 | p(ng-message="required") Manufacturer filed is required! 83 | lx-text-field.form-control(label="Manufacturer", error="vm.phoneForm.manufacturerInput.$invalid && vm.phoneForm.manufacturerInput.$dirty") 84 | input(name="manufacturerInput", type="text", 85 | ng-model="vm.phone.manufacturer", required) 86 | .error-icon(ng-show="vm.phoneForm.manufacturerInput.$invalid && vm.phoneForm.manufacturerInput.$dirty") 87 | i.icon.icon--red.icon--circled.mdi.mdi-minus.icon-breath-animation( 88 | ng-mouseenter="vm.phoneForm.manufacturerInput.showError = true", 89 | ng-mouseleave="vm.phoneForm.manufacturerInput.showError = false" 90 | ) 91 | .form-item 92 | .form-label 93 | span Release Date: 94 | span.form-value {{vm.phone.releaseDate | date: 'MMMM d, yyyy'}} 95 | .form-field 96 | .error-message(ng-show="vm.releaseDateError") 97 | p Release Date filed is required! 98 | lx-date-picker.form-control(label="Release Date", model="vm.phone.releaseDate", 99 | ng-click="vm.hasClicked = true") 100 | .error-icon(ng-show="!vm.phone.releaseDate && vm.hasClicked") 101 | i.icon.icon--red.icon--circled.mdi.mdi-minus.icon-breath-animation( 102 | ng-mouseenter="vm.releaseDateError = true", 103 | ng-mouseleave="vm.releaseDateError = false" 104 | ) 105 | .actions(class="mt p+") 106 | button.btn.btn--m.btn--white.btn--raised.btn-cancel( 107 | ng-click="vm.cancel()", 108 | ng-disabled="vm.isRequest" 109 | ) Cancel 110 | button.btn.btn--m.btn--green.btn--raised.btn-save(type="submit", 111 | ng-click="vm.submitForm(vm.phone)", 112 | ng-disabled="vm.phoneForm.$invalid || vm.phoneForm.$pristine || !vm.phone.releaseDate || vm.isRequest", 113 | aio-loading-button="vm.isRequest", 114 | ) Save 115 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.jade: -------------------------------------------------------------------------------- 1 | .phone-main-view.full-width 2 | a.btn.btn--m.btn--green.btn--raised(ui-sref="root.phone.add", lx-ripple) 3 | i.mdi.mdi-plus-box.mr 4 | span Add New 5 | .card(class="mt+") 6 | table.data-table 7 | thead 8 | tr.heading 9 | th Model 10 | th.hide-sm OS 11 | th.hide-sm Price 12 | th.text-center Action 13 | tr 14 | td.divider.divider--dark(colspan="4") 15 | tbody 16 | tr.empty-row(ng-if="vm.phones.length === 0") 17 | td(colspan="4") No Phones found! 18 | tr.data-table__clickable-row.phone-item(ng-repeat="phone in vm.phones track by phone.id") 19 | td 20 | span {{::phone.model}} 21 | td.hide-sm 22 | span {{::phone.os}} 23 | td.hide-sm 24 | span {{::phone.price}} 25 | td.text-center 26 | a.btn.btn--m.btn--blue.btn--fab( 27 | ui-sref="root.phone.detail({id: phone.id})", 28 | lx-ripple, lx-tooltip="View" 29 | ) 30 | i.mdi.mdi-information 31 | button.btn.btn--m.btn--red.btn--fab( 32 | ng-click="vm.deletePhone(phone.id, phone.model)", 33 | lx-ripple, lx-tooltip="Delete" 34 | ) 35 | i.mdi.mdi-delete 36 | 37 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone', ['app.core']); 6 | })(); 7 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.route.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | 10 | /* @ngInject */ 11 | function appRun (routerHelper) { 12 | routerHelper.configureStates(getStates()); 13 | } 14 | 15 | function getStates () { 16 | return [ 17 | { 18 | state: 'root.phone', 19 | config: { 20 | url: '/phone', 21 | views: { 22 | 'main@': { 23 | templateUrl: 'static/phone/phone.html', 24 | controller: 'PhoneController as vm' 25 | } 26 | }, 27 | data: { 28 | title: 'Phone', 29 | _class: 'phone', 30 | requireLogin: true 31 | }, 32 | sidebar: { 33 | icon: 'mdi-cellphone-android', 34 | text: 'Phones' 35 | }, 36 | breadcrumb: 'Phone List' 37 | } 38 | }, 39 | { 40 | state: 'root.phone.add', 41 | config: { 42 | url: '/add', 43 | views: { 44 | 'main@': { 45 | templateUrl: 'static/phone/phone.add.html', 46 | controller: 'PhoneAddController as vm' 47 | } 48 | }, 49 | breadcrumb: 'Add Phone' 50 | } 51 | }, 52 | { 53 | state: 'root.phone.detail', 54 | config: { 55 | url: '/:id', 56 | views: { 57 | 'main@': { 58 | templateUrl: 'static/phone/phone.detail.html', 59 | controller: 'PhoneDetailController as vm' 60 | } 61 | }, 62 | breadcrumb: 'Phone Detail' 63 | } 64 | } 65 | ]; 66 | } 67 | })(); 68 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.phone') 6 | .factory('phoneAPI', phoneSerivce); 7 | 8 | phoneSerivce.$inject = ['$http', '$q', 'ajaxErrorHandler']; 9 | /* @ngInject */ 10 | function phoneSerivce ($http, $q, ajaxError) { 11 | var service = { 12 | getPhones: getPhones, 13 | getPhoneDetail: getPhoneDetail, 14 | addNewPhone: addNewPhone, 15 | updatePhone: updatePhone, 16 | removePhone: removePhone 17 | }; 18 | 19 | return service; 20 | 21 | ///////////// 22 | 23 | function getPhones () { 24 | return $http.get('api/phones') 25 | .then(_success) 26 | .catch(ajaxError.catcher); 27 | 28 | function _success (response) { 29 | var data = response.data; 30 | if (response.status === 200 && data.code === 0) { 31 | return data.result.phones; 32 | } else { 33 | return $q.reject(data.message); 34 | } 35 | } 36 | } 37 | 38 | function getPhoneDetail (id) { 39 | return $http.get('api/phones/' + id) 40 | .then(_success) 41 | .catch(ajaxError.catcher); 42 | 43 | function _success (response) { 44 | var data = response.data; 45 | if (response.status === 200 && data.code === 0) { 46 | return data.result.phone; 47 | } else { 48 | return $q.reject(data.message); 49 | } 50 | } 51 | } 52 | 53 | function addNewPhone (phone) { 54 | var req = { 55 | 'phone': phone 56 | }; 57 | return $http.post('api/phones', req) 58 | .then(_success) 59 | .catch(ajaxError.catcher); 60 | 61 | function _success (response) { 62 | var data = response.data; 63 | if (response.status === 200 && data.code === 0) { 64 | return data.result.phone; 65 | } else { 66 | return $q.reject(data.message); 67 | } 68 | } 69 | } 70 | 71 | function updatePhone (id, phone) { 72 | var req = { 73 | 'phone': phone 74 | }; 75 | return $http.put('api/phones/' + id, req) 76 | .then(_success) 77 | .catch(ajaxError.catcher); 78 | 79 | function _success (response) { 80 | var data = response.data; 81 | if (response.status === 200 && data.code === 0) { 82 | return data.result.phone; 83 | } else { 84 | return $q.reject(data.message); 85 | } 86 | } 87 | } 88 | 89 | function removePhone (id) { 90 | return $http.delete('api/phones/' + id) 91 | .then(_success) 92 | .catch(ajaxError.catcher); 93 | 94 | function _success (response) { 95 | var data = response.data; 96 | if (response.status === 200 && data.code === 0) { 97 | return data.result.phone; 98 | } else { 99 | return $q.reject(data.message); 100 | } 101 | } 102 | } 103 | 104 | } 105 | })(); 106 | -------------------------------------------------------------------------------- /app/templates/client/source/app/phone/phone.styl: -------------------------------------------------------------------------------- 1 | @import '../../styles/common' 2 | 3 | @import '../../styles/phone/*' 4 | -------------------------------------------------------------------------------- /app/templates/client/source/images/layout/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PinkyJie/generator-aio-angular/6de67cf9573399e44265bc330c2b22db639b1324/app/templates/client/source/images/layout/bg.png -------------------------------------------------------------------------------- /app/templates/client/source/images/layout/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PinkyJie/generator-aio-angular/6de67cf9573399e44265bc330c2b22db639b1324/app/templates/client/source/images/layout/logo.png -------------------------------------------------------------------------------- /app/templates/client/source/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | //- variable `app` here is used by gulp 3 | html(lang="en", ng-app= app, ng-strict-di) 4 | head 5 | //- This helps the ng-show/ng-hide animations start at the right place. 6 | //- Since Angular has this but needs to load, this gives us the class early. 7 | style 8 | .ng-hide { display: none!important; } 9 | title(ng-bind="title") 10 | meta(charset="utf-8") 11 | meta(http-equiv="X-UA-Compatible", content="IE=edge, chrome=1") 12 | meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no") 13 | base(href="/") 14 | 15 | //- use HTML native comment syntax cause Jade comment syntax will lose a space 16 | // use this to break lines 17 | <!-- build:css static/styles/lib.css --> 18 | <!-- bower:css --> 19 | <!-- endbower --> 20 | <!-- endbuild --> 21 | 22 | <!-- build:css static/styles/app.css --> 23 | <!-- inject:css --> 24 | <!-- endinject --> 25 | <!-- endbuild --> 26 | 27 | body(ng-class="_class + '-page'") 28 | .wrapper 29 | //- header/content/footer view 30 | header.header-view(ui-view="header") 31 | .content 32 | .sidebar-view(ui-view="sidebar", ng-class="{'show': showSidebar}") 33 | .main-content 34 | .breadcrumb-view(ui-view="breadcrumb") 35 | .main-view(ui-view="main") 36 | footer.footer-view(ui-view="footer") 37 | 38 | // break line 39 | <!-- build:js static/js/lib.js --> 40 | <!-- bower:js --> 41 | <!-- endbower --> 42 | <!-- inject:mockDeps:js --> 43 | <!-- endinject --> 44 | <!-- endbuild --> 45 | 46 | <!-- build:js static/js/app.js --> 47 | <!-- inject:js --> 48 | <!-- endinject --> 49 | 50 | <!-- inject:templates:js --> 51 | <!-- endinject --> 52 | <!-- endbuild --> 53 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/common.styl: -------------------------------------------------------------------------------- 1 | @import 'variable' 2 | @import 'mixin' 3 | @import 'responsive' 4 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/core/animation.styl: -------------------------------------------------------------------------------- 1 | .dissolve-animation 2 | &.ng-hide-remove, &.ng-hide-add 3 | transition 0.5s linear all 4 | &.ng-hide-remove.ng-hide-remove-active, &.ng-hide-add 5 | opacity 1 6 | &.ng-hide-add.ng-hide-add-active, &.ng-hide-remove 7 | opacity 0 8 | 9 | .icon-breath-animation 10 | animation icon-breath 1.5s ease-in-out infinite 11 | 12 | .icon-rotate-animation 13 | animation icon-rotate .8s linear infinite 14 | margin-right 5px 15 | 16 | @keyframes icon-breath 17 | from 18 | box-shadow 0 0 2px 5px rgba(255, 138, 138, 0.48) 19 | opacity 0.5 20 | to 21 | box-shadow none 22 | opacity 1 23 | 24 | @keyframes icon-rotate 25 | from 26 | transform rotate(0deg) 27 | to 28 | transform rotate(360deg) 29 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/core/base.styl: -------------------------------------------------------------------------------- 1 | // generate handy class for show/hide in different screens 2 | .hide-sm 3 | +sm() 4 | display none 5 | 6 | .hide-gt-sm 7 | +generate-media($sizes[0]) 8 | display none 9 | 10 | .hide-md 11 | +md() 12 | display none 13 | 14 | .hide-gt-md 15 | +generate-media($sizes[1]) 16 | display none 17 | 18 | .hide-lg 19 | +lg() 20 | display none 21 | 22 | .hide-gt-lg 23 | +generate-media($sizes[2]) 24 | display none 25 | 26 | // date picker size for small screen 27 | .lx-date-picker__day 28 | +sm() 29 | line-height 16px 30 | a 31 | width 16px 32 | height 16px 33 | line-height 16px 34 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/core/fonts.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PinkyJie/generator-aio-angular/6de67cf9573399e44265bc330c2b22db639b1324/app/templates/client/source/styles/core/fonts.styl -------------------------------------------------------------------------------- /app/templates/client/source/styles/dashboard/base.styl: -------------------------------------------------------------------------------- 1 | .dashboard-view 2 | .boxes 3 | margin-top 20px 4 | display flex 5 | flex-wrap wrap 6 | justify-content center 7 | align-items center 8 | +sm() 9 | flex-direction column 10 | .box 11 | display block 12 | text-decoration none 13 | padding 30px 20px 14 | border-radius 2px 15 | text-shadow 0 0 2px #eee 16 | margin 20px 17 | width 200px 18 | height 150px 19 | &:hover 20 | box-shadow 2px 2px rgba(132, 143, 203, 0.4) 21 | .count 22 | display inline-block 23 | float right 24 | font-size 2rem 25 | margin-top 15px 26 | .name 27 | margin-top 10px 28 | font-weight bold 29 | text-transform uppercase 30 | margin-left 10px 31 | 32 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/home/base.styl: -------------------------------------------------------------------------------- 1 | .home-view 2 | .card 3 | .title 4 | font-weight normal 5 | padding-top 150px 6 | background url("../images/layout/logo.png") top center no-repeat 7 | .subtitle 8 | font-weight normal 9 | color #DDD1D1 10 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/layout/breadcrumb.styl: -------------------------------------------------------------------------------- 1 | .breadcrumb-view 2 | .breadcrumb 3 | list-style none 4 | li 5 | display inline-block 6 | .home-item > a 7 | margin-top -2px 8 | .breadcrumb-item 9 | margin-left -5px 10 | > i 11 | margin-top -1px 12 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/layout/content.styl: -------------------------------------------------------------------------------- 1 | body 2 | font-family Helvetica, arial 3 | .wrapper 4 | display flex 5 | flex-direction column 6 | min-height 100vh 7 | background-image url('../images/layout/bg.png') 8 | background-size cover 9 | .content 10 | margin-top $headerHeight 11 | +sm() 12 | margin-top $headerHeightForMobile 13 | display flex 14 | flex 1 15 | .main-content 16 | flex 1 17 | display flex 18 | flex-direction column 19 | overflow hidden 20 | .main-view 21 | flex 1 22 | display flex 23 | justify-content center 24 | align-items center 25 | +sm() 26 | margin-left 8px 27 | margin-right 8px 28 | &.ng-enter 29 | animation bounceIn .8s 30 | .full-width 31 | align-self flex-start 32 | padding 10px 30px 20px 30px 33 | width 100% 34 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/layout/footer.styl: -------------------------------------------------------------------------------- 1 | .footer-view 2 | .footer 3 | .copyright 4 | margin auto 5 | text-shadow 1px 1px rgba(0, 0, 0, .2) 6 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/layout/header.styl: -------------------------------------------------------------------------------- 1 | .header-view 2 | position fixed 3 | width 100% 4 | z-index 999 5 | .toolbar 6 | +sm() 7 | padding-left 0 8 | padding-right 0 9 | .logo 10 | width 40px 11 | height 40px 12 | .fs-title 13 | a 14 | text-decoration none 15 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/layout/loading.styl: -------------------------------------------------------------------------------- 1 | body 2 | #loading-bar 3 | .bar 4 | background $loadingColor 5 | height 3px 6 | .peg 7 | box-shadow 0 0 10px $loadingColor, 0 0 5px $loadingColor 8 | 9 | #loading-bar-spinner .spinner-icon 10 | border-top-color $loadingColor 11 | border-left-color $loadingColor 12 | 13 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/layout/sidebar.styl: -------------------------------------------------------------------------------- 1 | .sidebar-view 2 | background-color white 3 | border-right 1px solid rgba(0, 0, 0, 0.2) 4 | +sm() 5 | position fixed 6 | height 100% 7 | width $sidebarWidth 8 | box-shadow 3px 0 6px rgba(0, 0, 0, 0.2) 9 | transform translateX(-1 * $sidebarWidth) 10 | transition box-shadow, transform .6s cubic-bezier(0.23, 1, 0.32, 1) 11 | z-index 999 12 | &.show 13 | transform none 14 | .sidebar 15 | width $sidebarWidth 16 | .sidebar-menu 17 | list-style none 18 | .menu-item 19 | border-bottom 1px solid rgba(0, 0, 0, 0.2) 20 | &.active 21 | border-radius 5px 22 | background-color rgba(146, 205, 255, 0.42) 23 | i 24 | color #4CAF50 25 | &:hover 26 | background-color rgba(146, 205, 255, 0.42) 27 | &:hover 28 | text-shadow 1px 1px rgba(0, 0, 0, 0.2) 29 | background-color #eee 30 | .link 31 | display block 32 | text-decoration none 33 | .text 34 | line-height 32px 35 | font-size 14pt 36 | .sidebar-backdrop 37 | +sm() 38 | position absolute 39 | top 0 40 | left $sidebarWidth 41 | z-index 990 42 | background-color rgba(0, 0, 0, 0.5) 43 | height 100% 44 | width 100% 45 | 46 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/login/base.styl: -------------------------------------------------------------------------------- 1 | .login-view 2 | position relative 3 | .login-checking 4 | display flex 5 | flex-direction column 6 | justify-content center 7 | align-items center 8 | z-index 10 9 | background rgba(0, 0,0, .6) 10 | text-align center 11 | position absolute 12 | width 100% 13 | height 100% 14 | .loading-text 15 | animation pulse .8s linear infinite 16 | .card__header 17 | border-bottom 1px solid rgba(0, 0, 0, .2) 18 | .login-message 19 | padding 8px 20 | &.error 21 | background-color $errorColor 22 | &.success 23 | background-color $successColor 24 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/mixin.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PinkyJie/generator-aio-angular/6de67cf9573399e44265bc330c2b22db639b1324/app/templates/client/source/styles/mixin.styl -------------------------------------------------------------------------------- /app/templates/client/source/styles/phone/base.styl: -------------------------------------------------------------------------------- 1 | .phone-main-view 2 | .data-table 3 | .heading 4 | th 5 | font-weight bold 6 | +sm() 7 | text-align center 8 | .phone-item 9 | &.ng-enter 10 | animation slideInRight .8s 11 | &.ng-leave 12 | animation slideOutRight .8s 13 | 14 | .phone-detail-view, .phone-add-view 15 | .card 16 | .card-header 17 | border-bottom 1px solid rgba(0, 0, 0, 0.12) 18 | .title 19 | display inline-block 20 | button 21 | float right 22 | margin-top -5px 23 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/phone/phone.form.styl: -------------------------------------------------------------------------------- 1 | .form 2 | display flex 3 | flex-flow row wrap 4 | +sm() 5 | flex-direction column 6 | .form-group 7 | flex 1 8 | margin 16px 9 | .form-item 10 | padding 8px 11 | border-bottom 1px dashed rgba(0, 0, 0, 0.12) 12 | .form-label 13 | display flex 14 | span 15 | flex 1 16 | .form-value 17 | +sm() 18 | text-align right 19 | .form-field 20 | position relative 21 | display none 22 | .error-message 23 | &.ng-inactive 24 | display none 25 | &.ng-active 26 | display block 27 | border-radius 3px 28 | background-color rgba(0,0,0,.1) 29 | padding 5px 30 | position absolute 31 | right 0 32 | bottom 40px 33 | z-index 1 34 | &:after 35 | content '' 36 | border 7px solid transparent 37 | border-top-color rgba(0,0,0,0.1) 38 | position absolute 39 | right 2px 40 | margin-top 5px 41 | .form-control 42 | flex 1 43 | .error-icon 44 | align-self flex-end 45 | margin-bottom 6px 46 | 47 | .actions 48 | display none 49 | border-top 1px solid rgba(0, 0, 0, 0.12) 50 | flex 1 100% 51 | text-align center 52 | +sm() 53 | flex 1 54 | button 55 | width 100px 56 | &.btn-cancel 57 | margin-right 50px 58 | +sm() 59 | display block 60 | &.btn-cancel 61 | margin auto 62 | &.btn-save 63 | margin 10px auto 0 64 | 65 | .form-group + .form-group 66 | +sm() 67 | margin-top 0 68 | 69 | &.editing, &.adding 70 | .form-group > .form-item 71 | border-bottom none 72 | .form-label 73 | display none 74 | .form-field 75 | display flex 76 | .actions 77 | display block 78 | 79 | &.adding 80 | .actions 81 | button.btn-cancel 82 | display none 83 | 84 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/responsive.styl: -------------------------------------------------------------------------------- 1 | // simple responsive utility 2 | 3 | // pass (min, max) will generate (min-width) and (max-width) 4 | // pass (min) will generate (min-width) 5 | generate-media(args...) 6 | _min_str = 'only screen and (min-width: %s)' 7 | _max_str = ' and (max-width: %s)' 8 | if length(args) == 1 9 | _condition = _min_str % unit(args[0], 'px') 10 | else 11 | _condition = (_min_str % unit(args[0], 'px')) + (_max_str % unit(args[1], 'px')) 12 | @media _condition 13 | {block} 14 | 15 | // small device: 0 ~ 600px 16 | sm() 17 | +generate-media(0, $sizes[0]) 18 | {block} 19 | 20 | // middle device: 600px ~ 960px 21 | md() 22 | +generate-media($sizes[0], $sizes[1]) 23 | {block} 24 | 25 | // large device: 960px ~ 1200px 26 | lg() 27 | +generate-media($sizes[1], $sizes[2]) 28 | {block} 29 | 30 | // extra large device: 1200px ~ 31 | xl() 32 | +generate-media($sizes[2]) 33 | {block} 34 | 35 | -------------------------------------------------------------------------------- /app/templates/client/source/styles/variable.styl: -------------------------------------------------------------------------------- 1 | // screen sizes from https://material.angularjs.org 2 | $sizes = 600 960 1200 3 | 4 | // width/height 5 | $headerHeight = 64px 6 | $headerHeightForMobile = 56px 7 | $sidebarWidth = 200px 8 | 9 | // color 10 | $successColor = #4CAF50 11 | $errorColor = #F44336 12 | $warningColor = #FF9800 13 | $infoColor = #2196F3 14 | 15 | $loadingColor = #2c3e50 16 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/helper.js: -------------------------------------------------------------------------------- 1 | /* global $ */ 2 | 'use strict'; 3 | 4 | module.exports = function () { 5 | return { 6 | gotoUrl: gotoUrl, 7 | getHeader: getHeader, 8 | takeScreenshotIfFail: takeScreenshotIfFail, 9 | expectUrlToMatch: expectUrlToMatch 10 | }; 11 | 12 | ////////// 13 | 14 | function gotoUrl (url) { 15 | browser.get(browser.baseUrl + '/' + url); 16 | } 17 | 18 | function getHeader () { 19 | return { 20 | 'title': $('.header-title > a'), 21 | 'loginBtn': $('.header-login'), 22 | 'userName': $('.header-user-name'), 23 | 'dropdown': $('.header-dropdown'), 24 | 'dropdownToggle': $('.dropdown-toggle'), 25 | 'dropdownMenu': $('.header-dropdown-menu'), 26 | 'logoutLink': $('.logout-link') 27 | }; 28 | } 29 | 30 | function takeScreenshotIfFail () { 31 | var fs = require('fs'); 32 | var currentSpec = jasmine.getEnv().currentSpec; 33 | var passed = currentSpec.results().passed(); 34 | if (!passed) { 35 | browser.takeScreenshot().then(function (png) { 36 | browser.getCapabilities().then(function (capabilities) { 37 | var browserName = capabilities.caps_.browserName; 38 | var filename = browserName + '-' + 39 | currentSpec.description.replace(/[ :]/g, '-') + '.png'; 40 | try { 41 | fs.mkdirSync(browser.params.screenshotDir); 42 | } catch (e) { 43 | console.log(e); 44 | } 45 | var stream = fs.createWriteStream(browser.params.screenshotDir + filename); 46 | stream.on('open', function () { 47 | stream.write(new Buffer(png, 'base64')); 48 | }); 49 | }); 50 | }); 51 | } 52 | } 53 | 54 | function expectUrlToMatch (url) { 55 | expect(browser.getCurrentUrl()).toMatch(new RegExp(url)); 56 | } 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/mocks/e2e.config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('appTest') 6 | .config(appTestConfig) 7 | .run(appTestRun); 8 | 9 | appTestConfig.$inject = ['$httpProvider']; 10 | /* @ngInject */ 11 | function appTestConfig ($httpProvider) { 12 | $httpProvider.interceptors.push(apiDelayInterceptor); 13 | 14 | apiDelayInterceptor.$inject = ['$timeout', '$q']; 15 | /* @ngInject */ 16 | function apiDelayInterceptor ($timeout, $q) { 17 | return { 18 | 'response': function (response) { 19 | // all API response will be delayed 1s to simulate real network 20 | var delay = 1000; 21 | if (response.config.url.match(/^api\//)) { 22 | var d = $q.defer(); 23 | $timeout(function () { 24 | d.resolve(response); 25 | }, delay); 26 | return d.promise; 27 | } 28 | return response; 29 | } 30 | }; 31 | } 32 | } 33 | 34 | appTestRun.$inject = ['$httpBackend']; 35 | /* @ngInject */ 36 | function appTestRun ($httpBackend) { 37 | // pass through static files 38 | $httpBackend.whenGET(/^static\//).passThrough(); 39 | } 40 | 41 | })(); 42 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/mocks/e2e.data.js: -------------------------------------------------------------------------------- 1 | // all data used by e2e mock service is here 2 | (function () { 3 | 'use strict'; 4 | 5 | var _userInfo = { 6 | 'name': 'PinkyJie' 7 | }; 8 | 9 | var _userProducts = [ 10 | { 11 | 'name': 'phone', 12 | 'count': 5 13 | } 14 | ]; 15 | 16 | var _phones = [ 17 | { 18 | 'id': '1', 19 | 'model': 'iPhone 6', 20 | 'os': 'iOS', 21 | 'price': 5288, 22 | 'manufacturer': 'Apple', 23 | 'size': 4.7, 24 | 'releaseDate': _getTimestamp(2014, 10, 9) 25 | }, 26 | { 27 | 'id': '2', 28 | 'model': 'iPhone 6 Plus', 29 | 'os': 'iOS', 30 | 'price': 6088, 31 | 'size': 5.5, 32 | 'manufacturer': 'Apple', 33 | 'releaseDate': _getTimestamp(2014, 10, 9) 34 | }, 35 | { 36 | 'id': '3', 37 | 'model': 'Nexus 6', 38 | 'os': 'Android', 39 | 'price': 4400, 40 | 'size': 5.96, 41 | 'manufacturer': 'Motorola', 42 | 'releaseDate': _getTimestamp(2014, 10, 10) 43 | }, 44 | { 45 | 'id': '4', 46 | 'model': 'Galaxy S6', 47 | 'os': 'Android', 48 | 'price': 5288, 49 | 'size': 5.1, 50 | 'manufacturer': 'Samsung', 51 | 'releaseDate': _getTimestamp(2015, 3, 25) 52 | }, 53 | { 54 | 'id': '5', 55 | 'model': 'Mi Note', 56 | 'os': 'Android', 57 | 'price': 2299, 58 | 'size': 5.7, 59 | 'manufacturer': 'Xiaomi', 60 | 'releaseDate': _getTimestamp(2015, 1, 18) 61 | } 62 | ]; 63 | 64 | angular 65 | .module('appTest') 66 | .factory('mockData', mockData); 67 | 68 | function mockData () { 69 | var _loginStatus = false; 70 | var service = { 71 | loginStatus: _loginStatus, 72 | userInfo: _userInfo, 73 | userProducts: _userProducts, 74 | phones: _phones 75 | }; 76 | 77 | return service; 78 | } 79 | 80 | /////////////// 81 | 82 | function _getTimestamp (year, month, day) { 83 | var date = new Date(); 84 | date.setFullYear(year); 85 | date.setMonth(month - 1); 86 | date.setDate(day); 87 | date.setHours(0); 88 | date.setMinutes(0); 89 | date.setSeconds(0); 90 | return date; 91 | } 92 | 93 | })(); 94 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/mocks/e2e.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('appTest', [ 6 | 'app', 7 | 'ngMockE2E' 8 | ]); 9 | })(); 10 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/mocks/e2e.phone.js: -------------------------------------------------------------------------------- 1 | // API mock for phone.service.js 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('appTest') 7 | .run(phoneServiceMock); 8 | 9 | phoneServiceMock.$inject = ['mockData', '$httpBackend']; 10 | /* @ngInject */ 11 | function phoneServiceMock (mockData, $httpBackend) { 12 | $httpBackend.whenGET('api/phones').respond(getPhonesHandler); 13 | $httpBackend.whenGET(/api\/phones\/\d+/).respond(getPhoneDetailHandler); 14 | $httpBackend.whenPOST('api/phones').respond(addNewPhoneHandler); 15 | $httpBackend.whenPUT(/api\/phones\/\d+/).respond(updatePhoneDetailHandler); 16 | $httpBackend.whenDELETE(/api\/phones\/\d+/).respond(removePhoneHandler); 17 | 18 | function getPhonesHandler () { 19 | return [200, {code: 0, message: null, result: { 20 | phones: mockData.phones 21 | }}]; 22 | } 23 | 24 | function getPhoneDetailHandler (method, url) { 25 | var matches = url.match(/^api\/phones\/(\d+)/); 26 | var id; 27 | var targetPhone; 28 | if (matches.length === 2) { 29 | id = matches[1]; 30 | targetPhone = _getPhoneById(id); 31 | if (targetPhone.length > 0) { 32 | return [200, {code: 0, message: null, result: { 33 | 'phone': targetPhone[0] 34 | }}]; 35 | } 36 | } 37 | return [200, {code: 1, message: 'PHONE_QUERY_NOT_FOUND', result: null}]; 38 | } 39 | 40 | function addNewPhoneHandler (method, url, data) { 41 | var req = JSON.parse(data); 42 | var currentCount = mockData.phones.length; 43 | req.phone.id = '' + (currentCount + 1); 44 | mockData.phones.push(req.phone); 45 | return [200, {code: 0, message: null, result: { 46 | 'phone': req.phone 47 | }}]; 48 | } 49 | 50 | function updatePhoneDetailHandler (method, url, data) { 51 | var req = JSON.parse(data); 52 | var matches = url.match(/^api\/phones\/(\d+)/); 53 | var id; 54 | var targetPhone; 55 | var index; 56 | if (matches.length === 2) { 57 | id = matches[1]; 58 | targetPhone = _getPhoneById(id); 59 | if (targetPhone.length > 0) { 60 | mockData.phones.forEach(function (phone, idx) { 61 | if (phone.id === id) { 62 | index = idx; 63 | } 64 | }); 65 | req.phone.id = id; 66 | mockData.phones[index] = req.phone; 67 | return [200, {code: 0, message: null, result: { 68 | 'phone': req.phone 69 | }}]; 70 | } 71 | } 72 | return [200, {code: 1, message: 'PHONE_UPDATE_NOT_FOUND', result: null}]; 73 | } 74 | 75 | function removePhoneHandler (method, url) { 76 | var matches = url.match(/^api\/phones\/(\d+)/); 77 | var id; 78 | var targetPhone; 79 | var index; 80 | if (matches.length === 2) { 81 | id = matches[1]; 82 | targetPhone = _getPhoneById(id); 83 | if (targetPhone.length > 0) { 84 | mockData.phones.forEach(function (phone, idx) { 85 | if (phone.id === id) { 86 | index = idx; 87 | } 88 | }); 89 | mockData.phones.splice(index, 1); 90 | return [200, {code: 0, message: null, result: { 91 | 'phone': targetPhone[0] 92 | }}]; 93 | } 94 | } 95 | return [200, {code: 1, message: 'PHONE_DELETE_NOT_FOUND', result: null}]; 96 | } 97 | 98 | function _getPhoneById (id) { 99 | return mockData.phones.filter(function (phone) { 100 | return phone.id === id; 101 | }); 102 | } 103 | 104 | } 105 | 106 | })(); 107 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/mocks/e2e.user.js: -------------------------------------------------------------------------------- 1 | // API mock for user.service.js 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('appTest') 7 | .run(userServiceMock); 8 | 9 | userServiceMock.$inject = ['mockData', '$httpBackend']; 10 | /* @ngInject */ 11 | function userServiceMock (mockData, $httpBackend) { 12 | $httpBackend.whenGET('api/user/loginstatus').respond(loginStatusHandler); 13 | $httpBackend.whenPOST('api/user/login').respond(loginHandler); 14 | $httpBackend.whenPOST('api/user/logout').respond(logoutHandler); 15 | $httpBackend.whenGET('api/user/products').respond(productsHandler); 16 | 17 | function loginStatusHandler () { 18 | var code = mockData.loginStatus ? 0 : 1; 19 | var result = mockData.loginStatus ? {user: mockData.userInfo} : null; 20 | return [200, {code: code, message: null, result: result}]; 21 | } 22 | 23 | function loginHandler (method, url, data) { 24 | var req = JSON.parse(data); 25 | if (req.email === 'error@error.com') { 26 | return [200, { 27 | code: 1, 28 | message: 'LOGIN_WRONG_EMAIL_PASSWORD_PAIR', 29 | result: null 30 | }]; 31 | } else if (req.email === 'lock@lock.com') { 32 | return [200, { 33 | code: 1, 34 | message: 'LOGIN_USER_IN_LOCK', 35 | result: null 36 | }]; 37 | } else { 38 | mockData.loginStatus = true; 39 | return [200, { 40 | code: 0, message: null, 41 | result: { 42 | user: mockData.userInfo 43 | } 44 | }]; 45 | } 46 | } 47 | 48 | function logoutHandler () { 49 | mockData.loginStatus = false; 50 | return [200, {code: 0, message: null, result: null}]; 51 | } 52 | 53 | function productsHandler () { 54 | return [200, {code: 0, message: null, result: { 55 | summary: mockData.userProducts 56 | }}]; 57 | } 58 | } 59 | 60 | })(); 61 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/screenshots/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PinkyJie/generator-aio-angular/6de67cf9573399e44265bc330c2b22db639b1324/app/templates/client/source/test/e2e/screenshots/.gitkeep -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/specs/home.spec.js: -------------------------------------------------------------------------------- 1 | /* global $ */ 2 | 'use strict'; 3 | 4 | // page object 5 | var HomePage = function () { 6 | var self = this; 7 | 8 | self.url = ''; 9 | self.ele = _getAllElements(); 10 | 11 | self.load = load; 12 | 13 | //////// 14 | 15 | function _getAllElements () { 16 | var header = browser._.getHeader(); 17 | return { 18 | 'headerTitle': header.title, 19 | 'headerLoginBtn': header.loginBtn, 20 | 'mainTitle': $('.card > .title'), 21 | 'subTitle': $('.card > .subtitle'), 22 | 'getStartedBtn': $('.card > a') 23 | }; 24 | } 25 | 26 | function load () { 27 | browser._.gotoUrl(self.url); 28 | } 29 | }; 30 | 31 | module.exports = new HomePage(); 32 | 33 | // test scenarios 34 | describe('Home Page:', function () { 35 | var page; 36 | beforeEach(function () { 37 | page = new HomePage(); 38 | page.load(); 39 | }); 40 | 41 | afterEach(function () { 42 | browser._.takeScreenshotIfFail(); 43 | }); 44 | 45 | it('should display correct header', function () { 46 | expect(page.ele.headerTitle.getText()).toEqual('Aio Angular App'); 47 | expect(page.ele.headerLoginBtn.isDisplayed()).toBe(true); 48 | }); 49 | 50 | it('should display correct tilte and sub title', function () { 51 | expect(page.ele.mainTitle.getText()).toEqual('Aio Angular App'); 52 | expect(page.ele.subTitle.getText()).toEqual( 53 | 'Awesome web app built on AngularJS & Material Design.'); 54 | }); 55 | 56 | it('should go to login page if click get started', function () { 57 | page.ele.getStartedBtn.click(); 58 | browser._.expectUrlToMatch('login'); 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /app/templates/client/source/test/e2e/specs/login.spec.js: -------------------------------------------------------------------------------- 1 | /* global $ */ 2 | 'use strict'; 3 | 4 | // page object 5 | var LoginPage = function () { 6 | var self = this; 7 | 8 | self.url = 'login'; 9 | self.ele = _getAllElements(); 10 | self.urlAfterLogin = 'dashboard'; 11 | self.logoutUrl = 'login?action=logout'; 12 | 13 | self.load = load; 14 | self.loginWithCredential = loginWithCredential; 15 | 16 | //////// 17 | 18 | function _getAllElements () { 19 | return { 20 | 'loadingView': $('.login-checking'), 21 | 'loadingIcon': $('.login-checking > .mdi-sync'), 22 | 'accountIcon': $('.login-checking > .mdi-account'), 23 | 'loadingText': $('.login-checking > .loading-text'), 24 | 'loginMessage': $('.login-message > p'), 25 | 'emailInput': element(by.model('vm.credential.email')), 26 | 'passwordInput': element(by.model('vm.credential.password')), 27 | 'loginBtn': $('.btn-login') 28 | }; 29 | } 30 | 31 | function load () { 32 | browser._.gotoUrl(self.url); 33 | } 34 | 35 | function loginWithCredential (email, password) { 36 | self.ele.emailInput.sendKeys(email); 37 | self.ele.passwordInput.sendKeys(password); 38 | expect(self.ele.loginBtn.isEnabled()).toBe(true); 39 | self.ele.loginBtn.click(); 40 | } 41 | }; 42 | 43 | module.exports = new LoginPage(); 44 | 45 | // test scenarios 46 | describe('Login Page:', function () { 47 | var page; 48 | beforeEach(function () { 49 | page = new LoginPage(); 50 | page.load(); 51 | }); 52 | 53 | afterEach(function () { 54 | browser._.takeScreenshotIfFail(); 55 | }); 56 | 57 | it('should login successfully with correct credential', function () { 58 | expect(page.ele.loginBtn.isEnabled()).toBe(false); 59 | page.loginWithCredential('f@f', 'f'); 60 | browser._.expectUrlToMatch(page.urlAfterLogin); 61 | }); 62 | 63 | it('should display error message with incorrect credential', function () { 64 | expect(page.ele.loginMessage.isPresent()).toBe(false); 65 | page.loginWithCredential('error@error.com', 'f'); 66 | expect(page.ele.loginMessage.isDisplayed()).toBe(true); 67 | expect(page.ele.loginMessage.getText()) 68 | .toEqual('Incorrect email or password, please try again!'); 69 | }); 70 | 71 | it('should display error message with locked account', function () { 72 | expect(page.ele.loginMessage.isPresent()).toBe(false); 73 | page.loginWithCredential('lock@lock.com', 'f'); 74 | expect(page.ele.loginMessage.isDisplayed()).toBe(true); 75 | expect(page.ele.loginMessage.getText()) 76 | .toEqual('Your account is locked!'); 77 | }); 78 | 79 | }); 80 | 81 | describe('Logout Page:', function () { 82 | var page; 83 | beforeEach(function () { 84 | page = new LoginPage(); 85 | browser._.gotoUrl(page.logoutUrl); 86 | }); 87 | 88 | afterEach(function () { 89 | browser._.takeScreenshotIfFail(); 90 | }); 91 | 92 | it('should not display loading view', function () { 93 | expect(page.ele.loadingView.isPresent()).toBe(false); 94 | }); 95 | 96 | it('should display success logout message', function () { 97 | expect(page.ele.loginMessage.isDisplayed()).toBe(true); 98 | expect(page.ele.loginMessage.getText()) 99 | .toEqual('You have been successfully logged out!'); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /app/templates/client/source/test/unit/specs/focus-me.directive.spec.js: -------------------------------------------------------------------------------- 1 | /* globals inject */ 2 | describe('FocusMe Directive', function () { 3 | var scope; 4 | var element; 5 | 6 | beforeEach(function () { 7 | module('app.core'); 8 | }); 9 | 10 | beforeEach(inject(function ($rootScope, $compile) { 11 | scope = $rootScope.$new(); 12 | scope.isFocus = true; 13 | element = $compile('<input aio-focus-me="isFocus" />')(scope); 14 | // spy needs to be put before $digest 15 | spyOn(element[0], 'focus'); 16 | })); 17 | 18 | it('should make element get focus when attribute is true', function () { 19 | scope.$digest(); 20 | expect(element[0].focus).toHaveBeenCalled(); 21 | }); 22 | 23 | it('should make element lose focus when attribute is false', function () { 24 | scope.isFocus = false; 25 | scope.$digest(); 26 | expect(element[0].focus).not.toHaveBeenCalled(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /app/templates/gulp/gulp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | // dependencies used in this file 3 | var wiredep = require('wiredep'); 4 | var bowerJson = require('../bower.json'); 5 | var gulp = require('gulp'); 6 | var gUtil = require('gulp-util'); 7 | var gInject = require('gulp-inject'); 8 | var gIf = require('gulp-if'); 9 | var gOrder = require('gulp-order'); 10 | // base folder 11 | var _root = './'; 12 | var _clientBase = _root + 'client/'; 13 | var _serverBase = _root + 'server/'; 14 | var _buildBase = _clientBase + 'build/'; 15 | // client folder 16 | var client = { 17 | base: _clientBase, 18 | source: _clientBase + 'source/', 19 | test: _clientBase + 'source/test/', 20 | app: _clientBase + 'source/app/' 21 | }; 22 | // server folder 23 | var server = { 24 | base: _serverBase 25 | }; 26 | // build folder 27 | var build = { 28 | base: _buildBase, 29 | dev: _buildBase + 'dev/', 30 | temp: _buildBase + '.temp/', 31 | prod: _buildBase + 'prod/' 32 | }; 33 | // node dependency 34 | var nodeModules = _root + 'node_modules/'; 35 | // bower dependency 36 | var bowerFiles = wiredep({devDependencies: true})['js']; 37 | var bower = { 38 | json: bowerJson, 39 | source: client.source + 'vendor/', 40 | target: build.dev + 'static/vendor/', 41 | mockDeps: [ 42 | build.dev + 'static/vendor/angular-mocks/angular-mocks.js' 43 | ] 44 | }; 45 | 46 | // all configuration which will be returned 47 | var config = { 48 | // folders 49 | root: _root, 50 | client: client, 51 | server: server, 52 | build: build, 53 | // js 54 | js: { 55 | all: [ 56 | client.app + '**/*.js', 57 | client.test + '**/*.js', 58 | '!' + client.test + 'unit/results/**/*.js', 59 | _root + 'gulp/**/*.js', 60 | _root + '*.js' 61 | ], 62 | app: { 63 | source: [ 64 | client.app + '**/*.js' 65 | ], 66 | target: [ 67 | build.dev + 'static/**/*.js', 68 | '!' + build.dev + 'static/vendor/**/*.*' 69 | ], 70 | production: [ 71 | client.app + '**/production/*.js' 72 | ] 73 | }, 74 | test: { 75 | stubs: [ 76 | client.test + 'e2e/mocks/**/e2e.*.js' 77 | ], 78 | unit: { 79 | specs: [ 80 | client.test + 'unit/specs/**/*.spec.js' 81 | ] 82 | } 83 | }, 84 | order: [ 85 | '**/app.module.js', 86 | '**/*.module.js', 87 | '**/*.js' 88 | ] 89 | }, 90 | // css 91 | css: { 92 | source: client.app + '**/*.styl', 93 | target: [ 94 | build.dev + 'static/**/*.css', 95 | '!' + build.dev + 'static/vendor/**/*.*' 96 | ], 97 | singleSource: client.source + 'styles/**/*.styl' 98 | }, 99 | // html 100 | html: { 101 | source: client.source + 'index.jade', 102 | target: build.dev + 'index.html' 103 | }, 104 | templateCache: { 105 | sourceJade: client.app + '**/*.jade', 106 | sourceHtml: [ 107 | build.dev + 'static/**/*.html', 108 | '!' + build.dev + 'static/vendor/**/*.*', 109 | ], 110 | target: 'templates.js', 111 | options: { 112 | module: 'app.core', 113 | root: 'static/', 114 | standAlone: false 115 | } 116 | }, 117 | resource: { 118 | images: client.source + 'images/**/*.*', 119 | fonts: bower.source + 'mdi/fonts/*.*', 120 | }, 121 | bower: bower, 122 | browserSync: { 123 | hostName: 'localhost', 124 | reloadDelay: 1000, 125 | defaultPort: 8088 126 | }, 127 | optimized: { 128 | allCSS: '*.css', 129 | appJS: 'app.js', 130 | libJS: 'lib.js' 131 | }, 132 | packages: [ 133 | _root + 'package.json', 134 | _root + 'bower.json' 135 | ] 136 | }; 137 | 138 | config.karmaOption = getKarmaOptions(); 139 | config.wiredepOption = getWiredepDefaultOptions(); 140 | config.protractorOption = getProtractorOptions(); 141 | 142 | // common functions used by multiple tasks 143 | config.fn = {}; 144 | config.fn.log = log; 145 | config.fn.inject = inject; 146 | 147 | return config; 148 | 149 | //////////////// 150 | 151 | // Options for wiredep 152 | function getWiredepDefaultOptions () { 153 | return { 154 | bowerJson: config.bower.json, 155 | directory: config.bower.target 156 | }; 157 | } 158 | 159 | // Options for karma 160 | function getKarmaOptions () { 161 | var options = { 162 | files: [].concat( 163 | bowerFiles, 164 | client.app + '**/*.module.js', 165 | client.app + '**/*.js', 166 | config.js.test.unit.specs 167 | ), 168 | exclude: [], 169 | coverage: { 170 | dir: client.test + 'unit/results/coverage', 171 | reporters: [ 172 | // reporters not supporting the `file` property 173 | {type: 'html', subdir: '.'}, 174 | {type: 'text-summary'} 175 | ] 176 | }, 177 | junit: client.test + 'unit/results/unit-test-results.xml', 178 | preprocessors: {} 179 | }; 180 | options.preprocessors[config.js.test.unit.specs] = ['coverage']; 181 | return options; 182 | } 183 | 184 | // Options for protractor 185 | function getProtractorOptions () { 186 | // options used in protractor.conf.js need to be based on it's own path 187 | return { 188 | specs: [client.test + 'e2e/specs/*.spec.js'], 189 | suites: { 190 | home: '.' + client.test + 'e2e/specs/home.spec.js', 191 | login: '.' + client.test + 'e2e/specs/login.spec.js', 192 | dashboard: '.' + client.test + 'e2e/specs/dashboard.spec.js', 193 | phone: '.' + client.test + 'e2e/specs/phone.spec.js' 194 | }, 195 | helper: '.' + client.test + 'e2e/helper', 196 | screenshotDir: client.test + 'e2e/screenshots/' 197 | }; 198 | } 199 | 200 | // Log function for both object type and primitive type 201 | function log (msg) { 202 | if (typeof(msg) === 'object') { 203 | for (var item in msg) { 204 | if (msg.hasOwnProperty(item)) { 205 | gUtil.log(gUtil.colors.blue(msg[item])); 206 | } 207 | } 208 | } else { 209 | gUtil.log(gUtil.colors.blue(msg)); 210 | } 211 | } 212 | 213 | // Helper function for gulp-inject 214 | function inject (src, label, order) { 215 | var options = { 216 | read: false, 217 | relative: true, 218 | ignorePath: '/client/build/dev' 219 | }; 220 | if (label) { 221 | options.name = 'inject:' + label; 222 | } 223 | 224 | return gInject(orderSrc(src, order), options); 225 | } 226 | 227 | function orderSrc (src, order) { 228 | //order = order || ['**/*']; 229 | return gulp 230 | .src(src) 231 | .pipe(gIf(order, gOrder(order))); 232 | } 233 | }; 234 | -------------------------------------------------------------------------------- /app/templates/gulp/index.js: -------------------------------------------------------------------------------- 1 | // 4 necessary parameters every task needs 2 | var gulp = require('gulp'); 3 | var config = require('./gulp.config')(); 4 | var $ = require('gulp-load-plugins')({lazy: true}); 5 | var args = require('yargs').argv; 6 | 7 | // read task list from folder /tasks 8 | var taskList = require('fs').readdirSync('./gulp/tasks/'); 9 | taskList.forEach(function (file) { 10 | require('./tasks/' + file)(gulp, config, $, args); 11 | }); 12 | 13 | // default task: list all tasks 14 | gulp.task('default', ['help']); 15 | gulp.task('help', $.taskListing); 16 | -------------------------------------------------------------------------------- /app/templates/gulp/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | 3 | var gulpConfig = require('./gulp.config')(); 4 | 5 | config.set({ 6 | basePath: '../', 7 | frameworks: ['jasmine'], 8 | exclude: gulpConfig.karmaOption.exclude, 9 | files: gulpConfig.karmaOption.files, 10 | preprocessors: gulpConfig.karmaOption.preprocessors, 11 | reporters: ['mocha', 'coverage', 'junit'], 12 | coverageReporter: gulpConfig.karmaOption.coverage, 13 | junitReporter: gulpConfig.karmaOption.junit, 14 | reportSlowerThan: 500, 15 | browsers: ['Chrome'] 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /app/templates/gulp/protractor.conf.js: -------------------------------------------------------------------------------- 1 | var gulpConfig = require('./gulp.config')(); 2 | var port = process.env.PORT || gulpConfig.browserSync.defaultPort; 3 | 4 | exports.config = { 5 | baseUrl: 'http://' + gulpConfig.browserSync.hostName + ':' + port, 6 | framework: 'jasmine', 7 | jasmineNodeOpts: { 8 | showColors: true, 9 | defaultTimeoutInterval: 30000 10 | }, 11 | specs: gulpConfig.protractorOption.specs, 12 | suites: gulpConfig.protractorOption.suites, 13 | capabilities: { 14 | 'browserName': 'chrome' 15 | }, 16 | onPrepare: function () { 17 | browser._ = require(gulpConfig.protractorOption.helper)(); 18 | }, 19 | params: { 20 | timeout: 10000, 21 | screenshotDir: gulpConfig.protractorOption.screenshotDir 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/_serve.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | var browserSync = require('browser-sync'); 4 | var port = process.env.PORT || config.browserSync.defaultPort; 5 | var historyApiFallback = require('connect-history-api-fallback'); 6 | 7 | /** 8 | * serve the development environment 9 | * --mock: inject mock files 10 | */ 11 | gulp.task('serve:dev', ['build:dev'], function () { 12 | startBrowserSync(true); 13 | }); 14 | 15 | /** 16 | * serve the production environment 17 | * --mock: inject mock files 18 | */ 19 | gulp.task('serve:prod', ['build:prod'], function () { 20 | startBrowserSync(false); 21 | }); 22 | 23 | /////////// 24 | 25 | function startBrowserSync (isDev) { 26 | if (browserSync.active) { 27 | return; 28 | } 29 | 30 | config.fn.log('Starting BrowserSync on port ' + port); 31 | 32 | // only watch files for development environment 33 | var watchedFiles = [].concat( 34 | config.js.app.source, 35 | config.css.singleSource, 36 | config.html.source, 37 | config.templateCache.sourceJade 38 | ); 39 | if (args.mock) { 40 | watchedFiles = watchedFiles.concat(config.js.test.stubs); 41 | } 42 | if (isDev) { 43 | gulp.watch(watchedFiles, ['build:dev', browserSync.reload]) 44 | .on('change', changeEvent); 45 | } 46 | 47 | var baseDir = isDev ? config.build.dev : config.build.prod; 48 | var options = { 49 | port: port, 50 | online: false, 51 | logLevel: 'info', 52 | logPrefix: '<%= appName %>', 53 | reloadDelay: config.browserSync.reloadDelay, 54 | server: { 55 | baseDir: baseDir, 56 | index: 'index.html', 57 | middleware: [ 58 | historyApiFallback() 59 | ] 60 | } 61 | }; 62 | 63 | browserSync(options); 64 | } 65 | 66 | function changeEvent (event) { 67 | var srcPattern = new RegExp('/.*(?=/' + config.client.source + ')/'); 68 | config.fn.log('File ' + event.path.replace(srcPattern, '') + ' ' + event.type); 69 | } 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/build.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | var runSeq = require('run-sequence'); 4 | 5 | // Build for development environment 6 | gulp.task('build:dev', ['lint'], function (done) { 7 | runSeq('clean', 'jade', 'styles', 'inject:bower', 8 | 'inject:js:css', ['copy:images', 'copy:fonts'], done); 9 | }); 10 | 11 | // Build for production environment 12 | gulp.task('build:prod', function (done) { 13 | // set a flag on args so sub task can know it is production build 14 | args.prod = true; 15 | runSeq('build:dev', 'templatecache', 'optimize', 16 | 'copy:images:prod', 'copy:fonts:prod', 'clean:temp', done); 17 | }); 18 | 19 | gulp.task('optimize', function () { 20 | config.fn.log('Optimizing the js, css, and html'); 21 | 22 | var assets = $.useref.assets({searchPath: config.build.dev}); 23 | // Filters are named for the gulp-useref path 24 | var cssFilter = $.filter('**/' + config.optimized.allCSS); 25 | var jsAppFilter = $.filter('**/' + config.optimized.appJS); 26 | var jslibFilter = $.filter('**/' + config.optimized.libJS); 27 | 28 | var templateCache = config.build.temp + config.templateCache.target; 29 | 30 | return gulp 31 | .src(config.html.target) 32 | .pipe($.plumber()) 33 | .pipe(config.fn.inject(templateCache, 'templates')) 34 | .pipe(assets) // Gather all assets from the html with useref 35 | // Get the css 36 | .pipe(cssFilter) 37 | .pipe($.csso()) 38 | .pipe(cssFilter.restore()) 39 | // Get the custom javascript 40 | .pipe(jsAppFilter) 41 | .pipe($.ngAnnotate({add: true})) 42 | .pipe($.uglify()) 43 | .pipe(getHeader()) 44 | .pipe(jsAppFilter.restore()) 45 | // Get the vendor javascript 46 | .pipe(jslibFilter) 47 | .pipe($.uglify()) 48 | .pipe(jslibFilter.restore()) 49 | // Take inventory of the file names for future rev numbers 50 | .pipe($.rev()) 51 | // Apply the concat and file replacement with useref 52 | .pipe(assets.restore()) 53 | .pipe($.useref()) 54 | // Replace the file names in the html with rev numbers 55 | .pipe($.revReplace()) 56 | .pipe(gulp.dest(config.build.prod)); 57 | }); 58 | 59 | /////////// 60 | 61 | function getHeader () { 62 | var pkg = require('../../package.json'); 63 | var template = ['/**', 64 | ' * <%= pkg.name %> - <%= pkg.description %>', 65 | ' * @authors <%= pkg.authors %>', 66 | ' * @version v<%= pkg.version %>', 67 | ' * @link <%= pkg.homepage %>', 68 | ' * @license <%= pkg.license %>', 69 | ' */', 70 | '' 71 | ].join('\n'); 72 | return $.header(template, { 73 | pkg: pkg 74 | }); 75 | } 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/clean.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | var del = require('del'); 4 | 5 | // Remove all files from the build dev folder 6 | gulp.task('clean:dev', function (done) { 7 | clean(config.build.dev, done); 8 | }); 9 | 10 | // Remove all files from the build prod folder 11 | gulp.task('clean:prod', function (done) { 12 | clean(config.build.prod, done); 13 | }); 14 | 15 | // Remove all files from the build folder 16 | gulp.task('clean', function (done) { 17 | clean(config.build.base, done); 18 | }); 19 | 20 | // Remove all image files from the build folder 21 | gulp.task('clean:images', function (done) { 22 | clean(config.build.dev + 'static/images/**/*.*', done); 23 | }); 24 | 25 | // Remove all font files from the build folder 26 | gulp.task('clean:fonts', function (done) { 27 | clean(config.build.dev + 'static/fonts/**/*.*', done); 28 | }); 29 | 30 | // Remove all style files from the build folders 31 | gulp.task('clean:styles', function (done) { 32 | clean(config.build.dev + 'static/styles/*.css', done); 33 | }); 34 | 35 | // Remove all javascript files from the build folders 36 | gulp.task('clean:js', function (done) { 37 | clean(config.js.app.target, done); 38 | }); 39 | 40 | // Remove all html files from the build folders 41 | gulp.task('clean:html', function (done) { 42 | var files = [].concat( 43 | config.build.dev + 'index.html', 44 | config.build.dev + 'static/**/*.html' 45 | ); 46 | clean(files, done); 47 | }); 48 | 49 | // Remove all files from the temp folder 50 | gulp.task('clean:temp', function (done) { 51 | clean(config.build.temp, done); 52 | }); 53 | 54 | // Remove all bower dependency files from the build folder 55 | gulp.task('clean:vendor', function (done) { 56 | clean(config.build.dev + 'static/vendor', done); 57 | }); 58 | 59 | ///////// 60 | 61 | // Log and delete the path 62 | function clean (path, done) { 63 | config.fn.log('Cleaning: ' + $.util.colors.blue(path)); 64 | del(path, done); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/copy.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | var merge = require('merge-stream'); 4 | 5 | // Copy image files 6 | gulp.task('copy:images', ['clean:images'], function () { 7 | config.fn.log('Compressing and copying images'); 8 | 9 | return copy(config.resource.images, config.build.dev + 'static/images'); 10 | }); 11 | 12 | // Copy font files 13 | gulp.task('copy:fonts', ['clean:fonts'], function () { 14 | config.fn.log('Copying all fonts files'); 15 | 16 | return copy(config.resource.fonts, config.build.dev + 'static/fonts'); 17 | }); 18 | 19 | // Copy javascript files 20 | gulp.task('copy:js', ['clean:js'], function () { 21 | config.fn.log('Copying all javascript files'); 22 | 23 | // ignore production files when building for dev 24 | var productionJS = config.js.app.production.map(function (js) { 25 | return '!' + js; 26 | }); 27 | var excludes = args.prod ? [] : productionJS; 28 | 29 | var jsStream = gulp 30 | .src(config.js.app.source.concat(excludes)) 31 | .pipe(gulp.dest(config.build.dev + 'static')); 32 | 33 | // Also copy mock files if --mock is on 34 | if (args.mock) { 35 | var mockStream = gulp 36 | .src(config.js.test.stubs) 37 | .pipe(gulp.dest(config.build.dev + 'static/test')); 38 | return merge(jsStream, mockStream); 39 | } else { 40 | return jsStream; 41 | } 42 | }); 43 | 44 | // Copy bower dependency files 45 | gulp.task('copy:vendor', ['clean:vendor'], function () { 46 | config.fn.log('Copying bower dependency files'); 47 | 48 | return copy(config.bower.source + '/**/*', config.bower.target); 49 | }); 50 | 51 | // Optimize and Copy image files to prod folder 52 | gulp.task('copy:images:prod', function () { 53 | return gulp 54 | .src(config.build.dev + 'static/images/**/*') 55 | .pipe($.imagemin({optimizationLevel: 4})) 56 | .pipe(gulp.dest(config.build.prod + 'static/images')); 57 | 58 | }); 59 | 60 | // Copy fonts files to prod folder 61 | gulp.task('copy:fonts:prod', function () { 62 | return copy(config.build.dev + 'static/fonts/**/*', config.build.prod + 'static/fonts'); 63 | }); 64 | 65 | //////// 66 | 67 | function copy (src, dest) { 68 | return gulp 69 | .src(src) 70 | .pipe(gulp.dest(dest)); 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/css.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | gulp.task('styles', ['clean:styles'], function () { 4 | config.fn.log('Compiling Stylus file to CSS'); 5 | 6 | return gulp 7 | .src(config.css.source) 8 | .pipe($.plumber()) // exit gracefully if something fails after this 9 | .pipe($.stylus()) 10 | .pipe($.autoprefixer({browsers: ['last 2 version', '> 5%']})) 11 | .pipe($.flatten()) 12 | .pipe(gulp.dest(config.build.dev + 'static/styles')); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/js-style-check.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | // jshint/jscs all js files 4 | gulp.task('lint', function () { 5 | config.fn.log('Analyzing source with JSHint and JSCS'); 6 | 7 | return gulp 8 | .src(config.js.all) 9 | .pipe($.jshint()) 10 | .pipe($.jshint.reporter('jshint-stylish', {verbose: true})) 11 | .pipe($.jshint.reporter('fail')) 12 | .pipe($.jscs()); 13 | }); 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/template.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | var merge = require('merge-stream'); 4 | 5 | // Compile jade templates to html files 6 | gulp.task('jade', ['clean:html'], function () { 7 | config.fn.log('Compiling jade templates to html files'); 8 | 9 | var indexStream = jade(config.html.source, config.build.dev); 10 | var templateStream = jade(config.templateCache.sourceJade, config.build.dev + 'static'); 11 | 12 | return merge(indexStream, templateStream); 13 | }); 14 | 15 | // Inject all js/css files into the index.html file 16 | gulp.task('inject:js:css', ['copy:js'], function () { 17 | config.fn.log('Wire up css into the html, after files are ready'); 18 | 19 | return gulp 20 | .src(config.html.target) 21 | .pipe(config.fn.inject(config.css.target)) 22 | .pipe(config.fn.inject(config.js.app.target, '', config.js.order)) 23 | .pipe(gulp.dest(config.build.dev)); 24 | }); 25 | 26 | // Inject all the bower dependencies 27 | gulp.task('inject:bower', ['copy:vendor'], function () { 28 | config.fn.log('Wiring the bower dependencies into the html'); 29 | 30 | var wiredep = require('wiredep').stream; 31 | var options = config.wiredepOption; 32 | 33 | // Also include mock dependencies with --mock flag 34 | var mockDeps = []; 35 | if (args.mock) { 36 | mockDeps = config.bower.mockDeps; 37 | } 38 | 39 | return gulp 40 | .src(config.html.target) 41 | .pipe(wiredep(options)) 42 | .pipe(config.fn.inject(mockDeps, 'mockDeps')) 43 | .pipe(gulp.dest(config.build.dev)); 44 | }); 45 | 46 | // Compile all template files to $templateCache 47 | gulp.task('templatecache', ['clean:temp'], function () { 48 | config.fn.log('Creating an AngularJS $templateCache'); 49 | 50 | return gulp 51 | .src(config.templateCache.sourceHtml) 52 | .pipe($.if(args.verbose, $.bytediff.start())) 53 | .pipe($.minifyHtml({empty: true})) 54 | .pipe($.if(args.verbose, $.bytediff.stop(bytediffFormatter))) 55 | .pipe($.angularTemplatecache( 56 | config.templateCache.target, 57 | config.templateCache.options 58 | )) 59 | .pipe(gulp.dest(config.build.temp)); 60 | }); 61 | 62 | /////////// 63 | 64 | function bytediffFormatter (data) { 65 | var difference = (data.savings > 0) ? ' smaller.' : ' larger.'; 66 | return data.fileName + ' went from ' + 67 | (data.startSize / 1000).toFixed(2) + ' kB to ' + 68 | (data.endSize / 1000).toFixed(2) + ' kB and is ' + 69 | formatPercent(1 - data.percent, 2) + '%' + difference; 70 | } 71 | 72 | function formatPercent (num, precision) { 73 | return (num * 100).toFixed(precision); 74 | } 75 | 76 | function jade (src, dest) { 77 | // change `app` variable based on --mock parameter 78 | var data = { 79 | app: args.mock ? 'appTest' : 'app' 80 | }; 81 | return gulp 82 | .src(src) 83 | .pipe($.jade({ 84 | pretty: true, 85 | locals: data 86 | })) 87 | .pipe(gulp.dest(dest)); 88 | } 89 | 90 | }; 91 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/test.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | // Run unit test once 4 | gulp.task('test:unit', function (done) { 5 | startUnitTests(true, done); 6 | }); 7 | 8 | // Run unit test and watch for file changes then re-run tests 9 | gulp.task('test:tdd', function (done) { 10 | startUnitTests(false , done); 11 | }); 12 | 13 | // Run e2e test 14 | // support --suite 15 | gulp.task('test:e2e', function (done) { 16 | var protractor = $.protractor.protractor; 17 | var options = { 18 | // debug: true, 19 | configFile: __dirname + '/../protractor.conf.js' 20 | }; 21 | if (args.suite) { 22 | options['args'] = ['--suite', args.suite]; 23 | } 24 | 25 | return gulp 26 | .src(config.protractorOption.specs) 27 | .pipe(protractor(options)) 28 | .on('error', function (e) { 29 | throw e; 30 | }); 31 | }); 32 | 33 | ///////////// 34 | 35 | function startUnitTests (singleRun, done) { 36 | var child; 37 | var fork = require('child_process').fork; 38 | var karma = require('karma').server; 39 | 40 | if (singleRun) { 41 | karma.start({ 42 | configFile: __dirname + '/../karma.conf.js', 43 | singleRun: !!singleRun 44 | }, karmaCompleted); 45 | } else { 46 | // use phantomjs when auto-run tests 47 | karma.start({ 48 | configFile: __dirname + '/../karma.conf.js', 49 | singleRun: !!singleRun, 50 | browsers: ['PhantomJS'] 51 | }, karmaCompleted); 52 | } 53 | 54 | //////////////// 55 | 56 | function karmaCompleted (karmaResult) { 57 | config.fn.log('Karma completed'); 58 | if (child) { 59 | config.fn.log('shutting down the child process'); 60 | child.kill(); 61 | } 62 | if (karmaResult === 1) { 63 | done('karma: tests failed with code ' + karmaResult); 64 | } else { 65 | done(); 66 | } 67 | } 68 | } 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /app/templates/gulp/tasks/version.task.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, config, $, args) { 2 | 3 | /** 4 | * Bump the version 5 | * --type=pre will bump the prerelease version *.*.*-x 6 | * --type=patch or no flag will bump the patch version *.*.x 7 | * --type=minor will bump the minor version *.x.* 8 | * --type=major will bump the major version x.*.* 9 | * --ver=1.2.3 will bump to a specific version and ignore other flags 10 | */ 11 | gulp.task('bump', function () { 12 | var msg = 'Bumping versions'; 13 | var type = args.type; 14 | var version = args.ver; 15 | var options = {}; 16 | if (version) { 17 | options.version = version; 18 | msg += ' to ' + version; 19 | } else { 20 | options.type = type; 21 | msg += ' for a ' + type; 22 | } 23 | config.fn.log(msg); 24 | 25 | return gulp 26 | .src(config.packages) 27 | .pipe($.print()) 28 | .pipe($.bump(options)) 29 | .pipe(gulp.dest(config.root)); 30 | }); 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /app/templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | // All the gulp related files are located in gulp folder, 2 | // change gulp config at gulp/gulp.config.js, 3 | // add gulp task file in gulp/tasks folder. 4 | require('./gulp/'); 5 | -------------------------------------------------------------------------------- /app/templates/server/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PinkyJie/generator-aio-angular/6de67cf9573399e44265bc330c2b22db639b1324/app/templates/server/.gitkeep -------------------------------------------------------------------------------- /change-env.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // this file can help you do development in app/templates folder 4 | 5 | // parameter 6 | // - dev: change all _ files to normal file and replace the palceholder 7 | // - prod: reverse of dev 8 | 9 | 10 | var fs = require('fs'); 11 | 12 | var args = process.argv; 13 | var fileList = [ 14 | '_bower.json', 15 | '_package.json', 16 | '_README.md', 17 | 'client/source/app/core/_config.js', 18 | 'client/source/app/core/production/_production.config.js', 19 | 'client/source/app/layout/_header.jade', 20 | 'client/source/app/home/_home.jade', 21 | 'gulp/tasks/_serve.task.js', 22 | ]; 23 | 24 | var BASE = 'app/templates/'; 25 | var mapping = { 26 | name: { 27 | src: '<%= appName %>', 28 | dest: 'aio-angular-app' 29 | }, 30 | desc: { 31 | src: '<%= appDesc %>', 32 | dest: 'Aio Angular App' 33 | } 34 | }; 35 | 36 | if (args.length < 3) { 37 | console.error('Please provide an "env" parameter, possible value: dev, prod'); 38 | } else { 39 | if (args[2] === 'dev') { 40 | dev(); 41 | } else if (args[2] === 'prod') { 42 | prod(); 43 | } else { 44 | console.error('Invalid parameter, possible value: dev, prod'); 45 | } 46 | } 47 | 48 | // process.exit(); 49 | 50 | 51 | //////////////// 52 | 53 | function dev () { 54 | fileList.forEach(function (name) { 55 | var newName = name.replace('_', ''); 56 | fs.renameSync(BASE + name, BASE + newName); 57 | fs.readFile(BASE + newName, 'utf8', function (err, data) { 58 | if (err) { 59 | return console.log(err); 60 | } 61 | var result = data.replace(new RegExp(mapping.name.src, 'g'), mapping.name.dest); 62 | result = result.replace(new RegExp(mapping.desc.src, 'g'), mapping.desc.dest); 63 | fs.writeFile(BASE + newName, result, 'utf8', function (err) { 64 | if (err) { 65 | return console.log(err); 66 | } 67 | }); 68 | }); 69 | }); 70 | } 71 | 72 | function prod () { 73 | fileList.forEach(function (name) { 74 | var oldName = name.replace('_', ''); 75 | fs.renameSync(BASE + oldName, BASE + name); 76 | fs.readFile(BASE + name, 'utf8', function (err, data) { 77 | if (err) { 78 | return console.log(err); 79 | } 80 | var result = data.replace(new RegExp(mapping.name.dest, 'g'), mapping.name.src); 81 | result = result.replace(new RegExp(mapping.desc.dest, 'g'), mapping.desc.src); 82 | fs.writeFile(BASE + name, result, 'utf8', function (err) { 83 | if (err) { 84 | return console.log(err); 85 | } 86 | }); 87 | }); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /html5-mode-patch.diff: -------------------------------------------------------------------------------- 1 | diff --git a/app/templates/client/source/app/helper/router-helper.provider.js b/app/templates/client/source/app/helper/router-helper.provider.js 2 | index 7fc9aa5..646b409 100644 3 | --- a/app/templates/client/source/app/helper/router-helper.provider.js 4 | +++ b/app/templates/client/source/app/helper/router-helper.provider.js 5 | @@ -15,7 +15,7 @@ 6 | resolveAlways: {} 7 | }; 8 | 9 | - $locationProvider.html5Mode(true); 10 | + // $locationProvider.html5Mode(true); 11 | 12 | this.configure = function (cfg) { 13 | angular.extend(config, cfg); 14 | diff --git a/app/templates/client/source/index.jade b/app/templates/client/source/index.jade 15 | index 43b6985..5b5e12c 100644 16 | --- a/app/templates/client/source/index.jade 17 | +++ b/app/templates/client/source/index.jade 18 | @@ -10,7 +10,7 @@ html(lang="en", ng-app= app, ng-strict-di) 19 | meta(charset="utf-8") 20 | meta(http-equiv="X-UA-Compatible", content="IE=edge, chrome=1") 21 | meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no") 22 | - base(href="/") 23 | + base(href="./") 24 | 25 | //- use HTML native comment syntax cause Jade comment syntax will lose a space 26 | // use this to break lines 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-aio-angular", 3 | "version": "0.0.8", 4 | "description": "All In One AngularJS generator.", 5 | "license": "MIT", 6 | "main": "app/index.js", 7 | "repository": "PinkyJie/generator-aio-angular", 8 | "author": { 9 | "name": "马斯特", 10 | "email": "pilixiaoxuanfeng@gmail.com", 11 | "url": "https://github.com/PinkyJie" 12 | }, 13 | "scripts": { 14 | "env": "node change-env.js", 15 | "test": "mocha" 16 | }, 17 | "files": [ 18 | "app" 19 | ], 20 | "keywords": [ 21 | "yeoman-generator", 22 | "angular", 23 | "gulp", 24 | "jade", 25 | "stylus", 26 | "ui-router", 27 | "material design" 28 | ], 29 | "dependencies": { 30 | "chalk": "^1.0.0", 31 | "yeoman-generator": "^0.19.0", 32 | "yosay": "^1.0.2", 33 | "underscore.string": "^3.0.3" 34 | }, 35 | "devDependencies": { 36 | "mocha": "*" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /publish-to-gh-pages.sh: -------------------------------------------------------------------------------- 1 | git stash 2 | git apply html5-mode-patch.diff 3 | cd app/templates/ 4 | gulp build:prod --mock 5 | cd ../../ 6 | git apply -R html5-mode-patch.diff 7 | rm -rf static/ index.html 8 | cp -r app/templates/client/build/prod/* . 9 | git add static/ index.html 10 | git commit -m "update site `date +"%Y-%m-%d %H:%M"`" 11 | git push origin gh-pages 12 | git push gitcafe gh-pages:gitcafe-pages 13 | git checkout master 14 | git stash pop 15 | --------------------------------------------------------------------------------