├── .gitignore ├── README.md ├── angular_programming.jpg ├── backend ├── app.js └── package.json └── frontend ├── .docker ├── angular-seed.development.dockerfile ├── angular-seed.production.dockerfile └── nginx.conf ├── .dockerignore ├── .editorconfig ├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── appveyor.yml ├── docker-compose.production.yml ├── docker-compose.yml ├── gulpfile.ts ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── readme.md ├── src ├── client │ ├── app │ │ ├── about │ │ │ ├── about.component.css │ │ │ ├── about.component.html │ │ │ ├── about.component.spec.ts │ │ │ ├── about.component.ts │ │ │ ├── about.module.ts │ │ │ ├── about.routes.ts │ │ │ └── index.ts │ │ ├── admin │ │ │ ├── admin-routing.module.ts │ │ │ ├── admin.component.html │ │ │ ├── admin.component.ts │ │ │ ├── admin.module.ts │ │ │ └── index.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── center │ │ │ ├── center.component.css │ │ │ ├── center.component.html │ │ │ ├── center.component.ts │ │ │ ├── center.module.ts │ │ │ ├── center.routes.ts │ │ │ ├── index.ts │ │ │ └── shared │ │ │ │ ├── center-shared.module.ts │ │ │ │ ├── index.ts │ │ │ │ ├── questionnaire-controls │ │ │ │ ├── index.ts │ │ │ │ ├── questionnaire-controls.component.css │ │ │ │ ├── questionnaire-controls.component.html │ │ │ │ └── questionnaire-controls.component.ts │ │ │ │ ├── questionnaire-detail │ │ │ │ ├── index.ts │ │ │ │ ├── questionnaire-detail.component.html │ │ │ │ └── questionnaire-detail.component.ts │ │ │ │ └── questionnaire-item │ │ │ │ ├── index.ts │ │ │ │ ├── questionnaire-item.component.html │ │ │ │ └── questionnaire-item.component.ts │ │ ├── core │ │ │ ├── core.module.ts │ │ │ ├── index.ts │ │ │ ├── navbar │ │ │ │ ├── index.ts │ │ │ │ ├── navbar.component.css │ │ │ │ ├── navbar.component.html │ │ │ │ └── navbar.component.ts │ │ │ └── services │ │ │ │ ├── auth-guard.service.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login.service.ts │ │ │ │ ├── questionnaire.service.ts │ │ │ │ ├── register.service.ts │ │ │ │ └── user.service.ts │ │ ├── edit │ │ │ ├── edit.component.css │ │ │ ├── edit.component.html │ │ │ ├── edit.component.ts │ │ │ ├── edit.module.ts │ │ │ ├── edit.routes.ts │ │ │ ├── index.ts │ │ │ └── shared │ │ │ │ ├── edit-shared.module.ts │ │ │ │ ├── index.ts │ │ │ │ ├── question-select │ │ │ │ ├── index.ts │ │ │ │ ├── question-select.component.css │ │ │ │ ├── question-select.component.html │ │ │ │ └── question-select.component.ts │ │ │ │ └── questionnaire-outline │ │ │ │ ├── index.ts │ │ │ │ ├── questionnaire-outline.component.css │ │ │ │ ├── questionnaire-outline.component.html │ │ │ │ └── questionnaire-outline.component.ts │ │ ├── home │ │ │ ├── home.component.css │ │ │ ├── home.component.e2e-spec.ts │ │ │ ├── home.component.html │ │ │ ├── home.component.nonspec.ts │ │ │ ├── home.component.ts │ │ │ ├── home.module.ts │ │ │ ├── home.routes.ts │ │ │ └── index.ts │ │ ├── main-prod.ts │ │ ├── main.ts │ │ ├── published │ │ │ ├── index.ts │ │ │ ├── published-routing.module.ts │ │ │ ├── published.component.css │ │ │ ├── published.component.html │ │ │ ├── published.component.ts │ │ │ └── published.module.ts │ │ ├── shared │ │ │ ├── config │ │ │ │ └── env.config.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── index.ts │ │ │ │ ├── question.model.ts │ │ │ │ ├── questionnaire.model.ts │ │ │ │ └── user.model.ts │ │ │ ├── question │ │ │ │ ├── index.ts │ │ │ │ ├── question.component.ts │ │ │ │ ├── question.module.ts │ │ │ │ └── shared │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── question-checkbox │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── question-checkbox.component.html │ │ │ │ │ └── question-checkbox.component.ts │ │ │ │ │ ├── question-radio │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── question-radio.component.html │ │ │ │ │ └── question-radio.component.ts │ │ │ │ │ ├── question-score │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── question-score.component.html │ │ │ │ │ └── question-score.component.ts │ │ │ │ │ ├── question-shared.module.ts │ │ │ │ │ └── question-text │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── question-text.component.html │ │ │ │ │ └── question-text.component.ts │ │ │ ├── questionnaire │ │ │ │ ├── index.ts │ │ │ │ ├── questionnaire.component.css │ │ │ │ ├── questionnaire.component.html │ │ │ │ ├── questionnaire.component.ts │ │ │ │ └── questionnaire.module.ts │ │ │ └── shared.module.ts │ │ ├── system-config.ts │ │ └── user │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ ├── field │ │ │ │ ├── field-base.ts │ │ │ │ ├── field-radio.ts │ │ │ │ ├── field-select.ts │ │ │ │ ├── field-text.ts │ │ │ │ ├── field-validators.ts │ │ │ │ ├── field.component.css │ │ │ │ ├── field.component.html │ │ │ │ ├── field.component.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── login │ │ │ │ ├── index.ts │ │ │ │ ├── login.component.css │ │ │ │ ├── login.component.html │ │ │ │ └── login.component.ts │ │ │ ├── register │ │ │ │ ├── index.ts │ │ │ │ ├── register.component.css │ │ │ │ ├── register.component.html │ │ │ │ └── register.component.ts │ │ │ └── user-shared.module.ts │ │ │ ├── user.module.ts │ │ │ └── user.routes.ts │ ├── assets │ │ ├── data.json │ │ ├── img │ │ │ ├── about │ │ │ │ ├── create.jpg │ │ │ │ ├── faq.jpg │ │ │ │ └── my.jpg │ │ │ ├── edit │ │ │ │ ├── checkbox.png │ │ │ │ ├── radio.png │ │ │ │ ├── star.png │ │ │ │ └── text.png │ │ │ ├── home │ │ │ │ ├── banner_01.jpg │ │ │ │ ├── banner_02.jpg │ │ │ │ └── banner_03.jpg │ │ │ └── notfound.png │ │ └── svg │ │ │ └── more.svg │ ├── css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── libs │ │ │ └── bootstrap.min.css │ │ └── main.css │ ├── index.html │ └── tsconfig.json └── e2e │ ├── specs │ └── about.component.e2e-spec.ts │ └── tsconfig.json ├── test-config.js ├── test-main.js ├── tools ├── .gitignore ├── README.md ├── config.ts ├── config │ ├── banner-256.txt │ ├── banner.txt │ ├── project.config.ts │ ├── seed.config.interfaces.ts │ ├── seed.config.ts │ └── seed.tslint.json ├── debug.ts ├── env │ ├── base.ts │ ├── dev.ts │ ├── env-config.interface.ts │ └── prod.ts ├── manual_typings │ ├── project │ │ └── sample.package.d.ts │ └── seed │ │ ├── autoprefixer.d.ts │ │ ├── cssnano.d.ts │ │ ├── express-history-api-fallback.d.ts │ │ ├── istream.d.ts │ │ ├── karma.d.ts │ │ ├── merge-stream.d.ts │ │ ├── open.d.ts │ │ ├── operators.d.ts │ │ ├── slash.d.ts │ │ ├── systemjs-builder.d.ts │ │ └── tildify.d.ts ├── tasks │ ├── assets_task.ts │ ├── css_task.ts │ ├── project │ │ └── sample.task.ts │ ├── seed │ │ ├── build.assets.dev.ts │ │ ├── build.assets.prod.ts │ │ ├── build.bundle.rxjs.ts │ │ ├── build.bundles.app.exp.ts │ │ ├── build.bundles.app.ts │ │ ├── build.bundles.ts │ │ ├── build.docs.ts │ │ ├── build.html_css.ts │ │ ├── build.index.dev.ts │ │ ├── build.index.prod.ts │ │ ├── build.js.dev.ts │ │ ├── build.js.e2e.ts │ │ ├── build.js.prod.exp.ts │ │ ├── build.js.prod.ts │ │ ├── build.js.test.ts │ │ ├── build.tools.ts │ │ ├── check.tools.ts │ │ ├── check.versions.ts │ │ ├── clean.all.ts │ │ ├── clean.coverage.ts │ │ ├── clean.dev.ts │ │ ├── clean.e2e.ts │ │ ├── clean.prod.ts │ │ ├── clean.tools.ts │ │ ├── clear.files.ts │ │ ├── compile.ahead.prod.ts │ │ ├── copy.prod.ts │ │ ├── e2e.ts │ │ ├── generate.manifest.ts │ │ ├── karma.run.ts │ │ ├── karma.run.with_coverage.ts │ │ ├── karma.watch.ts │ │ ├── minify.bundles.ts │ │ ├── print.banner.ts │ │ ├── serve.coverage.ts │ │ ├── serve.coverage.watch.ts │ │ ├── serve.docs.ts │ │ ├── server.prod.ts │ │ ├── server.start.ts │ │ ├── start.deving.ts │ │ ├── tslint.ts │ │ ├── watch.dev.ts │ │ ├── watch.e2e.ts │ │ ├── watch.test.ts │ │ └── webdriver.ts │ ├── task.ts │ └── typescript_task.ts ├── utils.ts └── utils │ ├── project.utils.ts │ ├── project │ └── sample_util.ts │ ├── seed.utils.ts │ └── seed │ ├── clean.ts │ ├── code_change_tools.ts │ ├── karma.start.ts │ ├── server.ts │ ├── tasks_tools.ts │ ├── template_locals.ts │ ├── tsproject.ts │ └── watch.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | node_modules 3 | dist 4 | tmp 5 | .DS_Store 6 | .idea 7 | _* 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular 2 调查问卷示例项目 2 | 3 | ### 注意:新版本(Angular 4 + Angular CLI)已迁移至新repo 4 | 5 | https://github.com/angular-programming/angular-questionnaire 6 | 7 | ### 这是由广发证券互联网金融技术团队出品的原创书籍《揭秘 Angular 2》的第三部分项目实战的源代码。 8 | 9 | ![揭秘 Angular 2 封面](./angular_programming.jpg) 10 | 11 | 这个示例项目包含以下特点: 12 | 13 | * 遵循官方最佳实践的目录布局 14 | * 代码难易程度适中,方便学习 15 | * 功能丰富的脚手架,易于扩展使用 16 | * 简洁化的后端服务,聚焦前端框架学习 17 | 18 | 19 | ## 如何上手 20 | 21 | 调查问卷项目包括前端 frontend 目录以及后端 backend 目录。我们可以先运行后端服务,方便前端的注册与登录用户以及提供问卷相关的服务。安装过 Node.js 之后(确保你的 Node.js 版本为 6.x 及以上),在终端运行以下命令: 22 | 23 | ```bash 24 | cd backend 25 | npm install 26 | node app 27 | ``` 28 | 29 | 接下来,将终端目录定位 frontend 之中,再运行以下命令: 30 | 31 | ```bash 32 | npm install 33 | npm start 34 | ``` 35 | 36 | 以上前后台的命令都执行完后,即成功启动整个项目应用。 37 | -------------------------------------------------------------------------------- /angular_programming.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/angular_programming.jpg -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | const jsonServer = require('json-server') 2 | const uuid = require('node-uuid'); 3 | const bodyParser = require('body-parser') 4 | const low = require('lowdb') 5 | const storage = require('lowdb/file-async') 6 | 7 | // import jsonServer from 'json-server'; 8 | // import uuid from 'node-uuid'; 9 | // import bodyParser from 'body-parser'; 10 | 11 | // import low from 'lowdb'; 12 | // import storage from 'lowdb/file-async'; 13 | 14 | var crypto = require('crypto'); 15 | 16 | //创建一个Express服务器 17 | const server = jsonServer.create(); 18 | 19 | //使用json-server默认选择的中间件(logger,static, cors和no-cache) 20 | server.use(jsonServer.defaults()); 21 | 22 | //使用body-parser中间件 23 | server.use(bodyParser.json()); 24 | 25 | 26 | //数据文件 27 | const dbfile = process.env.prod === '1' ? 'db.json' : '_db.json'; 28 | 29 | //创建一个lowdb实例 30 | const db = low(dbfile, {storage}); 31 | 32 | 33 | 34 | var md5 = function(str) { 35 | return crypto 36 | .createHash('md5') 37 | .update(str.toString()) 38 | .digest('hex'); 39 | }; 40 | 41 | //添加新问卷 42 | server.post('/questionnaire/add', (req, res) => { 43 | const item = req.body; 44 | item.id = uuid.v1(); 45 | item.createDate = new Date().toLocaleDateString(); 46 | db('questionnaires').push(item).then(() => { 47 | res.json({'success':true, data:item}); 48 | }); 49 | }); 50 | 51 | //删除已有问卷 52 | server.get('/questionnaire/delete/:id', (req, res)=>{ 53 | db('questionnaires').remove({id: req.params.id}).then(()=>{ 54 | res.json({'success': true}); 55 | }); 56 | }); 57 | 58 | //获取所有问卷 59 | server.get('/questionnaires', (req, res) => { 60 | const questionnaires = db('questionnaires'); 61 | res.json({'success':true, data:questionnaires}); 62 | }); 63 | 64 | //根据id获取问卷数据 65 | server.get('/questionnaire/:id', (req, res) => { 66 | const questionnaire = db('questionnaires').find({id: req.params.id}); 67 | res.json({'success':true, data:questionnaire}); 68 | }); 69 | 70 | //更新已有问卷 71 | server.post('/questionnaire/update', (req, res) => { 72 | const item = req.body; 73 | db('questionnaires').chain().find({id:item.id}).assign(item).value(); 74 | res.json({'success':true, data:item}); 75 | }); 76 | 77 | //发布问卷 78 | server.get('/questionnaire/publish/:id', (req, res)=>{ 79 | const item = db('questionnaires').chain().find({id:req.params.id}); 80 | item.assign({state:1}).value(); 81 | res.json({'success':true, data:item}); 82 | }); 83 | 84 | // get userinfo 85 | server.get('/user/:username', function(req, res) { 86 | var user = db('user') 87 | .find({ 88 | username: req.params.username 89 | }); 90 | 91 | res.json({ 92 | success: true, 93 | data: { 94 | username: user.username, 95 | createDate: user.createDate 96 | } 97 | }); 98 | }); 99 | 100 | // register 101 | server.post('/user/add', function(req, res) { 102 | var item = req.body; 103 | var user = db('user') 104 | .find({ 105 | username: item.username 106 | }); 107 | if (user) { 108 | res.json({ 109 | success: false, 110 | message: '"' + item.username + '" is exists' 111 | }) 112 | } else { 113 | item.password = md5(item.password); 114 | item.createDate = new Date().toLocaleDateString(); 115 | db('user') 116 | .push(item) 117 | .then(function() { 118 | res.json({ 119 | success: true 120 | }); 121 | }); 122 | } 123 | }); 124 | 125 | // login 126 | server.post('/login', function(req, res) { 127 | var data = req.body || {}; 128 | var username = data.username; 129 | var user = db('user') 130 | .find({ 131 | username: username 132 | }); 133 | 134 | if (user && user.password === md5(data.password)) { 135 | // todo reset session 136 | res.json({ 137 | success: true 138 | }); 139 | } else { 140 | res.json({ 141 | success: false, 142 | message: 'username or password error' 143 | }); 144 | } 145 | }); 146 | 147 | //路由配置 148 | const router = jsonServer.router(dbfile); 149 | server.use('/api', router); 150 | 151 | //启动服务,并监听5000端口 152 | server.listen(5000, () => { 153 | console.log('server is running at ', 5000, dbfile); 154 | }); 155 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "body-parser": "^1.14.2", 13 | "json-server": "^0.8.7", 14 | "lowdb": "^0.12.2", 15 | "node-uuid": "^1.4.7" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/.docker/angular-seed.development.dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6.6 2 | 3 | # prepare a user which runs everything locally! - required in child images! 4 | RUN useradd --user-group --create-home --shell /bin/false app 5 | 6 | ENV HOME=/home/app 7 | WORKDIR $HOME 8 | 9 | ENV APP_NAME=angular-seed 10 | 11 | # before switching to user we need to set permission properly 12 | # copy all files, except the ignored files from .dockerignore 13 | COPY . $HOME/$APP_NAME/ 14 | RUN chown -R app:app $HOME/* 15 | 16 | USER app 17 | WORKDIR $HOME/$APP_NAME 18 | 19 | RUN npm install 20 | -------------------------------------------------------------------------------- /frontend/.docker/angular-seed.production.dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6.6 2 | 3 | # prepare a user which runs everything locally! - required in child images! 4 | RUN useradd --user-group --create-home --shell /bin/false app 5 | 6 | ENV HOME=/home/app 7 | WORKDIR $HOME 8 | 9 | ENV APP_NAME=angular-seed 10 | 11 | # before switching to user we need to set permission properly 12 | # copy all files, except the ignored files from .dockerignore 13 | COPY . $HOME/$APP_NAME/ 14 | RUN chown -R app:app $HOME/* 15 | 16 | USER app 17 | WORKDIR $HOME/$APP_NAME 18 | 19 | RUN npm install 20 | -------------------------------------------------------------------------------- /frontend/.docker/nginx.conf: -------------------------------------------------------------------------------- 1 | map $http_upgrade $connection_upgrade { 2 | default upgrade; 3 | '' close; 4 | } 5 | 6 | server { 7 | 8 | listen ${NGINX_PORT}; 9 | 10 | server_name ${NGINX_HOST}; 11 | # ssl on; 12 | # ssl_certificate angular-seed_server.crt; 13 | # ssl_certificate_key angular-seed_server.pem; 14 | # 15 | # ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; 16 | # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 17 | # ssl_prefer_server_ciphers on; 18 | # ssl_session_cache shared:SSL:10m; 19 | # ssl_session_tickets off; # Requires nginx >= 1.5.9 20 | # add_header Strict-Transport-Security "max-age=63072000; preload"; 21 | # add_header X-Frame-Options DENY; 22 | # 23 | # location /api/microservice1 { 24 | # rewrite ^/api/microservice1/(.*)$ /$1 break; 25 | # proxy_pass https://microservice1/; 26 | # proxy_http_version 1.1; 27 | # proxy_set_header X-Forwarded-For $remote_addr; 28 | # } 29 | 30 | location / { 31 | root /var/www/dist/prod; 32 | try_files $uri /index.html; 33 | index index.html; 34 | gzip on; 35 | gzip_types text/css text/javascript application/x-javascript application/json; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | dist 3 | tmp 4 | 5 | # dependencies 6 | node_modules 7 | bower_components 8 | 9 | # IDEs and editors 10 | .idea 11 | .vscode 12 | .project 13 | .classpath 14 | *.launch 15 | .settings/ 16 | 17 | # misc 18 | .sass-cache 19 | connect.lock 20 | coverage/* 21 | libpeerconnection.log 22 | npm-debug.log 23 | testem.log 24 | typings 25 | 26 | # e2e 27 | e2e/*.js 28 | e2e/*.map 29 | 30 | #System Files 31 | .DS_Store 32 | Thumbs.db 33 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /frontend/.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting Pull Requests 2 | 3 | **Please follow these basic steps to simplify pull request reviews - if you don't you'll probably just be asked to anyway.** 4 | 5 | * Please rebase your branch against the current master 6 | * Run ```npm install``` to make sure your development dependencies are up-to-date 7 | * Please ensure that the test suite passes **and** that code is lint free before submitting a PR by running: 8 | * ```npm test``` 9 | * If you've added new functionality, **please** include tests which validate its behaviour 10 | * Make reference to possible [issues](https://github.com/mgechev/angular2-seed/issues) on PR comment 11 | 12 | ## Submitting bug reports 13 | 14 | * Please detail the affected browser(s) and operating system(s) 15 | * Please be sure to state which version of node **and** npm you're using 16 | -------------------------------------------------------------------------------- /frontend/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | **I'm submitting a ...** (check one with "x") 6 | ``` 7 | [ ] bug report => search github for a similar issue or PR before submitting 8 | [ ] feature request 9 | [ ] support request => Please do not submit support request here, instead see use [gitter](https://gitter.im/mgechev/angular2-seed) or [stackoverflow](https://stackoverflow.com/questions/tagged/angular2) 10 | ``` 11 | 12 | **Current behavior** 13 | 14 | 15 | **Expected behavior** 16 | 17 | 18 | **Minimal reproduction of the problem with instructions** 19 | 23 | 24 | **What is the motivation / use case for changing the behavior?** 25 | 26 | 27 | **Please tell us about your environment:** 28 | 29 | 30 | * **Angular Seed Version:** `aaaaf75` 31 | 32 | 33 | * **Node:** `node --version` = 34 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | coverage_js 16 | 17 | # ignore documentation 18 | documentation 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # Commenting this out is preferred by some people, see 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 29 | node_modules 30 | typings 31 | ts-node 32 | 33 | # Users Environment Variables 34 | .lock-wscript 35 | .tsdrc 36 | .typingsrc 37 | 38 | #IDE configuration files 39 | .idea 40 | *.iml 41 | 42 | /tools/**/*.js 43 | dist 44 | dev 45 | docs 46 | lib 47 | test 48 | tmp 49 | 50 | gulpfile.js 51 | gulpfile.js.map 52 | 53 | # OS X trash files 54 | .DS_Store 55 | src/client/app/shared/config/env.config.js 56 | src/client/app/shared/config/env.config.js.map 57 | 58 | npm-* 59 | -------------------------------------------------------------------------------- /frontend/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | 4 | sudo: false 5 | 6 | install: true # yarn bug 7 | 8 | addons: 9 | firefox: "45.0" 10 | 11 | os: 12 | - linux 13 | - osx 14 | 15 | before_install: 16 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi 17 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated xctool || brew upgrade xctool; fi 18 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CHROME_BIN=chromium-browser; fi # Karma CI 19 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew cask install google-chrome; fi # Karma CI 20 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; fi 21 | 22 | before_script: 23 | - npm install 24 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh -e /etc/init.d/xvfb start; fi 25 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then nohup bash -c "webdriver-manager start 2>&1 &"; fi # Protractor CI 26 | 27 | after_failure: 28 | - cat /home/travis/build/mgechev/angular-seed/npm-debug.log 29 | 30 | branches: 31 | only: master 32 | 33 | notifications: 34 | email: true 35 | webhooks: 36 | urls: https://webhooks.gitter.im/e/565e4b2fed3b96c1b964 37 | on_success: change # options: [always|never|change] default: always 38 | on_failure: always # options: [always|never|change] default: always 39 | on_start: never # options: [always|never|change] default: always 40 | 41 | cache: 42 | directories: node_modules 43 | 44 | script: 45 | - npm run tests.all && npm run build.prod.exp 46 | -------------------------------------------------------------------------------- /frontend/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Chrome against localhost, with sourcemaps", 6 | "type": "chrome", 7 | "preLaunchTask": "start", 8 | "request": "launch", 9 | "url": "http://localhost:5555", 10 | "sourceMaps": true, 11 | "webRoot": "${workspaceRoot}/src/client", 12 | "sourceMapPathOverrides": { 13 | "app/*": "${webRoot}/app/*" 14 | } 15 | }, 16 | { 17 | "name": "Attach to Chrome, with sourcemaps", 18 | "type": "chrome", 19 | "request": "attach", 20 | "port": 9222, 21 | "sourceMaps": true, 22 | "webRoot": "${workspaceRoot}/src/client", 23 | "sourceMapPathOverrides": { 24 | "app/*": "${webRoot}/app/*" 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "typescript.tsdk": "node_modules/typescript/lib", 4 | "search.exclude": { 5 | "**/coverage_js": true, 6 | "**/coverage": true, 7 | "**/dist": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "npm", 4 | "isShellCommand": true, 5 | "showOutput": "always", 6 | "suppressTaskName": true, 7 | "tasks": [ 8 | { 9 | "taskName": "tslint", 10 | "args": [ "run", "gulp", "tslint", "--color" ], 11 | "problemMatcher": { 12 | "owner": "tslint", 13 | "fileLocation": [ 14 | "relative", 15 | "${workspaceRoot}" 16 | ], 17 | "severity": "warning", 18 | "pattern": { 19 | "regexp": "^(\\S.*)\\[(\\d+), (\\d+)\\]:\\s+(.*)$", 20 | "file": 1, 21 | "line": 2, 22 | "column": 3, 23 | "message": 4 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /frontend/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Minko Gechev 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 | -------------------------------------------------------------------------------- /frontend/appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor file 2 | # http://www.appveyor.com/docs/appveyor-yml 3 | # This file: cloned from https://github.com/gruntjs/grunt/blob/master/appveyor.yml 4 | 5 | # Build version format 6 | version: "{build}" 7 | 8 | # Test against this version of Node.js 9 | environment: 10 | nodejs_version: "Stable" 11 | 12 | build: off 13 | 14 | clone_depth: 10 15 | 16 | # Fix line endings on Windows 17 | init: 18 | - git config --global core.autocrlf true 19 | 20 | install: 21 | - ps: Install-Product node $env:nodejs_version 22 | - npm install -g npm@3.10.8 23 | - ps: $env:path = $env:appdata + "\npm;" + $env:path 24 | - npm install && npm install karma-ie-launcher 25 | 26 | test_script: 27 | # Output useful info for debugging. 28 | - node --version && npm --version 29 | # We test multiple Windows shells because of prior stdout buffering issues 30 | # filed against Grunt. https://github.com/joyent/node/issues/3584 31 | - ps: "npm --version # PowerShell" # Pass comment to PS for easier debugging 32 | - npm run tests.all && npm run build.prod.exp 33 | 34 | notifications: 35 | - provider: Webhook 36 | url: https://webhooks.gitter.im/e/cfd8ce5ddee6f3a0b0c9 37 | on_build_success: false 38 | on_build_failure: true 39 | on_build_status_changed: true 40 | 41 | cache: node_modules -> package.json 42 | -------------------------------------------------------------------------------- /frontend/docker-compose.production.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | angular-seed: 6 | build: 7 | context: . 8 | dockerfile: ./.docker/angular-seed.production.dockerfile 9 | command: npm run build.prod 10 | container_name: angular-seed-build-prod 11 | image: angular-seed 12 | networks: 13 | - prod-network 14 | volumes: 15 | - ./dist:/home/app/angular-seed/dist 16 | 17 | angular-seed-nginx: 18 | command: /bin/bash -c "envsubst '$$NGINX_HOST $$NGINX_PORT' < /etc/nginx/conf.d/angular-seed.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" 19 | container_name: angular-seed-nginx-prod 20 | environment: 21 | - NGINX_HOST=localhost 22 | - NGINX_PORT=80 23 | image: nginx 24 | networks: 25 | - prod-network 26 | ports: 27 | - '5555:80' 28 | volumes: 29 | - ./.docker/nginx.conf:/etc/nginx/conf.d/angular-seed.template 30 | - ./dist/prod:/var/www/dist/prod 31 | 32 | networks: 33 | prod-network: 34 | driver: bridge 35 | -------------------------------------------------------------------------------- /frontend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | angular-seed: 6 | build: 7 | context: . 8 | dockerfile: ./.docker/angular-seed.development.dockerfile 9 | command: npm start 10 | container_name: angular-seed-start 11 | image: angular-seed 12 | networks: 13 | - dev-network 14 | ports: 15 | - '5555:5555' 16 | 17 | networks: 18 | dev-network: 19 | driver: bridge 20 | -------------------------------------------------------------------------------- /frontend/protractor.conf.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | baseUrl: 'http://localhost:5555/', 3 | 4 | specs: [ 5 | './dist/e2e/**/*.e2e-spec.js' 6 | ], 7 | 8 | exclude: [], 9 | 10 | // 'jasmine' by default will use the latest jasmine framework 11 | framework: 'jasmine', 12 | 13 | // allScriptsTimeout: 110000, 14 | 15 | jasmineNodeOpts: { 16 | // showTiming: true, 17 | showColors: true, 18 | isVerbose: false, 19 | includeStackTrace: false, 20 | // defaultTimeoutInterval: 400000 21 | }, 22 | 23 | directConnect: true, 24 | 25 | capabilities: { 26 | browserName: 'chrome' 27 | }, 28 | 29 | onPrepare: function() { 30 | browser.ignoreSynchronization = false; 31 | }, 32 | 33 | 34 | /** 35 | * Angular 2 configuration 36 | * 37 | * useAllAngular2AppRoots: tells Protractor to wait for any angular2 apps on the page instead of just the one matching 38 | * `rootEl` 39 | */ 40 | useAllAngular2AppRoots: true 41 | }; 42 | 43 | if (process.env.TRAVIS) { 44 | config.capabilities = { 45 | browserName: 'firefox' 46 | }; 47 | } 48 | 49 | exports.config = config; 50 | -------------------------------------------------------------------------------- /frontend/readme.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | *. -------------------------------------------------------------------------------- /frontend/src/client/app/about/about.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 16px; 4 | } 5 | 6 | accordion-group .col-lg-3 { 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/client/app/about/about.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 |
6 |
7 | {{item.text}} 8 |
9 |
10 | {{ item?.desc }} 11 |
12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /frontend/src/client/app/about/about.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { By } from '@angular/platform-browser'; 3 | 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { AboutComponent } from './about.component'; 7 | import { AccordionModule } from 'ng2-bootstrap/components/accordion'; 8 | import { SharedModule } from '../shared/shared.module'; 9 | 10 | export function main() { 11 | describe('About component', () => { 12 | 13 | let comp: AboutComponent; 14 | let fixture: ComponentFixture; 15 | let aboutEl: DebugElement; 16 | 17 | // setting module for testing 18 | // compile template and css 19 | beforeEach( async(() => { 20 | TestBed.configureTestingModule({ 21 | imports: [SharedModule, AccordionModule], 22 | declarations: [AboutComponent], 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | // synchronous beforeEach 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(AboutComponent); 30 | comp = fixture.componentInstance; 31 | aboutEl = fixture.debugElement; 32 | 33 | fixture.detectChanges(); // trigger initial data binding 34 | }); 35 | 36 | it('should render accordion', ()=>{ 37 | 38 | const de = aboutEl.queryAll(By.css('accordion')); 39 | expect(de.length).toBe(1); 40 | }); 41 | 42 | it('should render correct accordion text', ()=>{ 43 | 44 | // const de = aboutEl.queryAll(By.css('accordion')); 45 | expect(aboutEl.nativeElement.textContent).toContain('常见FAQ'); 46 | }); 47 | }); 48 | } 49 | 50 | ////// Test Host Component ////// 51 | import { Component } from '@angular/core'; 52 | 53 | @Component({ 54 | selector: 'test-cmp', 55 | template: '' 56 | }) 57 | class TestComponent {} 58 | -------------------------------------------------------------------------------- /frontend/src/client/app/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'sd-help', 6 | templateUrl: 'about.component.html', 7 | styleUrls: ['about.component.css'] 8 | }) 9 | export class AboutComponent { 10 | 11 | private items: any[]; 12 | 13 | constructor() { 14 | this.items = [ 15 | { 16 | icon:'./assets/img/about/faq.jpg', 17 | text:'常见FAQ', 18 | desc:'使用问卷收集数据的基本流程是:创建问卷、编辑问卷、设置问卷、预览问卷、回收问卷、数据分析等几个步骤。问卷提供三种创建问卷方式,创建空白问卷、选择问卷模板、文本编辑器。可以灵活设置问卷的显示、回收条件,在线实时统计回收结果,并可以导出Excel和SPSS数据。详细操作指引请参照帮助中心”操作指引“部分。', 19 | open: true 20 | }, 21 | { 22 | icon:'./assets/img/about/create.jpg', 23 | text:'创建问卷', 24 | desc:'问卷,共有三种创建问卷的方式让您来选择使用:创建空白问卷、选择问卷模板、文本编辑器,可根据自身的使用习惯,选择最合适的方式来快速创建一份问卷。', 25 | open: false 26 | }, 27 | { 28 | icon:'./assets/img/about/my.jpg', 29 | text:'我的问卷', 30 | desc:'问卷编辑、预览完成后,一份在线问卷即完成,此时可以通过对问卷的复制、编辑、删除、发布、统计、导出、分享等功能,实现对问卷的全面管理', 31 | open: false 32 | } 33 | ]; 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/client/app/about/about.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { AboutComponent } from './about.component'; 5 | import { AccordionModule } from 'ng2-bootstrap/components/accordion'; 6 | 7 | @NgModule({ 8 | imports: [CommonModule, AccordionModule], 9 | declarations: [AboutComponent], 10 | exports: [AboutComponent] 11 | }) 12 | export class AboutModule { } 13 | -------------------------------------------------------------------------------- /frontend/src/client/app/about/about.routes.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import { AboutComponent } from './index'; 4 | 5 | export const AboutRoutes: Route[] = [ 6 | { 7 | path: 'about', 8 | component: AboutComponent 9 | } 10 | ]; 11 | -------------------------------------------------------------------------------- /frontend/src/client/app/about/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded AboutComponent. 3 | */ 4 | export * from './about.component'; 5 | export * from './about.routes'; 6 | -------------------------------------------------------------------------------- /frontend/src/client/app/admin/admin-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { AdminComponent } from './index'; 5 | import { AboutRoutes } from '../about/index'; 6 | import { CenterRoutes } from '../center/index'; 7 | import { EditRoutes } from '../edit/index'; 8 | import { UserRoutes } from '../user/index'; 9 | 10 | const routes: Routes = [ 11 | { 12 | path: 'admin', 13 | component: AdminComponent, 14 | children: [ 15 | ...AboutRoutes, 16 | ...EditRoutes, 17 | ...UserRoutes, 18 | ...CenterRoutes 19 | ] 20 | } 21 | ]; 22 | 23 | @NgModule({ 24 | imports: [RouterModule.forChild(routes)], 25 | exports: [RouterModule] 26 | }) 27 | export class AdminRoutingModule { } 28 | 29 | -------------------------------------------------------------------------------- /frontend/src/client/app/admin/admin.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/src/client/app/admin/admin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'sd-admin', 6 | templateUrl: 'admin.component.html', 7 | }) 8 | export class AdminComponent { } 9 | -------------------------------------------------------------------------------- /frontend/src/client/app/admin/admin.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { AdminComponent } from './admin.component'; 5 | import { AdminRoutingModule } from './admin-routing.module'; 6 | import { CoreModule } from '../core/core.module'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule, CoreModule, AdminRoutingModule], 10 | declarations: [AdminComponent], 11 | exports: [AdminComponent] 12 | }) 13 | export class AdminModule { } 14 | -------------------------------------------------------------------------------- /frontend/src/client/app/admin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './admin.component'; 2 | -------------------------------------------------------------------------------- /frontend/src/client/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { HomeRoutes, HomeComponent } from './home/index'; 5 | 6 | export const routes: Routes = [ 7 | ...HomeRoutes 8 | //{ path: '**', component: HomeComponent } 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forRoot(routes)], 13 | exports: [RouterModule] 14 | }) 15 | export class AppRoutingModule { } 16 | -------------------------------------------------------------------------------- /frontend/src/client/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/client/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { TestBed } from '@angular/core/testing'; 4 | import { APP_BASE_HREF } from '@angular/common'; 5 | 6 | import { 7 | async 8 | } from '@angular/core/testing'; 9 | import { 10 | Route 11 | } from '@angular/router'; 12 | import { 13 | RouterTestingModule 14 | } from '@angular/router/testing'; 15 | import { AppComponent } from './app.component'; 16 | import { HomeComponent } from './home/home.component'; 17 | import { AboutComponent } from './about/about.component'; 18 | import { ToolbarComponent } from './shared/toolbar/toolbar.component'; 19 | import { NavbarComponent } from './core/navbar/navbar.component'; 20 | 21 | 22 | export function main() { 23 | 24 | describe('App component', () => { 25 | 26 | it('should just work dead simple', ()=>{ 27 | expect(true).toBeTruthy(); 28 | }); 29 | 30 | /* let config: Route[] = [ 31 | { path: '', component: HomeComponent }, 32 | { path: 'about', component: AboutComponent } 33 | ]; 34 | beforeEach(() => { 35 | TestBed.configureTestingModule({ 36 | imports: [FormsModule, RouterTestingModule.withRoutes(config)], 37 | declarations: [TestComponent, ToolbarComponent, 38 | NavbarComponent, AppComponent, 39 | HomeComponent, AboutComponent], 40 | providers: [ 41 | { provide: APP_BASE_HREF, useValue: '/' } 42 | ] 43 | }); 44 | }); 45 | 46 | it('should build without a problem', 47 | async(() => { 48 | TestBed 49 | .compileComponents() 50 | .then(() => { 51 | let fixture = TestBed.createComponent(TestComponent); 52 | let compiled = fixture.nativeElement; 53 | 54 | expect(compiled).toBeTruthy(); 55 | }); 56 | }));*/ 57 | }); 58 | } 59 | 60 | @Component({ 61 | selector: 'test-cmp', 62 | template: '' 63 | }) 64 | 65 | class TestComponent { 66 | } 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /frontend/src/client/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Config } from './shared/index'; 4 | import {UserService} from "./core/services/user.service"; 5 | 6 | /** 7 | * This class represents the main application component. Within the @Routes annotation is the configuration of the 8 | * applications routes, configuring the paths for the lazy loaded components (HomeComponent, AboutComponent). 9 | */ 10 | @Component({ 11 | moduleId: module.id, 12 | selector: 'sd-app', 13 | templateUrl: 'app.component.html', 14 | }) 15 | export class AppComponent implements OnInit { 16 | constructor(private userService: UserService) { 17 | console.log('Environment config', Config); 18 | } 19 | 20 | ngOnInit():void { 21 | this.userService.getUser().subscribe(); // 获取用户信息 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/client/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { APP_BASE_HREF } from '@angular/common'; 4 | import { HttpModule } from '@angular/http'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { AppRoutingModule } from './app-routing.module'; 8 | import { CoreModule } from './core/core.module'; 9 | import { AdminModule } from './admin/admin.module'; 10 | import { AboutModule } from './about/about.module'; 11 | import { HomeModule } from './home/home.module'; 12 | import { EditModule } from './edit/edit.module'; 13 | import { CenterModule } from './center/center.module'; 14 | import { UserModule } from './user/user.module'; 15 | import { PublishedModule } from './published/published.module'; 16 | 17 | @NgModule({ 18 | imports: [CoreModule, AdminModule, BrowserModule, HttpModule, AppRoutingModule, AboutModule, HomeModule, UserModule, 19 | EditModule, CenterModule, PublishedModule], 20 | declarations: [AppComponent], 21 | providers: [{ 22 | provide: APP_BASE_HREF, 23 | useValue: '<%= APP_BASE %>' 24 | }], 25 | bootstrap: [AppComponent] 26 | }) 27 | export class AppModule { } 28 | -------------------------------------------------------------------------------- /frontend/src/client/app/center/center.component.css: -------------------------------------------------------------------------------- 1 | .center-container{ 2 | padding:20px; 3 | } 4 | 5 | .center-container .show-empty{ 6 | width: 360px; 7 | margin: 0 auto; 8 | height: 260px; 9 | text-align: center; 10 | padding-top: 200px; 11 | background: url(./assets/img/notfound.png) no-repeat center center; 12 | } -------------------------------------------------------------------------------- /frontend/src/client/app/center/center.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 尚未创建任何问卷,马上创建一个吧! 4 |
5 |
6 |
7 |
8 |
9 |
问卷列表
10 |
11 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
问卷详情
21 |
22 | 23 |
24 |
25 |
26 |
问卷管理
27 |
28 | 31 |
32 |
33 |
34 |
-------------------------------------------------------------------------------- /frontend/src/client/app/center/center.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; 2 | 3 | import { QuestionnaireService } from '../core/services/questionnaire.service'; 4 | import { QuestionnaireModel, QuestionnaireState } from '../shared/models/questionnaire.model'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'sd-center', 9 | templateUrl: 'center.component.html', 10 | styleUrls: ['center.component.css'] 11 | }) 12 | export class CenterComponent implements OnInit { 13 | 14 | private questionnaires:QuestionnaireModel[] = []; 15 | private selectedQuestionnaire:QuestionnaireModel; 16 | private selectedIndex:number; 17 | private isEmpty:boolean; 18 | 19 | constructor(private cd:ChangeDetectorRef, private questionnaireService:QuestionnaireService) { } 20 | 21 | ngOnInit() { 22 | this.questionnaireService.getQuestionnaires() 23 | .subscribe( 24 | questionnaires => { 25 | // 后端返回空对象或者空的问卷数组 26 | if(!questionnaires || questionnaires.length === 0){ 27 | this.isEmpty = true; 28 | return; 29 | } 30 | this.isEmpty = false; 31 | this.questionnaires = questionnaires; 32 | this.selectedQuestionnaire = this.questionnaires[0]; 33 | this.selectedIndex = 0; 34 | }, 35 | error => console.error(error) 36 | ); 37 | } 38 | 39 | onSelect(questionnaire:QuestionnaireModel, index:number) { 40 | this.selectedQuestionnaire = questionnaire; 41 | this.selectedIndex = index; 42 | } 43 | 44 | onDeleteQuestionnaire() { 45 | this.questionnaireService.deleteQuestionnaire(this.selectedQuestionnaire.id) 46 | .subscribe( 47 | res => { 48 | this.questionnaires.splice(this.selectedIndex, 1); 49 | //全部删除 50 | if(this.questionnaires.length === 0){ 51 | this.isEmpty = true; 52 | } else { 53 | this.selectedQuestionnaire = this.questionnaires[0]; 54 | this.selectedIndex = 0; 55 | } 56 | }, 57 | error => console.log(error) 58 | 59 | ); 60 | } 61 | 62 | onPublishQuestionnaire(){ 63 | this.questionnaireService.publishQuestionnaire(this.selectedQuestionnaire.id) 64 | .subscribe( 65 | questionnaire => { 66 | this.selectedQuestionnaire.state = QuestionnaireState.Published; 67 | this.questionnaires[this.selectedIndex] = Object.assign({}, this.selectedQuestionnaire); 68 | this.cd.detectChanges(); 69 | }, 70 | error => console.log(error) 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /frontend/src/client/app/center/center.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | 6 | import { CenterComponent } from './center.component'; 7 | import { CenterSharedModule } from './shared/center-shared.module'; 8 | import { UserModule } from '../user/user.module'; 9 | 10 | @NgModule({ 11 | imports: [CommonModule, RouterModule, CenterSharedModule, UserModule], 12 | declarations: [CenterComponent], 13 | exports: [CenterComponent, CenterSharedModule] 14 | }) 15 | export class CenterModule { } 16 | -------------------------------------------------------------------------------- /frontend/src/client/app/center/center.routes.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import { CenterComponent } from './index'; 4 | import { AuthGuard } from '../core/services/auth-guard.service'; 5 | 6 | export const CenterRoutes: Route[] = [ 7 | { 8 | path: 'center', 9 | component: CenterComponent, 10 | canActivate: [AuthGuard] 11 | } 12 | ]; 13 | -------------------------------------------------------------------------------- /frontend/src/client/app/center/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded EditComponent. 3 | */ 4 | export * from './center.component'; 5 | export * from './center.routes'; 6 | export * from './shared/index'; 7 | -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/center-shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { QuestionnaireControlsComponent } from './questionnaire-controls/index'; 5 | import { QuestionnaireDetailComponent } from './questionnaire-detail/index'; 6 | import { QuestionnaireItemComponent } from './questionnaire-item/index'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule], 10 | declarations: [QuestionnaireItemComponent, QuestionnaireDetailComponent, QuestionnaireControlsComponent], 11 | exports: [QuestionnaireItemComponent, QuestionnaireDetailComponent, QuestionnaireControlsComponent] 12 | }) 13 | export class CenterSharedModule { } 14 | -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './questionnaire-controls/index'; 2 | export * from './questionnaire-detail/index'; 3 | export * from './questionnaire-item/index'; 4 | -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-controls/index.ts: -------------------------------------------------------------------------------- 1 | export * from './questionnaire-controls.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-controls/questionnaire-controls.component.css: -------------------------------------------------------------------------------- 1 | .control-text{ 2 | display: block; 3 | text-align: center; 4 | font-size: 16px; 5 | } -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-controls/questionnaire-controls.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 |
9 | 13 |
14 |
15 | 19 |
20 |
21 | 25 |
26 |
27 | 31 |
32 |
-------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-controls/questionnaire-controls.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { QuestionnaireModel } from '../../../shared/models/questionnaire.model'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'questionnaire-controls', 9 | templateUrl: 'questionnaire-controls.component.html', 10 | styleUrls: ['questionnaire-controls.component.css'] 11 | }) 12 | export class QuestionnaireControlsComponent { 13 | 14 | @Input() questionnaire:QuestionnaireModel; 15 | @Output() deleteQuestionnaire: EventEmitter = new EventEmitter(); 16 | @Output() publishQuestionnaire: EventEmitter = new EventEmitter(); 17 | 18 | constructor(private router: Router) {} 19 | 20 | onEdit(){ 21 | this.router.navigateByUrl('admin/edit/' + this.questionnaire.id); 22 | } 23 | 24 | onPreview(){ 25 | this.router.navigateByUrl('published/' + this.questionnaire.id); 26 | } 27 | 28 | onDelete(){ 29 | this.deleteQuestionnaire.emit(); 30 | } 31 | 32 | onPublish(){ 33 | this.publishQuestionnaire.emit(); 34 | } 35 | } -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-detail/index.ts: -------------------------------------------------------------------------------- 1 | export * from './questionnaire-detail.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-detail/questionnaire-detail.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
问卷标题:{{questionnaire.title}}
问卷简介:{{questionnaire.starter}}
问题总数:{{questionnaire.questionList.length}}
创建时间:{{questionnaire.createDate}}
-------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-detail/questionnaire-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | import { QuestionnaireModel } from '../../../shared/models/questionnaire.model'; 4 | 5 | @Component({ 6 | moduleId: module.id, 7 | selector: 'questionnaire-detail', 8 | templateUrl: 'questionnaire-detail.component.html' 9 | }) 10 | export class QuestionnaireDetailComponent { 11 | 12 | @Input() questionnaire: QuestionnaireModel; 13 | 14 | } -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-item/index.ts: -------------------------------------------------------------------------------- 1 | export * from './questionnaire-item.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-item/questionnaire-item.component.html: -------------------------------------------------------------------------------- 1 | {{questionnaire.title}} 2 | {{stateText}} 3 | {{questionnaire.createDate}} -------------------------------------------------------------------------------- /frontend/src/client/app/center/shared/questionnaire-item/questionnaire-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnChanges, SimpleChanges, Input } from '@angular/core'; 2 | 3 | import { QuestionnaireModel, QuestionnaireState } from '../../../shared/models/questionnaire.model'; 4 | 5 | @Component({ 6 | moduleId: module.id, 7 | selector: 'questionnaire-item', 8 | templateUrl: 'questionnaire-item.component.html' 9 | }) 10 | export class QuestionnaireItemComponent implements OnInit, OnChanges { 11 | 12 | @Input() questionnaire:QuestionnaireModel; 13 | 14 | private stateText:String; 15 | private stateClass:String; 16 | 17 | ngOnChanges(changes: SimpleChanges){ 18 | let questionnaireChange = changes['questionnaire']; 19 | if(questionnaireChange.previousValue.state && 20 | questionnaireChange.currentValue.state !== questionnaireChange.previousValue.state){ 21 | this.questionnaire = changes['questionnaire'].currentValue; 22 | this.setState(); 23 | } 24 | } 25 | 26 | ngOnInit() { 27 | this.setState(); 28 | } 29 | 30 | setState(){ 31 | switch(this.questionnaire.state){ 32 | case QuestionnaireState.Created: 33 | this.stateText = '已创建'; 34 | this.stateClass = 'label-warning'; 35 | break; 36 | case QuestionnaireState.Published: 37 | this.stateText = '回收中'; 38 | this.stateClass = 'label-info'; 39 | break; 40 | case QuestionnaireState.Finished: 41 | this.stateText = '已结束'; 42 | this.stateClass = 'label-success'; 43 | break; 44 | default: 45 | break; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Optional, SkipSelf } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { AuthGuard } from './services/auth-guard.service'; 6 | import { LoginService } from './services/login.service'; 7 | import { NavbarComponent } from './navbar/index'; 8 | import { QuestionnaireService } from './services/questionnaire.service'; 9 | import { RegisterService } from './services/register.service'; 10 | import { UserService } from './services/user.service'; 11 | 12 | @NgModule({ 13 | imports: [CommonModule, RouterModule], 14 | declarations: [NavbarComponent], 15 | providers: [QuestionnaireService, UserService, LoginService, RegisterService, AuthGuard], 16 | exports: [NavbarComponent] 17 | }) 18 | export class CoreModule { 19 | 20 | constructor (@Optional() @SkipSelf() parentModule: CoreModule) { 21 | if (parentModule) { 22 | throw new Error( 23 | 'CoreModule is already loaded. Import it in the AppModule only'); 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the exports for the core resources (services, components). 3 | */ 4 | export * from './navbar/index'; 5 | export * from './services/index'; 6 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/navbar/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the shared NavbarComponent. 3 | */ 4 | export * from './navbar.component'; 5 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/navbar/navbar.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | border-color: #e1e1e1; 3 | border-style: solid; 4 | border-width: 0 0 1px; 5 | display: block; 6 | height: 48px; 7 | padding: 0 16px; 8 | } 9 | 10 | nav a { 11 | color: #8f8f8f; 12 | font-size: 14px; 13 | font-weight: 500; 14 | line-height: 48px; 15 | margin-right: 20px; 16 | text-decoration: none; 17 | vertical-align: middle; 18 | } 19 | 20 | nav a.router-link-active { 21 | color: #106cc8; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { LoginService } from '../services/login.service'; 5 | import { UserService } from '../services/user.service'; 6 | 7 | /** 8 | * This class represents the navigation bar component. 9 | */ 10 | @Component({ 11 | moduleId: module.id, 12 | selector: 'sd-navbar', 13 | templateUrl: 'navbar.component.html', 14 | styleUrls: ['navbar.component.css'], 15 | }) 16 | export class NavbarComponent { 17 | 18 | constructor(private userService:UserService, private loginService:LoginService, private router:Router) { } 19 | 20 | logout() { 21 | //this.loginService.logout(); 22 | this.userService.isLogin = false; 23 | this.userService.userInfo = null; 24 | this.router.navigate(['']); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/services/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; 3 | 4 | import { UserService } from './user.service'; 5 | 6 | @Injectable() 7 | export class AuthGuard implements CanActivate { 8 | 9 | constructor(private userService:UserService, private route:Router) { } 10 | 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 12 | if(this.userService.isLogin) { 13 | return true; 14 | } 15 | this.route.navigate(['/admin/login'],{queryParams:{returnUrl:state.url}}); 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.service'; 2 | export * from './questionnaire.service'; 3 | export * from './login.service'; 4 | export * from './register.service'; 5 | export * from './auth-guard.service'; 6 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/services/login.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Http } from '@angular/http'; 4 | import { Headers } from '@angular/http'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import { Observer } from 'rxjs/Observer'; 7 | 8 | import { FieldBase } from '../../user/shared/field/field-base'; 9 | import { FieldText } from '../../user/shared/field/field-text'; 10 | import { FieldValidators } from '../../user/shared/field/field-validators'; 11 | import { SITE_HOST_URL } from '../../shared/index'; 12 | 13 | @Injectable() 14 | export class LoginService { 15 | 16 | private loginUrl = `${SITE_HOST_URL}login`; 17 | 18 | constructor(private http: Http) { } 19 | 20 | getFields() { 21 | let fields: FieldBase[] = [ 22 | new FieldText({ 23 | key: 'username', 24 | label: '用户名', 25 | value: '', 26 | required: true, 27 | pattern: 'username', 28 | order: 1 29 | }), 30 | new FieldText({ 31 | key: 'password', 32 | label: '密码', 33 | type: 'password', 34 | value: '', 35 | required: true, 36 | pattern: 'password', 37 | order: 2 38 | }), 39 | ]; 40 | return fields.sort((a, b) => a.order - b.order); 41 | } 42 | 43 | toFormGroup(fields: FieldBase[]) { 44 | let group: any = {}; 45 | 46 | fields.forEach(field => { 47 | group[field.key] = 48 | field.pattern ? 49 | new FormControl(field.value || '', (FieldValidators)[field.pattern]) : 50 | field.required ? 51 | new FormControl(field.value || '', Validators.required) : 52 | new FormControl(field.value || ''); 53 | }); 54 | return new FormGroup(group); 55 | } 56 | 57 | login(data: Object) { 58 | let body = JSON.stringify(data); 59 | let headers = new Headers(); 60 | headers.append('Content-Type', 'application/json'); 61 | return new Observable((observer: Observer)=>{ 62 | this.http.post(this.loginUrl, body, { headers }).subscribe(res=>{ 63 | let body = res.json(); 64 | if (body && body.success) { 65 | // this.userService.isLogin = true; 66 | // this.userService.userName = data['username']; 67 | observer.next(res); 68 | observer.complete(); 69 | } 70 | }); 71 | }); 72 | } 73 | 74 | logout() { 75 | //this.userService.isLogin = false; 76 | //this.userService.userName = ''; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/services/questionnaire.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Headers, RequestOptions, Response } from '@angular/http'; 3 | import { Observable } from 'rxjs/Rx'; 4 | 5 | import { QuestionnaireModel } from '../../shared/models/questionnaire.model'; 6 | import { SITE_HOST_URL } from '../../shared/index'; 7 | 8 | @Injectable() 9 | export class QuestionnaireService { 10 | 11 | constructor(private http:Http) { } 12 | 13 | private handleError(error: Response) { 14 | console.error(error); 15 | return Observable.throw(error.json().error || 'server error'); 16 | } 17 | 18 | //根据id获取问卷信息 19 | getQuestionnaireById(id: string) { 20 | return this.http.get(SITE_HOST_URL + 'questionnaire/' + id) 21 | .map(res => res.json().data) 22 | .catch(this.handleError); 23 | } 24 | 25 | getQuestionnaires() { 26 | return this.http.get(SITE_HOST_URL + 'questionnaires') 27 | .map(res => res.json().data) 28 | .catch(this.handleError); 29 | } 30 | 31 | //添加新问卷 32 | addQuestionnaire(questionnaire:QuestionnaireModel) { 33 | let body = JSON.stringify(questionnaire); 34 | let headers = new Headers({'Content-Type':'application/json'}); 35 | let options = new RequestOptions({headers:headers}); 36 | 37 | return this.http.post(SITE_HOST_URL + 'questionnaire/add', body, options) 38 | .map(res => res.json().data) 39 | .catch(this.handleError); 40 | } 41 | 42 | //删除已有问卷 43 | deleteQuestionnaire(id: string) { 44 | return this.http.get(SITE_HOST_URL + 'questionnaire/delete/' + id) 45 | .map(res => res.json().data) 46 | .catch(this.handleError); 47 | } 48 | 49 | //更新已有问卷 50 | updateQuestionnaire(questionnaire:QuestionnaireModel) { 51 | let body = JSON.stringify(questionnaire); 52 | let headers = new Headers({'Content-Type':'application/json'}); 53 | let options = new RequestOptions({headers:headers}); 54 | 55 | return this.http.post(SITE_HOST_URL + 'questionnaire/update', body, options) 56 | .map(res => res.json().data) 57 | .catch(this.handleError); 58 | } 59 | 60 | //发布问卷 61 | publishQuestionnaire(id: string){ 62 | return this.http.get(SITE_HOST_URL + 'questionnaire/publish/' + id) 63 | .map(res => res.json().data) 64 | .catch(this.handleError); 65 | } 66 | 67 | //回收问卷 68 | reclaimQuestionnaire(questionnaire:QuestionnaireModel) { } 69 | 70 | } -------------------------------------------------------------------------------- /frontend/src/client/app/core/services/register.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Http, Headers } from '@angular/http'; 4 | 5 | import { FieldBase } from '../../user/shared/field/field-base'; 6 | import { FieldText } from '../../user/shared/field/field-text'; 7 | import { FieldValidators } from '../../user/shared/field/field-validators'; 8 | import { SITE_HOST_URL } from '../../shared/index'; 9 | 10 | @Injectable() 11 | export class RegisterService { 12 | 13 | private registerUrl = `${SITE_HOST_URL}user/add`; 14 | 15 | constructor(private http: Http) { } 16 | 17 | getFields() { 18 | let fields: FieldBase[] = [ 19 | new FieldText({ 20 | key: 'username', 21 | label: '用户名', 22 | value: '', 23 | required: true, 24 | pattern: 'username', 25 | order: 1 26 | }), 27 | new FieldText({ 28 | key: 'password', 29 | label: '密码', 30 | type: 'password', 31 | value: '', 32 | required: true, 33 | pattern: 'password', 34 | order: 2 35 | }), 36 | ]; 37 | return fields.sort((a, b) => a.order - b.order); 38 | } 39 | 40 | toFormGroup(fields: FieldBase[]) { 41 | let group: any = {}; 42 | 43 | fields.forEach(field => { 44 | group[field.key] = 45 | field.pattern ? 46 | new FormControl(field.value || '', (FieldValidators)[field.pattern]) : 47 | field.required ? 48 | new FormControl(field.value || '', Validators.required) : 49 | new FormControl(field.value || ''); 50 | }); 51 | return new FormGroup(group); 52 | } 53 | 54 | addUser(data: Object) { 55 | let body = JSON.stringify(data); 56 | let headers = new Headers(); 57 | headers.append('Content-Type', 'application/json'); 58 | 59 | return this.http 60 | .post(this.registerUrl, body, { headers }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/src/client/app/core/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Observer } from 'rxjs/Observer'; 5 | 6 | import { SITE_HOST_URL } from '../../shared/index'; 7 | import { UserModel } from '../../shared/models/user.model'; 8 | 9 | @Injectable() 10 | export class UserService { 11 | 12 | public isLogin:boolean = false; 13 | public userName:string = ''; 14 | public userInfo:UserModel; 15 | private getUserInfoUrl = (api: string, userName: string) => `${api}user/get/${userName}`; 16 | 17 | constructor(private http: Http) { } 18 | 19 | getUser(){ 20 | return new Observable((observer: Observer) => { 21 | if(!this.userName) { 22 | observer.next(''); 23 | observer.complete(); 24 | } else { 25 | this.http.get(this.getUserInfoUrl(SITE_HOST_URL, this.userName)).map(res=>res.json().data).subscribe(data => { 26 | this.isLogin = true; 27 | this.userInfo = data; 28 | observer.next(data); 29 | observer.complete(); 30 | }, err => { 31 | this.isLogin = false; 32 | this.userInfo = null; 33 | observer.error(err); 34 | }); 35 | } 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/edit.component.css: -------------------------------------------------------------------------------- 1 | .questionnaire-container{ 2 | background-color: #f0f0f0; 3 | min-height: 800px; 4 | } 5 | 6 | .edit-container .sidebar{ 7 | padding:20px; 8 | } -------------------------------------------------------------------------------- /frontend/src/client/app/edit/edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 13 |
14 |
15 | 16 |
17 |
-------------------------------------------------------------------------------- /frontend/src/client/app/edit/edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | 4 | import { QuestionType } from '../shared/models/question.model'; 5 | import { QuestionnaireService } from '../core/services/questionnaire.service'; 6 | import { QuestionnaireModel, QuestionnaireState } from '../shared/models/questionnaire.model'; 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'sd-edit', 11 | templateUrl: 'edit.component.html', 12 | styleUrls: ['edit.component.css'] 13 | }) 14 | export class EditComponent implements OnInit { 15 | 16 | private questionnaire: QuestionnaireModel; 17 | private id: string; 18 | 19 | constructor(private questionnaireService:QuestionnaireService, private activatedRoute:ActivatedRoute, 20 | private router: Router) { 21 | 22 | //初始化一个空的问卷对象 23 | this.questionnaire = { 24 | title: '', 25 | starter: '', 26 | ending: '', 27 | state: QuestionnaireState.Created, 28 | questionList: [] 29 | }; 30 | } 31 | 32 | ngOnInit() { 33 | //初始化问卷数据 34 | this.id = this.activatedRoute.snapshot.params['id']; 35 | 36 | if(this.id && this.id !== '0'){ 37 | //id存在,代表当前页面为编辑已有问卷页面,调用服务获取问卷对象信息 38 | this.questionnaireService.getQuestionnaireById(this.id) 39 | .subscribe( 40 | questionnaire => this.questionnaire = questionnaire, 41 | error => console.log(error) 42 | ); 43 | } 44 | } 45 | 46 | onAddQuestion(type: QuestionType) { 47 | //添加问题到问卷的问题列表 48 | switch(type){ 49 | case QuestionType.Text: 50 | case QuestionType.Score: 51 | this.questionnaire.questionList.push({ 52 | title: '问题标题', 53 | type: type, 54 | answer: '' 55 | }); 56 | break; 57 | case QuestionType.SingleSelect: 58 | this.questionnaire.questionList.push({ 59 | title: '问题标题', 60 | type: type, 61 | options: [{'key': 0, 'value': '选项1'}], 62 | answer:'' 63 | }); 64 | break; 65 | case QuestionType.MultiSelect: 66 | this.questionnaire.questionList.push({ 67 | title: '问题标题', 68 | type: type, 69 | options: [{'key': 0, 'value': '选项1'}], 70 | answer:{} 71 | }); 72 | break; 73 | } 74 | } 75 | 76 | onSubmitQuestionniare(questionnaire: QuestionnaireModel) { 77 | //保存问卷或回收答案 78 | if (questionnaire.state === QuestionnaireState.Created) { 79 | if (this.id && this.id !== '0') { 80 | //编辑已有问卷 81 | this.questionnaireService.updateQuestionnaire(questionnaire) 82 | .subscribe( 83 | questionnaire => this.gotoCenter(), 84 | error => console.log(error)); 85 | } else { 86 | //创建新问卷 87 | this.questionnaireService.addQuestionnaire(questionnaire) 88 | .subscribe( 89 | questionnaire => this.gotoCenter(), 90 | error=> console.error(error)); 91 | } 92 | } 93 | } 94 | 95 | gotoCenter() { 96 | this.router.navigateByUrl('admin/center'); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/edit.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { EditComponent } from './edit.component'; 5 | import { EditSharedModule } from './shared/edit-shared.module'; 6 | import { QuestionnaireModule } from '../shared/questionnaire/questionnaire.module'; 7 | import { TabsModule } from 'ng2-bootstrap/ng2-bootstrap'; 8 | import { UserModule } from '../user/user.module'; 9 | 10 | @NgModule({ 11 | imports: [CommonModule, TabsModule, EditSharedModule, QuestionnaireModule, UserModule], 12 | declarations: [EditComponent], 13 | exports: [EditComponent, EditSharedModule] 14 | }) 15 | export class EditModule { } 16 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/edit.routes.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import { AuthGuard } from '../core/services/auth-guard.service'; 4 | import { EditComponent } from './index'; 5 | 6 | export const EditRoutes: Route[] = [ 7 | { 8 | path: 'edit/:id', 9 | component: EditComponent, 10 | canActivate: [AuthGuard] 11 | } 12 | ]; 13 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded EditComponent. 3 | */ 4 | export * from './edit.component'; 5 | export * from './edit.routes'; 6 | export * from './shared/index'; 7 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/edit-shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { QuestionSelectComponent } from './question-select/index'; 5 | import { QuestionnaireOutlineComponent } from './questionnaire-outline/index'; 6 | 7 | @NgModule({ 8 | imports: [CommonModule], 9 | declarations: [QuestionSelectComponent, QuestionnaireOutlineComponent], 10 | exports: [QuestionSelectComponent, QuestionnaireOutlineComponent, CommonModule] 11 | }) 12 | export class EditSharedModule { } 13 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question-select/index'; 2 | export * from './questionnaire-outline/index'; 3 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/question-select/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question-select.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/question-select/question-select.component.css: -------------------------------------------------------------------------------- 1 | .question-controls li{ 2 | list-style-type: none; 3 | border:none; 4 | } 5 | 6 | .question-controls li.list-group-item{ 7 | border-bottom: 1px solid #ccc; 8 | } 9 | 10 | .question-controls li a{ 11 | background-repeat: no-repeat; 12 | padding-left: 24px; 13 | cursor: pointer; 14 | text-decoration: none; 15 | } 16 | 17 | .icon-text { 18 | background-image: url(./assets/img/edit/text.png); 19 | } 20 | 21 | .icon-star { 22 | background-image: url(./assets/img/edit/star.png); 23 | } 24 | 25 | .icon-radio { 26 | background-image: url(./assets/img/edit/radio.png); 27 | } 28 | 29 | .icon-checkbox { 30 | background-image: url(./assets/img/edit/checkbox.png); 31 | } -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/question-select/question-select.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/question-select/question-select.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | import { QuestionType } from '../../../shared/models/question.model'; 3 | 4 | @Component({ 5 | moduleId: module.id, 6 | selector: 'question-select', 7 | styleUrls: ['question-select.component.css'], 8 | templateUrl: 'question-select.component.html' 9 | }) 10 | export class QuestionSelectComponent { 11 | 12 | @Output() addQuestionRequest = new EventEmitter(); 13 | 14 | private controls:any[]; 15 | 16 | constructor() { 17 | this.controls = [ 18 | {type: QuestionType.Text, label: '文本问题', iconClass: 'icon-text'}, 19 | {type: QuestionType.SingleSelect, label: '单选问题', iconClass: 'icon-radio'}, 20 | {type: QuestionType.MultiSelect, label: '多选问题', iconClass: 'icon-checkbox'}, 21 | {type: QuestionType.Score, label: '分值问题', iconClass: 'icon-star'} 22 | ]; 23 | } 24 | 25 | onAddQuestion(control: any) { 26 | this.addQuestionRequest.emit(control.type); 27 | } 28 | } -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/questionnaire-outline/index.ts: -------------------------------------------------------------------------------- 1 | export * from './questionnaire-outline.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/questionnaire-outline/questionnaire-outline.component.css: -------------------------------------------------------------------------------- 1 | .questionnaire-outline{ 2 | margin-top: 20px; 3 | } 4 | 5 | .questionnaire-outline li{ 6 | font-size: 12px; 7 | line-height: 20px; 8 | } -------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/questionnaire-outline/questionnaire-outline.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 3 | {{(i + 1) + ". " + q.title}} 4 |
  • 5 |
-------------------------------------------------------------------------------- /frontend/src/client/app/edit/shared/questionnaire-outline/questionnaire-outline.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | import { QuestionnaireModel } from '../../../shared/models/questionnaire.model'; 4 | 5 | @Component({ 6 | moduleId: module.id, 7 | selector: 'questionnaire-outline', 8 | styleUrls: ['questionnaire-outline.component.css'], 9 | templateUrl: 'questionnaire-outline.component.html' 10 | }) 11 | export class QuestionnaireOutlineComponent { 12 | 13 | @Input() questionnaire: QuestionnaireModel; 14 | 15 | } -------------------------------------------------------------------------------- /frontend/src/client/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 0 16px; 4 | 5 | } 6 | .login-title { 7 | color: #337ab7; 8 | } 9 | 10 | .home .slide-image { 11 | text-align: center; 12 | } 13 | 14 | .hero-title { 15 | margin-top: 50px; 16 | text-align: center; 17 | font-size: 18px; 18 | } 19 | 20 | .hero-title span { 21 | color: green; 22 | font-size: 22px; 23 | } 24 | 25 | .hero-title a { 26 | margin-left: 80px; 27 | width: 250px; 28 | } 29 | 30 | .carousel-indicators li { 31 | margin: 1px 5px; 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/client/app/home/home.component.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | describe('Home', () => { 2 | 3 | beforeEach( () => { 4 | browser.get('/'); 5 | }); 6 | 7 | it('should have an input', () => { 8 | expect(element(by.css('sd-home form input')).isPresent()).toEqual(true); 9 | }); 10 | 11 | it('should have a list of computer scientists', () => { 12 | expect(element(by.css('sd-home ul')).getText()) 13 | .toEqual('Edsger Dijkstra\nDonald Knuth\nAlan Turing\nGrace Hopper'); 14 | }); 15 | 16 | it('should add a name to the list using the form', () => { 17 | element(by.css('sd-home form input')).sendKeys('Tim Berners-Lee'); 18 | element(by.css('sd-home form button')).click(); 19 | 20 | expect(element(by.css('sd-home ul')).getText()) 21 | .toEqual('Edsger Dijkstra\nDonald Knuth\nAlan Turing\nGrace Hopper\nTim Berners-Lee'); 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/client/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 7 |
8 | 9 |
10 | 11 | 14 |
15 |
16 | 17 |
18 |

19 | 10,145,267个用户正在使用 20 | 创建新问卷 21 |

22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/client/app/home/home.component.nonspec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { By } from '@angular/platform-browser'; 3 | 4 | import { DebugElement } from '@angular/core'; 5 | import { CommonModule } from '@angular/common'; 6 | 7 | import { HomeComponent } from './home.component'; 8 | import { CarouselModule } from 'ng2-bootstrap/components/carousel'; 9 | import { SharedModule } from '../shared/shared.module'; 10 | // import { NavbarComponent } from '../core/navbar/index'; 11 | import { CoreModule } from '../core/core.module'; 12 | 13 | import { Router, ActivatedRoute, RouterLink, RouterOutlet} from '@angular/router'; 14 | 15 | export function main() { 16 | describe('Home component', () => { 17 | 18 | let comp: HomeComponent; 19 | let fixture: ComponentFixture; 20 | let homeEl: DebugElement; 21 | 22 | it('should just work', ()=>{ 23 | expect(true).toBeTruthy(); 24 | }); 25 | 26 | // setting module for testing 27 | // compile template and css 28 | beforeEach( async(() => { 29 | TestBed.configureTestingModule({ 30 | imports: [CommonModule, CoreModule, SharedModule, CarouselModule], 31 | declarations: [HomeComponent], 32 | providers: [ 33 | { provide: Router, useClass: RouterStub } 34 | ] 35 | }) 36 | .compileComponents(); 37 | })); 38 | 39 | // synchronous beforeEach 40 | beforeEach(() => { 41 | fixture = TestBed.createComponent(HomeComponent); 42 | comp = fixture.componentInstance; 43 | homeEl = fixture.debugElement; 44 | 45 | fixture.detectChanges(); // trigger initial data binding 46 | }); 47 | 48 | it('should render slides', ()=>{ 49 | 50 | const de = homeEl.queryAll(By.css('.slide-image')); 51 | 52 | expect(de.length).toBe(3); 53 | }); 54 | }); 55 | } 56 | 57 | class RouterStub { 58 | navigateByUrl(url: string) { return url; } 59 | } 60 | 61 | ////// Test Host Component ////// 62 | import { Component } from '@angular/core'; 63 | 64 | @Component({ 65 | selector: 'test-cmp', 66 | template: '' 67 | }) 68 | class TestComponent { } 69 | -------------------------------------------------------------------------------- /frontend/src/client/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'sd-home', 6 | styleUrls:[ 'home.component.css'], 7 | templateUrl: 'home.component.html' 8 | }) 9 | export class HomeComponent { 10 | 11 | // public myInterval:number = 5000; 12 | // public noWrapSlides:boolean = false; 13 | slides:Array = []; 14 | slogns:Array = ['免费简约的问卷系统', '简单 好用 在线调查网站', '多方式创建编辑问卷']; 15 | 16 | constructor() { 17 | for (let i = 0; i < 3; i++) { 18 | this.addSlide(i); 19 | } 20 | } 21 | 22 | addSlide(idx: number) { 23 | this.slides.push({ 24 | image: `./assets/img/home/banner_0${idx+1}.jpg`, 25 | text: this.slogns[idx] 26 | }); 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/client/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { CoreModule } from '../core/core.module'; 5 | import { HomeComponent } from './home.component'; 6 | import { CarouselModule } from 'ng2-bootstrap/components/carousel'; 7 | import { SharedModule } from '../shared/shared.module'; 8 | 9 | @NgModule({ 10 | imports: [CommonModule, CoreModule, SharedModule, CarouselModule], 11 | declarations: [HomeComponent], 12 | exports: [HomeComponent] 13 | }) 14 | export class HomeModule { } 15 | -------------------------------------------------------------------------------- /frontend/src/client/app/home/home.routes.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import { HomeComponent } from './index'; 4 | 5 | export const HomeRoutes: Route[] = [ 6 | { 7 | path: '', 8 | component: HomeComponent 9 | } 10 | ]; 11 | -------------------------------------------------------------------------------- /frontend/src/client/app/home/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded HomeComponent. 3 | */ 4 | export * from './home.component'; 5 | export * from './home.routes'; 6 | -------------------------------------------------------------------------------- /frontend/src/client/app/main-prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstraps the application and makes the ROUTER_PROVIDERS and the APP_BASE_HREF available to it. 3 | * @see https://angular.io/docs/ts/latest/api/platform-browser-dynamic/index/bootstrap-function.html 4 | */ 5 | import { enableProdMode } from '@angular/core'; 6 | import { platformBrowser } from '@angular/platform-browser'; 7 | 8 | import { AppModuleNgFactory } from './app.module.ngfactory'; 9 | 10 | enableProdMode(); 11 | 12 | platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); 13 | 14 | // In order to start the Service Worker located at "./worker.js" 15 | // uncomment this line. More about Service Workers here 16 | // https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers 17 | // 18 | // if ('serviceWorker' in navigator) { 19 | // (navigator).serviceWorker.register('./worker.js').then((registration: any) => 20 | // console.log('ServiceWorker registration successful with scope: ', registration.scope)) 21 | // .catch((err: any) => 22 | // console.log('ServiceWorker registration failed: ', err)); 23 | // } 24 | -------------------------------------------------------------------------------- /frontend/src/client/app/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstraps the application and makes the ROUTER_PROVIDERS and the APP_BASE_HREF available to it. 3 | * @see https://angular.io/docs/ts/latest/api/platform-browser-dynamic/index/bootstrap-function.html 4 | */ 5 | import { enableProdMode } from '@angular/core'; 6 | // The browser platform with a compiler 7 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 8 | // Load i18n providers 9 | // import { TranslationProviders } from './i18n.providers'; 10 | 11 | // The app module 12 | import { AppModule } from './app.module'; 13 | 14 | if (String('<%= BUILD_TYPE %>') === 'prod') { enableProdMode(); } 15 | 16 | // Compile and launch the module with i18n providers 17 | // let TP = new TranslationProviders(); 18 | // TP.getTranslationFile().then((providers: any) => { 19 | // const options: any = { providers }; 20 | platformBrowserDynamic().bootstrapModule(AppModule/*, options*/); 21 | // }); 22 | -------------------------------------------------------------------------------- /frontend/src/client/app/published/index.ts: -------------------------------------------------------------------------------- 1 | export * from './published.component'; 2 | export * from './published-routing.module'; 3 | -------------------------------------------------------------------------------- /frontend/src/client/app/published/published-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { PublishedComponent } from './index'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: 'published/:id', 9 | component: PublishedComponent 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class PublishedRoutingModule { } 18 | -------------------------------------------------------------------------------- /frontend/src/client/app/published/published.component.css: -------------------------------------------------------------------------------- 1 | .published-container{ 2 | width: 1024px; 3 | margin: 0 auto; 4 | } 5 | 6 | .published-container .questionnaire-container{ 7 | background: #ccc !important; 8 | } -------------------------------------------------------------------------------- /frontend/src/client/app/published/published.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /frontend/src/client/app/published/published.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { QuestionnaireModel } from '../shared/models/questionnaire.model'; 5 | import { QuestionnaireService } from '../core/services/questionnaire.service'; 6 | 7 | @Component({ 8 | moduleId: module.id, 9 | selector: 'sd-published', 10 | templateUrl: 'published.component.html', 11 | styleUrls: ['published.component.css'] 12 | }) 13 | export class PublishedComponent implements OnInit { 14 | 15 | private questionnaire: QuestionnaireModel; 16 | private id: string; 17 | 18 | constructor(private questionnaireService:QuestionnaireService, private activatedRoute:ActivatedRoute) { } 19 | 20 | ngOnInit() { 21 | this.id = this.activatedRoute.snapshot.params['id']; 22 | if (this.id && this.id !== '0') { 23 | this.questionnaireService.getQuestionnaireById(this.id) 24 | .subscribe( 25 | questionnaire => this.questionnaire = questionnaire, 26 | error => console.log(error) 27 | ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/client/app/published/published.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { PublishedComponent } from './published.component'; 5 | import { PublishedRoutingModule } from './published-routing.module'; 6 | import { QuestionnaireModule } from '../shared/questionnaire/questionnaire.module'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule, PublishedRoutingModule, QuestionnaireModule], 10 | declarations: [PublishedComponent], 11 | exports: [PublishedComponent] 12 | }) 13 | export class PublishedModule { } 14 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/config/env.config.ts: -------------------------------------------------------------------------------- 1 | // Feel free to extend this interface 2 | // depending on your app specific config. 3 | export interface EnvConfig { 4 | API?: string; 5 | ENV?: string; 6 | } 7 | 8 | export const Config: EnvConfig = JSON.parse('<%= ENV_CONFIG %>'); 9 | 10 | export const SITE_HOST_URL: string = 'http://localhost:5000/'; 11 | 12 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the exports for the shared resources (services, components). 3 | */ 4 | export * from './config/env.config'; 5 | export * from './models/index'; 6 | export * from './question/index'; 7 | export * from './questionnaire/index'; 8 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question.model'; 2 | export * from './questionnaire.model'; 3 | export * from './user.model'; 4 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/models/question.model.ts: -------------------------------------------------------------------------------- 1 | // 问题模型数据定义 2 | 3 | // export interface QuestionModel{ 4 | // title:string; //问题标题(描述) 5 | // type:QuestionType; //问题类型 6 | // options?:any[]; //答案选项 7 | // answer:any; //问题答案 8 | // } 9 | 10 | export class QuestionModel { 11 | title:string; //问题标题(描述) 12 | type:QuestionType; //问题类型 13 | options?:any[]; //答案选项 14 | answer:any; //问题答案 15 | } 16 | 17 | // 问题类型 18 | export const enum QuestionType{ 19 | Text, 20 | SingleSelect, 21 | MultiSelect, 22 | Score 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/models/questionnaire.model.ts: -------------------------------------------------------------------------------- 1 | import { QuestionModel } from './question.model'; 2 | 3 | // //问卷数据模型定义 4 | // export interface QuestionnaireModel{ 5 | // id?:string; //问卷ID 6 | // title:string; //问卷标题 7 | // starter:string; //开始问候语 8 | // ending:string; //结束问候语 9 | // state:QuestionnaireState; //问卷状态 10 | // questionList: QuestionModel[]; //问题列表 11 | // createDate?:string; //创建日期 12 | // } 13 | 14 | //问卷数据模型定义 15 | export class QuestionnaireModel{ 16 | id?:string; //问卷ID 17 | title:string; //问卷标题 18 | starter:string; //开始问候语 19 | ending:string; //结束问候语 20 | state:QuestionnaireState; //问卷状态 21 | questionList: QuestionModel[]; //问题列表 22 | createDate?:string; //创建日期 23 | } 24 | 25 | //问卷状态枚举类型 26 | export const enum QuestionnaireState{ 27 | Created, //已创建状态 28 | Published, //发布回收状态 29 | Finished //完成状态 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/models/user.model.ts: -------------------------------------------------------------------------------- 1 | // 用户信息 2 | // export interface UserModel{ 3 | // username:string; //问卷标题 4 | // createDate?:string; //创建日期 5 | // } 6 | 7 | export class UserModel{ 8 | username:string; //问卷标题 9 | createDate?:string; //创建日期 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question.component'; 2 | export * from './shared/index'; 3 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/question.component.ts: -------------------------------------------------------------------------------- 1 | import { OnInit, EventEmitter } from '@angular/core'; 2 | 3 | import { QuestionModel } from '../models/question.model'; 4 | 5 | export class QuestionComponent implements OnInit { 6 | 7 | question: QuestionModel; 8 | backupQuestion: QuestionModel; 9 | editable: boolean = false; 10 | isEditing: boolean = false; 11 | deleteQuestionRequest: EventEmitter = new EventEmitter(); 12 | 13 | ngOnInit(){ 14 | this.copyQuestion(); 15 | } 16 | 17 | private copy(source: QuestionModel): QuestionModel { 18 | return JSON.parse(JSON.stringify(source)); 19 | } 20 | 21 | public copyQuestion() { 22 | this.backupQuestion = this.copy(this.question); 23 | 24 | } 25 | 26 | onEdit() { 27 | this.isEditing = true; 28 | } 29 | 30 | onSave() { 31 | this.copyQuestion(); 32 | this.isEditing = false; 33 | } 34 | 35 | onCancel() { 36 | this.question = this.copy(this.backupQuestion); 37 | this.isEditing = false; 38 | } 39 | 40 | onDelete() { 41 | this.deleteQuestionRequest.emit(this.question); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/question.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { QuestionSharedModule } from './shared/question-shared.module'; 6 | 7 | @NgModule({ 8 | imports: [CommonModule, FormsModule, QuestionSharedModule], 9 | exports: [CommonModule, QuestionSharedModule] 10 | }) 11 | export class QuestionModule { 12 | 13 | static forRoot(): ModuleWithProviders { 14 | return { 15 | ngModule: QuestionModule 16 | }; 17 | } 18 | } -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question-checkbox/index'; 2 | export * from './question-radio/index'; 3 | export * from './question-score/index'; 4 | export * from './question-text/index'; 5 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question-checkbox.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-checkbox/question-checkbox.component.html: -------------------------------------------------------------------------------- 1 |

复选题

2 |
3 |

{{question.title}}

4 |
5 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | X 19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 |
28 |

{{question.title}}

29 |
30 | 34 |
35 |
36 | 37 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-checkbox/question-checkbox.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | import { QuestionComponent } from '../../index'; 4 | import { QuestionModel } from '../../../models/question.model'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'question-checkbox', 9 | templateUrl: 'question-checkbox.component.html' 10 | }) 11 | export class QuestionCheckboxComponent extends QuestionComponent { 12 | 13 | @Input() question: QuestionModel; 14 | @Input() editable: boolean; 15 | @Output() deleteQuestionRequest: EventEmitter = new EventEmitter(); 16 | 17 | private key: number; 18 | 19 | ngOnInit() { 20 | this.copyQuestion(); 21 | let options = this.question.options; 22 | this.key = options[options.length-1].key; 23 | if(!this.question.answer.selected){ 24 | this.question.answer.selected = []; 25 | } 26 | } 27 | 28 | onDeleteOption(index: number) { 29 | if(this.question.options.length <= 1) { 30 | return; 31 | } 32 | 33 | this.question.options.splice(index, 1); 34 | } 35 | 36 | onAddOption() { 37 | this.question.options.push({key: ++this.key, value:'' }); 38 | } 39 | 40 | setSelectedValue(checked: boolean, value: string) { 41 | let selected = this.question.answer.selected; 42 | let index:number = selected.indexOf(value); 43 | if(checked){ 44 | if(index < 0){ 45 | selected.push(value); 46 | } 47 | }else{ 48 | if(index > -1){ 49 | selected.splice(index, 1); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-radio/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question-radio.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-radio/question-radio.component.html: -------------------------------------------------------------------------------- 1 |

单选题

2 |
3 |

{{question.title}}

4 |
5 | 8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | X 18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 |
27 |

{{question.title}}

28 |
29 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-radio/question-radio.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | import { QuestionComponent } from '../../index'; 4 | import { QuestionModel } from '../../../models/question.model'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'question-radio', 9 | templateUrl: 'question-radio.component.html' 10 | }) 11 | export class QuestionRadioComponent extends QuestionComponent { 12 | 13 | @Input() question: QuestionModel; 14 | @Input() editable: boolean; 15 | @Output() deleteQuestionRequest: EventEmitter = new EventEmitter(); 16 | 17 | private key: number; 18 | 19 | ngOnInit() { 20 | this.copyQuestion(); 21 | let options = this.question.options; 22 | this.key = options[options.length-1].key; 23 | } 24 | 25 | onDeleteOption(index:number) { 26 | if(this.question.options.length <= 1) { 27 | return; 28 | } 29 | 30 | this.question.options.splice(index, 1); 31 | } 32 | 33 | onAddOption() { 34 | this.question.options.push({key: ++this.key, value:'' }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-score/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question-score.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-score/question-score.component.html: -------------------------------------------------------------------------------- 1 |

分值题

2 | 3 |
4 |

{{question.title}}

5 |

6 | 7 | 8 |

9 |
10 | 11 |
12 | 13 |
14 | 15 | 16 |
17 |
18 | 19 |
20 |

{{question.title}}

21 |

22 | 23 | 24 |

25 |
26 | 27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-score/question-score.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | import { QuestionComponent } from '../../index'; 4 | import { QuestionModel } from '../../../models/question.model'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'question-score', 9 | templateUrl: 'question-score.component.html' 10 | }) 11 | export class QuestionScoreComponent extends QuestionComponent { 12 | 13 | @Input() question: QuestionModel; 14 | @Input() editable: boolean; 15 | @Output() deleteQuestionRequest: EventEmitter = new EventEmitter(); 16 | 17 | } -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { QuestionCheckboxComponent } from './question-checkbox/index'; 6 | import { QuestionRadioComponent } from './question-radio/index'; 7 | import { QuestionScoreComponent } from './question-score/index'; 8 | import { QuestionTextComponent } from './question-text/index'; 9 | 10 | @NgModule({ 11 | imports: [CommonModule, FormsModule], 12 | declarations: [QuestionCheckboxComponent, QuestionRadioComponent, QuestionScoreComponent, QuestionTextComponent], 13 | exports: [QuestionCheckboxComponent, QuestionRadioComponent, QuestionScoreComponent, QuestionTextComponent, CommonModule, FormsModule] 14 | }) 15 | export class QuestionSharedModule { } 16 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-text/index.ts: -------------------------------------------------------------------------------- 1 | export * from './question-text.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-text/question-text.component.html: -------------------------------------------------------------------------------- 1 |

问答题

2 |
3 |

{{question.title}}

4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 |

{{question.title}}

17 | 18 |
19 | 20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /frontend/src/client/app/shared/question/shared/question-text/question-text.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | import { QuestionComponent } from '../../index'; 4 | import { QuestionModel } from '../../../models/question.model'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'question-text', 9 | templateUrl: 'question-text.component.html' 10 | }) 11 | export class QuestionTextComponent extends QuestionComponent { 12 | 13 | @Input() question: QuestionModel; 14 | @Input() editable: boolean; 15 | @Output() deleteQuestionRequest: EventEmitter = new EventEmitter(); 16 | 17 | } -------------------------------------------------------------------------------- /frontend/src/client/app/shared/questionnaire/index.ts: -------------------------------------------------------------------------------- 1 | export * from './questionnaire.component'; -------------------------------------------------------------------------------- /frontend/src/client/app/shared/questionnaire/questionnaire.component.css: -------------------------------------------------------------------------------- 1 | .questionnaire-container{ 2 | padding:30px; 3 | background-color: #fff; 4 | margin-top:20px; 5 | } 6 | 7 | .questionnaire-container .btn-primary{ 8 | width: 120px; 9 | } 10 | 11 | .questionnaire ul li { 12 | border-bottom: 1px solid lightgrey; 13 | padding-bottom: 10px; 14 | margin-bottom: 20px; 15 | } 16 | 17 | .questionnaire{ 18 | margin-bottom: 30px; 19 | } -------------------------------------------------------------------------------- /frontend/src/client/app/shared/questionnaire/questionnaire.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{questionnaire.title}}

4 |

5 | 6 |

7 |

{{questionnaire.starter}}

8 |

9 | 10 |

11 |
    12 |
  • 13 | 14 |
    15 | 16 |
    17 |
    18 | 19 |
    20 |
    21 | 22 |
    23 |
    24 | 25 |
    26 |
  • 27 |
28 |

{{questionnaire.ending}}

29 |

30 | 31 |

32 |
33 |
34 | 36 |
37 |
-------------------------------------------------------------------------------- /frontend/src/client/app/shared/questionnaire/questionnaire.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | import { QuestionnaireModel, QuestionnaireState } from '../models/questionnaire.model'; 4 | 5 | @Component({ 6 | moduleId: module.id, 7 | selector: 'questionnaire', 8 | styleUrls:['questionnaire.component.css'], 9 | templateUrl: 'questionnaire.component.html' 10 | }) 11 | export class QuestionnaireComponent implements OnInit { 12 | 13 | @Input() questionnaire: QuestionnaireModel; 14 | @Output() submitQuestionnaire = new EventEmitter(); 15 | 16 | private editable:boolean; 17 | 18 | ngOnInit() { 19 | this.editable = this.questionnaire && this.questionnaire.state === QuestionnaireState.Created; 20 | } 21 | 22 | onDeleteQuestion(index: number) { 23 | this.questionnaire.questionList.splice(index, 1); 24 | } 25 | 26 | onSubmit() { 27 | this.submitQuestionnaire.emit(this.questionnaire); 28 | } 29 | } -------------------------------------------------------------------------------- /frontend/src/client/app/shared/questionnaire/questionnaire.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { QuestionModule } from '../question/question.module'; 6 | import { QuestionnaireComponent } from './questionnaire.component'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule, FormsModule, QuestionModule], 10 | declarations: [QuestionnaireComponent], 11 | exports: [QuestionnaireComponent, CommonModule, FormsModule] 12 | }) 13 | export class QuestionnaireModule { } -------------------------------------------------------------------------------- /frontend/src/client/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | @NgModule({ 7 | imports: [CommonModule, RouterModule], 8 | declarations: [], 9 | exports: [CommonModule, FormsModule, RouterModule] 10 | }) 11 | export class SharedModule { 12 | 13 | static forRoot(): ModuleWithProviders { 14 | return { 15 | ngModule: SharedModule 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/client/app/system-config.ts: -------------------------------------------------------------------------------- 1 | declare var System: SystemJSLoader.System; 2 | 3 | System.config(JSON.parse('<%= SYSTEM_CONFIG_DEV %>')); 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.routes'; 2 | export * from './shared/index'; 3 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field-base.ts: -------------------------------------------------------------------------------- 1 | export class FieldBase{ 2 | value: T; 3 | key: string; 4 | label: string; 5 | required: boolean; 6 | pattern: string; 7 | order: number; 8 | controlType: string; 9 | 10 | constructor(options: { 11 | value?: T, 12 | key?: string, 13 | label?: string, 14 | required?: boolean, 15 | pattern?: string, 16 | order?: number, 17 | controlType?: string 18 | } = {}) { 19 | this.value = options.value; 20 | this.key = options.key || ''; 21 | this.label = options.label || ''; 22 | this.required = !!options.required; 23 | this.pattern = options.pattern || ''; 24 | this.order = options.order === undefined ? 1 : options.order; 25 | this.controlType = options.controlType || ''; 26 | } 27 | } -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field-radio.ts: -------------------------------------------------------------------------------- 1 | import { FieldBase } from './field-base'; 2 | 3 | export class FieldRadio extends FieldBase { 4 | 5 | controlType = 'radio'; 6 | type: string; 7 | items: {name: string, value: string}[] = []; 8 | 9 | constructor(options: any) { 10 | super(options); 11 | this.items = options['items'] || []; 12 | this.type = 'radio'; 13 | } 14 | } -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field-select.ts: -------------------------------------------------------------------------------- 1 | import { FieldBase } from './field-base'; 2 | 3 | export class SelectField extends FieldBase { 4 | 5 | controlType = 'select'; 6 | options: {key: string, value: string}[] = []; 7 | 8 | constructor(options: any) { 9 | super(options); 10 | this.options = options['options'] || []; 11 | } 12 | } -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field-text.ts: -------------------------------------------------------------------------------- 1 | import { FieldBase } from './field-base'; 2 | 3 | export class FieldText extends FieldBase { 4 | 5 | controlType = 'text'; 6 | type: string; 7 | 8 | constructor(options: any) { 9 | super(options); 10 | this.type = options['type']; 11 | } 12 | } -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field-validators.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from '@angular/forms'; 2 | 3 | const REG = { 4 | USERNAME: /^\w{1,20}$/, 5 | PASSWORD: /^\w{6,20}$/ 6 | }; 7 | 8 | interface ValidationResult { 9 | [key: string]: boolean; 10 | } 11 | 12 | export class FieldValidators { 13 | 14 | public static username(control: FormControl): ValidationResult { 15 | if (control.value.length === 0) { 16 | return { 17 | empty: true 18 | }; 19 | } 20 | if (REG.USERNAME.test(control.value)) { 21 | return null; 22 | } 23 | return { 'invalid': true }; 24 | } 25 | 26 | public static password(control: FormControl): ValidationResult { 27 | if (control.value.length === 0) { 28 | return { 29 | empty: true 30 | }; 31 | } 32 | if (REG.PASSWORD.test(control.value)) { 33 | return null; 34 | } 35 | return { 'invalid': true }; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 0 16px; 4 | margin: 0 auto; 5 | } 6 | 7 | input { 8 | border: 1px solid #106cc8 !important; 9 | box-shadow: none !important; 10 | width: 100%; 11 | border-radius: 5px; 12 | } 13 | 14 | select { 15 | display: block; 16 | border: 1px solid #106cc8; 17 | /*border-radius: 0;*/ 18 | /*-webkit-appearance: none;*/ 19 | } 20 | 21 | label { 22 | font-size: 1.2rem; 23 | color: #666; 24 | margin-top: 20px; 25 | } 26 | 27 | .ng-valid[required] { 28 | border-left: 5px solid #42A948; /* green */ 29 | } 30 | 31 | .ng-invalid { 32 | /*border-left: 5px solid #a94442; */ 33 | } 34 | 35 | input[type=text], 36 | input[type=password] { 37 | padding-left: 10px; 38 | letter-spacing: 2px; 39 | margin-bottom: 0; 40 | } 41 | 42 | [type=submit] { 43 | margin-right: 20px; 44 | } 45 | 46 | .errorMessage { 47 | margin-top: 5px; 48 | color: #ee6e73; 49 | } 50 | 51 | .tipsMessage { 52 | margin-top: 5px; 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |
12 | 18 |
19 | 20 | 23 | 24 |
25 | 26 |
{{field.label}}格式不正确
27 |
请填写{{field.label}}
28 |
29 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/field.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { FormGroup } from '@angular/forms'; 3 | 4 | import { FieldBase } from './field-base'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'field', 9 | templateUrl: 'field.component.html', 10 | styleUrls: ['field.component.css'] 11 | }) 12 | export class FieldComponent { 13 | 14 | @Input() field: FieldBase; 15 | @Input() form: FormGroup; 16 | 17 | get isValid() { 18 | return this.form.controls[this.field.key].valid; 19 | } 20 | 21 | get isEmpty() { 22 | let errors = this.form.controls[this.field.key].errors || {}; 23 | return errors['empty']; 24 | } 25 | } -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/field/index.ts: -------------------------------------------------------------------------------- 1 | export * from './field.component'; 2 | export * from './field-base'; 3 | export * from './field-radio'; 4 | export * from './field-select'; 5 | export * from './field-text'; 6 | export * from './field-validators'; -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './field/index'; 2 | export * from './login/index'; 3 | export * from './register/index'; 4 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/login/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded LoginComponent. 3 | */ 4 | export * from './login.component'; 5 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/login/login.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | margin: 20px 20%; 4 | } 5 | 6 | h2, h3 { 7 | font-size: 52px; 8 | font-weight: 500; 9 | letter-spacing: 0.005em; 10 | margin-bottom: 0; 11 | margin-top: 0.83em; 12 | text-align: center; 13 | } 14 | 15 | h3 { 16 | font-size: 40px; 17 | } 18 | 19 | .form-row { 20 | height: 115px; 21 | } 22 | 23 | .form-checkbox { 24 | height: 30px; 25 | margin: 36px 16px 0; 26 | } 27 | 28 | button { 29 | margin: 40px 16px; 30 | padding: 0 32px; 31 | text-align: center; 32 | } 33 | 34 | button[disabled] { 35 | background-color: #ccc; 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
12 |
13 |
-------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { FormGroup } from '@angular/forms'; 4 | import { Response } from '@angular/http'; 5 | 6 | import { FieldBase } from '../field/field-base'; 7 | import { LoginService } from '../../../core/services/login.service'; 8 | import { UserService } from '../../../core/services/user.service'; 9 | 10 | @Component({ 11 | moduleId: module.id, 12 | selector: 'login', 13 | templateUrl: 'login.component.html', 14 | styleUrls: ['login.component.css'] 15 | }) 16 | export class LoginComponent implements OnInit { 17 | 18 | form: FormGroup; 19 | fields: FieldBase[] = []; 20 | returnUrl: string =''; 21 | 22 | constructor(private rs: LoginService, 23 | private activeRoute: ActivatedRoute, 24 | private route:Router, 25 | private userService:UserService) { 26 | this.fields = rs.getFields(); 27 | this.activeRoute.queryParams.subscribe(params => { 28 | this.returnUrl = params['returnUrl']; 29 | }); 30 | } 31 | 32 | ngOnInit() { 33 | this.form = this.rs.toFormGroup(this.fields); 34 | } 35 | 36 | login() { 37 | this.rs 38 | .login(this.form.value) 39 | .subscribe((res: Response) => { 40 | let body = res.json(); 41 | if (body && body.success) { 42 | this.userService.isLogin = true; 43 | this.userService.userInfo = { username: this.form.value.username,createDate:new Date().toLocaleString()} 44 | this.route.navigateByUrl(this.returnUrl?this.returnUrl:'/'); 45 | } 46 | }, error => { 47 | console.error(error); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/register/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded RegisterComponent. 3 | */ 4 | export * from './register.component'; 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/register/register.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | margin: 20px 20%; 4 | } 5 | 6 | h2, h3 { 7 | font-size: 52px; 8 | font-weight: 500; 9 | letter-spacing: 0.005em; 10 | margin-bottom: 0; 11 | margin-top: 0.83em; 12 | text-align: center; 13 | } 14 | 15 | h3 { 16 | font-size: 40px; 17 | } 18 | 19 | .form-row { 20 | height: 115px; 21 | } 22 | 23 | .form-checkbox { 24 | height: 30px; 25 | margin: 36px 16px 0; 26 | } 27 | 28 | button { 29 | margin: 40px 16px; 30 | padding: 0 32px; 31 | text-align: center; 32 | } 33 | 34 | button[disabled] { 35 | background-color: #ccc; 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

注册

4 |
5 | 6 |
7 | 8 |
9 | 10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | 23 | {{ alert?.msg }} 24 | 25 |
26 |
-------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import { Observer } from 'rxjs/Observer'; 6 | 7 | import { FieldBase } from '../field/field-base'; 8 | import { RegisterService } from '../../../core/services/register.service'; 9 | import { UserService } from '../../../core/services/user.service'; 10 | 11 | @Component({ 12 | moduleId: module.id, 13 | selector: 'register', 14 | templateUrl: 'register.component.html', 15 | styleUrls: ['register.component.css'] 16 | }) 17 | export class RegisterComponent implements OnInit { 18 | 19 | form: FormGroup; 20 | registered = false; 21 | fields: FieldBase[] = []; 22 | alert:any = {msg: '注册成功', type: 'success', closable: true}; 23 | 24 | constructor(private rs: RegisterService, 25 | private userService:UserService, 26 | private route:Router) { 27 | this.fields = rs.getFields(); 28 | } 29 | 30 | ngOnInit() { 31 | this.form = this.rs.toFormGroup(this.fields); 32 | } 33 | 34 | showPassword () { 35 | this.fields.forEach((field : any) => { 36 | if (field.key === 'password') { 37 | field.type = field.type === 'password' ? 'text' : 'password'; 38 | } 39 | }); 40 | } 41 | 42 | resetForm () { 43 | this.form.reset(); 44 | } 45 | 46 | register() { 47 | this.rs 48 | .addUser(this.form.value) 49 | .subscribe((res: any) => { 50 | let body = res.json(); 51 | this.registered = true; 52 | new Observable((observer: Observer) => { 53 | this.alert.msg = body.message || "注册成功"; // 操作提示信息 54 | this.alert.type =body.success ? "success" : "danger"; 55 | this.registered = true; 56 | observer.next(true); 57 | }).delay(2000).subscribe(data=>{ // 操作提示,2秒后跳转到首页 58 | if(body.success) { 59 | this.userService.isLogin = true; // 设置当前用户已登录 60 | this.userService.userInfo = { username: this.form.value.username,createDate:new Date().toLocaleString()} // 缓存用户信息,显示在导航栏上 61 | this.route.navigate(['']); // 跳到首页 62 | } 63 | this.registered = false; 64 | }); 65 | }, (error: any) => { 66 | console.error(error); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/shared/user-shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { AlertModule } from 'ng2-bootstrap/ng2-bootstrap'; 6 | import { SharedModule } from '../../shared/shared.module'; 7 | import { FieldComponent } from './field/index'; 8 | import { LoginComponent } from './login/index'; 9 | import { RegisterComponent } from './register/index'; 10 | 11 | @NgModule({ 12 | imports: [CommonModule, ReactiveFormsModule, SharedModule, AlertModule], 13 | declarations: [FieldComponent, LoginComponent, RegisterComponent], 14 | exports: [FieldComponent, LoginComponent, RegisterComponent, CommonModule] 15 | }) 16 | export class UserSharedModule { } 17 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | import { AlertModule } from 'ng2-bootstrap/ng2-bootstrap'; 6 | import { UserSharedModule } from './shared/user-shared.module'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule, SharedModule, AlertModule, UserSharedModule], 10 | exports: [CommonModule, UserSharedModule] 11 | }) 12 | export class UserModule { } 13 | -------------------------------------------------------------------------------- /frontend/src/client/app/user/user.routes.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import { LoginComponent } from './shared/login/index'; 4 | import { RegisterComponent } from './shared/register/index'; 5 | 6 | export const UserRoutes: Route[] = [ 7 | { 8 | path: 'login', 9 | component: LoginComponent 10 | }, 11 | { 12 | path: 'register', 13 | component: RegisterComponent 14 | } 15 | ]; 16 | -------------------------------------------------------------------------------- /frontend/src/client/assets/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | "Edsger Dijkstra", 3 | "Donald Knuth", 4 | "Alan Turing", 5 | "Grace Hopper" 6 | ] 7 | -------------------------------------------------------------------------------- /frontend/src/client/assets/img/about/create.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/about/create.jpg -------------------------------------------------------------------------------- /frontend/src/client/assets/img/about/faq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/about/faq.jpg -------------------------------------------------------------------------------- /frontend/src/client/assets/img/about/my.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/about/my.jpg -------------------------------------------------------------------------------- /frontend/src/client/assets/img/edit/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/edit/checkbox.png -------------------------------------------------------------------------------- /frontend/src/client/assets/img/edit/radio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/edit/radio.png -------------------------------------------------------------------------------- /frontend/src/client/assets/img/edit/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/edit/star.png -------------------------------------------------------------------------------- /frontend/src/client/assets/img/edit/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/edit/text.png -------------------------------------------------------------------------------- /frontend/src/client/assets/img/home/banner_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/home/banner_01.jpg -------------------------------------------------------------------------------- /frontend/src/client/assets/img/home/banner_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/home/banner_02.jpg -------------------------------------------------------------------------------- /frontend/src/client/assets/img/home/banner_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/home/banner_03.jpg -------------------------------------------------------------------------------- /frontend/src/client/assets/img/notfound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/assets/img/notfound.png -------------------------------------------------------------------------------- /frontend/src/client/assets/svg/more.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/client/css/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/css/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /frontend/src/client/css/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/css/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /frontend/src/client/css/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-programming/angular2-questionnaire/c000597bda2635482c5876efb5805fba8a5cae68/frontend/src/client/css/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /frontend/src/client/css/main.css: -------------------------------------------------------------------------------- 1 | /* Reset */ 2 | html, 3 | body, 4 | div { 5 | border: 0; 6 | margin: 0; 7 | padding: 0; 8 | font-family: "Hiragino Sans GB","Microsoft Yahei",arial,\5b8b\4f53,"Helvetica Neue",Helvetica,STHeiTi,sans-serif; 9 | } 10 | 11 | /* Box-sizing border-box */ 12 | * { 13 | box-sizing: border-box; 14 | } 15 | 16 | /* Set up a default font and some padding to provide breathing room */ 17 | body { 18 | font-family: Roboto, "Helvetica Neue", sans-serif; 19 | font-size: 16px; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | } 23 | 24 | p { 25 | font-weight: 400; 26 | letter-spacing: 0.01em; 27 | line-height: 20px; 28 | margin-bottom: 1em; 29 | margin-top: 1em; 30 | } 31 | 32 | ul { 33 | margin: 10px 0 0; 34 | padding: 0 0 0 20px; 35 | } 36 | 37 | li { 38 | font-weight: 400; 39 | margin-top: 4px; 40 | list-style: none; 41 | } 42 | 43 | .btns{ 44 | padding:10px 0; 45 | } 46 | 47 | .pt-20{ 48 | padding-top:20px; 49 | } 50 | 51 | .del-icon{ 52 | padding: 5px 0; 53 | font-size: 1.5em; 54 | cursor: pointer; 55 | } 56 | 57 | .carousel-indicators li,.carousel-indicators .active { 58 | margin-left: 10px !important; 59 | } 60 | 61 | .carousel-control .icon-next,.carousel-control .icon-prev { 62 | font-size: 50px !important; 63 | } -------------------------------------------------------------------------------- /frontend/src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= APP_TITLE %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Loading... 18 | 19 | 23 | 24 | 25 | 26 | 27 | <% if (BUILD_TYPE === 'dev') { %> 28 | 29 | <% } %> 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | <% if (BUILD_TYPE === 'dev') { %> 38 | 45 | <% } %> 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /frontend/src/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "sourceMap": true, 11 | "pretty": true, 12 | "allowUnreachableCode": false, 13 | "allowUnusedLabels": false, 14 | "noImplicitAny": true, 15 | "noImplicitReturns": true, 16 | "noImplicitUseStrict": false, 17 | "noFallthroughCasesInSwitch": true, 18 | "typeRoots": [ 19 | "../../node_modules/@types", 20 | "../../node_modules" 21 | ], 22 | "types": [ 23 | "core-js", 24 | "express", 25 | "jasmine", 26 | "node", 27 | "protractor", 28 | "systemjs" 29 | ] 30 | }, 31 | "exclude": [ 32 | "node_modules", 33 | "dist", 34 | "src" 35 | ], 36 | "compileOnSave": false 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/e2e/specs/about.component.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | describe('About', () => { 2 | 3 | beforeEach(async () => { 4 | return await browser.get('/admin/about'); 5 | }); 6 | 7 | it('should have correct feature heading', () => { 8 | expect(element(by.css('sd-about .panel-heading h4')).getText()).toEqual('常见FAQ'); 9 | }); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/src/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "sourceMap": true, 11 | "pretty": true, 12 | "allowUnreachableCode": false, 13 | "allowUnusedLabels": false, 14 | "noImplicitAny": false, 15 | "noImplicitReturns": true, 16 | "noImplicitUseStrict": false, 17 | "noFallthroughCasesInSwitch": true, 18 | "typeRoots": [ 19 | "../../node_modules/@types", 20 | "../../node_modules" 21 | ], 22 | "types": [ 23 | "express", 24 | "jasmine", 25 | "node", 26 | "protractor", 27 | "systemjs" 28 | ] 29 | }, 30 | "exclude": [ 31 | "desktop", 32 | "nativescript", 33 | "node_modules", 34 | "dist", 35 | "src" 36 | ], 37 | "compileOnSave": false 38 | } 39 | -------------------------------------------------------------------------------- /frontend/test-config.js: -------------------------------------------------------------------------------- 1 | // Load our SystemJS configuration. 2 | System.config({ 3 | baseURL: '/base/' 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /frontend/test-main.js: -------------------------------------------------------------------------------- 1 | if (!Object.hasOwnProperty('name')) { 2 | Object.defineProperty(Function.prototype, 'name', { 3 | get: function () { 4 | var matches = this.toString().match(/^\s*function\s*(\S*)\s*\(/); 5 | var name = matches && matches.length > 1 ? matches[1] : ""; 6 | Object.defineProperty(this, 'name', { value: name }); 7 | return name; 8 | } 9 | }); 10 | } 11 | 12 | // Turn on full stack traces in errors to help debugging 13 | Error.stackTraceLimit = Infinity; 14 | 15 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; 16 | 17 | // Cancel Karma's synchronous start, 18 | // we will call `__karma__.start()` later, once all the specs are loaded. 19 | __karma__.loaded = function () { }; 20 | 21 | Promise.all([ 22 | System.import('@angular/core/testing'), 23 | System.import('@angular/platform-browser-dynamic/testing') 24 | ]).then(function (providers) { 25 | var testing = providers[0]; 26 | var testingBrowser = providers[1]; 27 | 28 | testing.TestBed.initTestEnvironment( 29 | testingBrowser.BrowserDynamicTestingModule, 30 | testingBrowser.platformBrowserDynamicTesting() 31 | ); 32 | }).then(function () { 33 | return Promise.all( 34 | Object.keys(window.__karma__.files) // All files served by Karma. 35 | .filter(onlySpecFiles) 36 | .map(file2moduleName) 37 | .map(function (path) { 38 | return System.import(path).then(function (module) { 39 | if (module.hasOwnProperty('main')) { 40 | module.main(); 41 | } else { 42 | throw new Error('Module ' + path + ' does not implement main() method.'); 43 | } 44 | }); 45 | })); 46 | }) 47 | .then(function () { 48 | __karma__.start(); 49 | }, function (error) { 50 | console.error(error.stack || error); 51 | __karma__.start(); 52 | }); 53 | 54 | function onlySpecFiles(path) { 55 | // check for individual files, if not given, always matches to all 56 | var patternMatched = __karma__.config.files ? 57 | path.match(new RegExp(__karma__.config.files)) : true; 58 | 59 | return patternMatched && /[\.|_]spec\.js$/.test(path); 60 | } 61 | 62 | // Normalize paths to module names. 63 | function file2moduleName(filePath) { 64 | return filePath.replace(/\\/g, '/') 65 | .replace(/^\/base\//, '') 66 | .replace(/\.js$/, ''); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /frontend/tools/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.js.map 3 | 4 | -------------------------------------------------------------------------------- /frontend/tools/config.ts: -------------------------------------------------------------------------------- 1 | import { ProjectConfig } from './config/project.config'; 2 | 3 | const config: ProjectConfig = new ProjectConfig(); 4 | export default config; 5 | -------------------------------------------------------------------------------- /frontend/tools/config/banner.txt: -------------------------------------------------------------------------------- 1 | Welcome to _ _ 2 | __ _ _ __ __ _ _ _| | __ _ _ __ ___ ___ ___ __| | 3 | / _` | '_ \ / _` | | | | |/ _` | '__|____/ __|/ _ \/ _ \/ _` | 4 | | (_| | | | | (_| | |_| | | (_| | | |_____\__ \ __/ __/ (_| | 5 | \__,_|_| |_|\__, |\__,_|_|\__,_|_| |___/\___|\___|\__,_| 6 | |___/ 7 | -------------------------------------------------------------------------------- /frontend/tools/config/project.config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import { SeedConfig } from './seed.config'; 4 | import { ExtendPackages } from './seed.config.interfaces'; 5 | 6 | /** 7 | * This class extends the basic seed configuration, allowing for project specific overrides. A few examples can be found 8 | * below. 9 | */ 10 | export class ProjectConfig extends SeedConfig { 11 | 12 | PROJECT_TASKS_DIR = join(process.cwd(), this.TOOLS_DIR, 'tasks', 'project'); 13 | 14 | constructor() { 15 | super(); 16 | // this.APP_TITLE = 'Put name of your app here'; 17 | this.APP_TITLE = '调查问卷管理系统'; 18 | 19 | /* Enable typeless compiler runs (faster) between typed compiler runs. */ 20 | // this.TYPED_COMPILE_INTERVAL = 5; 21 | 22 | // Add `NPM` third-party libraries to be injected/bundled. 23 | this.NPM_DEPENDENCIES = [ 24 | ...this.NPM_DEPENDENCIES 25 | //{src: 'ng2-bootstrap/bundles/ng2-bootstrap.umd.min.js', inject: 'libs'} 26 | // {src: 'jquery/dist/jquery.min.js', inject: 'libs'}, 27 | // {src: 'lodash/lodash.min.js', inject: 'libs'}, 28 | ]; 29 | 30 | // Add `local` third-party libraries to be injected/bundled. 31 | this.APP_ASSETS = [ 32 | ...this.APP_ASSETS, 33 | {src: `${this.CSS_SRC}/libs/bootstrap.min.css`, inject: true, vendor: false} 34 | // {src: `${this.APP_SRC}/your-path-to-lib/libs/jquery-ui.js`, inject: true, vendor: false} 35 | // {src: `${this.CSS_SRC}/path-to-lib/test-lib.css`, inject: true, vendor: false}, 36 | ]; 37 | 38 | // Add packages (e.g. lodash) 39 | // let additionalPackages: ExtendPackages[] = [{ 40 | // name: 'lodash', 41 | // path: `${this.APP_BASE}node_modules/lodash/lodash.js`, 42 | // packageMeta: { 43 | // main: 'index.js', 44 | // defaultExtension: 'js' 45 | // } 46 | // }]; 47 | // 48 | // or 49 | // 50 | let additionalPackages: ExtendPackages[] = [ 51 | // required for dev build 52 | { 53 | name:'ng2-bootstrap', 54 | path:'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.umd.min.js' 55 | }, 56 | 57 | // required for prod build 58 | { 59 | name:'ng2-bootstrap/*', 60 | path:'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.umd.min.js' 61 | } 62 | ]; 63 | 64 | 65 | this.addPackagesBundles(additionalPackages); 66 | 67 | /* Add to or override NPM module configurations: */ 68 | // this.mergeObject(this.PLUGIN_CONFIGS['browser-sync'], { ghostMode: false }); 69 | this.mergeObject(this.SYSTEM_CONFIG_DEV.paths, { 70 | moment: 'node_modules/moment/moment.js' 71 | }); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /frontend/tools/config/seed.config.interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface InjectableDependency { 2 | src: string; 3 | inject: string | boolean; 4 | vendor?: boolean; 5 | buildType?: string[] | string; 6 | 7 | // @deprecated 8 | env?: string[] | string; 9 | } 10 | 11 | export interface BuildType { 12 | DEVELOPMENT: string; 13 | PRODUCTION: string; 14 | [key: string]: string; 15 | } 16 | 17 | export interface ExtendPackages { 18 | name: string; 19 | path?: string; 20 | packageMeta?: any; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/tools/config/seed.tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["../../node_modules/codelyzer"], 3 | "rules": { 4 | "class-name": true, 5 | "curly": false, 6 | "eofline": false, 7 | "indent": [true, "spaces"], 8 | "max-line-length": [true, 140], 9 | "member-ordering": [true, 10 | "public-before-private", 11 | "static-before-instance", 12 | "variables-before-functions" 13 | ], 14 | "no-arg": true, 15 | "no-construct": true, 16 | "no-duplicate-key": true, 17 | "no-duplicate-variable": true, 18 | "no-empty": true, 19 | "no-eval": true, 20 | "no-trailing-whitespace": false, 21 | "no-unused-expression": true, 22 | "no-unused-variable": true, 23 | "no-unreachable": true, 24 | "no-use-before-declare": true, 25 | "one-line": [true, 26 | "check-open-brace", 27 | "check-catch", 28 | "check-else", 29 | "check-whitespace" 30 | ], 31 | "quotemark": [true, "single"], 32 | "semicolon": [true, "always"], 33 | "trailing-comma": true, 34 | "triple-equals": true, 35 | "variable-name": false, 36 | 37 | "directive-selector-name": [true, "camelCase"], 38 | "component-selector-name": [true, "kebab-case"], 39 | "directive-selector-type": [true, "attribute"], 40 | "component-selector-type": [true, "element"], 41 | "use-input-property-decorator": true, 42 | "use-output-property-decorator": true, 43 | "use-host-property-decorator": true, 44 | "no-input-rename": true, 45 | "no-output-rename": true, 46 | "use-life-cycle-interface": true, 47 | "use-pipe-transform-interface": true, 48 | "component-class-suffix": true, 49 | "directive-class-suffix": true, 50 | "import-destructuring-spacing": true, 51 | "templates-use-public": true, 52 | "no-access-missing-member": true, 53 | "invoke-injectable": true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /frontend/tools/debug.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import { argv } from 'yargs'; 3 | 4 | require('../gulpfile'); 5 | 6 | const TASK = argv['task']; 7 | 8 | if (!TASK) { 9 | throw new Error('You must specify a task name.'); 10 | } 11 | 12 | console.log('**********************'); 13 | console.log('* angular2-seed tools '); 14 | console.log('* debugging task:', TASK); 15 | console.log('**********************'); 16 | 17 | gulp.start(TASK); 18 | -------------------------------------------------------------------------------- /frontend/tools/env/base.ts: -------------------------------------------------------------------------------- 1 | import { EnvConfig } from './env-config.interface'; 2 | 3 | const BaseConfig: EnvConfig = { 4 | // Sample API url 5 | API: 'https://demo.com' 6 | }; 7 | 8 | export = BaseConfig; 9 | 10 | -------------------------------------------------------------------------------- /frontend/tools/env/dev.ts: -------------------------------------------------------------------------------- 1 | import { EnvConfig } from './env-config.interface'; 2 | 3 | const DevConfig: EnvConfig = { 4 | ENV: 'DEV' 5 | }; 6 | 7 | export = DevConfig; 8 | 9 | -------------------------------------------------------------------------------- /frontend/tools/env/env-config.interface.ts: -------------------------------------------------------------------------------- 1 | export { EnvConfig } from '../../src/client/app/shared/config/env.config'; 2 | 3 | -------------------------------------------------------------------------------- /frontend/tools/env/prod.ts: -------------------------------------------------------------------------------- 1 | import { EnvConfig } from './env-config.interface'; 2 | 3 | const ProdConfig: EnvConfig = { 4 | ENV: 'PROD' 5 | }; 6 | 7 | export = ProdConfig; 8 | 9 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/project/sample.package.d.ts: -------------------------------------------------------------------------------- 1 | // declare module "moment/moment" { 2 | // export = moment; 3 | // } 4 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/autoprefixer.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'autoprefixer' { 2 | 3 | interface IOptions { 4 | browsers: string[]; 5 | } 6 | 7 | interface IAutoprefixer { 8 | (opts?: IOptions): NodeJS.ReadWriteStream; 9 | } 10 | 11 | const autoprefixer: IAutoprefixer; 12 | export = autoprefixer; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/cssnano.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'cssnano' { 2 | 3 | interface IOptions { 4 | discardComments?: { 5 | removeAll: boolean; 6 | }; 7 | discardUnused?: boolean; 8 | zindex?: boolean; 9 | reduceIdents?: boolean; 10 | } 11 | 12 | interface ICssnano { 13 | (opts?: IOptions): NodeJS.ReadWriteStream; 14 | } 15 | 16 | const cssnano: ICssnano; 17 | export = cssnano; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/express-history-api-fallback.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'express-history-api-fallback' { 2 | 3 | import { RequestHandler } from 'express'; 4 | 5 | interface IOptions { 6 | maxAge?: number; 7 | root?: string; 8 | lastModified?: number; 9 | headers?: { [key: string]: string; }; 10 | dotfiles?: boolean; 11 | } 12 | 13 | function fallback(index: string, options?: IOptions): RequestHandler; 14 | 15 | module fallback {} 16 | 17 | export = fallback; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/istream.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'isstream' { 2 | function istream(stream: any): boolean; 3 | interface Istream { 4 | isReadable(stream: any): boolean; 5 | isWritable(stream: any): boolean; 6 | isDuplex(stream: any): boolean; 7 | } 8 | module istream {} 9 | export = istream; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/karma.d.ts: -------------------------------------------------------------------------------- 1 | // Use this minimalistic definition file as bluebird dependency 2 | // generate a lot of errors. 3 | 4 | declare module 'karma' { 5 | var karma: IKarma; 6 | export = karma; 7 | interface IKarma { 8 | server: { 9 | start(options: any, callback: Function): void 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/merge-stream.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'merge-stream' { 2 | function mergeStream(...streams: NodeJS.ReadWriteStream[]): MergeStream; 3 | interface MergeStream extends NodeJS.ReadWriteStream { 4 | add(stream: NodeJS.ReadWriteStream): MergeStream; 5 | } 6 | module mergeStream {} 7 | export = mergeStream; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/open.d.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/borisyankov/DefinitelyTyped/tree/master/open 2 | // Does not support ES2015 import (import * as open from 'open'). 3 | 4 | declare module 'open' { 5 | function open(target: string, app?: string): void; 6 | module open {} 7 | export = open; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/operators.d.ts: -------------------------------------------------------------------------------- 1 | import '../../../src/client/app/operators'; 2 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/slash.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'slash' { 2 | function slash(path: string): string; 3 | module slash {} 4 | export = slash; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/systemjs-builder.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'systemjs-builder' { 2 | class Builder { 3 | constructor(configObject?: any, baseUrl?: string, configPath?: string); 4 | bundle(source: string, target: string, options?: any): Promise; 5 | buildStatic(source: string, target: string, options?: any): Promise; 6 | } 7 | 8 | module Builder {} 9 | export = Builder; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/tools/manual_typings/seed/tildify.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'tildify' { 2 | function tildify(path: string): string; 3 | module tildify {} 4 | export = tildify; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/tools/tasks/assets_task.ts: -------------------------------------------------------------------------------- 1 | import { Task } from './task'; 2 | 3 | export abstract class AssetsTask extends Task { 4 | shallRun(files: String[]) { 5 | return files.reduce((a, f) => { 6 | return a || (!f.endsWith('.css') && !f.endsWith('.sass') && 7 | !f.endsWith('.scss') && !f.endsWith('.ts')); 8 | }, false); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/tools/tasks/css_task.ts: -------------------------------------------------------------------------------- 1 | import { Task } from './task'; 2 | import Config from '../config'; 3 | 4 | export abstract class CssTask extends Task { 5 | 6 | shallRun(files: String[]) { 7 | return Config.ENABLE_SCSS || files.some(f => 8 | f.endsWith('.css') || f.endsWith('.sass') || f.endsWith('.scss')); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /frontend/tools/tasks/project/sample.task.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import { join } from 'path'; 3 | 4 | import Config from '../../config'; 5 | 6 | /** 7 | * This sample task copies all TypeScript files over to the appropiate `dist/dev|prod|test` directory, depending on the 8 | * current application environment. 9 | */ 10 | export = () => { 11 | return gulp.src(join(Config.APP_SRC, '**/*.ts')) 12 | .pipe(gulp.dest(Config.APP_DEST)); 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.assets.dev.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import { join } from 'path'; 3 | 4 | import { AssetsTask } from '../assets_task'; 5 | import Config from '../../config'; 6 | 7 | /** 8 | * Executes the build process, copying the assets located in `src/client` over to the appropriate 9 | * `dist/dev` directory. 10 | */ 11 | export = 12 | class BuildAssetsTask extends AssetsTask { 13 | run() { 14 | let paths: string[] = [ 15 | join(Config.APP_SRC, '**'), 16 | '!' + join(Config.APP_SRC, '**', '*.ts'), 17 | '!' + join(Config.APP_SRC, '**', '*.scss'), 18 | '!' + join(Config.APP_SRC, '**', '*.sass') 19 | ].concat(Config.TEMP_FILES.map((p) => { return '!' + p; })); 20 | 21 | return gulp.src(paths) 22 | .pipe(gulp.dest(Config.APP_DEST)); 23 | } 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.assets.prod.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import { join } from 'path'; 3 | 4 | import Config from '../../config'; 5 | 6 | // TODO There should be more elegant to prevent empty directories from copying 7 | var onlyDirs = function (es: any) { 8 | return es.map(function (file: any, cb: any) { 9 | if (file.stat.isFile()) { 10 | return cb(null, file); 11 | } else { 12 | return cb(); 13 | } 14 | }); 15 | }; 16 | 17 | /** 18 | * Executes the build process, copying the assets located in `src/client` over to the appropriate 19 | * `dist/prod` directory. 20 | */ 21 | export = () => { 22 | let es: any = require('event-stream'); 23 | return gulp.src([ 24 | join(Config.APP_SRC, '**'), 25 | '!' + join(Config.APP_SRC, 'tsconfig.json'), 26 | '!' + join(Config.APP_SRC, '**', '*.ts'), 27 | '!' + join(Config.APP_SRC, '**', '*.css'), 28 | '!' + join(Config.APP_SRC, '**', '*.html'), 29 | '!' + join(Config.APP_SRC, '**', '*.scss'), 30 | '!' + join(Config.APP_SRC, '**', '*.sass'), 31 | '!' + join(Config.ASSETS_SRC, '**', '*.js') 32 | ].concat(Config.TEMP_FILES.map((p) => { return '!' + p; }))) 33 | .pipe(onlyDirs(es)) 34 | .pipe(gulp.dest(Config.APP_DEST)); 35 | }; 36 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.bundle.rxjs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Temporary fix. See https://github.com/angular/angular/issues/9359 3 | */ 4 | 5 | const Builder = require('systemjs-builder'); 6 | 7 | export = (done: any) => { 8 | const options = { 9 | normalize: true, 10 | runtime: false, 11 | sourceMaps: true, 12 | sourceMapContents: true, 13 | minify: true, 14 | mangle: false 15 | }; 16 | var builder = new Builder('./'); 17 | builder.config({ 18 | paths: { 19 | 'n:*': 'node_modules/*', 20 | 'rxjs/*': 'node_modules/rxjs/*.js', 21 | }, 22 | map: { 23 | 'rxjs': 'n:rxjs', 24 | }, 25 | packages: { 26 | 'rxjs': {main: 'Rx.js', defaultExtension: 'js'}, 27 | } 28 | }); 29 | builder.bundle('rxjs', 'node_modules/.tmp/Rx.min.js', options) 30 | .then(() => done()) 31 | .catch((error:any) => done(error)); 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.bundles.app.exp.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import * as Builder from 'systemjs-builder'; 3 | 4 | import Config from '../../config'; 5 | 6 | const BUNDLER_OPTIONS = { 7 | format: 'cjs', 8 | minify: true, 9 | mangle: false 10 | }; 11 | 12 | /** 13 | * Executes the build process, bundling the JavaScript files using the SystemJS builder. 14 | */ 15 | export = (done: any) => { 16 | let builder = new Builder(Config.SYSTEM_BUILDER_CONFIG); 17 | builder 18 | .buildStatic(join(Config.TMP_DIR, Config.BOOTSTRAP_FACTORY_PROD_MODULE), 19 | join(Config.JS_DEST, Config.JS_PROD_APP_BUNDLE), 20 | BUNDLER_OPTIONS) 21 | .then(() => done()) 22 | .catch((err: any) => done(err)); 23 | }; 24 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.bundles.app.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import * as Builder from 'systemjs-builder'; 3 | 4 | import Config from '../../config'; 5 | 6 | const BUNDLER_OPTIONS = { 7 | format: 'cjs', 8 | minify: true, 9 | mangle: false 10 | }; 11 | 12 | /** 13 | * Executes the build process, bundling the JavaScript files using the SystemJS builder. 14 | */ 15 | export = (done: any) => { 16 | let builder = new Builder(Config.SYSTEM_BUILDER_CONFIG); 17 | builder 18 | .buildStatic(join(Config.TMP_DIR, Config.BOOTSTRAP_PROD_MODULE), 19 | join(Config.JS_DEST, Config.JS_PROD_APP_BUNDLE), 20 | BUNDLER_OPTIONS) 21 | .then(() => done()) 22 | .catch((err: any) => done(err)); 23 | }; 24 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.bundles.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import * as merge from 'merge-stream'; 4 | 5 | import Config from '../../config'; 6 | 7 | const plugins = gulpLoadPlugins(); 8 | 9 | /** 10 | * Executes the build process, bundling the shim files. 11 | */ 12 | export = () => merge(bundleShims()); 13 | 14 | /** 15 | * Returns the shim files to be injected. 16 | */ 17 | function getShims() { 18 | let libs = Config.DEPENDENCIES 19 | .filter(d => /\.js$/.test(d.src)); 20 | 21 | return libs.filter(l => l.inject === 'shims') 22 | .concat(libs.filter(l => l.inject === 'libs')) 23 | .concat(libs.filter(l => l.inject === true)) 24 | .map(l => l.src); 25 | } 26 | 27 | /** 28 | * Bundles the shim files. 29 | */ 30 | function bundleShims() { 31 | return gulp.src(getShims()) 32 | .pipe(plugins.concat(Config.JS_PROD_SHIMS_BUNDLE)) 33 | // Strip the first (global) 'use strict' added by reflect-metadata, but don't strip any others to avoid unintended scope leaks. 34 | .pipe(plugins.replace(/('|")use strict\1;var Reflect;/, 'var Reflect;')) 35 | .pipe(gulp.dest(Config.JS_DEST)); 36 | } 37 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.docs.ts: -------------------------------------------------------------------------------- 1 | // import * as gulp from 'gulp'; 2 | // import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | // import { join } from 'path'; 4 | // 5 | // import { APP_SRC, APP_TITLE, DOCS_DEST } from '../../config'; 6 | // 7 | // const plugins = gulpLoadPlugins(); 8 | // 9 | // /** 10 | // * Executes the build process, building the documentation for the TypeScript 11 | // * files (excluding spec and e2e-spec files) using `typedoc`. 12 | // */ 13 | // export = () => { 14 | // 15 | // let src = [ 16 | // 'typings/index.d.ts', 17 | // join(APP_SRC, '**/*.ts'), 18 | // '!' + join(APP_SRC, '**/*.spec.ts'), 19 | // '!' + join(APP_SRC, '**/*.e2e-spec.ts') 20 | // ]; 21 | // 22 | // return gulp.src(src) 23 | // .pipe(plugins.typedoc({ 24 | // // TypeScript options (see typescript docs) 25 | // module: 'commonjs', 26 | // target: 'es5', 27 | // // excludeExternals: true, 28 | // includeDeclarations: true, 29 | // // Output options (see typedoc docs) 30 | // out: DOCS_DEST, 31 | // json: join(DOCS_DEST, 'data/docs.json'), 32 | // name: APP_TITLE, 33 | // ignoreCompilerErrors: false, 34 | // experimentalDecorators: true, 35 | // version: true 36 | // })); 37 | // }; 38 | 39 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.index.dev.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import { join } from 'path'; 4 | import * as slash from 'slash'; 5 | 6 | import Config from '../../config'; 7 | import { TemplateLocalsBuilder } from '../../utils'; 8 | 9 | const plugins = gulpLoadPlugins(); 10 | 11 | 12 | /** 13 | * Executes the build process, injecting the shims and libs into the `index.hml` for the development environment. 14 | */ 15 | export = () => { 16 | return gulp.src(join(Config.APP_SRC, 'index.html')) 17 | .pipe(inject('shims')) 18 | .pipe(inject('libs')) 19 | .pipe(inject()) 20 | .pipe(plugins.template(new TemplateLocalsBuilder().wihtoutStringifiedEnvConfig().build())) 21 | .pipe(gulp.dest(Config.APP_DEST)); 22 | }; 23 | 24 | /** 25 | * Injects the file with the given name. 26 | * @param {string} name - The file to be injected. 27 | */ 28 | function inject(name?: string) { 29 | return plugins.inject(gulp.src(getInjectablesDependenciesRef(name), { read: false }), { 30 | name, 31 | transform: transformPath() 32 | }); 33 | } 34 | 35 | /** 36 | * Returns the injectable dependency, mapping its filename to its path. 37 | * @param {string} name - The dependency to be mapped. 38 | */ 39 | function getInjectablesDependenciesRef(name?: string) { 40 | return Config.DEPENDENCIES 41 | .filter(dep => dep['inject'] && dep['inject'] === (name || true)) 42 | .map(mapPath); 43 | } 44 | 45 | /** 46 | * Maps the path of the given dependency to its path according to the applications environment. 47 | * @param {any} dep - The dependency to be mapped. 48 | */ 49 | function mapPath(dep: any) { 50 | let envPath = dep.src; 51 | if (envPath.startsWith(Config.APP_SRC) && !envPath.endsWith('.scss')) { 52 | envPath = join(Config.APP_DEST, envPath.replace(Config.APP_SRC, '')); 53 | } else if (envPath.startsWith(Config.APP_SRC) && envPath.endsWith('.scss')) { 54 | envPath = envPath.replace(Config.ASSETS_SRC, Config.CSS_DEST).replace('.scss', '.css'); 55 | } 56 | return envPath; 57 | } 58 | 59 | /** 60 | * Transform the path of a dependency to its location within the `dist` directory according to the applications 61 | * environment. 62 | */ 63 | function transformPath() { 64 | return function (filepath: string) { 65 | if (filepath.startsWith(`/${Config.APP_DEST}`)) { 66 | filepath = filepath.replace(`/${Config.APP_DEST}`, ''); 67 | } 68 | arguments[0] = join(Config.APP_BASE, filepath) + `?${Date.now()}`; 69 | return slash(plugins.inject.transform.apply(plugins.inject.transform, arguments)); 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.index.prod.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import { join, sep, normalize } from 'path'; 4 | import * as slash from 'slash'; 5 | 6 | import Config from '../../config'; 7 | import { TemplateLocalsBuilder } from '../../utils'; 8 | 9 | const plugins = gulpLoadPlugins(); 10 | 11 | /** 12 | * Executes the build process, injecting the JavaScript and CSS dependencies into the `index.html` for the production 13 | * environment. 14 | */ 15 | export = () => { 16 | return gulp.src(join(Config.APP_SRC, 'index.html')) 17 | .pipe(injectJs()) 18 | .pipe(injectCss()) 19 | .pipe(plugins.template(new TemplateLocalsBuilder().wihtoutStringifiedEnvConfig().build())) 20 | .pipe(gulp.dest(Config.APP_DEST)); 21 | }; 22 | 23 | /** 24 | * Injects the given file array and transforms the path of the files. 25 | * @param {Array} files - The files to be injected. 26 | */ 27 | function inject(...files: Array) { 28 | return plugins.inject(gulp.src(files, { read: false }), { 29 | files, 30 | transform: transformPath() 31 | }); 32 | } 33 | 34 | /** 35 | * Injects the bundled JavaScript shims and application bundles for the production environment. 36 | */ 37 | function injectJs() { 38 | return inject(join(Config.JS_DEST, Config.JS_PROD_SHIMS_BUNDLE), join(Config.JS_DEST, Config.JS_PROD_APP_BUNDLE)); 39 | } 40 | 41 | /** 42 | * Injects the bundled CSS files for the production environment. 43 | */ 44 | function injectCss() { 45 | return inject(join(Config.CSS_DEST, Config.CSS_PROD_BUNDLE)); 46 | } 47 | 48 | /** 49 | * Transform the path of a dependency to its location within the `dist` directory according to the applications 50 | * environment. 51 | */ 52 | function transformPath() { 53 | return function(filepath: string) { 54 | let path: Array = normalize(filepath).split(sep); 55 | let slice_after = path.indexOf(Config.APP_DEST); 56 | if (slice_after > -1) { 57 | slice_after++; 58 | } else { 59 | slice_after = 3; 60 | } 61 | arguments[0] = Config.APP_BASE + path.slice(slice_after, path.length).join(sep) + `?${Date.now()}`; 62 | return slash(plugins.inject.transform.apply(plugins.inject.transform, arguments)); 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.js.dev.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import * as merge from 'merge-stream'; 4 | import * as util from 'gulp-util'; 5 | import { join/*, sep, relative*/ } from 'path'; 6 | 7 | import Config from '../../config'; 8 | import { makeTsProject, TemplateLocalsBuilder } from '../../utils'; 9 | import { TypeScriptTask } from '../typescript_task'; 10 | 11 | const plugins = gulpLoadPlugins(); 12 | 13 | let typedBuildCounter = Config.TYPED_COMPILE_INTERVAL; // Always start with the typed build. 14 | 15 | /** 16 | * Executes the build process, transpiling the TypeScript files (except the spec and e2e-spec files) for the development 17 | * environment. 18 | */ 19 | export = 20 | class BuildJsDev extends TypeScriptTask { 21 | run() { 22 | let tsProject: any; 23 | let typings = gulp.src([ 24 | Config.TOOLS_DIR + '/manual_typings/**/*.d.ts' 25 | ]); 26 | let src = [ 27 | join(Config.APP_SRC, '**/*.ts'), 28 | '!' + join(Config.APP_SRC, '**/*.spec.ts'), 29 | '!' + join(Config.APP_SRC, '**/*.e2e-spec.ts'), 30 | '!' + join(Config.APP_SRC, `**/${Config.NG_FACTORY_FILE}.ts`) 31 | ]; 32 | 33 | let projectFiles = gulp.src(src); 34 | let result: any; 35 | let isFullCompile = true; 36 | 37 | // Only do a typed build every X builds, otherwise do a typeless build to speed things up 38 | if (typedBuildCounter < Config.TYPED_COMPILE_INTERVAL) { 39 | isFullCompile = false; 40 | tsProject = makeTsProject({isolatedModules: true}); 41 | projectFiles = projectFiles.pipe(plugins.cached()); 42 | util.log('Performing typeless TypeScript compile.'); 43 | } else { 44 | tsProject = makeTsProject(); 45 | projectFiles = merge(typings, projectFiles); 46 | } 47 | 48 | result = projectFiles 49 | .pipe(plugins.plumber()) 50 | .pipe(plugins.sourcemaps.init()) 51 | .pipe(tsProject()) 52 | .on('error', () => { 53 | typedBuildCounter = Config.TYPED_COMPILE_INTERVAL; 54 | }); 55 | 56 | if (isFullCompile) { 57 | typedBuildCounter = 0; 58 | } else { 59 | typedBuildCounter++; 60 | } 61 | 62 | return result.js 63 | .pipe(plugins.sourcemaps.write()) 64 | // Use for debugging with Webstorm/IntelliJ 65 | // https://github.com/mgechev/angular2-seed/issues/1220 66 | // .pipe(plugins.sourcemaps.write('.', { 67 | // includeContent: false, 68 | // sourceRoot: (file: any) => 69 | // relative(file.path, PROJECT_ROOT + '/' + APP_SRC).replace(sep, '/') + '/' + APP_SRC 70 | // })) 71 | .pipe(plugins.template(new TemplateLocalsBuilder().withStringifiedSystemConfigDev().build())) 72 | .pipe(gulp.dest(Config.APP_DEST)); 73 | } 74 | }; 75 | 76 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.js.e2e.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import { join } from 'path'; 4 | 5 | import Config from '../../config'; 6 | import { makeTsProject, TemplateLocalsBuilder } from '../../utils'; 7 | 8 | const plugins = gulpLoadPlugins(); 9 | 10 | /** 11 | * Executes the build process, transpiling the TypeScript files (including the e2e-spec files, excluding the spec files) 12 | * for the e2e environment. 13 | */ 14 | export = () => { 15 | let tsProject = makeTsProject({ 'target': 'es2015' }, Config.E2E_SRC); 16 | let src = [ 17 | Config.TOOLS_DIR + '/manual_typings/**/*.d.ts', 18 | join(Config.E2E_SRC, '**/*.ts') 19 | ]; 20 | let result = gulp.src(src) 21 | .pipe(plugins.plumber()) 22 | .pipe(plugins.sourcemaps.init()) 23 | .pipe(tsProject()); 24 | 25 | return result.js 26 | .pipe(plugins.sourcemaps.write()) 27 | .pipe(plugins.template(new TemplateLocalsBuilder().withStringifiedSystemConfigDev().build())) 28 | .pipe(gulp.dest(Config.E2E_DEST)); 29 | }; 30 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.js.prod.exp.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync, lstatSync } from 'fs'; 2 | import * as gulp from 'gulp'; 3 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 4 | import { join } from 'path'; 5 | 6 | import Config from '../../config'; 7 | import { makeTsProject, TemplateLocalsBuilder } from '../../utils'; 8 | 9 | const plugins = gulpLoadPlugins(); 10 | 11 | /** 12 | * Executes the build process, transpiling the TypeScript files for the production environment. 13 | */ 14 | 15 | export = () => { 16 | let tsProject = makeTsProject({}, Config.TMP_DIR); 17 | let toIgnore = readdirSync(Config.TMP_DIR).filter((f: string) => 18 | lstatSync(join(Config.TMP_DIR, f)).isDirectory() && f !== Config.BOOTSTRAP_DIR) 19 | .map((f: string) => '!' + join(Config.TMP_DIR, f, Config.NG_FACTORY_FILE + '.ts')); 20 | 21 | let src = [ 22 | Config.TOOLS_DIR + '/manual_typings/**/*.d.ts', 23 | join(Config.TMP_DIR, '**/*.ts'), 24 | join(Config.TMP_DIR, `${Config.BOOTSTRAP_FACTORY_PROD_MODULE}.ts`), 25 | ...toIgnore 26 | ]; 27 | let result = gulp.src(src) 28 | .pipe(plugins.plumber()) 29 | .pipe(tsProject()) 30 | .once('error', function(e: any) { 31 | this.once('finish', () => process.exit(1)); 32 | }); 33 | 34 | return result.js 35 | .pipe(plugins.template(new TemplateLocalsBuilder().build())) 36 | .pipe(gulp.dest(Config.TMP_DIR)) 37 | .on('error', (e: any) => { 38 | console.log(e); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.js.prod.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import { join } from 'path'; 4 | 5 | import Config from '../../config'; 6 | import { makeTsProject, TemplateLocalsBuilder } from '../../utils'; 7 | 8 | const plugins = gulpLoadPlugins(); 9 | 10 | const INLINE_OPTIONS = { 11 | base: Config.TMP_DIR, 12 | target: 'es5', 13 | useRelativePaths: true, 14 | removeLineBreaks: true 15 | }; 16 | 17 | /** 18 | * Executes the build process, transpiling the TypeScript files for the production environment. 19 | */ 20 | 21 | export = () => { 22 | let tsProject = makeTsProject({}, Config.TMP_DIR); 23 | let src = [ 24 | Config.TOOLS_DIR + '/manual_typings/**/*.d.ts', 25 | join(Config.TMP_DIR, '**/*.ts'), 26 | '!' + join(Config.TMP_DIR, `**/${Config.NG_FACTORY_FILE}.ts`) 27 | ]; 28 | let result = gulp.src(src) 29 | .pipe(plugins.plumber()) 30 | .pipe(plugins.inlineNg2Template(INLINE_OPTIONS)) 31 | .pipe(tsProject()) 32 | .once('error', function(e: any) { 33 | this.once('finish', () => process.exit(1)); 34 | }); 35 | 36 | 37 | return result.js 38 | .pipe(plugins.template(new TemplateLocalsBuilder().build())) 39 | .pipe(gulp.dest(Config.TMP_DIR)) 40 | .on('error', (e: any) => { 41 | console.log(e); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.js.test.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import * as merge from 'merge-stream'; 4 | import * as util from 'gulp-util'; 5 | import { join } from 'path'; 6 | 7 | import Config from '../../config'; 8 | import { makeTsProject } from '../../utils'; 9 | import { TypeScriptTask } from '../typescript_task'; 10 | 11 | const plugins = gulpLoadPlugins(); 12 | 13 | let typedBuildCounter = Config.TYPED_COMPILE_INTERVAL; // Always start with the typed build. 14 | 15 | /** 16 | * Executes the build process, transpiling the TypeScript files (excluding the spec and e2e-spec files) for the test 17 | * environment. 18 | */ 19 | export = 20 | class BuildJsTest extends TypeScriptTask { 21 | run() { 22 | let tsProject: any; 23 | let typings = gulp.src( [ 24 | Config.TOOLS_DIR + '/manual_typings/**/*.d.ts' 25 | ] ); 26 | let src = [ 27 | join(Config.APP_SRC, '**/*.spec.ts') 28 | ]; 29 | 30 | let projectFiles = gulp.src(src); 31 | let result: any; 32 | let isFullCompile = true; 33 | 34 | // Only do a typed build every X builds, otherwise do a typeless build to speed things up 35 | if (typedBuildCounter < Config.TYPED_COMPILE_INTERVAL) { 36 | isFullCompile = false; 37 | tsProject = makeTsProject({isolatedModules: true}); 38 | projectFiles = projectFiles.pipe(plugins.cached()); 39 | util.log('Performing typeless TypeScript compile of specs.'); 40 | } else { 41 | tsProject = makeTsProject(); 42 | projectFiles = merge(typings, projectFiles); 43 | } 44 | 45 | //noinspection TypeScriptUnresolvedFunction 46 | result = projectFiles 47 | .pipe(plugins.plumber()) 48 | .pipe(plugins.sourcemaps.init()) 49 | .pipe(tsProject()) 50 | .on('error', () => { 51 | typedBuildCounter = Config.TYPED_COMPILE_INTERVAL; 52 | }); 53 | 54 | if (isFullCompile) { 55 | typedBuildCounter = 0; 56 | } else { 57 | typedBuildCounter++; 58 | } 59 | 60 | return result.js 61 | .pipe(plugins.sourcemaps.write()) 62 | // Use for debugging with Webstorm/IntelliJ 63 | // https://github.com/mgechev/angular2-seed/issues/1220 64 | // .pipe(plugins.sourcemaps.write('.', { 65 | // includeContent: false, 66 | // sourceRoot: (file: any) => 67 | // relative(file.path, PROJECT_ROOT + '/' + APP_SRC).replace(sep, '/') + '/' + APP_SRC 68 | // })) 69 | .pipe(gulp.dest(Config.APP_DEST)); 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/build.tools.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import { join } from 'path'; 4 | 5 | import Config from '../../config'; 6 | import { makeTsProject, TemplateLocalsBuilder } from '../../utils'; 7 | 8 | const plugins = gulpLoadPlugins(); 9 | 10 | /** 11 | * Executes the build process, transpiling the TypeScript files within the `tools` directory. 12 | */ 13 | export = () => { 14 | 15 | let tsProject = makeTsProject(); 16 | 17 | let src = [ 18 | join(Config.PROJECT_ROOT, 'gulpfile.ts'), 19 | join(Config.TOOLS_DIR, 'manual_typings/**/*.d.ts'), 20 | join(Config.TOOLS_DIR, '**/*.ts') 21 | ]; 22 | let result = gulp.src(src, { base: './' }) 23 | .pipe(plugins.plumber()) 24 | .pipe(plugins.sourcemaps.init()) 25 | .pipe(tsProject()); 26 | 27 | return result.js 28 | .pipe(plugins.template(new TemplateLocalsBuilder().build())) 29 | .pipe(plugins.sourcemaps.write()) 30 | .pipe(gulp.dest('./')); 31 | }; 32 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/check.tools.ts: -------------------------------------------------------------------------------- 1 | import * as util from 'gulp-util'; 2 | import { join } from 'path'; 3 | 4 | let fs = require('fs'); 5 | 6 | 7 | // if gulpfile.ts has been compiled then we need to rebuild the toolchain 8 | 9 | export = (done: any) => { 10 | 11 | let checkFile = join(process.cwd(), 'tools', 'config.js'); 12 | 13 | // need to require the build.toolchain task as it won't be able to run after we run clear.files 14 | let buildTools = require('./build.tools'); 15 | let cleanTools = require('./clean.tools'); 16 | 17 | let rebuild = false; 18 | 19 | try { 20 | fs.accessSync(checkFile, fs.F_OK); 21 | util.log('Gulpfile has previously been compiled, rebuilding toolchain'); 22 | rebuild = true; 23 | 24 | } catch (e) { 25 | util.log('Tools not compiled, skipping rebuild'); 26 | done(); 27 | } 28 | 29 | // continue here to prevent other errors being caught... 30 | if (rebuild) { 31 | util.log('Running \'clean.tools\' from check.tools'); 32 | cleanTools(); 33 | 34 | util.log('Running \'build.tools\' from check.tools'); 35 | let build = buildTools(); 36 | 37 | build.on('end', done); 38 | 39 | } 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/check.versions.ts: -------------------------------------------------------------------------------- 1 | import * as util from 'gulp-util'; 2 | import Config from '../../config'; 3 | 4 | function reportError(message: string) { 5 | console.error(util.colors.white.bgRed.bold(message)); 6 | process.exit(1); 7 | } 8 | 9 | /** 10 | * Executes the build process, verifying that the installed NodeJS and NPM version matches the required versions as 11 | * defined in the application configuration. 12 | */ 13 | export = () => { 14 | let exec = require('child_process').exec; 15 | let semver = require('semver'); 16 | 17 | exec('npm --version', 18 | function(error: Error, stdout: NodeBuffer, stderr: NodeBuffer) { 19 | if (error !== null) { 20 | reportError('npm preinstall error: ' + error + stderr); 21 | } 22 | 23 | if (!semver.gte(stdout, Config.VERSION_NPM)) { 24 | reportError('NPM is not in required version! Required is ' + Config.VERSION_NPM + ' and you\'re using ' + stdout); 25 | } 26 | }); 27 | 28 | exec('node --version', 29 | function(error: Error, stdout: NodeBuffer, stderr: NodeBuffer) { 30 | if (error !== null) { 31 | reportError('npm preinstall error: ' + error + stderr); 32 | } 33 | 34 | if (!semver.gte(stdout, Config.VERSION_NODE)) { 35 | reportError('NODE is not in required version! Required is ' + Config.VERSION_NODE + ' and you\'re using ' + stdout); 36 | } 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/clean.all.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config'; 2 | import { clean } from '../../utils'; 3 | 4 | /** 5 | * Executes the build process, cleaning all files within the `/dist` directory. 6 | */ 7 | export = clean([Config.DIST_DIR, Config.COVERAGE_DIR]); 8 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/clean.coverage.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config'; 2 | import { clean } from '../../utils'; 3 | 4 | /** 5 | * Executes the build process, cleaning all files within the `/dist/dev` directory. 6 | */ 7 | export = clean([Config.COVERAGE_DIR, Config.COVERAGE_TS_DIR]); 8 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/clean.dev.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config'; 2 | import { clean } from '../../utils'; 3 | 4 | /** 5 | * Executes the build process, cleaning all files within the `/dist/dev` directory. 6 | */ 7 | export = clean(Config.DEV_DEST); 8 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/clean.e2e.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config'; 2 | import { clean } from '../../utils'; 3 | 4 | /** 5 | * Executes the build process, cleaning all files within the `/dist/dev` directory. 6 | */ 7 | export = clean(Config.E2E_DEST); 8 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/clean.prod.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config'; 2 | import { clean } from '../../utils'; 3 | 4 | /** 5 | * Executes the build process, cleaning all files within the `/dist/dev` and `dist/tmp` directory. 6 | */ 7 | export = clean([Config.PROD_DEST, Config.TMP_DIR]); 8 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/clean.tools.ts: -------------------------------------------------------------------------------- 1 | import * as rimraf from 'rimraf'; 2 | import { join } from 'path'; 3 | 4 | import Config from '../../config'; 5 | 6 | /** 7 | * Removes all the js, js.map from the tools directories 8 | * 9 | * NB: this needs to remain syncronus, or check.tools will 10 | * need to be updated to handle the returned promise/stream 11 | * 12 | */ 13 | export = () => { 14 | let source = [ 15 | 'gulpfile.js', 16 | 'gulpfile.js.map', 17 | join(Config.TOOLS_DIR, '**/*.js'), 18 | join(Config.TOOLS_DIR, '**/*.js.map'), 19 | ]; 20 | 21 | return source.forEach(p => rimraf.sync(p)); 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/clear.files.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import * as rimraf from 'rimraf'; 3 | 4 | import Config from '../../config'; 5 | 6 | /** 7 | * Removes all the js, js.map and metadata.json from the src and tools directories 8 | */ 9 | export = () => { 10 | let source = [ 11 | 'gulpfile.js', 12 | 'gulpfile.js.map', 13 | join(Config.TOOLS_DIR, '**/*.js'), 14 | join(Config.TOOLS_DIR, '**/*.js.map'), 15 | join(Config.TOOLS_DIR, '**/*.metadata.json'), 16 | join(Config.APP_SRC, '**/*.js'), 17 | join(Config.APP_SRC, '**/*.js.map'), 18 | join(Config.APP_SRC, '**/*.metadata.json') 19 | ]; 20 | 21 | return source.forEach(p => rimraf.sync(p)); 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/compile.ahead.prod.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import * as ts from 'typescript'; 3 | import { argv } from 'yargs'; 4 | import { join } from 'path'; 5 | import { writeFileSync, readFileSync } from 'fs'; 6 | import { CodeGenerator, AngularCompilerOptions, NgcCliOptions, main } from '@angular/compiler-cli'; 7 | 8 | import Config from '../../config'; 9 | 10 | function codegen( 11 | ngOptions: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, 12 | host: ts.CompilerHost) { 13 | return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({ transitiveModules: true }); 14 | } 15 | 16 | const modifyFile = (path: string, mod: any = (f: string) => f) => { 17 | const file = readFileSync(path); 18 | writeFileSync(path, mod(file.toString())); 19 | }; 20 | 21 | export = (done: any) => { 22 | // Note: dirty hack until we're able to set config easier 23 | modifyFile(join(Config.TMP_DIR, 'tsconfig.json'), (content: string) => { 24 | const parsed = JSON.parse(content); 25 | parsed.files = parsed.files || []; 26 | parsed.files.push(join(Config.BOOTSTRAP_DIR, 'main.ts')); 27 | return JSON.stringify(parsed, null, 2); 28 | }); 29 | const args = argv; 30 | 31 | // If a translation, tell the compiler 32 | if (args.lang) { 33 | args['i18nFile'] = `./src/client/assets/locale/messages.${args.lang}.xlf`; 34 | args['locale'] = args.lang; 35 | args['i18nFormat'] = 'xlf'; 36 | } 37 | 38 | const cliOptions = new NgcCliOptions(args); 39 | main(Config.TMP_DIR, cliOptions, codegen) 40 | .then(done) 41 | .catch(e => { 42 | console.error(e.stack); 43 | console.error('Compilation failed'); 44 | process.exit(1); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/copy.prod.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import { join } from 'path'; 3 | 4 | import Config from '../../config'; 5 | 6 | /** 7 | * Executes the build task, copying all TypeScript files over to the `dist/tmp` directory. 8 | */ 9 | export = () => { 10 | return gulp.src([ 11 | join(Config.APP_SRC, '**/*.ts'), 12 | join(Config.APP_SRC, '**/*.json'), 13 | '!' + join(Config.APP_SRC, '**/*.spec.ts'), 14 | '!' + join(Config.APP_SRC, '**/*.e2e-spec.ts') 15 | ]) 16 | .pipe(gulp.dest(Config.TMP_DIR)); 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/e2e.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as history from 'express-history-api-fallback'; 3 | import * as gulp from 'gulp'; 4 | import { resolve, join } from 'path'; 5 | import { protractor } from 'gulp-protractor'; 6 | import Config from '../../config'; 7 | 8 | class Protractor { 9 | server(port: number, dir: string) { 10 | let app = express(); 11 | let root = resolve(process.cwd(), dir); 12 | for (let proxy of Config.getProxyMiddleware()) { 13 | app.use(proxy); 14 | } 15 | app.use(express.static(root)); 16 | app.use(history('index.html', { root })); 17 | return new Promise((resolve, reject) => { 18 | let server = app.listen(port, () => { 19 | resolve(server); 20 | }); 21 | }); 22 | } 23 | } 24 | 25 | /** 26 | * Executes the build process, running all e2e specs using `protractor`. 27 | */ 28 | export = (done: any) => { 29 | process.env.LANG='en_US.UTF-8'; 30 | new Protractor() 31 | .server(Config.PORT, Config.PROD_DEST) 32 | .then((server: any) => { 33 | gulp 34 | .src(join(Config.DEV_DEST, '**/*.e2e-spec.js')) 35 | .pipe(protractor({ configFile: 'protractor.conf.js' })) 36 | .on('error', (error: string) => { throw error; }) 37 | .on('end', () => { server.close(done); }); 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/generate.manifest.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | 3 | import Config from '../../config'; 4 | 5 | /** 6 | * Executes the build process, generating the manifest file using `angular2-service-worker`. 7 | */ 8 | export = () => { 9 | return require('angular2-service-worker') 10 | .gulpGenManifest({ 11 | group: [{ 12 | name: 'css', 13 | sources: gulp.src(`${Config.APP_DEST}/**/*.css`) 14 | }, { 15 | name: 'js', 16 | sources: gulp.src(`${Config.APP_DEST}/**/*.js`) 17 | }] 18 | }) 19 | .pipe(gulp.dest(Config.APP_DEST)); 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/karma.run.ts: -------------------------------------------------------------------------------- 1 | import { startKarma } from '../../utils/seed/karma.start'; 2 | import Config from '../../config'; 3 | 4 | /** 5 | * Executes the build process, running all unit tests using `karma`. 6 | */ 7 | export = (done: any) => { 8 | return startKarma(done, Config.getKarmaReporters()); 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/karma.run.with_coverage.ts: -------------------------------------------------------------------------------- 1 | import * as karma from 'karma'; 2 | import { join } from 'path'; 3 | 4 | import Config from '../../config'; 5 | 6 | let repeatableStartKarma = (done: any, config: any = {}) => { 7 | return new (karma).Server(Object.assign({ 8 | configFile: join(process.cwd(), 'karma.conf.js'), 9 | singleRun: true 10 | }, config), (exitCode: any) => { 11 | // Karma run is finished but do not exit the process for failure. Rather just mark this task as done. 12 | done(); 13 | }).start(); 14 | }; 15 | 16 | export = (done: any) => { 17 | let karmaReporters = Config.getKarmaReporters(); 18 | karmaReporters.singleRun = true; 19 | return repeatableStartKarma(done, karmaReporters); 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/karma.watch.ts: -------------------------------------------------------------------------------- 1 | import { startKarma } from '../../utils/seed/karma.start'; 2 | /** 3 | * Executes the build process, running all unit tests using `karma`. 4 | */ 5 | export = (done: any) => setTimeout(() => { 6 | return startKarma(done, { 7 | singleRun: false 8 | }); 9 | }, 10 | // On some OS, the default max opened file descriptor limit might cause karma to not start. 11 | // By setting this timeout, there should be enough time before other tasks release the descriptors. 12 | // Karma itself can have as many opened files as it needs, it uses graceful-fs. 13 | 1000); 14 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/minify.bundles.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import * as merge from 'merge-stream'; 4 | import { join } from 'path'; 5 | 6 | import Config from '../../config'; 7 | 8 | const plugins = gulpLoadPlugins(); 9 | 10 | const getTask = (target: string, destDir: string) => { 11 | return gulp.src(join(destDir, target)) 12 | .pipe(plugins.uglify({ 13 | compress: true, 14 | mangle: true 15 | })) 16 | .pipe(gulp.dest(destDir)); 17 | }; 18 | 19 | export = () => { 20 | return merge( 21 | getTask(Config.JS_PROD_APP_BUNDLE, Config.JS_DEST), 22 | getTask(Config.JS_PROD_SHIMS_BUNDLE, Config.JS_DEST) 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/print.banner.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config'; 2 | import { readFile } from 'fs'; 3 | import * as util from 'gulp-util'; 4 | import { join } from 'path'; 5 | 6 | export = (done: any) => { 7 | let bannerPath = join(Config.TOOLS_DIR, 'config', 'banner.txt'); 8 | if (require('supports-color').has256) { 9 | bannerPath = join(Config.TOOLS_DIR, 'config', 'banner-256.txt'); 10 | } 11 | readFile(bannerPath, (e, content) => { 12 | if (!e) { 13 | console.log(util.colors.green(content.toString())); 14 | } 15 | done(); 16 | }); 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/serve.coverage.ts: -------------------------------------------------------------------------------- 1 | import { serveCoverage } from '../../utils'; 2 | 3 | /** 4 | * Executes the build process, serving unit test coverage report using an `express` server. 5 | */ 6 | export = serveCoverage; 7 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/serve.coverage.watch.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import * as browserSync from 'browser-sync'; 3 | 4 | import Config from '../../config'; 5 | 6 | export = () => { 7 | let coverageFolder = Config.COVERAGE_TS_DIR; 8 | let watchedFiles: string[] = [join(coverageFolder, '**')]; 9 | 10 | // Serve files from the coverage of this project 11 | browserSync.create().init({ 12 | server: { 13 | baseDir: './' + coverageFolder 14 | }, 15 | port: Config.COVERAGE_PORT, 16 | files: watchedFiles, 17 | logFileChanges: false 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/serve.docs.ts: -------------------------------------------------------------------------------- 1 | // import { serveDocs } from '../../utils'; 2 | // 3 | // /** 4 | // * Executes the build process, serving the application documentation using an `express` server. 5 | // */ 6 | // export = serveDocs; 7 | 8 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/server.prod.ts: -------------------------------------------------------------------------------- 1 | import { serveProd } from '../../utils'; 2 | 3 | /** 4 | * Executes the build process, serving the files of the production environment using an `express` server. 5 | */ 6 | export = serveProd; 7 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/server.start.ts: -------------------------------------------------------------------------------- 1 | import { serveSPA } from '../../utils'; 2 | 3 | /** 4 | * Executes the build process, serving the files of the development environment using an `express` server. 5 | */ 6 | export = serveSPA; 7 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/start.deving.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import { join } from 'path'; 4 | import * as runSequence from 'run-sequence'; 5 | 6 | import Config from '../../config'; 7 | 8 | const plugins = gulpLoadPlugins(); 9 | 10 | import { notifyLiveReload } from '../../utils'; 11 | 12 | function watchAppFiles(path: string, fileChangeCallback: (e: any, done: () => void) => void) { 13 | 14 | let paths: string[] = [ 15 | join(Config.APP_SRC, path) 16 | ].concat(Config.TEMP_FILES.map((p) => { return '!' + p; })); 17 | 18 | let busyWithCall : boolean = false; 19 | let changesWaiting : any = null; 20 | let afterCall = () => { 21 | busyWithCall = false; 22 | if (changesWaiting) { 23 | fileChangeCallback(changesWaiting, afterCall); 24 | changesWaiting = null; 25 | } 26 | }; 27 | plugins.watch(paths, (e: any) => { 28 | if (busyWithCall) { 29 | changesWaiting = e; 30 | return; 31 | } 32 | busyWithCall = true; 33 | fileChangeCallback(e, afterCall); 34 | }); 35 | 36 | } 37 | 38 | gulp.task('watch.while_deving', function () { 39 | watchAppFiles('**/!(*.ts)', (e: any, done: any) => 40 | runSequence('build.assets.dev', 'build.html_css', 'build.index.dev', () => { notifyLiveReload(e); done(); })); 41 | watchAppFiles('**/(*.ts)', (e: any, done: any) => 42 | runSequence('build.js.dev', 'build.index.dev', () => { 43 | notifyLiveReload(e); 44 | runSequence('build.js.test', 'karma.run.with_coverage', done); 45 | })); 46 | }); 47 | 48 | export = (done: any) => 49 | runSequence('build.test', 50 | 'watch.while_deving', 51 | 'server.start', 52 | 'karma.run.with_coverage', 53 | 'serve.coverage.watch', 54 | done); 55 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/tslint.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 3 | import { join } from 'path'; 4 | 5 | import Config from '../../config'; 6 | 7 | const plugins = gulpLoadPlugins(); 8 | 9 | /** 10 | * Executes the build process, linting the TypeScript files using `codelyzer`. 11 | */ 12 | export = () => { 13 | let src = [ 14 | join(Config.APP_SRC, '**/*.ts'), 15 | '!' + join(Config.APP_SRC, '**/*.d.ts'), 16 | join(Config.E2E_SRC, '**/*.ts'), 17 | '!' + join(Config.E2E_SRC, '**/*.d.ts'), 18 | join(Config.TOOLS_DIR, '**/*.ts'), 19 | '!' + join(Config.TOOLS_DIR, '**/*.d.ts') 20 | ]; 21 | 22 | return gulp.src(src, {'base': '.'}) 23 | .pipe(plugins.tslint()) 24 | .pipe(plugins.tslint.report({ 25 | emitError: require('is-ci') 26 | })); 27 | }; 28 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/watch.dev.ts: -------------------------------------------------------------------------------- 1 | import { watch } from '../../utils'; 2 | 3 | /** 4 | * Executes the build process, watching for file changes and rebuilding the development environment. 5 | */ 6 | export = watch('build.dev'); 7 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/watch.e2e.ts: -------------------------------------------------------------------------------- 1 | import { watch } from '../../utils'; 2 | import Config from '../../config'; 3 | 4 | /** 5 | * Executes the build process, watching for file changes and rebuilding the e2e environment. 6 | */ 7 | export = watch('build.e2e', Config.E2E_SRC); 8 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/watch.test.ts: -------------------------------------------------------------------------------- 1 | import { watch } from '../../utils'; 2 | 3 | /** 4 | * Executes the build process, watching for file changes and rebuilding the test environment. 5 | */ 6 | export = watch('build.test'); 7 | -------------------------------------------------------------------------------- /frontend/tools/tasks/seed/webdriver.ts: -------------------------------------------------------------------------------- 1 | import { webdriver_update } from 'gulp-protractor'; 2 | 3 | /** 4 | * Executes the build process, installing the selenium webdriver used for the protractor e2e specs. 5 | */ 6 | export = webdriver_update; 7 | -------------------------------------------------------------------------------- /frontend/tools/tasks/task.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base class for all tasks. 3 | */ 4 | export abstract class Task { 5 | /** 6 | * Override this task if you want to implement some custom 7 | * task activation mechanism. By default each task will be always executed. 8 | * 9 | * @param {string[]} files A list of files changed since the previous watch. 10 | */ 11 | shallRun(files: string[]): boolean { 12 | return true; 13 | } 14 | 15 | /** 16 | * Implements your task behavior. 17 | * 18 | * @param {any} done A function which should be activated once your task completes. 19 | * @return {ReadWriteStream | Promise | void} This method can either return a promise 20 | * which should be resolved once your task execution completes, a stream 21 | * which should throw an end event once your task execution completes 22 | * or nothing in case you will manually invoke the `done` method. 23 | */ 24 | abstract run(done?: any): any | Promise | void; 25 | } 26 | -------------------------------------------------------------------------------- /frontend/tools/tasks/typescript_task.ts: -------------------------------------------------------------------------------- 1 | import { Task } from './task'; 2 | 3 | export abstract class TypeScriptTask extends Task { 4 | shallRun(files: String[]) { 5 | return files.reduce((a, f) => { 6 | return a || f.endsWith('.ts'); 7 | }, false); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/tools/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the utilities provided by the project and the seed. 3 | */ 4 | export * from './utils/project.utils'; 5 | export * from './utils/seed.utils'; 6 | -------------------------------------------------------------------------------- /frontend/tools/utils/project.utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the exports for the project specific utilities. 3 | */ 4 | export * from './project/sample_util'; 5 | -------------------------------------------------------------------------------- /frontend/tools/utils/project/sample_util.ts: -------------------------------------------------------------------------------- 1 | export function myUtil() { 2 | // Code goes here 3 | } 4 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed.utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the utilities provided by the seed. 3 | */ 4 | export * from './seed/clean'; 5 | export * from './seed/code_change_tools'; 6 | export * from './seed/server'; 7 | export * from './seed/tasks_tools'; 8 | export * from './seed/template_locals'; 9 | export * from './seed/tsproject'; 10 | export * from './seed/watch'; 11 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/clean.ts: -------------------------------------------------------------------------------- 1 | import * as util from 'gulp-util'; 2 | import * as rimraf from 'rimraf'; 3 | 4 | /** 5 | * Cleans the given path(s) using `rimraf`. 6 | * @param {string or string[]} paths - The path or list of paths to clean. 7 | */ 8 | export function clean(paths: string|string[]): (done: () => void) => void { 9 | return done => { 10 | let pathsToClean: string[]; 11 | if (paths instanceof Array) { 12 | pathsToClean = paths; 13 | } else { 14 | pathsToClean = [paths]; 15 | } 16 | 17 | let promises = pathsToClean.map(p => { 18 | return new Promise(resolve => { 19 | rimraf(p, e => { 20 | if (e) { 21 | util.log('Clean task failed with', e); 22 | } else { 23 | util.log('Deleted', util.colors.yellow(p || '-')); 24 | } 25 | resolve(); 26 | }); 27 | }); 28 | }); 29 | Promise.all(promises).then(() => done()); 30 | }; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/code_change_tools.ts: -------------------------------------------------------------------------------- 1 | import * as browserSync from 'browser-sync'; 2 | // import * as path from 'path'; 3 | 4 | import Config from '../../config'; 5 | 6 | class ChangeFileManager { 7 | private _files: string[] = []; 8 | private _pristine = true; 9 | 10 | get lastChangedFiles() { 11 | return this._files.slice(); 12 | } 13 | 14 | get pristine() { 15 | return this._pristine; 16 | } 17 | 18 | addFile(file: string) { 19 | this._pristine = false; 20 | this._files.push(file); 21 | } 22 | 23 | addFiles(files: string[]) { 24 | files.forEach(f => this.addFile(f)); 25 | } 26 | 27 | clear() { 28 | this._files = []; 29 | } 30 | } 31 | 32 | export let changeFileManager = new ChangeFileManager(); 33 | 34 | /** 35 | * Initialises BrowserSync with the configuration defined in seed.config.ts (or if overriden: project.config.ts). 36 | */ 37 | let runServer = () => { 38 | browserSync.init(Config.getPluginConfig('browser-sync')); 39 | }; 40 | 41 | /** 42 | * Runs BrowserSync as the listening process for the application. 43 | */ 44 | let listen = () => { 45 | runServer(); 46 | }; 47 | 48 | /** 49 | * Provides a flag to mark which files have changed and reloads BrowserSync accordingly. 50 | */ 51 | let changed = (files: any) => { 52 | if (!(files instanceof Array)) { 53 | files = [files]; 54 | } 55 | browserSync.reload(files); 56 | }; 57 | 58 | export { listen, changed }; 59 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/karma.start.ts: -------------------------------------------------------------------------------- 1 | import * as karma from 'karma'; 2 | import { join } from 'path'; 3 | 4 | export const startKarma = (done: any, config: any = {}) => { 5 | return new (karma).Server(Object.assign({ 6 | configFile: join(process.cwd(), 'karma.conf.js'), 7 | singleRun: true 8 | }, config)).start(done); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/server.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as fallback from 'express-history-api-fallback'; 3 | import * as openResource from 'open'; 4 | import { resolve } from 'path'; 5 | 6 | import * as codeChangeTool from './code_change_tools'; 7 | import Config from '../../config'; 8 | 9 | /** 10 | * Serves the Single Page Application. More specifically, calls the `listen` method, which itself launches BrowserSync. 11 | */ 12 | export function serveSPA() { 13 | codeChangeTool.listen(); 14 | } 15 | 16 | /** 17 | * This utility method is used to notify that a file change has happened and subsequently calls the `changed` method, 18 | * which itself initiates a BrowserSync reload. 19 | * @param {any} e - The file that has changed. 20 | */ 21 | export function notifyLiveReload(e:any) { 22 | let fileName = e.path; 23 | codeChangeTool.changed(fileName); 24 | } 25 | 26 | /** 27 | * Starts a new `express` server, serving the static documentation files. 28 | */ 29 | export function serveDocs() { 30 | let server = express(); 31 | 32 | server.use( 33 | Config.APP_BASE, 34 | express.static(resolve(process.cwd(), Config.DOCS_DEST)) 35 | ); 36 | 37 | server.listen(Config.DOCS_PORT, () => 38 | openResource('http://localhost:' + Config.DOCS_PORT + Config.APP_BASE) 39 | ); 40 | } 41 | 42 | /** 43 | * Starts a new `express` server, serving the static unit test code coverage report. 44 | */ 45 | export function serveCoverage() { 46 | let server = express(); 47 | 48 | server.use( 49 | Config.APP_BASE, 50 | express.static(resolve(process.cwd(), Config.COVERAGE_TS_DIR)) 51 | ); 52 | 53 | server.listen(Config.COVERAGE_PORT, () => 54 | openResource('http://localhost:' + Config.COVERAGE_PORT + Config.APP_BASE) 55 | ); 56 | } 57 | 58 | /** 59 | * Starts a new `express` server, serving the built files from `dist/prod`. 60 | */ 61 | export function serveProd() { 62 | let root = resolve(process.cwd(), Config.PROD_DEST); 63 | let server = express(); 64 | 65 | for (let proxy of Config.getProxyMiddleware()) { 66 | server.use(proxy); 67 | } 68 | 69 | server.use(Config.APP_BASE, express.static(root)); 70 | 71 | server.use(fallback('index.html', { root })); 72 | 73 | server.listen(Config.PORT, () => 74 | openResource('http://localhost:' + Config.PORT + Config.APP_BASE) 75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/tasks_tools.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, lstatSync, readdirSync } from 'fs'; 2 | import * as gulp from 'gulp'; 3 | import * as util from 'gulp-util'; 4 | import * as isstream from 'isstream'; 5 | import { join } from 'path'; 6 | import * as tildify from 'tildify'; 7 | 8 | import { changeFileManager } from './code_change_tools'; 9 | import { Task } from '../../tasks/task'; 10 | 11 | /** 12 | * Loads the tasks within the given path. 13 | * @param {string} path - The path to load the tasks from. 14 | */ 15 | export function loadTasks(path: string): void { 16 | util.log('Loading tasks folder', util.colors.yellow(path)); 17 | readDir(path, taskname => registerTask(taskname, path)); 18 | } 19 | 20 | function normalizeTask(task: any, taskName: string) { 21 | if (task instanceof Task) { 22 | return task; 23 | } 24 | if (task.prototype && task.prototype instanceof Task) { 25 | return new task(); 26 | } 27 | if (typeof task === 'function') { 28 | return new class AnonTask extends Task { 29 | run(done: any) { 30 | if (task.length > 0) { 31 | return task(done); 32 | } 33 | 34 | const taskReturnedValue = task(); 35 | if (isstream(taskReturnedValue)) { 36 | return taskReturnedValue; 37 | } 38 | 39 | done(); 40 | } 41 | }; 42 | } 43 | throw new Error(taskName + ' should be instance of the class ' + 44 | 'Task, a function or a class which extends Task.'); 45 | } 46 | 47 | /** 48 | * Registers the task by the given taskname and path. 49 | * @param {string} taskname - The name of the task. 50 | * @param {string} path - The path of the task. 51 | */ 52 | function registerTask(taskname: string, path: string): void { 53 | const TASK = join(path, taskname); 54 | util.log('Registering task', util.colors.yellow(tildify(TASK))); 55 | 56 | gulp.task(taskname, (done: any) => { 57 | const task = normalizeTask(require(TASK), TASK); 58 | 59 | if (changeFileManager.pristine || task.shallRun(changeFileManager.lastChangedFiles)) { 60 | const result = task.run(done); 61 | if (result && typeof result.catch === 'function') { 62 | result.catch((e: any) => { 63 | util.log(`Error while running "${TASK}"`, e); 64 | }); 65 | } 66 | return result; 67 | } else { 68 | done(); 69 | } 70 | }); 71 | } 72 | 73 | /** 74 | * Reads the files in the given root directory and executes the given callback per found file. 75 | * @param {string} root - The root directory to read. 76 | * @param {function} cb - The callback to execute per found file. 77 | */ 78 | function readDir(root: string, cb: (taskname: string) => void) { 79 | if (!existsSync(root)) { 80 | return; 81 | } 82 | 83 | walk(root); 84 | 85 | function walk(path: string) { 86 | let files = readdirSync(path); 87 | for (let i = 0; i < files.length; i += 1) { 88 | let file = files[i]; 89 | let curPath = join(path, file); 90 | if (lstatSync(curPath).isFile() && /\.ts$/.test(file)) { 91 | let taskname = file.replace(/\.ts$/, ''); 92 | cb(taskname); 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/template_locals.ts: -------------------------------------------------------------------------------- 1 | import * as util from 'gulp-util'; 2 | import { argv } from 'yargs'; 3 | import { join } from 'path'; 4 | 5 | import Config from '../../config'; 6 | 7 | /** 8 | * Builds an object consisting of the base configuration provided by confg/seed.config.ts, the additional 9 | * project specific overrides as defined in config/project.config.ts and including the base environment config as defined in env/base.ts 10 | * and the environment specific overrides (for instance if env=dev then as defined in env/dev.ts). 11 | */ 12 | export class TemplateLocalsBuilder { 13 | private stringifySystemConfigDev = false; 14 | private stringifyEnvConfig = true; 15 | 16 | withStringifiedSystemConfigDev() { 17 | this.stringifySystemConfigDev = true; 18 | return this; 19 | } 20 | wihtoutStringifiedEnvConfig() { 21 | this.stringifyEnvConfig = false; 22 | return this; 23 | } 24 | 25 | 26 | build() { 27 | const configEnvName = argv['env-config'] || argv['config-env'] || 'dev'; 28 | const configPath = Config.getPluginConfig('environment-config'); 29 | const envOnlyConfig = this.getConfig(configPath, configEnvName); 30 | const baseConfig = this.getConfig(configPath, 'base'); 31 | 32 | if (!envOnlyConfig) { 33 | throw new Error(configEnvName + ' is an invalid configuration name'); 34 | } 35 | 36 | const envConfig = Object.assign({}, baseConfig, envOnlyConfig); 37 | let locals = Object.assign({}, 38 | Config, 39 | { ENV_CONFIG: this.stringifyEnvConfig ? JSON.stringify(envConfig) : envConfig } 40 | ); 41 | if (this.stringifySystemConfigDev) { 42 | Object.assign(locals, {SYSTEM_CONFIG_DEV: JSON.stringify(Config.SYSTEM_CONFIG_DEV)}); 43 | } 44 | return locals; 45 | } 46 | 47 | private getConfig(path: string, env: string) { 48 | const configPath = join(path, env); 49 | let config: any; 50 | try { 51 | config = JSON.parse(JSON.stringify(require(configPath))); 52 | } catch (e) { 53 | config = null; 54 | util.log(util.colors.red(e.message)); 55 | } 56 | 57 | return config; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/tsproject.ts: -------------------------------------------------------------------------------- 1 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 2 | import { join } from 'path'; 3 | import ts = require('gulp-typescript/release/main'); 4 | 5 | import Config from '../../config'; 6 | 7 | const plugins = gulpLoadPlugins(); 8 | 9 | let tsProjects: any = {}; 10 | 11 | /** 12 | * Creates a TypeScript project with the given options using the gulp typescript plugin. 13 | * @param {Object} options - The additional options for the project configuration. 14 | */ 15 | export function makeTsProject(options: ts.Settings = {}, pathToTsConfig: string = Config.APP_SRC, projectName = Config.APP_PROJECTNAME) { 16 | let optionsHash = JSON.stringify(options); 17 | if (!tsProjects[optionsHash]) { 18 | let config = Object.assign({ 19 | typescript: require('typescript') 20 | }, options); 21 | tsProjects[optionsHash] = 22 | plugins.typescript.createProject(join(pathToTsConfig, projectName), config); 23 | } 24 | return tsProjects[optionsHash]; 25 | } 26 | -------------------------------------------------------------------------------- /frontend/tools/utils/seed/watch.ts: -------------------------------------------------------------------------------- 1 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 2 | import { join } from 'path'; 3 | import * as runSequence from 'run-sequence'; 4 | 5 | import Config from '../../config'; 6 | import { changeFileManager } from './code_change_tools'; 7 | import { notifyLiveReload } from '../../utils'; 8 | 9 | const plugins = gulpLoadPlugins(); 10 | 11 | /** 12 | * Watches the task with the given taskname. 13 | * @param {string} taskname - The name of the task. 14 | */ 15 | export function watch(taskname: string, root: string = Config.APP_SRC) { 16 | return function () { 17 | let paths: string[] = [ 18 | join(root, '**') 19 | ].concat(Config.TEMP_FILES.map((p) => { return '!' + p; })); 20 | 21 | plugins.watch(paths, (e: any) => { 22 | changeFileManager.addFile(e.path); 23 | 24 | 25 | // Resolves issue in IntelliJ and other IDEs/text editors which 26 | // save multiple files at once. 27 | // https://github.com/mgechev/angular-seed/issues/1615 for more details. 28 | setTimeout(() => { 29 | 30 | runSequence(taskname, () => { 31 | changeFileManager.clear(); 32 | notifyLiveReload(e); 33 | }); 34 | 35 | }, 100); 36 | }); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "lib": ["es2015"], 11 | "sourceMap": true, 12 | "pretty": true, 13 | "allowUnreachableCode": false, 14 | "allowUnusedLabels": false, 15 | "noImplicitAny": true, 16 | "noImplicitReturns": true, 17 | "noImplicitUseStrict": false, 18 | "noFallthroughCasesInSwitch": true, 19 | "typeRoots": [ 20 | "./node_modules/@types", 21 | "./node_modules" 22 | ], 23 | "types": [ 24 | "node" 25 | ] 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "dist", 30 | "src" 31 | ], 32 | "compileOnSave": false 33 | } 34 | -------------------------------------------------------------------------------- /frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tools/config/seed.tslint.json", 3 | "rules": { 4 | } 5 | } 6 | --------------------------------------------------------------------------------