diff --git a/deploy.php b/deploy.php
new file mode 100644
index 0000000..3de6e74
--- /dev/null
+++ b/deploy.php
@@ -0,0 +1,58 @@
+stage('production')
+ ->user('edvidan')
+ ->forwardAgent()
+ ->set('ng_basehref', '/taurus-tv/')
+ ->set('ng_target', 'production')
+ ->set('ng_environment', 'prod')
+ ->set('env_vars', 'NODE_ENV=production')
+ ->set('deploy_path', '/home/edvidan/applications/taurus-tv');
+
+// Tasks
+desc('Prepare release');
+task('deploy:ng-prepare', function() {
+ runLocally("ng build --base-href={{ng_basehref}} --target={{ng_target}} --environment={{ng_environment}}");
+ runLocally("tar -cJf dist.tar.xz dist");
+});
+
+desc('Upload release');
+task('deploy:ng-upload', function() {
+ upload("dist.tar.xz", "{{release_path}}/dist.tar.xz");
+ run("tar -C {{release_path}} -xJf {{release_path}}/dist.tar.xz");
+ run("rm -f {{release_path}}/dist.tar.xz");
+ runLocally("rm -rf dist.tar.xz dist");
+ upload("htaccess", "{{release_path}}/dist/.htaccess");
+});
+
+desc('Deploy your project');
+task('deploy', [
+ 'deploy:prepare',
+ 'deploy:lock',
+ 'deploy:release',
+ 'deploy:ng-prepare',
+ 'deploy:ng-upload',
+ 'deploy:shared',
+ 'deploy:clear_paths',
+ 'deploy:symlink',
+ 'deploy:unlock',
+ 'cleanup',
+]);
+after('deploy', 'success');
diff --git a/htaccess b/htaccess
new file mode 100644
index 0000000..a1d332c
--- /dev/null
+++ b/htaccess
@@ -0,0 +1,24 @@
+RewriteEngine On
+# The following rule tells Apache that if the requested filename
+# exists, simply serve it.
+RewriteCond %{REQUEST_FILENAME} -s [OR]
+RewriteCond %{REQUEST_FILENAME} -l [OR]
+RewriteCond %{REQUEST_FILENAME} -d
+RewriteRule ^.*$ - [NC,L]
+
+# The following rewrites all other queries to index.php. The
+# condition ensures that if you are using Apache aliases to do
+# mass virtual hosting, the base path will be prepended to
+# allow proper resolution of the index.php file; it will work
+# in non-aliased environments as well, providing a safe, one-size
+# fits all solution.
+RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
+RewriteRule ^(.*) - [E=BASE:%1]
+RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]
+
+
+ Require all granted
+
+
+ Require all denied
+
\ No newline at end of file
diff --git a/semantic.json b/semantic.json
new file mode 100644
index 0000000..489bb08
--- /dev/null
+++ b/semantic.json
@@ -0,0 +1,22 @@
+{
+ "base": "semantic/",
+ "paths": {
+ "source": {
+ "config": "src/theme.config",
+ "definitions": "src/definitions/",
+ "site": "src/site/",
+ "themes": "src/themes/"
+ },
+ "output": {
+ "packaged": "dist/",
+ "uncompressed": "dist/components/",
+ "compressed": "dist/components/",
+ "themes": "dist/themes/"
+ },
+ "clean": "dist/"
+ },
+ "permission": false,
+ "autoInstall": true,
+ "rtl": false,
+ "version": "2.2.11"
+}
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 7b0f672..16d8349 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,10 +1,31 @@
-import { Component } from '@angular/core';
+import {Component, OnDestroy, OnInit} from '@angular/core';
+import {Subscription} from "rxjs/Subscription";
+import {TimerObservable} from "rxjs/observable/TimerObservable";
+
+import {KanbanService} from "./kanban/shared/kanban.service";
+
+const RENEW_TIMER_INITIAL = 300000;
+const RENEW_TIMER_PERIOD = 300000;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
-export class AppComponent {
+export class AppComponent implements OnInit, OnDestroy{
title = 'app';
+ authRenewTimer: Subscription;
+
+ constructor( private kanbanService: KanbanService ) {}
+
+ public ngOnInit() {
+ let timer = TimerObservable.create(RENEW_TIMER_INITIAL, RENEW_TIMER_PERIOD);
+ this.authRenewTimer = timer.subscribe(() => {
+ this.kanbanService.reload();
+ });
+ }
+
+ public ngOnDestroy() {
+ this.authRenewTimer.unsubscribe();
+ }
}
diff --git a/src/app/kanban/kanban-board/kanban-board.component.html b/src/app/kanban/kanban-board/kanban-board.component.html
index ceb6cdc..8e4a084 100644
--- a/src/app/kanban/kanban-board/kanban-board.component.html
+++ b/src/app/kanban/kanban-board/kanban-board.component.html
@@ -1,13 +1,12 @@
diff --git a/src/app/kanban/kanban-board/kanban-board.component.ts b/src/app/kanban/kanban-board/kanban-board.component.ts
index 86929e0..03f36e9 100644
--- a/src/app/kanban/kanban-board/kanban-board.component.ts
+++ b/src/app/kanban/kanban-board/kanban-board.component.ts
@@ -5,6 +5,10 @@ import {ActivatedRoute} from '@angular/router';
import {
KanbanBoard
} from "../shared";
+import {KanbanService} from "../shared/kanban.service";
+
+const WIP_LIMIT_INPROGRESS = 12;
+const WIP_LIMIT_VERIFICATION = 8;
@Component({
selector: 'app-kanban-board',
@@ -13,10 +17,9 @@ import {
})
export class KanbanBoardComponent implements OnInit {
- public kanbanBoard: KanbanBoard = new KanbanBoard;
-
constructor(private titleService: Title,
- private route: ActivatedRoute) {
+ private route: ActivatedRoute,
+ private kanbanService: KanbanService) {
}
ngOnInit() {
@@ -24,4 +27,23 @@ export class KanbanBoardComponent implements OnInit {
this.route.data.subscribe((data: { kanbanBoard: KanbanBoard }) => this.kanbanBoard = data.kanbanBoard);
}
+ get kanbanBoard(): KanbanBoard {
+ return this.kanbanService.kanbanBoard;
+ }
+
+ set kanbanBoard(kanbanBoard: KanbanBoard) {
+ this.kanbanService.kanbanBoard = kanbanBoard;
+ }
+
+ get inprogressWipClass() {
+ return {
+ 'over-wip': this.kanbanBoard.inProgress.length > WIP_LIMIT_INPROGRESS,
+ };
+ }
+
+ get verificationWipClass() {
+ return {
+ 'over-wip': this.kanbanBoard.verification.length > WIP_LIMIT_VERIFICATION,
+ };
+ }
}
diff --git a/src/app/kanban/kanban-entry-item/kanban-entry-item.component.css b/src/app/kanban/kanban-entry-item/kanban-entry-item.component.css
index e69de29..a54daae 100644
--- a/src/app/kanban/kanban-entry-item/kanban-entry-item.component.css
+++ b/src/app/kanban/kanban-entry-item/kanban-entry-item.component.css
@@ -0,0 +1,25 @@
+.task-description {
+ font-size: 14pt;
+ line-height: 1.25em;
+ /*text-align: justify;*/
+}
+
+.ui.jira-avatar.image {
+ width: 45px;
+ height: auto;
+ /*font-size: 1em;*/
+ margin-right: 4px;
+ margin-bottom: 0;
+}
+
+.ui.jira-avatar.image > img {
+ border-radius: 4px;
+}
+
+.ui.divided.items > .item:first-child {
+ border-top: 0;
+}
+
+.ui.divided.items > .item {
+ border-top: 1px solid rgba(250, 250, 250, 0.25);
+}
diff --git a/src/app/kanban/kanban-entry-item/kanban-entry-item.component.html b/src/app/kanban/kanban-entry-item/kanban-entry-item.component.html
index e8f538e..af29494 100644
--- a/src/app/kanban/kanban-entry-item/kanban-entry-item.component.html
+++ b/src/app/kanban/kanban-entry-item/kanban-entry-item.component.html
@@ -1,15 +1,11 @@
-
-
}})
-
-
-
-
{{kanbanEntry.summary}}
-
-
diff --git a/src/app/kanban/kanban-entry-item/kanban-entry-item.component.ts b/src/app/kanban/kanban-entry-item/kanban-entry-item.component.ts
index 9f115c6..f7e2d92 100644
--- a/src/app/kanban/kanban-entry-item/kanban-entry-item.component.ts
+++ b/src/app/kanban/kanban-entry-item/kanban-entry-item.component.ts
@@ -3,8 +3,10 @@ import {Component, Input, OnInit} from '@angular/core';
import {environment} from "../../../environments/environment";
import {KanbanEntry} from "../shared/kanban-entry.model";
+const DEFAULT_AVATAR = '/assets/riddler.png';
+
@Component({
- selector: 'app-kanban-entry-item',
+ selector: 'app-kanban-entry-item,[app-kanban-entry-item]',
templateUrl: './kanban-entry-item.component.html',
styleUrls: ['./kanban-entry-item.component.css']
})
@@ -16,10 +18,6 @@ export class KanbanEntryItemComponent implements OnInit {
ngOnInit() {}
public avatarUrl(avatarPath: string): string {
- try {
- return environment.apiUri + avatarPath;
- } catch (e) {
- return "";
- }
+ return environment.apiUri + ( avatarPath ? avatarPath : DEFAULT_AVATAR );
}
}
diff --git a/src/app/kanban/kanban.module.ts b/src/app/kanban/kanban.module.ts
index 638020c..ef793cb 100644
--- a/src/app/kanban/kanban.module.ts
+++ b/src/app/kanban/kanban.module.ts
@@ -7,6 +7,7 @@ import { KanbanBoardComponent } from './kanban-board/kanban-board.component';
import { KanbanService } from './shared/kanban.service';
import { KanbanEntryItemComponent } from './kanban-entry-item/kanban-entry-item.component';
import { KanbanEntryCardComponent } from './kanban-entry-card/kanban-entry-card.component';
+import { PriorityColorPipe } from './shared/priority-color.pipe';
@NgModule({
imports: [
@@ -17,6 +18,7 @@ import { KanbanEntryCardComponent } from './kanban-entry-card/kanban-entry-card.
KanbanBoardComponent,
KanbanEntryItemComponent,
KanbanEntryCardComponent,
+ PriorityColorPipe,
],
providers: [
KanbanService,
diff --git a/src/app/kanban/shared/kanban.service.ts b/src/app/kanban/shared/kanban.service.ts
index fa6667b..0ecaaa9 100644
--- a/src/app/kanban/shared/kanban.service.ts
+++ b/src/app/kanban/shared/kanban.service.ts
@@ -13,6 +13,8 @@ import {
export class KanbanService {
private url = environment.apiUri + '/api/kanban';
+ private cachedKanbanBoard: KanbanBoard = new KanbanBoard();
+
constructor(private httpService: Http) {}
public getList(): Observable
{
@@ -22,4 +24,17 @@ export class KanbanService {
public resolve(route: ActivatedRouteSnapshot): Promise {
return this.getList().toPromise().then(result => result ? result : false);
}
+
+ public reload() {
+ this.getList().subscribe(result => this.cachedKanbanBoard = result);
+ }
+
+ get kanbanBoard(): KanbanBoard {
+ return this.cachedKanbanBoard;
+ }
+
+ set kanbanBoard(kanbanBoard: KanbanBoard) {
+ this.cachedKanbanBoard = kanbanBoard;
+ }
+
}
diff --git a/src/app/kanban/shared/priority-color.pipe.spec.ts b/src/app/kanban/shared/priority-color.pipe.spec.ts
new file mode 100644
index 0000000..35713b4
--- /dev/null
+++ b/src/app/kanban/shared/priority-color.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { PriorityColorPipe } from './priority-color.pipe';
+
+describe('PriorityColorPipe', () => {
+ it('create an instance', () => {
+ const pipe = new PriorityColorPipe();
+ expect(pipe).toBeTruthy();
+ });
+});
diff --git a/src/app/kanban/shared/priority-color.pipe.ts b/src/app/kanban/shared/priority-color.pipe.ts
new file mode 100644
index 0000000..290504b
--- /dev/null
+++ b/src/app/kanban/shared/priority-color.pipe.ts
@@ -0,0 +1,32 @@
+import {Pipe, PipeTransform} from '@angular/core';
+
+@Pipe({
+ name: 'priorityColor'
+})
+export class PriorityColorPipe implements PipeTransform {
+
+ transform(value: any, args?: any): any {
+ let mhrMatch = /(\[.*mhr\])/ig;
+ value = value.replace(mhrMatch, (fullMatch: string, mhrMatched: string) => {
+ return `${mhrMatched} `;
+ });
+ let sMatch = /(\[s\])/ig;
+ value = value.replace(sMatch, (fullMatch: string, mhrMatched: string) => {
+ return `${mhrMatched} `;
+ });
+ let mMatch = /(\[m\])/ig;
+ value = value.replace(mMatch, (fullMatch: string, mhrMatched: string) => {
+ return `${mhrMatched} `;
+ });
+ let lMatch = /(\[l\])/ig;
+ value = value.replace(lMatch, (fullMatch: string, mhrMatched: string) => {
+ return `${mhrMatched} `;
+ });
+ let xlMatch = /(\[xl\])/ig;
+ value = value.replace(xlMatch, (fullMatch: string, mhrMatched: string) => {
+ return `${mhrMatched} `;
+ });
+ return value;
+ }
+
+}
diff --git a/src/styles.css b/src/styles.css
index 8c3b279..dcfc8f1 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1,9 +1,42 @@
/* You can add global styles to this file, and also import other style files */
body {
- background-color: #FFFFFF;
+ background-color: #303030 !important;
+ color: #cccccc !important;
+ margin-top: 1em !important;
+ margin-bottom: 1em !important;
+ overflow: hidden;
}
.ui.fullwide-container {
margin-left: 1em;
margin-right: 1em;
}
+
+.match-mhr {
+ text-justify: none;
+ color: red;
+}
+
+.match-s {
+ text-justify: none;
+ color: #00b5ad;
+}
+
+.match-m {
+ text-justify: none;
+ color: #0ea432;
+}
+
+.match-l {
+ text-justify: none;
+ color: yellow;
+}
+
+.match-xl {
+ text-justify: none;
+ color: coral;
+}
+
+.over-wip {
+ background-color: rgba(194,59,34, 0.3);
+}