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.key}} -
-

{{kanbanEntry.summary}}

-
-
- Additional Details +
+
+ +
+
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); +}