├── .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
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
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('')(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 |
--------------------------------------------------------------------------------