├── .editorconfig
├── .gitignore
├── .travis.yml
├── .vscode
└── symbols.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── angular-cli.json
├── docs
└── images
│ └── demo.gif
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.json
├── package.json
├── src
├── demo-app
│ ├── demo-app.component.html
│ ├── demo-app.component.ts
│ ├── demo-app.module.ts
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── index.html
│ ├── index.ts
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ ├── test.ts
│ ├── tsconfig.json
│ └── typings.d.ts
└── lib
│ ├── activity
│ ├── background
│ │ └── activity-background.component.ts
│ ├── bars
│ │ └── activity-bars.component.ts
│ ├── gantt-activity.component.ts
│ ├── gantt-activity.module.ts
│ └── time-scale
│ │ └── gantt-time-scale.component.ts
│ ├── footer
│ └── gantt-footer.component.ts
│ ├── gantt.component.ts
│ ├── gantt.module.ts
│ ├── header
│ └── gantt-header.component.ts
│ ├── index.ts
│ ├── package.json
│ ├── shared
│ ├── interfaces.ts
│ ├── pipes
│ │ └── groupBy.pipe.ts
│ └── services
│ │ ├── gantt-config.service.ts
│ │ ├── gantt.service.spec.ts
│ │ └── gantt.service.ts
│ └── tsconfig.json
├── test
├── karma.config.js
└── protractor.conf.js
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # IDEs and editors
12 | /.idea
13 | /.vscode
14 | .project
15 | .classpath
16 | *.launch
17 | .settings/
18 |
19 | # misc
20 | /.sass-cache
21 | /connect.lock
22 | /coverage/*
23 | /libpeerconnection.log
24 | npm-debug.log
25 | testem.log
26 | /typings
27 |
28 | # e2e
29 | /e2e/*.js
30 | /e2e/*.map
31 |
32 | #System Files
33 | .DS_Store
34 | Thumbs.db
35 |
36 | debug.log
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
--------------------------------------------------------------------------------
/.vscode/symbols.json:
--------------------------------------------------------------------------------
1 | {"symbols":{"Angular2GanttFinalPage":{"hasNamespace":false,"type":0,"moduleName":"app.po","relativePath":"e2e\\app.po"},"GanttHeaderComponent":{"hasNamespace":false,"type":0,"moduleName":"gantt-header.component","relativePath":"src\\app\\gantt\\header\\gantt-header.component"},"GanttActivityModule":{"hasNamespace":false,"type":0,"moduleName":"gantt-activity.module","relativePath":"src\\app\\gantt\\activity\\gantt-activity.module"},"GanttFooterComponent":{"hasNamespace":false,"type":0,"moduleName":"gantt-footer.component","relativePath":"src\\app\\gantt\\footer\\gantt-footer.component"},"GanttConfig":{"hasNamespace":false,"type":0,"moduleName":"gantt-config","relativePath":"src\\app\\gantt\\gantt-config"},"AppModule":{"hasNamespace":false,"type":0,"moduleName":"app.module","relativePath":"src\\app\\app.module"},"AppComponent":{"hasNamespace":false,"type":0,"moduleName":"app.component","relativePath":"src\\app\\app.component"},"GanttModule":{"hasNamespace":false,"type":0,"moduleName":"gantt.module","relativePath":"src\\app\\gantt\\gantt.module"},"GanttActivityBackgroundComponent":{"hasNamespace":false,"type":0,"moduleName":"activity-background.component","relativePath":"src\\app\\gantt\\activity\\background\\activity-background.component"},"GanttTimeScaleComponent":{"hasNamespace":false,"type":0,"moduleName":"gantt-time-scale.component","relativePath":"src\\app\\gantt\\activity\\time-scale\\gantt-time-scale.component"},"GanttActivityComponent":{"hasNamespace":false,"type":0,"moduleName":"gantt-activity.component","relativePath":"src\\app\\gantt\\activity\\gantt-activity.component"},"GanttActivityBarsComponent":{"hasNamespace":false,"type":0,"moduleName":"activity-bars.component","relativePath":"src\\app\\gantt\\activity\\bars\\activity-bars.component"},"GanttService":{"hasNamespace":false,"type":0,"moduleName":"gantt.service","relativePath":"src\\app\\gantt\\services\\gantt.service"},"GanttComponent":{"hasNamespace":false,"type":0,"moduleName":"gantt.component","relativePath":"src\\app\\gantt\\gantt.component"}},"files":{"e2e\\app.e2e-spec.ts":"2016-11-01T13:01:04.095Z","e2e\\app.po.ts":"2016-11-01T13:01:04.096Z","src\\main.ts":"2016-11-01T14:14:57.907Z","src\\polyfills.ts":"2016-11-01T13:01:04.096Z","src\\test.ts":"2016-11-01T13:01:04.096Z","src\\typings.d.ts":"2016-11-01T13:01:04.096Z","src.webpacktemplate\\main.ts":"2016-11-01T13:01:04.095Z","src.webpacktemplate\\polyfills.ts":"2016-11-01T13:01:04.096Z","src.webpacktemplate\\test.ts":"2016-11-01T13:01:04.096Z","src.webpacktemplate\\typings.d.ts":"2016-11-01T13:01:04.096Z","src\\app\\app.component.ts":"2016-11-01T17:47:34.451Z","src\\app\\app.module.ts":"2016-11-01T17:46:35.005Z","src.webpacktemplate\\app\\app.component.spec.ts":"2016-11-01T13:01:04.098Z","src.webpacktemplate\\app\\app.component.ts":"2016-11-01T13:01:04.098Z","src.webpacktemplate\\app\\app.module.ts":"2016-11-01T13:01:04.098Z","src.webpacktemplate\\environments\\environment.prod.ts":"2016-11-01T13:01:04.098Z","src.webpacktemplate\\environments\\environment.ts":"2016-11-01T13:01:04.099Z","src.webpacktemplate\\app\\index.ts":"2016-11-01T13:01:04.099Z","src\\app\\gantt\\gantt.component.ts":"2016-11-01T20:18:11.215Z","src\\app\\gantt\\gantt.module.ts":"2016-11-01T17:48:11.448Z","src\\app\\gantt\\activity\\gantt-activity.component.ts":"2016-11-01T18:04:57.998Z","src\\app\\gantt\\activity\\gantt-activity.module.ts":"2016-11-01T14:51:02.335Z","src\\app\\gantt\\header\\gantt-header.component.ts":"2016-11-01T14:02:04.351Z","src\\app\\gantt\\footer\\gantt-footer.component.ts":"2016-11-01T15:00:21.110Z","src\\app\\gantt\\activity\\background\\activity-background.component.ts":"2016-11-01T17:48:23.615Z","src\\app\\gantt\\activity\\bars\\activity-bars.component.ts":"2016-11-01T18:06:07.027Z","src\\app\\gantt\\activity\\time-scale\\gantt-time-scale.component.ts":"2016-11-01T17:50:49.038Z","src\\app\\index.ts":"2016-11-01T14:14:55.226Z","src\\app\\gantt\\services\\gantt.service.ts":"2016-11-01T18:11:58.435Z","src\\environments\\environment.prod.ts":"2016-11-01T13:01:04.098Z","src\\environments\\environment.ts":"2016-11-01T13:01:04.099Z","src\\app\\gantt\\gantt-config.ts":"2016-11-01T17:49:56.000Z","src\\app\\gantt\\services\\gantt.service.spec.ts":"2016-11-01T17:47:56.000Z"}}
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dalestone/angular2-gantt/91d3522f072ba09771e912a193ce75e219cb04cb/CHANGELOG.md
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2016 Google, Inc.
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
13 | all 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
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular 2 Gantt
2 | [](https://www.npmjs.com/package/angular2-gantt)
3 | [](https://travis-ci.org/dalestone/angular2-gantt)
4 |
5 | This is the home for the Angular 2 gantt component. This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.18.
6 |
7 | The aim of this project is to make a material design angular 2 gantt component.
8 |
9 | ### Project status
10 | Angular 2 gantt is currently in alpha and under active development.
11 | During alpha, breaking API and behaviour changes will be occuring regularly.
12 |
13 | ### Getting Started
14 | If you want to view the and modify the source directly do the following:
15 |
16 | 1. git clone https://github.com/dalestone/angular2-gantt.git
17 | 2. npm install -g angular-cli
18 | 2. npm install
19 | 3. npm start
20 |
21 | ### Install Angular 2 Gantt (Webpack only)
22 | `npm install ng2-gantt --save`
23 |
24 | Import the `ng2-gantt` NgModule into your app module
25 |
26 | ```
27 | import { GanttModule } from 'ng2-gantt';
28 | // other imports
29 | @NgModule({
30 | imports: [ GanttModule ]
31 | })
32 | export class AppModule { }
33 | ```
34 |
35 | ## Demo
36 | 
37 |
38 | ## Browser support
39 | Angular 2 Gantt supports the most recent versions of major browsers: Chrome (including Android), Firefox, Safari (including iOS), and IE11 / Edge.
40 |
--------------------------------------------------------------------------------
/angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "project": {
3 | "version": "1.0.0-alpha.0",
4 | "name": "angular2-gantt"
5 | },
6 | "apps": [
7 | {
8 | "root": "src/demo-app",
9 | "outDir": "dist",
10 | "assets": [
11 |
12 | ],
13 | "index": "index.html",
14 | "main": "main.ts",
15 | "test": "test.ts",
16 | "tsconfig": "tsconfig.json",
17 | "prefix": "app",
18 | "mobile": false,
19 | "styles": [
20 | "styles.css"
21 | ],
22 | "scripts": [],
23 | "environments": {
24 | "source": "environments/environment.ts",
25 | "dev": "environments/environment.ts",
26 | "prod": "environments/environment.prod.ts"
27 | }
28 | }
29 | ],
30 | "addons": [],
31 | "packages": [],
32 | "e2e": {
33 | "protractor": {
34 | "config": "./protractor.conf.js"
35 | }
36 | },
37 | "test": {
38 | "karma": {
39 | "config": "./karma.conf.js"
40 | }
41 | },
42 | "defaults": {
43 | "styleExt": "css",
44 | "prefixInterfaces": false,
45 | "inline": {
46 | "style": false,
47 | "template": false
48 | },
49 | "spec": {
50 | "class": false,
51 | "component": true,
52 | "directive": true,
53 | "module": false,
54 | "pipe": true,
55 | "service": true
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/docs/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dalestone/angular2-gantt/91d3522f072ba09771e912a193ce75e219cb04cb/docs/images/demo.gif
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Angular2GanttFinalPage } from './app.po';
2 |
3 | describe('angular2-gantt-final App', function() {
4 | let page: Angular2GanttFinalPage;
5 |
6 | beforeEach(() => {
7 | page = new Angular2GanttFinalPage();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('app works!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class Angular2GanttFinalPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "outDir": "../dist/out-tsc-e2e",
10 | "sourceMap": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "../node_modules/@types"
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-gantt",
3 | "version": "1.0.0-alpha.1",
4 | "description": "Gantt component for Angular 2",
5 | "bugs": "https://github.com/dalestone/angular2-gantt/issues",
6 | "license": "MIT",
7 | "angular-cli": {},
8 | "scripts": {
9 | "start": "ng serve",
10 | "lint": "tslint \"src/**/*.ts\"",
11 | "test": "ng test",
12 | "pree2e": "webdriver-manager update",
13 | "e2e": "protractor"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/dalestone/angular2-gantt.git"
18 | },
19 | "private": true,
20 | "dependencies": {
21 | "@angular/common": "^2.4.5",
22 | "@angular/compiler": "^2.4.5",
23 | "@angular/core": "^2.4.5",
24 | "@angular/forms": "^2.4.5",
25 | "@angular/http": "^2.4.5",
26 | "@angular/platform-browser": "^2.4.5",
27 | "@angular/platform-browser-dynamic": "^2.4.5",
28 | "@angular/router": "^3.4.5",
29 | "angular2-tree-component": "^2.7.0",
30 | "core-js": "^2.4.1",
31 | "ng2-gantt": "1.0.0-alpha.1",
32 | "rxjs": "5.0.0-beta.12",
33 | "ts-helpers": "^1.1.1",
34 | "zone.js": "^0.6.23"
35 | },
36 | "devDependencies": {
37 | "@types/jasmine": "^2.2.30",
38 | "@types/node": "^6.0.42",
39 | "angular-cli": "1.0.0-beta.18",
40 | "codelyzer": "1.0.0-beta.1",
41 | "jasmine-core": "2.4.1",
42 | "jasmine-spec-reporter": "2.5.0",
43 | "karma": "1.2.0",
44 | "karma-chrome-launcher": "^2.0.0",
45 | "karma-cli": "^1.0.1",
46 | "karma-jasmine": "^1.0.2",
47 | "karma-remap-istanbul": "^0.2.1",
48 | "protractor": "4.0.9",
49 | "ts-node": "1.2.1",
50 | "tslint": "3.13.0",
51 | "typescript": "~2.0.3"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/demo-app/demo-app.component.html:
--------------------------------------------------------------------------------
1 |
Angular 2 Gantt Demo
2 |
3 |
4 |
5 |
Project
6 |
7 |
30 |
31 |
Playground
32 | Task Status:
33 |
39 |
40 |
41 |
42 |
43 |
Output
44 |
{{ project | json }}
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/demo-app/demo-app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Project, IGanttOptions, Zooming, Task } from '../lib';
3 |
4 | @Component({
5 | selector: 'demo-app',
6 | templateUrl: './demo-app.component.html',
7 | })
8 | export class DemoAppComponent {
9 |
10 | // Default options
11 | options: IGanttOptions = {
12 | scale: {
13 | start: new Date(2017, 0, 1),
14 | end: new Date(2017, 1, 1)
15 | },
16 | zooming: Zooming[Zooming.days]
17 | };
18 |
19 | project: Project = {
20 | 'id': 'dd10f0b6-b8a4-4b2d-a7df-b2c3d63b4a01',
21 | 'name': 'Angular2 Gantt',
22 | 'startDate': new Date("2017-02-27T08:32:09.6972999Z"),
23 | 'tasks': [
24 | {
25 | 'id': 'ea2a8d86-1d4b-4807-844d-d5417fcf618d',
26 | 'treePath': 'parent 1',
27 | 'parentId': 'ea2a8d86-1d4b-4807-844d-d5417fcf618d',
28 | 'name': 'parent 1',
29 | 'resource': 'res1',
30 | 'start': new Date('2017-01-01T00:00:00.0Z'),
31 | 'end': new Date('2017-01-03T00:00:00.0Z'),
32 | 'percentComplete': 100,
33 | 'status': 'Completed'
34 | },
35 | {
36 | 'id': 'dd755f20-360a-451f-b200-b83b89a35ad1',
37 | 'treePath': 'Cras sollicitudin egestas velit sit amet aliquam',
38 | 'parentId': 'dd755f20-360a-451f-b200-b83b89a35ad1',
39 | 'name': 'Cras sollicitudin egestas velit sit amet aliquam',
40 | 'resource': 'res2',
41 | 'start': new Date('2017-01-05T00:00:00.0Z'),
42 | 'end': new Date('2017-01-06T00:00:00.0Z'),
43 | 'percentComplete': 0
44 | },
45 | {
46 | 'id': 'j1b997ef-bb89-4ca2-b134-62a08a19aef6',
47 | 'treePath': 'Donec ac augue est',
48 | 'parentId': 'j1b997ef-bb89-4ca2-b134-62a08a19aef6',
49 | 'name': 'Donec ac augue est',
50 | 'resource': 'res2',
51 | 'start': new Date('2017-01-06T00:00:00.0Z'),
52 | 'end': new Date('2017-01-07T00:00:00.0Z'),
53 | 'percentComplete': 0
54 | },
55 | {
56 | 'id': 'ub12f674-d5cb-408f-a941-ec76af2ec47e',
57 | 'treePath': 'Lorem ipsum dolor sit amet',
58 | 'parentId': 'ub12f674-d5cb-408f-a941-ec76af2ec47e',
59 | 'name': 'Lorem ipsum dolor sit amet',
60 | 'resource': 'res1',
61 | 'start': new Date('2017-01-07T00:00:00.0Z'),
62 | 'end': new Date('2017-01-22T00:00:00.0Z'),
63 | 'percentComplete': 0,
64 | 'status': 'Error'
65 | },
66 | {
67 | 'id': 'xafa430b-d4da-4d7d-90ed-69056a042d7a',
68 | 'treePath': 'Praesent molestie lobortis mi non tempor',
69 | 'parentId': 'xafa430b-d4da-4d7d-90ed-69056a042d7a',
70 | 'name': 'Praesent molestie lobortis mi non tempor',
71 | 'resource': 'res1',
72 | 'start': new Date('2017-01-22T00:00:00.0Z'),
73 | 'end': new Date('2017-01-23T00:00:00.0Z')
74 | },
75 | {
76 | 'id': 'mc9d8d41-1995-4b38-9256-bcc0da171146',
77 | 'treePath': 'Cras sollicitudin egestas velit sit amet aliquam',
78 | 'parentId': 'mc9d8d41-1995-4b38-9256-bcc0da171146',
79 | 'name': 'Cras sollicitudin egestas velit sit amet aliquam',
80 | 'resource': 'res2',
81 | 'start': new Date('2017-01-23T00:00:00.0Z'),
82 | 'end': new Date('2017-01-24T00:00:00.0Z')
83 | },
84 | {
85 | 'id': 'b5c071a5-430c-4d61-acf4-799cbdf61c49',
86 | 'treePath': 'Donec ac augue est',
87 | 'parentId': 'b5c071a5-430c-4d61-acf4-799cbdf61c49',
88 | 'name': 'Donec ac augue est',
89 | 'resource': 'res2',
90 | 'start': new Date('2017-01-24T00:00:00.0Z'),
91 | 'end': new Date('2017-01-24T00:34:00.0Z')
92 | },
93 | {
94 | 'id': 't9ba762e-bde7-47bf-b628-75f99fdd5bef',
95 | 'treePath': 'Lorem Ipsum',
96 | 'parentId': 't9ba762e-bde7-47bf-b628-75f99fdd5bef',
97 | 'name': 'Lorem Ipsum',
98 | 'resource': 'res2',
99 | 'start': new Date('2017-01-24T00:00:00.0Z'),
100 | 'end': new Date('2017-01-24T00:00:36.0Z')
101 | }
102 | ]
103 | };
104 |
105 |
106 |
107 | constructor() {
108 |
109 | }
110 |
111 | groupData(array: any[], f: any): any[] {
112 | var groups = {};
113 | array.forEach((o: any) => {
114 | var group = JSON.stringify(f(o));
115 |
116 | groups[group] = groups[group] || [];
117 | groups[group].push(o);
118 | });
119 | return Object.keys(groups).map((group: any) => {
120 | return groups[group];
121 | });
122 | }
123 |
124 | createTask(element: any) {
125 | var selectedStatus = element.options[element.selectedIndex].value;
126 |
127 | var parentTask = {
128 | 'id': 'parent_task_' + Math.random(),
129 | 'parentId': 'parent_task',
130 | 'treePath': 'parent_task',
131 | 'name': 'parent_task',
132 | 'percentComplete': 0,
133 | 'start': new Date('2017-01-01T03:30:00.0Z'),
134 | 'end': new Date('2017-01-01T12:45:00.0Z'),
135 | 'status': selectedStatus
136 | }
137 | this.project.tasks.push(parentTask);
138 |
139 | var childTask = {
140 | 'id': 'child_task_' + Math.random(),
141 | 'parentId': 'ea2a8d86-1d4b-4807-844d-d5417fcf618d',
142 | 'treePath': 'parent 1/child3',
143 | 'name': 'child3',
144 | 'percentComplete': 0,
145 | 'start': new Date('2017-01-01T03:30:00.0Z'),
146 | 'end': new Date('2017-01-01T12:45:00.0Z'),
147 | 'status': selectedStatus
148 | }
149 | this.project.tasks.push(childTask);
150 |
151 | }
152 |
153 | updateTasks() {
154 | for (var i = 0; i < this.project.tasks.length; i++) {
155 | let task = this.project.tasks[i];
156 |
157 | let progress = setInterval(function () {
158 | if (task.percentComplete === 100) {
159 | task.status = "Completed";
160 | clearInterval(progress);
161 | } else {
162 | if (task.percentComplete === 25) {
163 | task.status = "Warning";
164 | } else if (task.percentComplete === 50) {
165 | task.status = "Error";
166 | } else if (task.percentComplete === 75) {
167 | task.status = "Information";
168 | }
169 |
170 | task.percentComplete += 1;
171 | }
172 | }, 200);
173 | }
174 | }
175 |
176 | loadBigDataSet() {
177 | var tasks = [];
178 |
179 | for (var i = 11; i < 1000; i++) {
180 | var task = {
181 | id: `parent${i}`,
182 | name: 'task testing',
183 | percentComplete: 0,
184 | start: new Date(),
185 | end: new Date(),
186 | status: ''
187 | }
188 |
189 | tasks.push(task);
190 | }
191 |
192 | this.project.tasks.push(...tasks);
193 | }
194 |
195 | gridRowClicked(event) {
196 | console.log(event);
197 | }
198 | }
--------------------------------------------------------------------------------
/src/demo-app/demo-app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { FormsModule } from '@angular/forms';
3 | import { BrowserModule } from '@angular/platform-browser';
4 |
5 | import { DemoAppComponent } from './demo-app.component';
6 | //import { GanttModule } from 'ng2-gantt';
7 | import { GanttModule } from '../lib'
8 |
9 | // import { TreeModule } from 'angular2-tree-component';
10 |
11 | @NgModule({
12 | imports: [
13 | BrowserModule,
14 | FormsModule,
15 | GanttModule,
16 | // TreeModule
17 | ],
18 | exports: [],
19 | declarations: [
20 | DemoAppComponent,
21 | ],
22 | providers: [],
23 | bootstrap: [DemoAppComponent]
24 | })
25 | export class DemoAppModule { }
26 |
--------------------------------------------------------------------------------
/src/demo-app/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/demo-app/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/demo-app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular 2 Gantt
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Loading...
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/demo-app/index.ts:
--------------------------------------------------------------------------------
1 | export * from './demo-app.component';
2 | export * from './demo-app.module';
3 |
--------------------------------------------------------------------------------
/src/demo-app/main.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.ts';
2 |
3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4 | import { enableProdMode } from '@angular/core';
5 | import { environment } from './environments/environment';
6 | import { DemoAppModule } from './demo-app.module';
7 |
8 | if (environment.production) {
9 | enableProdMode();
10 | }
11 |
12 | platformBrowserDynamic().bootstrapModule(DemoAppModule);
13 |
--------------------------------------------------------------------------------
/src/demo-app/polyfills.ts:
--------------------------------------------------------------------------------
1 | // This file includes polyfills needed by Angular 2 and is loaded before
2 | // the app. You can add your own extra polyfills to this file.
3 | import 'core-js/es6/symbol';
4 | import 'core-js/es6/object';
5 | import 'core-js/es6/function';
6 | import 'core-js/es6/parse-int';
7 | import 'core-js/es6/parse-float';
8 | import 'core-js/es6/number';
9 | import 'core-js/es6/math';
10 | import 'core-js/es6/string';
11 | import 'core-js/es6/date';
12 | import 'core-js/es6/array';
13 | import 'core-js/es6/regexp';
14 | import 'core-js/es6/map';
15 | import 'core-js/es6/set';
16 | import 'core-js/es6/reflect';
17 |
18 | import 'core-js/es7/reflect';
19 | import 'zone.js/dist/zone';
20 |
--------------------------------------------------------------------------------
/src/demo-app/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/src/demo-app/test.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.ts';
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 |
10 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
11 | declare var __karma__: any;
12 | declare var require: any;
13 |
14 | // Prevent Karma from running prematurely.
15 | __karma__.loaded = function () {};
16 |
17 |
18 | Promise.all([
19 | System.import('@angular/core/testing'),
20 | System.import('@angular/platform-browser-dynamic/testing')
21 | ])
22 | // First, initialize the Angular testing environment.
23 | .then(([testing, testingBrowser]) => {
24 | testing.getTestBed().initTestEnvironment(
25 | testingBrowser.BrowserDynamicTestingModule,
26 | testingBrowser.platformBrowserDynamicTesting()
27 | );
28 | })
29 | // Then we find all the tests.
30 | .then(() => require.context('./', true, /\.spec\.ts/))
31 | // And load the modules.
32 | .then(context => context.keys().map(context))
33 | // Finally, start Karma to run the tests.
34 | .then(__karma__.start, __karma__.error);
35 |
--------------------------------------------------------------------------------
/src/demo-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "emitDecoratorMetadata": true,
5 | "experimentalDecorators": true,
6 | "lib": ["es6", "dom"],
7 | "mapRoot": "./",
8 | "module": "es6",
9 | "moduleResolution": "node",
10 | "outDir": "../../dist/out-tsc",
11 | "sourceMap": true,
12 | "target": "es5",
13 | "typeRoots": [
14 | "../../node_modules/@types"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/demo-app/typings.d.ts:
--------------------------------------------------------------------------------
1 | // Typings reference file, you can add your own global typings here
2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
3 |
4 | declare var System: any;
5 |
--------------------------------------------------------------------------------
/src/lib/activity/background/activity-background.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
2 | import { GanttService } from '../../shared/services/gantt.service';
3 | import { Zooming } from '../../shared/interfaces';
4 |
5 | @Component({
6 | selector: 'activity-background',
7 | template: `
8 |
13 | `,
14 | styles: [`
15 | .gantt_activity_bg {
16 | overflow: hidden;
17 | }
18 |
19 | .gantt_activity_row {
20 | border-bottom: 1px solid #ebebeb;
21 | background-color: #fff;
22 | box-sizing: border-box;
23 | }
24 |
25 | .gantt_activity_cell {
26 | display: inline-block;
27 | height: 100%;
28 | border-right: 1px solid #ebebeb;
29 | }
30 |
31 | .weekend {
32 | background-color:whitesmoke;
33 | }
34 | `]
35 | })
36 | export class GanttActivityBackgroundComponent implements OnInit {
37 | @Input() tasks: any;
38 | @Input() timeScale:any;
39 | @Input() zoom: any;
40 | @Input() zoomLevel: string;
41 |
42 | @ViewChild('bg') bg: ElementRef;
43 |
44 | private rows: any[] = [];
45 | private cells: any[] = [];
46 |
47 | constructor(private ganttService: GanttService) { }
48 |
49 | ngOnInit() {
50 | this.drawGrid();
51 |
52 | this.zoom.subscribe((zoomLevel: string) => {
53 | this.zoomLevel = zoomLevel;
54 | this.drawGrid();
55 | });
56 | }
57 |
58 | isDayWeekend(date: Date): boolean {
59 | return this.ganttService.isDayWeekend(date);
60 | }
61 |
62 | private setRowStyle() {
63 | return {
64 | 'height': this.ganttService.rowHeight + 'px'
65 | };
66 | }
67 |
68 | private setCellStyle() {
69 | var width = this.ganttService.cellWidth;
70 |
71 | if (this.zoomLevel === Zooming[Zooming.hours]) {
72 | width = this.ganttService.hourCellWidth;
73 | }
74 |
75 | return {
76 | 'width': width + 'px'
77 | };
78 | }
79 |
80 | private drawGrid(): void {
81 | if (this.zoomLevel === Zooming[Zooming.hours]) {
82 | this.cells = [];
83 |
84 | this.timeScale.forEach((date: any) => {
85 | for (var i = 0; i <= 23; i++) {
86 | this.cells.push(date);
87 | }
88 | });
89 | } else {
90 | this.cells = this.timeScale;
91 | }
92 | }
93 | }
94 |
95 |
96 | //TODO(dale): replace with either svg or canvas
97 | // exceeding the maximum length/width/area on most browsers renders the canvas
98 | // unusable (it will ignore any draw commands, even in the usable area)
99 | // private drawGrid2(): void {
100 | // this.bg.nativeElement.innerHTML = '';
101 | // //grid width and height
102 | // var bw = 1384; // maximum width 16384 CHROME
103 | // var bh = 300; //this.project.tasks.length * this.ganttService.rowHeight;
104 | // var rowHeight = this.ganttService.rowHeight;
105 | // var cellWidth = 0;
106 |
107 | // if (this.zoomLevel === Zooming[Zooming.hours]) {
108 | // cellWidth = this.ganttService.hourCellWidth;
109 | // } else {
110 | // cellWidth = this.ganttService.cellWidth;
111 | // }
112 |
113 | // var canvas = document.createElement('canvas');
114 | // canvas.setAttribute("width", bw.toString());
115 | // canvas.setAttribute("height", bh.toString());
116 | // var context = canvas.getContext("2d");
117 |
118 | // var lineSpacer = 0;
119 | // // vertical lines
120 | // for (var x = 0; x <= bw; x += cellWidth) {
121 | // lineSpacer += cellWidth / cellWidth;
122 |
123 | // context.moveTo(x + lineSpacer - 1.5, 0);
124 | // context.lineTo(x + lineSpacer - 1.5, bh);
125 | // }
126 |
127 | // // horizontal lines
128 | // for (var x = 0; x <= bh; x += rowHeight) {
129 | // context.moveTo(0, x);
130 | // context.lineTo(bw , x);
131 | // }
132 |
133 | // context.strokeStyle = "#e0e0e0";
134 | // context.stroke();
135 |
136 | // this.bg.nativeElement.append(canvas);
137 | // }
138 |
139 |
140 | // var canvas = $('').attr({width: cw, height: ch}).appendTo('body');
141 |
142 | // var context = canvas.get(0).getContext("2d");
143 |
144 | // function drawBoard(){
145 | // for (var x = 0; x <= bw; x += 40) {
146 | // context.moveTo(0.5 + x + p, p);
147 | // context.lineTo(0.5 + x + p, bh + p);
148 | // }
149 |
150 |
151 | // for (var x = 0; x <= bh; x += 40) {
152 | // context.moveTo(p, 0.5 + x + p);
153 | // context.lineTo(bw + p, 0.5 + x + p);
154 | // }
155 |
156 | // context.strokeStyle = "black";
157 | // context.stroke();
158 | // }
159 |
160 | // drawBoard();
--------------------------------------------------------------------------------
/src/lib/activity/bars/activity-bars.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, ElementRef } from '@angular/core';
2 | import { GanttService } from '../../shared/services/gantt.service';
3 | import { Zooming } from '../../shared/interfaces';
4 |
5 | @Component({
6 | selector: 'activity-bars',
7 | template: `
8 |
9 |
10 |
11 |
12 |
13 |
16 |
19 |
20 |
21 | `,
22 | styles: [`
23 | .gantt_activity_line {
24 | /*border-radius: 2px;*/
25 | position: absolute;
26 | box-sizing: border-box;
27 | background-color: rgb(18,195,244);
28 | border: 1px solid #2196F3;
29 | -webkit-user-select: none;
30 | }
31 |
32 | .gantt_activity_line:hover {
33 | /*cursor: move;*/
34 | }
35 |
36 | .gantt_activity_progress {
37 | text-align: center;
38 | z-index: 0;
39 | background: #2196F3;
40 | position: absolute;
41 | min-height: 18px;
42 | display: block;
43 | height: 18px;
44 | }
45 |
46 | .gantt_activity_progress_drag {
47 | height: 8px;
48 | width: 8px;
49 | bottom: -4px;
50 | margin-left: 4px;
51 | background-position: bottom;
52 | background-image: "";
53 | background-repeat: no-repeat;
54 | z-index: 2;
55 | }
56 |
57 | .gantt_activity_content {
58 | font-size: 12px;
59 | color: #fff;
60 | width: 100%;
61 | top: 0;
62 | position: absolute;
63 | white-space: nowrap;
64 | text-align: center;
65 | line-height: inherit;
66 | overflow: hidden;
67 | height: 100%;
68 | }
69 |
70 | .gantt_activity_link_control {
71 | position: absolute;
72 | width: 13px;
73 | top: 0;
74 | }
75 |
76 | .gantt_activity_right {
77 | right: 0;
78 | }
79 |
80 | .gantt_activity_left {
81 | left: 0;
82 | }
83 |
84 | .gantt_activity_right:hover {
85 | cursor:w-resize;
86 | }
87 |
88 | .gantt_activity_left:hover {
89 | cursor:w-resize;
90 | }
91 | `],
92 | providers: [
93 | GanttService
94 | ]
95 | })
96 | export class GanttActivityBarsComponent implements OnInit {
97 | @Input() timeScale: any;
98 | @Input() dimensions: any;
99 | @Input() tasks: any;
100 | @Input() zoom: any;
101 | @Input() zoomLevel: any;
102 |
103 | private containerHeight: number = 0;
104 | private containerWidth: number = 0;
105 |
106 | constructor(private ganttService: GanttService) { }
107 |
108 | ngOnInit() {
109 | this.containerHeight = this.dimensions.height;
110 | this.containerWidth = this.dimensions.width;
111 |
112 | this.zoom.subscribe((zoomLevel: string) => {
113 | this.zoomLevel = zoomLevel;
114 | });;
115 | }
116 |
117 | //TODO(dale): the ability to move bars needs reviewing and there are a few quirks
118 | expandLeft($event: any, bar: any) {
119 | $event.stopPropagation();
120 |
121 | let ganttService = this.ganttService;
122 | let startX = $event.clientX;
123 | let startBarWidth = bar.style.width;
124 | let startBarLeft = bar.style.left;
125 |
126 | function doDrag(e: any) {
127 | let cellWidth = ganttService.cellWidth;
128 | let barWidth = startBarWidth - e.clientX + startX;
129 | let days = Math.round(barWidth / cellWidth);
130 |
131 | bar.style.width = days * cellWidth + days;
132 | bar.style.left = (startBarLeft - (days * cellWidth) - days);
133 | }
134 |
135 | this.addMouseEventListeners(doDrag);
136 |
137 | return false;
138 | }
139 |
140 | expandRight($event: any, bar: any) {
141 | $event.stopPropagation();
142 |
143 | let ganttService = this.ganttService;
144 | let startX = $event.clientX;
145 | let startBarWidth = bar.style.width;
146 | let startBarEndDate = bar.task.end;
147 | let startBarLeft = bar.style.left;
148 |
149 | function doDrag(e: any) {
150 | let cellWidth = ganttService.cellWidth;
151 | let barWidth = startBarWidth + e.clientX - startX;
152 | let days = Math.round(barWidth / cellWidth);
153 |
154 | if (barWidth < cellWidth) {
155 | barWidth = cellWidth;
156 | days = Math.round(barWidth / cellWidth);
157 | }
158 | bar.style.width = ((days * cellWidth) + days); // rounds to the nearest cell
159 | }
160 |
161 | this.addMouseEventListeners(doDrag);
162 |
163 | return false;
164 | }
165 |
166 | move($event: any, bar: any) {
167 | $event.stopPropagation();
168 |
169 | let ganttService = this.ganttService;
170 | let startX = $event.clientX;
171 | let startBarLeft = bar.style.left;
172 |
173 | function doDrag(e: any) {
174 | let cellWidth = ganttService.cellWidth;
175 | let barLeft = startBarLeft + e.clientX - startX;
176 | let days = Math.round(barLeft / cellWidth);
177 |
178 | // TODO: determine how many days the bar can be moved
179 | // if (days < maxDays) {
180 | bar.style.left = ((days * cellWidth) + days); // rounded to nearest cell
181 |
182 | // keep bar in bounds of grid
183 | if (barLeft < 0) {
184 | bar.style.left = 0;
185 | }
186 | // }
187 | // TODO: it needs to take into account the max number of days.
188 | // TODO: it needs to take into account the current days.
189 | // TODO: it needs to take into account the right boundary.
190 | }
191 |
192 | this.addMouseEventListeners(doDrag);
193 |
194 | return false;
195 | }
196 |
197 | private drawBar(task: any, index: number) {
198 | let style = {};
199 |
200 | if (this.zoomLevel === Zooming[Zooming.hours]) {
201 | style = this.ganttService.calculateBar(task, index, this.timeScale, true);
202 | } else {
203 | style = this.ganttService.calculateBar(task, index, this.timeScale);
204 | }
205 | return style;
206 | }
207 |
208 | private drawProgress(task: any, bar: any):any {
209 | var barStyle = this.ganttService.getBarProgressStyle(task.status);
210 | var width = this.ganttService.calculateBarProgress(this.ganttService.getComputedStyle(bar, 'width'), task.percentComplete);
211 |
212 | return {
213 | 'width': width,
214 | 'background-color': barStyle["background-color"],
215 | };
216 | }
217 |
218 | private addMouseEventListeners(dragFn: any) {
219 |
220 | function stopFn() {
221 | document.documentElement.removeEventListener('mousemove', dragFn, false);
222 | document.documentElement.removeEventListener('mouseup', stopFn, false);
223 | document.documentElement.removeEventListener('mouseleave', stopFn, false);
224 | }
225 |
226 | document.documentElement.addEventListener('mousemove', dragFn, false);
227 | document.documentElement.addEventListener('mouseup', stopFn, false);
228 | document.documentElement.addEventListener('mouseleave', stopFn, false);
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/lib/activity/gantt-activity.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, Output, EventEmitter, ElementRef, AfterViewInit, ViewChild, ChangeDetectionStrategy, OnChanges, DoCheck } from '@angular/core';
2 |
3 | import { GanttService } from '../shared/services/gantt.service';
4 | import { GanttConfig } from '../shared/services/gantt-config.service';
5 | import { IGanttOptions, Zooming } from '../shared/interfaces';
6 |
7 | @Component({
8 | selector: 'gantt-activity',
9 | template: `
10 |
11 |
12 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
38 |
39 |
40 |
{{ data.percentComplete }}
41 |
42 |
43 |
{{ ganttService.calculateDuration(data) }}
44 |
45 |
46 |
47 |
54 |
57 | `,
58 | styles: [`
59 | .gantt_activity {
60 | /*overflow-x: hidden;*/
61 | overflow-x: auto;
62 | height: 250px;
63 | overflow-y: hidden;
64 | overflow-x: scroll;
65 | display: inline-block;
66 | vertical-align: top;
67 | position:relative;
68 | }
69 |
70 | .gantt_activity_area {
71 | position: relative;
72 | overflow-x: hidden;
73 | overflow-y: hidden;
74 | -webkit-user-select: none;
75 | }
76 |
77 | .gantt_vertical_scroll {
78 | background-color: transparent;
79 | overflow-x: hidden;
80 | overflow-y: scroll;
81 | position: absolute;
82 | right: 0;
83 | display: block;
84 | height: 283px;
85 | width: 18px;
86 | top: 70px;
87 | }
88 |
89 | .grid {
90 | overflow-x: hidden;
91 | overflow-y: hidden;
92 | display: inline-block;
93 | vertical-align: top;
94 | border-right: 1px solid #cecece;
95 | }
96 |
97 | .grid_scale {
98 | color: #6b6b6b;
99 | font-size: 12px;
100 | border-bottom: 1px solid #e0e0e0;
101 | background-color: whitesmoke;
102 | }
103 |
104 | .grid_head_cell {
105 | /*color: #a6a6a6;*/
106 | border-top: none !important;
107 | border-right: none !important;
108 | line-height: inherit;
109 | box-sizing: border-box;
110 | display: inline-block;
111 | vertical-align: top;
112 | border-right: 1px solid #cecece;
113 | /*text-align: center;*/
114 | position: relative;
115 | cursor: default;
116 | height: 100%;
117 | -moz-user-select: -moz-none;
118 | -webkit-user-select: none;
119 | overflow: hidden;
120 | }
121 |
122 | .grid_data {
123 | overflow:hidden;
124 | }
125 |
126 | .grid_row {
127 | box-sizing: border-box;
128 | border-bottom: 1px solid #e0e0e0;
129 | background-color: #fff;
130 | position: relative;
131 | -webkit-user-select: none;
132 | }
133 |
134 | .grid_row:hover {
135 | background-color: #eeeeee;
136 | }
137 |
138 | .grid_cell {
139 | border-right: none;
140 | color: #454545;
141 | display: inline-block;
142 | vertical-align: top;
143 | padding-left: 6px;
144 | padding-right: 6px;
145 | height: 100%;
146 | overflow: hidden;
147 | white-space: nowrap;
148 | font-size: 13px;
149 | box-sizing: border-box;
150 | }
151 |
152 | .actions_bar {
153 | /*border-top: 1px solid #cecece;*/
154 | border-bottom: 1px solid #e0e0e0;
155 | clear: both;
156 | /*margin-top: 90px;*/
157 | height: 28px;
158 | background: whitesmoke;
159 | color: #494949;
160 | font-family: Arial, sans-serif;
161 | font-size: 13px;
162 | padding-left: 15px;
163 | line-height: 25px;
164 | }
165 |
166 | .gantt_tree_content {
167 | padding-left:15px;
168 | }
169 | `],
170 | changeDetection: ChangeDetectionStrategy.Default
171 | })
172 | export class GanttActivityComponent implements OnInit, DoCheck {
173 | @Input() project: any;
174 | @Input() options: any;
175 | @Output() onGridRowClick: EventEmitter = new EventEmitter();
176 |
177 | private upTriangle: string = '▲' // BLACK UP-POINTING TRIANGLE
178 | private downTriangle: string = '▼'; // BLACK DOWN-POINTING TRIANGLE
179 | private zoom: EventEmitter = new EventEmitter();
180 | private activityActions = {
181 | expanded: false,
182 | expandedIcon: this.downTriangle
183 | }
184 |
185 | private timeScale: any;
186 |
187 | private start: Date;
188 | private end: Date;
189 | private containerHeight: any;
190 | private containerWidth: any;
191 |
192 | private activityContainerSizes: any;
193 | private ganttActivityHeight: any;
194 | private ganttActivityWidth: any;
195 | private zoomLevel: string = Zooming[Zooming.hours];
196 |
197 | private treeExpanded = false;
198 |
199 | private scale: any = {
200 | start: null,
201 | end: null
202 | };
203 |
204 | private dimensions = {
205 | height: 0,
206 | width: 0
207 | };
208 |
209 | private data: any[] = [];
210 |
211 | public gridColumns: any[] = [
212 | { name: '', left: 0, width: 16 },
213 | { name: 'Task', left: 20, width: 330 },
214 | { name: '%', left: 8, width: 40 },
215 | { name: 'Duration', left: 14, width: 140 }
216 | ];
217 |
218 | constructor(
219 | public elem: ElementRef,
220 | private ganttService: GanttService) {
221 | }
222 |
223 | ngOnInit() {
224 | // Cache the project data and only work with that. Only show parent tasks by default
225 | this.ganttService.TASK_CACHE = this.project.tasks.slice(0).filter((item: any) => {
226 | return item.treePath.split('/').length === 1;
227 | });
228 | this.ganttService.TIME_SCALE = this.ganttService.calculateScale(this.options.scale.start, this.options.scale.end);
229 |
230 | this.zoomLevel = this.options.zooming;
231 | this.start = this.options.scale.start;
232 | this.end = this.options.scale.end;
233 | this.containerWidth = this.calculateContainerWidth();
234 | this.containerHeight = this.calculateContainerHeight();
235 | this.activityContainerSizes = this.ganttService.calculateActivityContainerDimensions();
236 |
237 | // important that these are called last as it relies on values calculated above.
238 | this.setScale();
239 | this.setDimensions();
240 | this.setSizes();
241 |
242 | this.expand(); // default to expanded
243 | }
244 |
245 | /** Custom model check */
246 | ngDoCheck() {
247 | // do a check to see whether any new tasks have been added. If the task is a child then push into array if tree expanded?
248 | var tasksAdded = this.ganttService.doTaskCheck(this.project.tasks, this.treeExpanded);
249 |
250 | // only force expand if tasks are added and tree is already expanded
251 | if (tasksAdded && this.activityActions.expanded) {
252 | this.expand(true);
253 | }
254 | }
255 |
256 | /** On vertical scroll set the scroll top of grid and activity */
257 | onVerticalScroll(verticalScroll: any, ganttGrid: any, ganttActivityArea: any): void {
258 | this.ganttService.scrollTop(verticalScroll, ganttGrid, ganttActivityArea);
259 | }
260 |
261 | /** Removes or adds children for given parent tasks back into DOM by updating TASK_CACHE */
262 | toggleChildren(rowElem: any, task: any) {
263 | try {
264 | let isParent: boolean = "true" === rowElem.getAttribute('data-isparent');
265 | let parentId: string = rowElem.getAttribute('data-parentid').replace("_", ""); // remove id prefix
266 | let children: any = document.querySelectorAll('[data-parentid=' + rowElem.getAttribute('data-parentid') + '][data-isparent=false]');
267 |
268 | // use the task cache to allow deleting of items without polluting the project.tasks array
269 | if (isParent) {
270 | // remove children from the DOM as we don't want them if we are collapsing the parent
271 | if (children.length > 0) {
272 | let childrenIds: any[] = this.ganttService.TASK_CACHE.filter((task: any) => {
273 | return task.parentId == parentId && task.treePath.split('/').length > 1;
274 | }).map((item: any) => { return item.id });
275 |
276 | childrenIds.forEach((item: any) => {
277 | var removedIndex = this.ganttService.TASK_CACHE.map((item: any) => { return item.id }).indexOf(item);
278 |
279 | this.ganttService.TASK_CACHE.splice(removedIndex, 1);
280 | });
281 |
282 | if (this.activityActions.expanded) {
283 | this.expand(true);
284 | }
285 |
286 | } else {
287 | // CHECK the project cache to see if this parent id has any children
288 | // and if so push them back into array so DOM is updated
289 | let childrenTasks: any[] = this.project.tasks.filter((task: any) => {
290 | return task.parentId === parentId && task.treePath.split('/').length > 1;
291 | });
292 |
293 | childrenTasks.forEach((task: any) => {
294 | this.ganttService.TASK_CACHE.push(task);
295 | });
296 |
297 | if (this.activityActions.expanded) {
298 | this.expand(true);
299 | }
300 | }
301 | }
302 |
303 | this.onGridRowClick.emit(task);
304 |
305 | } catch (err) { }
306 | }
307 |
308 | /** Removes or adds children tasks back into DOM by updating TASK_CACHE */
309 | toggleAllChildren() {
310 | try {
311 | var children: any = document.querySelectorAll('[data-isparent=false]');
312 | var childrenIds: string[] = Array.prototype.slice.call(children).map((item: any) => {
313 | return item.getAttribute('data-id').replace("_", ""); // remove id prefix
314 | });
315 |
316 | // push all the children array items into cache
317 | if (this.treeExpanded) {
318 | if (children.length > 0) {
319 | let childrenIds: string[] = this.ganttService.TASK_CACHE.filter((task: any) => {
320 | return task.treePath.split('/').length > 1;
321 | }).map((item: any) => { return item.id });
322 |
323 | childrenIds.forEach((item: any) => {
324 | var removedIndex = this.ganttService.TASK_CACHE.map((item: any) => { return item.id }).indexOf(item);
325 | this.ganttService.TASK_CACHE.splice(removedIndex, 1);
326 | });
327 | }
328 |
329 | this.treeExpanded = false;
330 |
331 | if (this.activityActions.expanded) {
332 | this.expand(true);
333 | }
334 | } else {
335 | // get all children tasks in project input
336 | let childrenTasks: any[] = this.project.tasks.filter((task: any) => {
337 | return task.treePath.split('/').length > 1;
338 | });
339 |
340 | if (children.length > 0) {
341 | // filter out these children as they already exist in task cache
342 | childrenTasks = childrenTasks.filter((task: any) => {
343 | return childrenIds.indexOf(task.id) === -1;
344 | });
345 | }
346 |
347 | childrenTasks.forEach((task: any) => {
348 | this.ganttService.TASK_CACHE.push(task);
349 | });
350 |
351 | this.treeExpanded = true;
352 |
353 | if (this.activityActions.expanded) {
354 | this.expand(true);
355 | }
356 | }
357 | } catch (err) { }
358 | }
359 |
360 | /** On resize of browser window dynamically adjust gantt activity height and width */
361 | onResize(event: any): void {
362 | let activityContainerSizes = this.ganttService.calculateActivityContainerDimensions();
363 | if (this.activityActions.expanded) {
364 | this.ganttActivityHeight = this.ganttService.TASK_CACHE.length * this.ganttService.rowHeight + this.ganttService.rowHeight * 3 + 'px';
365 | } else {
366 | this.ganttActivityHeight = activityContainerSizes.height + 'px';;
367 | }
368 |
369 | this.ganttActivityWidth = activityContainerSizes.width;
370 | }
371 |
372 | setScale() {
373 | this.scale.start = this.start;
374 | this.scale.end = this.end;
375 | }
376 |
377 | setDimensions() {
378 | this.dimensions.height = this.containerHeight;
379 | this.dimensions.width = this.containerWidth;
380 | }
381 |
382 | setGridRowStyle(isParent: boolean): any {
383 | if (isParent) {
384 | return {
385 | 'height': this.ganttService.rowHeight + 'px',
386 | 'line-height': this.ganttService.rowHeight + 'px',
387 | 'font-weight': 'bold',
388 | 'cursor': 'pointer'
389 | };
390 | }
391 |
392 | return {
393 | 'height': this.ganttService.rowHeight + 'px',
394 | 'line-height': this.ganttService.rowHeight + 'px'
395 | };
396 | }
397 |
398 | /** Set the zoom level e.g hours, days */
399 | zoomTasks(level: string) {
400 | this.zoomLevel = level;
401 | this.zoom.emit(this.zoomLevel);
402 | this.containerWidth = this.calculateContainerWidth();
403 | this.setDimensions();
404 | document.querySelector('.gantt_activity').scrollLeft = 0 // reset scroll left, replace with @ViewChild?
405 | }
406 |
407 | /** Expand the gantt grid and activity area height */
408 | expand(force?: boolean): void {
409 | var verticalScroll = document.querySelector('.gantt_vertical_scroll');
410 | var ganttActivityHeight: string = `${this.ganttService.TASK_CACHE.length * this.ganttService.rowHeight + this.ganttService.rowHeight * 3}px`;
411 |
412 | if (force && this.activityActions.expanded) {
413 | this.ganttActivityHeight = ganttActivityHeight;
414 | } else if (this.activityActions.expanded) {
415 | this.activityActions.expanded = false;
416 | this.activityActions.expandedIcon = this.downTriangle;
417 | this.ganttActivityHeight = '300px';
418 | } else {
419 | verticalScroll.scrollTop = 0;
420 |
421 | this.activityActions.expanded = true;
422 | this.activityActions.expandedIcon = this.upTriangle;
423 | this.ganttActivityHeight = ganttActivityHeight;
424 | }
425 | }
426 |
427 | /** Get the status icon unicode string */
428 | getStatusIcon(status: string, percentComplete: number): string {
429 | var checkMarkIcon: string = '✔';
430 | var upBlackPointer: string = '▲';
431 | var crossMarkIcon: string = '✘';
432 |
433 | if (status === "Completed" || percentComplete === 100 && status !== "Error") {
434 | return checkMarkIcon;
435 | } else if (status === "Warning") {
436 | return upBlackPointer;
437 | } else if (status === "Error") {
438 | return crossMarkIcon;
439 | }
440 | return '';
441 | }
442 |
443 | /** Get the status icon color */
444 | getStatusIconColor(status: string, percentComplete: number): string {
445 | if (status === "Completed" || percentComplete === 100 && status !== "Error") {
446 | return 'green';
447 | } else if (status === "Warning") {
448 | return 'orange';
449 | } else if (status === "Error") {
450 | return 'red';
451 | }
452 | return '';
453 | }
454 |
455 | private setGridScaleStyle() {
456 | var height = this.ganttService.rowHeight;
457 |
458 | if (this.zoomLevel === Zooming[Zooming.hours]) {
459 | height *= 2;
460 | }
461 |
462 | return {
463 | 'height': height + 'px',
464 | 'line-height': height + 'px',
465 | 'width': this.ganttService.gridWidth + 'px'
466 | };
467 | }
468 |
469 | private calculateContainerHeight(): number {
470 | return this.ganttService.TASK_CACHE.length * this.ganttService.rowHeight;
471 | }
472 |
473 | private calculateContainerWidth(): number {
474 | if (this.zoomLevel === Zooming[Zooming.hours]) {
475 | return this.ganttService.TIME_SCALE.length * this.ganttService.hourCellWidth * 24 + this.ganttService.hourCellWidth
476 | } else {
477 | return this.ganttService.TIME_SCALE.length * this.ganttService.cellWidth + this.ganttService.cellWidth;
478 | }
479 | }
480 |
481 | private setSizes(): void {
482 | this.ganttActivityHeight = this.activityContainerSizes.height + 'px';
483 | this.ganttActivityWidth = this.activityContainerSizes.width;
484 | }
485 |
486 | }
487 |
488 |
489 | //** */
490 | // @Component({
491 | // selector: 'tree-builder',
492 | // template: ''
493 | // })
494 | // export class TreeBuilder {
495 | // _nodes: any = [
496 | // {
497 | // name: "testing", children: [
498 | // { name: "testing2", children: [{ name: "testing4", children: [] }] }
499 | // ]
500 | // },
501 | // {
502 | // name: "testing3", children: []
503 | // }
504 | // ];
505 |
506 | // @Input() nodes: any;
507 | // }
508 |
509 | // @Component({
510 | // selector: 'tree-parent-repeater',
511 | // styles: [`
512 | // .grid_row {
513 | // box-sizing: border-box;
514 | // border-bottom: 1px solid #e0e0e0;
515 | // background-color: #fff;
516 | // position: relative;
517 | // -webkit-user-select: none;
518 | // cursor:pointer;
519 | // height:25px;
520 | // line-height:25px;
521 | // font-size:16px;
522 | // }
523 |
524 | // .grid_row:hover {
525 | // background-color: whitesmoke;
526 | // }
527 | // `],
528 | // template: `
529 | //
530 | //
531 | //
{{ expanded !== true ? '▶' : '▼' }}
532 | //
{{ node.name }}
533 | //
{{ node.percentComplete }}
534 | //
0
535 | //
536 | //
537 | //
538 | // `
539 | // })
540 | // export class TreeParentRepeater {
541 | // @Input() node: any;
542 | // expanded: boolean = true;
543 |
544 | // toggle = () => {
545 | // if (this.expanded) {
546 | // this.expanded = false;
547 | // } else {
548 | // this.expanded = true;
549 | // }
550 | // }
551 | // }
552 |
553 | // @Component({
554 | // selector: 'tree-children-repeater',
555 | // styles: [`
556 | // .grid_row {
557 | // box-sizing: border-box;
558 | // border-bottom: 1px solid #e0e0e0;
559 | // background-color: #fff;
560 | // position: relative;
561 | // -webkit-user-select: none;
562 | // cursor:pointer;
563 | // height:25px;
564 | // line-height:25px;
565 | // font-size:16px;
566 | // }
567 |
568 | // .grid_row:hover {
569 | // background-color: whitesmoke;
570 | // }
571 | // `],
572 | // template: `
573 | // 0">
574 | //
575 | //
576 | //
{{ expanded !== true ? '▶' : '▼' }}
577 | //
{{ child.name }}
578 | //
{{ child.percentComplete }}
579 | //
0
580 | //
581 | //
582 | //
583 | //
584 | // `
585 | // })
586 | // export class TreeChildrenRepeater {
587 | // @Input() root: any;
588 | // expanded: boolean = true;
589 |
590 | // toggle = () => {
591 | // if (this.expanded) {
592 | // this.expanded = false;
593 | // } else {
594 | // this.expanded = true;
595 | // }
596 | // }
597 | // }
--------------------------------------------------------------------------------
/src/lib/activity/gantt-activity.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { GanttActivityComponent } from './gantt-activity.component';
5 | import { GanttTimeScaleComponent } from './time-scale/gantt-time-scale.component';
6 | import { GanttActivityBackgroundComponent } from './background/activity-background.component';
7 | import { GanttActivityBarsComponent } from './bars/activity-bars.component';
8 |
9 | @NgModule({
10 | imports: [
11 | CommonModule,
12 | ],
13 | exports: [
14 | GanttActivityComponent,
15 | GanttTimeScaleComponent,
16 | GanttActivityBackgroundComponent,
17 | GanttActivityBarsComponent
18 | ],
19 | declarations: [
20 | GanttActivityComponent,
21 | GanttTimeScaleComponent,
22 | GanttActivityBackgroundComponent,
23 | GanttActivityBarsComponent,
24 | ///TreeBuilder, TreeParentRepeater, TreeChildrenRepeater,
25 | ],
26 | providers: [],
27 | })
28 | export class GanttActivityModule { }
29 |
--------------------------------------------------------------------------------
/src/lib/activity/time-scale/gantt-time-scale.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, EventEmitter, ViewChild } from '@angular/core';
2 | import { GanttService } from '../../shared/services/gantt.service';
3 | import { Zooming } from '../../shared/interfaces';
4 |
5 | @Component({
6 | selector: 'time-scale',
7 | template: `
8 |
9 |
10 |
{{date | date: 'dd-MM-yyyy'}}
11 |
12 |
15 |
`,
16 | styles: [`
17 | .weekend {
18 | background-color:whitesmoke;
19 | }
20 |
21 | .time_scale {
22 | font-size: 12px;
23 | border-bottom: 1px solid #cecece;
24 | background-color: #fff;
25 | }
26 |
27 | .time_scale_line {
28 | box-sizing: border-box;
29 | }
30 |
31 | .time_scale_line:first-child {
32 | border-top: none;
33 | }
34 |
35 | .time_scale_cell {
36 | display: inline-block;
37 | white-space: nowrap;
38 | overflow: hidden;
39 | border-right: 1px solid #cecece;
40 | text-align: center;
41 | height: 100%;
42 | }`
43 | ],
44 | providers: [
45 | GanttService
46 | ]
47 | })
48 | export class GanttTimeScaleComponent implements OnInit {
49 | @Input() timeScale: any;
50 | @Input() dimensions: any;
51 | @Input() zoom: any;
52 | @Input() zoomLevel: any;
53 |
54 | constructor(private ganttService: GanttService) { }
55 |
56 | ngOnInit() {
57 | this.zoom.subscribe((zoomLevel: string) => {
58 | this.zoomLevel = zoomLevel;
59 | });;
60 | }
61 |
62 | private setTimescaleStyle() {
63 | return {
64 | 'width': this.dimensions.width + 'px'
65 | };
66 | }
67 |
68 | private setTimescaleLineStyle(borderTop: string) {
69 | return {
70 | 'height': this.ganttService.rowHeight + 'px',
71 | 'line-height': this.ganttService.rowHeight + 'px',
72 | 'position': 'relative',
73 | 'border-top': borderTop
74 | };
75 | }
76 |
77 | private setTimescaleCellStyle() {
78 | var width = this.ganttService.cellWidth;
79 | var hoursInDay = 24;
80 | var hourSeperatorPixels = 23; // we don't include the first
81 |
82 | if(this.zoomLevel === Zooming[Zooming.hours]) {
83 | width = this.ganttService.hourCellWidth * hoursInDay + hourSeperatorPixels;
84 | }
85 |
86 | return {
87 | 'width': width + 'px'
88 | };
89 | }
90 |
91 | private isDayWeekend(date: Date): boolean {
92 | return this.ganttService.isDayWeekend(date);
93 | }
94 |
95 | private getHours(): string[] {
96 | return this.ganttService.getHours(this.timeScale.length);
97 | }
98 | }
--------------------------------------------------------------------------------
/src/lib/footer/gantt-footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'gantt-footer',
5 | template: ``,
6 | styles: [`
7 | .gantt-footer {
8 | background-color: whitesmoke;
9 | height: 36px;
10 | border-top: 1px solid #e0e0e0;
11 | }
12 |
13 | .gantt-footer-actions {
14 | float:right;
15 | }
16 | `]
17 | })
18 | export class GanttFooterComponent {
19 | @Input() project: any;
20 | constructor() { }
21 | }
--------------------------------------------------------------------------------
/src/lib/gantt.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
2 | import { NgStyle } from '@angular/common';
3 | import { GanttService } from './shared/services/gantt.service';
4 | import { IGanttOptions, Project } from './shared/interfaces';
5 |
6 | @Component({
7 | selector: 'gantt',
8 | template: `
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | `,
17 | styles: [`
18 | .gantt_container {
19 | font-family: Arial;
20 | font-size: 13px;
21 | border: 1px solid #cecece;
22 | position: relative;
23 | white-space: nowrap;
24 | margin-top: 0px;
25 | }
26 | `],
27 | providers: []
28 | })
29 | export class GanttComponent implements OnInit {
30 | private _project: Project;
31 | private _options: IGanttOptions;
32 |
33 | //TODO(dale): this may be causing an issue in the tree builder?
34 | @Input()
35 | set project(project: any) {
36 | if (project) {
37 | this._project = project;
38 | } else {
39 | this.setDefaultProject();
40 | }
41 | }
42 | get project() { return this._project };
43 |
44 | @Input()
45 | set options(options: any) {
46 | if (options.scale) {
47 | this._options = options;
48 | } else {
49 | this.setDefaultOptions();
50 | }
51 | }
52 | get options() { return this._options };
53 |
54 | @Output() onGridRowClick: EventEmitter = new EventEmitter();
55 |
56 | private ganttContainerWidth: number;
57 |
58 | constructor(private ganttService: GanttService) { }
59 |
60 | ngOnInit() {
61 |
62 | }
63 |
64 | setSizes(): void {
65 | this.ganttContainerWidth = this.ganttService.calculateContainerWidth();
66 | }
67 |
68 | setDefaultOptions() {
69 | var scale = this.ganttService.calculateGridScale(this._project.tasks);
70 |
71 | this._options = {
72 | scale: scale
73 | }
74 | }
75 |
76 | setDefaultProject() {
77 | this._project = {
78 | id: '',
79 | name: '',
80 | startDate: null,
81 | tasks: []
82 | }
83 | }
84 |
85 | gridRowClicked(task:any) {
86 | this.onGridRowClick.emit(task);
87 | }
88 |
89 | onResize($event: any): void {
90 | this.setSizes();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/lib/gantt.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { GanttComponent } from './gantt.component';
6 | import { GanttHeaderComponent } from './header/gantt-header.component';
7 | import { GanttFooterComponent } from './footer/gantt-footer.component';
8 | import { GanttService } from './shared/services/gantt.service';
9 | import { GanttActivityModule } from './activity/gantt-activity.module';
10 |
11 | import { GroupByPipe } from './shared/pipes/groupBy.pipe';
12 |
13 | @NgModule({
14 | imports: [
15 | CommonModule,
16 | FormsModule,
17 | GanttActivityModule,
18 | ],
19 | exports: [
20 | GanttComponent
21 | ],
22 | declarations: [
23 | GanttComponent,
24 | GanttHeaderComponent,
25 | GanttFooterComponent,
26 | GroupByPipe
27 | ],
28 | providers: [GanttService],
29 | })
30 | export class GanttModule { }
--------------------------------------------------------------------------------
/src/lib/header/gantt-header.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'gantt-header',
5 | template: `
6 |
12 | `,
13 | styles: [`
14 | .gantt-header {
15 | background-color: whitesmoke;
16 | height: 40px;
17 | border-bottom: 1px solid #e0e0e0;
18 | }
19 |
20 | .gantt-header-title {
21 | padding: 12px;
22 | display: flex;
23 | flex-wrap:wrap;
24 | font-family: Arial, Helvetica, sans-serif;
25 | font-size: 16px;
26 | }
27 |
28 | .gantt-header-actions {
29 | display: inline;
30 | float: right;
31 | padding: 6px;
32 | }
33 | `]
34 | })
35 | export class GanttHeaderComponent {
36 | @Input() name:any;
37 | @Input() startDate: Date;
38 | }
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './gantt.module';
2 | export * from './shared/interfaces';
--------------------------------------------------------------------------------
/src/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng2-gantt",
3 | "version": "1.11.1-alpha.9",
4 | "description": "Angular 2 Gantt",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/dalestone/angular2-gantt.git"
8 | },
9 | "private": false,
10 | "keywords": [
11 | "angular",
12 | "material",
13 | "gantt",
14 | "angular2",
15 | "gantt html"
16 | ],
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/dalestone/angular2-gantt/issues"
20 | },
21 | "homepage": "https://github.com/dalestone/angular2-gantt/#readme",
22 | "peerDependencies": {
23 | "@angular/common": "2.4.5",
24 | "@angular/compiler": "2.4.5",
25 | "@angular/core": "2.4.5"
26 | },
27 | "dependencies": {
28 |
29 | }
30 | }
--------------------------------------------------------------------------------
/src/lib/shared/interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface Project {
2 | id: string;
3 | name: string;
4 | startDate?: Date;
5 | tasks: Task[]
6 | }
7 |
8 | export interface Task {
9 | id: string;
10 | treePath: string;
11 | parentId: string;
12 | name: string;
13 | resource?: string;
14 | start: Date;
15 | end?: Date;
16 | percentComplete?: number;
17 | status?: string;
18 | }
19 |
20 | export interface IGanttOptions {
21 | scale?: IScale;
22 | zooming?: string;
23 | }
24 |
25 | export interface IScale {
26 | start?: Date;
27 | end?: Date;
28 | }
29 |
30 | export interface IBarStyle {
31 | status: string;
32 | backgroundColor: string;
33 | border: string;
34 | progressBackgroundColor: string;
35 | }
36 |
37 | export enum Zooming {
38 | hours,
39 | days
40 | }
--------------------------------------------------------------------------------
/src/lib/shared/pipes/groupBy.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | /*
3 | * Group the array by given function
4 | * Takes an array argument that defaults to 1.
5 | * Usage:
6 | * array | groupBy:func()
7 | * Example:
8 | * {{ [ { id: '1'}] | groupBy: }}
9 | * formats to: []
10 | */
11 | @Pipe({name: 'groupBy'})
12 | export class GroupByPipe implements PipeTransform {
13 | transform(array: any[], f: any): any[] {
14 | var groups = {};
15 | array.forEach((o:any) => {
16 | var group = JSON.stringify(f(o));
17 |
18 | groups[group] = groups[group] || [];
19 | groups[group].push(o);
20 | });
21 | return Object.keys(groups).map((group:any) => {
22 | return groups[group];
23 | });
24 | }
25 | }
--------------------------------------------------------------------------------
/src/lib/shared/services/gantt-config.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class GanttConfig {
5 | public cellWidth: number = 76;
6 | public rowHeight: number = 25;
7 | public activityHeight: number = 300;
8 | public barHeight = 20;
9 | public barLineHeight = 20;
10 | public barMoveable = false;
11 | }
12 |
--------------------------------------------------------------------------------
/src/lib/shared/services/gantt.service.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { TestBed, async, inject } from '@angular/core/testing';
3 |
4 | import { GanttService } from './gantt.service';
5 |
6 | describe('Gantt Service', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | providers: [GanttService]
10 | });
11 | });
12 | it('should return day is weekend', inject([GanttService], (ganttService: GanttService) => {
13 | let result = ganttService.isDayWeekend(new Date(2016, 5, 11));
14 | let expectedResult = true;
15 |
16 | expect(result).toBe(expectedResult);
17 | }));
18 | it('should return day is not weekend', inject([GanttService], (ganttService: GanttService) => {
19 | let result = ganttService.isDayWeekend(new Date(2016, 5, 13));
20 | let expectedResult = false;
21 |
22 | expect(result).toBe(expectedResult);
23 | }));
24 | it('should add x days to date', inject([GanttService], (ganttService: GanttService) => {
25 | let result = ganttService.addDays(new Date(2016, 5, 13), 2);
26 | let expectedResult = new Date(2016, 5, 15);
27 |
28 | expect(result).toEqual(expectedResult);
29 | }));
30 | it('should remove x days from date', inject([GanttService], (ganttService: GanttService) => {
31 | let result = ganttService.removeDays(new Date(2016, 5, 20), 2);
32 | let expectedResult = new Date(2016, 5, 18);
33 |
34 | expect(result).toEqual(expectedResult);
35 | }));
36 | it('should return difference in dates in days', inject([GanttService], (ganttService: GanttService) => {
37 | let result = ganttService.calculateDiffDays(new Date(2016, 5, 15), new Date(2016, 5, 20));
38 | let expectedResult = 5;
39 |
40 | expect(result).toBe(expectedResult);
41 | }));
42 | it('should create date range array given start and end dates', inject([GanttService], (ganttService: GanttService) => {
43 | let start = new Date(2016, 5, 15);
44 | let end = new Date(2016, 5, 18);
45 | let result = ganttService.calculateScale(start, end);
46 | let expectedResult = [ new Date(2016, 5, 15), new Date(2016, 5, 16), new Date(2016, 5, 17), new Date(2016, 5, 18)];
47 |
48 | expect(result).toEqual(expectedResult);
49 | }));
50 | it('should calculate the progress bar width', inject([GanttService], (ganttService) => {
51 | let barWidth = 100;
52 | let percentComplete = 50;
53 | let result = ganttService.calculateBarProgress(barWidth, percentComplete);
54 | let expectedResult = 48;
55 |
56 | expect(result).toBe(expectedResult);
57 | }));
58 | });
59 |
60 |
--------------------------------------------------------------------------------
/src/lib/shared/services/gantt.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { GanttConfig } from './gantt-config.service';
3 | import { IBarStyle, Task, IScale, Zooming } from '../interfaces';
4 | import { GroupByPipe } from '../../shared/pipes/groupBy.pipe';
5 |
6 | @Injectable()
7 | export class GanttService {
8 | public rowHeight: number = 0;
9 | public hourCellWidth: number = 60; // change to 60 so minutes can been seen more easily
10 | public hoursCellWidth: number = this.hourCellWidth * 25;
11 | public cellWidth: number = 0;
12 | public windowInnerWidth: number = 0;
13 | public activityHeight: number = 0;
14 | public barHeight: number = 0;
15 | public barLineHeight: number = 0;
16 | public barTop: number = 0;
17 | public barMoveable: boolean = false;
18 | public gridWidth: number = 560;
19 | private barStyles: IBarStyle[] = [
20 | { status: "information", backgroundColor: "rgb(18,195, 244)", border: "1px solid #2196F3", progressBackgroundColor: "#2196F3" },
21 | { status: "warning", backgroundColor: "#FFA726", border: "1px solid #EF6C00", progressBackgroundColor: "#EF6C00" },
22 | { status: "error", backgroundColor: "#EF5350", border: "1px solid #C62828", progressBackgroundColor: "#C62828" },
23 | { status: "completed", backgroundColor: "#66BB6A", border: "1px solid #2E7D32", progressBackgroundColor: "#2E7D32" }
24 | ];
25 | public TASK_CACHE: any[];
26 | public TIME_SCALE: any[];
27 |
28 | constructor() {
29 | let _ganttConfig = new GanttConfig();
30 |
31 | this.rowHeight = _ganttConfig.rowHeight;
32 | this.cellWidth = _ganttConfig.cellWidth;
33 | this.activityHeight = _ganttConfig.activityHeight;
34 | this.barHeight = _ganttConfig.barHeight;
35 | this.barLineHeight = _ganttConfig.barLineHeight;
36 | this.barTop = _ganttConfig.rowHeight;
37 | this.barMoveable = _ganttConfig.barMoveable;
38 | }
39 |
40 | private calculateBarWidth(start: Date, end: Date, hours?: boolean): number {
41 | if (typeof start === "string") {
42 | start = new Date(start);
43 | }
44 |
45 | if (typeof end === "string") {
46 | end = new Date(end);
47 | }
48 |
49 | let days = this.calculateDiffDays(start, end);
50 | let width: number = days * this.cellWidth + days;
51 |
52 | if (hours) {
53 | width = days * this.hourCellWidth * 24 + days * 24;
54 | }
55 |
56 | return width;
57 | }
58 |
59 | private calculateBarLeft(start: Date, scale: any[], hours?: boolean): number {
60 | var left: number = 0;
61 | var hoursInDay: number = 24;
62 |
63 | if (start != null) {
64 | if (typeof start === "string") {
65 | start = new Date();
66 | }
67 |
68 | for (var i = 0; i < scale.length; i++) {
69 | if (start.getDate() === scale[i].getDate()) {
70 | if (hours) {
71 | left = i * hoursInDay * this.hourCellWidth + hoursInDay * i + this.calculateBarLeftDelta(start, hours);
72 | } else {
73 | left = i * this.cellWidth + i + this.calculateBarLeftDelta(start, hours);
74 | }
75 | break;
76 | }
77 | }
78 | }
79 | return left;
80 | }
81 |
82 | /** Calculates the height of the gantt grid, activity and vertical scroll */
83 | public calculateGanttHeight(): string {
84 | return `${this.TASK_CACHE.length * this.rowHeight + this.rowHeight * 3}px`;
85 | }
86 |
87 | private calculateBarLeftDelta(start: Date, hours?: boolean): number {
88 | var offset: number = 0;
89 | var hoursInDay: number = 24;
90 | var minutesInHour: number = 60;
91 | var secondsInHour: number = 3600;
92 | var startHours: number = start.getHours() + start.getMinutes() / minutesInHour + start.getSeconds() / secondsInHour;
93 |
94 | if (hours) {
95 | offset = this.hoursCellWidth / hoursInDay * startHours - startHours;
96 | } else {
97 | offset = this.cellWidth / hoursInDay * startHours;
98 | }
99 | return offset;
100 | }
101 |
102 | public isParent(treePath: string): boolean {
103 |
104 | try {
105 | var depth = treePath.split('/').length;
106 |
107 | if (depth === 1) {
108 | return true;
109 | }
110 | }
111 | catch (err) {
112 | return false;
113 | }
114 | return false;
115 | }
116 |
117 | public isChild(treePath: string) {
118 | if (this.isParent(treePath)) {
119 | return '0px';
120 | }
121 | return '20px';
122 | }
123 |
124 | /** Calculate the bar styles */
125 | public calculateBar(task: any, index: number, scale: any, hours?: boolean) {
126 | var barStyle = this.getBarStyle(task.status);
127 | //console.log(this.barTop * index + 2)
128 | return {
129 | 'top': this.barTop * index + 2 + 'px',
130 | 'left': this.calculateBarLeft(task.start, scale, hours) + 'px',
131 | 'height': this.barHeight + 'px',
132 | 'line-height': this.barLineHeight + 'px',
133 | 'width': this.calculateBarWidth(task.start, task.end, hours) + 'px',
134 | 'background-color': barStyle["background-color"],
135 | 'border': barStyle["border"]
136 | }
137 | }
138 |
139 | /** Get the bar style based on task status */
140 | private getBarStyle(taskStatus: string = ""): any {
141 | var style = {};
142 |
143 | try {
144 | taskStatus = taskStatus.toLowerCase();
145 | } catch (err) {
146 | taskStatus = "";
147 | }
148 |
149 | switch (taskStatus) {
150 | case "information":
151 | style["background-color"] = this.barStyles[0].backgroundColor;
152 | style["border"] = this.barStyles[0].border;
153 | break;
154 | case "warning":
155 | style["background-color"] = this.barStyles[1].backgroundColor;
156 | style["border"] = this.barStyles[1].border;
157 | break;
158 | case "error":
159 | style["background-color"] = this.barStyles[2].backgroundColor;
160 | style["border"] = this.barStyles[2].border;
161 | break;
162 | case "completed":
163 | style["background-color"] = this.barStyles[3].backgroundColor;
164 | style["border"] = this.barStyles[3].border;
165 | break;
166 | default:
167 | style["background-color"] = "rgb(18,195, 244)";
168 | style["border"] = "1px solid #2196F3";
169 | break;
170 | }
171 |
172 | return style;
173 | }
174 |
175 | /** Get the progresss bar background colour based on task status */
176 | public getBarProgressStyle(taskStatus: string = ""): any {
177 | var style = {};
178 |
179 | try {
180 | taskStatus = taskStatus.toLowerCase();
181 | } catch (err) {
182 | taskStatus = "";
183 | }
184 |
185 | switch (taskStatus) {
186 | case "information":
187 | style["background-color"] = this.barStyles[0].progressBackgroundColor;
188 | break;
189 | case "warning":
190 | style["background-color"] = this.barStyles[1].progressBackgroundColor;
191 | break;
192 | case "error":
193 | style["background-color"] = this.barStyles[2].progressBackgroundColor;
194 | break;
195 | case "completed":
196 | style["background-color"] = this.barStyles[3].progressBackgroundColor;
197 | break;
198 | default:
199 | style["background-color"] = this.barStyles[0].progressBackgroundColor;
200 | break;
201 | }
202 |
203 | return style;
204 | }
205 |
206 | /** Calculates the bar progress width in pixels given task percent complete */
207 | public calculateBarProgress(width: number, percent: number): string {
208 | if (typeof percent === "number") {
209 | if (percent > 100) {
210 | percent = 100;
211 | }
212 | let progress: number = (width / 100) * percent - 2;
213 |
214 | return `${progress}px`;
215 | }
216 | return `${0}px`;
217 | }
218 |
219 | /** Calculates the difference in two dates and returns number of days */
220 | public calculateDiffDays(start: Date, end: Date): number {
221 | try {
222 | let oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds /ms
223 | let diffDays = Math.abs((start.getTime() - end.getTime()) / (oneDay));
224 | let days = diffDays; // don't use Math.round as it will draw an incorrect bar
225 |
226 | return days;
227 | } catch (err) {
228 | return 0;
229 | }
230 | }
231 |
232 | /** Calculates the difference in two dates and returns number of hours */
233 | public calculateDuration(task: any): string {
234 | try {
235 | if (task.start != null && task.end != null) {
236 | let oneHour = 60 * 60 * 1000;
237 | let diffHours = (Math.abs((task.start.getTime() - task.end.getTime()) / oneHour));
238 | let duration = diffHours;
239 |
240 | if (duration > 24) {
241 | return `${Math.round(duration / 24)} day(s)`; // duration in days
242 | } else if (duration > 1) {
243 | return `${Math.round(duration)} hr(s)`; // duration in hours
244 | } else {
245 | let minutes = duration * 60;
246 |
247 | if (minutes < 1) {
248 | return `${Math.round(minutes * 60)} second(s)`; // duration in seconds
249 | }
250 | return `${Math.round(minutes)} min(s)` // duration in minutes
251 | }
252 | }
253 |
254 | return '';
255 | } catch (err) {
256 | return '';
257 | }
258 | }
259 |
260 | calculateTotalDuration(tasks: any[]): string {
261 | try {
262 | tasks = tasks.filter(t => t.parentId === t.id); // only calculate total duration with parent tasks
263 |
264 | let totalHours = 0;
265 | let oneHour = 60 * 60 * 1000;
266 | for (let i = 0; i < tasks.length; i++) {
267 | let start = tasks[i].start;
268 | let end = tasks[i].end;
269 |
270 | if (start != null && end != null) {
271 | let duration = Math.abs(tasks[i].end.getTime() - tasks[i].start.getTime()) / oneHour; // duration in hours
272 | totalHours += duration;
273 | }
274 | }
275 |
276 | if (totalHours === 0) {
277 | return '';
278 | }
279 |
280 | if (totalHours > 24) {
281 | return `${Math.round(totalHours / 24)} day(s)`; // duration in days
282 | } else if (totalHours > 1) {
283 | return `${Math.round(totalHours)} hr(s)`; // duration in hours
284 | } else {
285 | let minutes = totalHours * 60;
286 |
287 | if (minutes < 1) {
288 | return `${Math.round(minutes * 60)} second(s)`; // duration in seconds
289 | }
290 | return `${Math.round(minutes)} min(s)` // duration in minutes
291 | }
292 | } catch (err) {
293 | return '';
294 | }
295 | }
296 |
297 | /** Calculate the total percentage of a group of tasks */
298 | calculateTotalPercentage(node: any): number {
299 | var totalPercent: number = 0;
300 | var children = node.children;
301 |
302 | if (children.length > 0) {
303 | children.forEach((child: any) => {
304 | totalPercent += isNaN(child.percentComplete) ? 0 : child.percentComplete;
305 | });
306 |
307 | return Math.ceil(totalPercent / children.length);
308 | } else {
309 | return isNaN(node.percentComplete) ? 0 : node.percentComplete;
310 | }
311 | }
312 |
313 | /** Calculate the total percent of the parent task */
314 | calculateParentTotalPercentage(parent: any, tasks: any[]) {
315 | var children = tasks.filter((task:any) => {
316 | return task.parentId === parent.id && task.id != parent.id
317 | }); // get only children tasks ignoring parent.
318 |
319 | var totalPercent: number = 0;
320 |
321 | if (children.length > 0) {
322 | children.forEach((child:any) => {
323 | totalPercent += isNaN(child.percentComplete) ? 0 : child.percentComplete;
324 | });
325 |
326 | return Math.ceil(totalPercent / children.length);
327 | } else {
328 | return isNaN(parent.percentComplete) ? 0 : parent.percentComplete;
329 | }
330 | }
331 |
332 | /** Calculate the gantt scale range given the start and end date of tasks*/
333 | public calculateScale(start: Date = new Date(), end: Date = this.addDays(start, 7)) {
334 | let scale: any[] = [];
335 |
336 | try {
337 | while (start.getTime() <= end.getTime()) {
338 | scale.push(start);
339 | start = this.addDays(start, 1);
340 | }
341 | return scale;
342 |
343 | } catch (err) {
344 | return scale;
345 | }
346 | }
347 |
348 | /** Determines whether given date is a weekend */
349 | public isDayWeekend(date: Date): boolean {
350 | let day = date.getDay();
351 |
352 | if (day === 6 || day === 0) {
353 | return true;
354 | }
355 | return false;
356 | }
357 |
358 | /** Add x number of days to a date object */
359 | public addDays(date: Date, days: number): Date {
360 | let result = new Date(date);
361 | result.setDate(result.getDate() + days);
362 | return result;
363 | }
364 |
365 | //** Remove x number of days from a date object */
366 | public removeDays(date: Date, days: number): Date {
367 | let result = new Date(date);
368 | result.setDate(result.getDate() - days);
369 | return result;
370 | }
371 |
372 | /** Calculates the grid scale for gantt based on tasks start and end dates */
373 | public calculateGridScale(tasks: Task[]): IScale {
374 | var start: Date;
375 | var end: Date;
376 | var dates = tasks.map((task:any) => {
377 | return {
378 | start: new Date(task.start),
379 | end: new Date(task.end)
380 | }
381 | });
382 |
383 | start = new Date(Math.min.apply(null, dates.map(function (t) {
384 | return t.start;
385 | })));
386 |
387 | end = new Date(Math.max.apply(null, dates.map(function (t) {
388 | return t.end;
389 | })));
390 |
391 | return {
392 | start: start,
393 | end: end
394 | }
395 | }
396 |
397 | /** Create an hours array for use in time scale component */
398 | public getHours(cols: number): string[] {
399 | var hours: string[] = [];
400 |
401 | while (hours.length <= cols * 24) {
402 | for (var i = 0; i <= 23; i++) {
403 | if (i < 10) {
404 | hours.push('0' + i.toString());
405 | } else {
406 | hours.push(i.toString());
407 | }
408 | }
409 | }
410 |
411 | return hours;
412 | }
413 |
414 | public getComputedStyle(element: any, attribute: any) {
415 | return parseInt(document.defaultView.getComputedStyle(element)[attribute], 10);
416 | }
417 |
418 | //TODO(dale): determine whether this is needed
419 | public calculateContainerWidth(): number {
420 | this.windowInnerWidth = window.innerWidth;
421 | let containerWidth = (innerWidth - 18);
422 |
423 | return containerWidth;
424 | }
425 |
426 | public calculateActivityContainerDimensions(): any {
427 | var scrollWidth: number = 18;
428 | this.windowInnerWidth = window.innerWidth;
429 | let width = this.windowInnerWidth - this.gridWidth - scrollWidth;
430 |
431 | return { height: this.activityHeight, width: width };
432 | }
433 |
434 | /** Set the vertical scroll top positions for gantt */
435 | public scrollTop(verticalScrollElem: any, ganttGridElem: any, ganttActivityAreaElem: any) {
436 | let verticalScrollTop = verticalScrollElem.scrollTop;
437 | let scroll = this.setScrollTop;
438 |
439 | // debounce
440 | if (verticalScrollTop !== null && verticalScrollTop !== undefined) {
441 | setTimeout(function() {
442 | scroll(verticalScrollTop, ganttActivityAreaElem);
443 | scroll(ganttActivityAreaElem.scrollTop, ganttGridElem);
444 |
445 | }, 50);
446 | }
447 | }
448 |
449 | /** Group data by id , only supports one level*/
450 | public groupData(tasks: any): any {
451 | var merged:any = [];
452 | var groups:any = new GroupByPipe().transform(tasks, (task:any) => {
453 | return [task.treePath.split('/')[0]]
454 | });
455 | return merged.concat.apply([], groups);
456 | }
457 |
458 | /** Create tree of data */
459 | public transformData(input: any): any {
460 | var output:any = [];
461 | for (var i = 0; i < input.length; i++) {
462 | var chain:any = input[i].id.split('/');
463 | var currentNode:any = output;
464 | for (var j = 0; j < chain.length; j++) {
465 | var wantedNode:any = chain[j];
466 | var lastNode:any = currentNode;
467 | for (var k = 0; k < currentNode.length; k++) {
468 | if (currentNode[k].name == wantedNode) {
469 | currentNode = currentNode[k].children;
470 | break;
471 | }
472 | }
473 | // If we couldn't find an item in this list of children
474 | // that has the right name, create one:
475 | if (lastNode == currentNode) {
476 | //TODO(dale): determine way to show percent complete on correct child
477 | var newNode:any = currentNode[k] = {
478 | name: wantedNode,
479 | percentComplete: input[i].percentComplete,
480 | start: input[i].start,
481 | end: input[i].end,
482 | children: []
483 | };
484 | currentNode = newNode.children;
485 | }
486 | }
487 | }
488 | return output;
489 | }
490 |
491 | /** Checks whether any new data needs to be added to task cache */
492 | public doTaskCheck(tasks: any[], treeExpanded: boolean): boolean {
493 | var cachedTaskIds = this.TASK_CACHE.map((task:any) => { return task.id });
494 | var itemsToCache: any[] = [];
495 |
496 | if (treeExpanded) {
497 | // push children and parent tasks that are not cached
498 | tasks.filter((task:any) => {
499 | return cachedTaskIds.indexOf(task.id) === -1
500 | }).forEach((task:any) => {
501 | itemsToCache.push(task);
502 | })
503 | } else {
504 | // only look at tasks that are not cached
505 | tasks.filter((task:any) => {
506 | return cachedTaskIds.indexOf(task.id) === -1 && task.treePath.split('/').length === 1
507 | }).forEach((task:any) => {
508 | itemsToCache.push(task);
509 | });
510 | }
511 |
512 | itemsToCache.forEach((item:any) => {
513 | this.TASK_CACHE.push(item);
514 | });
515 |
516 | if (itemsToCache.length > 0) {
517 | return true;
518 | }
519 |
520 | return false;
521 | }
522 |
523 | /** Set a id prefix so CSS3 query selector can work with ids that contain numbers */
524 | public setIdPrefix(id: string): string {
525 | return `_${id}`;
526 | }
527 |
528 | // /** Remove the id prefix to allow querying of data */
529 | // public removeIdPrefix(id: string): string {
530 | // return id.substring(1, id.length - 1);
531 | // }
532 |
533 | /** Set the scroll top property of a native DOM element */
534 | private setScrollTop(scrollTop: number, element: any): void {
535 | if (element !== null && element !== undefined) {
536 | element.scrollTop = scrollTop;
537 | }
538 | }
539 | }
540 |
--------------------------------------------------------------------------------
/src/lib/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "target": "es5",
6 | "sourceMap": true,
7 | "inlineSources": false,
8 | "declaration": false,
9 | "experimentalDecorators": true,
10 | "emitDecoratorMetadata": true,
11 | "stripInternal": true,
12 | "skipLibCheck": true,
13 | "outDir": "../../dist"
14 | },
15 | "files": [
16 | "index.ts"
17 | ]
18 | }
--------------------------------------------------------------------------------
/test/karma.config.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', 'angular-cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-remap-istanbul'),
12 | require('angular-cli/plugins/karma')
13 | ],
14 | files: [
15 | { pattern: './src/test.ts', watched: false }
16 | ],
17 | preprocessors: {
18 | './src/test.ts': ['angular-cli']
19 | },
20 | remapIstanbulReporter: {
21 | reports: {
22 | html: 'coverage',
23 | lcovonly: './coverage/coverage.lcov'
24 | }
25 | },
26 | angularCli: {
27 | config: './angular-cli.json',
28 | environment: 'dev'
29 | },
30 | reporters: ['progress', 'karma-remap-istanbul'],
31 | port: 9876,
32 | colors: true,
33 | logLevel: config.LOG_INFO,
34 | autoWatch: true,
35 | browsers: ['Chrome'],
36 | singleRun: false
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/test/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js
3 |
4 | /*global jasmine */
5 | var SpecReporter = require('jasmine-spec-reporter');
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | './e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | useAllAngular2AppRoots: true,
24 | beforeLaunch: function() {
25 | require('ts-node').register({
26 | project: 'e2e'
27 | });
28 | },
29 | onPrepare: function() {
30 | jasmine.getEnv().addReporter(new SpecReporter());
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "class-name": true,
7 | "comment-format": [
8 | true,
9 | "check-space"
10 | ],
11 | "curly": true,
12 | "eofline": true,
13 | "forin": true,
14 | "indent": [
15 | true,
16 | "spaces"
17 | ],
18 | "label-position": true,
19 | "label-undefined": true,
20 | "max-line-length": [
21 | true,
22 | 140
23 | ],
24 | "member-access": false,
25 | "member-ordering": [
26 | true,
27 | "static-before-instance",
28 | "variables-before-functions"
29 | ],
30 | "no-arg": true,
31 | "no-bitwise": true,
32 | "no-console": [
33 | true,
34 | "debug",
35 | "info",
36 | "time",
37 | "timeEnd",
38 | "trace"
39 | ],
40 | "no-construct": true,
41 | "no-debugger": true,
42 | "no-duplicate-key": true,
43 | "no-duplicate-variable": true,
44 | "no-empty": false,
45 | "no-eval": true,
46 | "no-inferrable-types": true,
47 | "no-shadowed-variable": true,
48 | "no-string-literal": false,
49 | "no-switch-case-fall-through": true,
50 | "no-trailing-whitespace": true,
51 | "no-unused-expression": true,
52 | "no-unused-variable": true,
53 | "no-unreachable": true,
54 | "no-use-before-declare": true,
55 | "no-var-keyword": true,
56 | "object-literal-sort-keys": false,
57 | "one-line": [
58 | true,
59 | "check-open-brace",
60 | "check-catch",
61 | "check-else",
62 | "check-whitespace"
63 | ],
64 | "quotemark": [
65 | true,
66 | "single"
67 | ],
68 | "radix": true,
69 | "semicolon": [
70 | "always"
71 | ],
72 | "triple-equals": [
73 | true,
74 | "allow-null-check"
75 | ],
76 | "typedef-whitespace": [
77 | true,
78 | {
79 | "call-signature": "nospace",
80 | "index-signature": "nospace",
81 | "parameter": "nospace",
82 | "property-declaration": "nospace",
83 | "variable-declaration": "nospace"
84 | }
85 | ],
86 | "variable-name": false,
87 | "whitespace": [
88 | true,
89 | "check-branch",
90 | "check-decl",
91 | "check-operator",
92 | "check-separator",
93 | "check-type"
94 | ],
95 |
96 | "directive-selector-prefix": [true, "app"],
97 | "component-selector-prefix": [true, "app"],
98 | "directive-selector-name": [true, "camelCase"],
99 | "component-selector-name": [true, "kebab-case"],
100 | "directive-selector-type": [true, "attribute"],
101 | "component-selector-type": [true, "element"],
102 | "use-input-property-decorator": true,
103 | "use-output-property-decorator": true,
104 | "use-host-property-decorator": true,
105 | "no-input-rename": true,
106 | "no-output-rename": true,
107 | "use-life-cycle-interface": true,
108 | "use-pipe-transform-interface": true,
109 | "component-class-suffix": true,
110 | "directive-class-suffix": true,
111 | "templates-use-public": true,
112 | "invoke-injectable": true
113 | }
114 | }
115 |
--------------------------------------------------------------------------------