Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf7ec832b2 | ||
|
|
1a357f4121 | ||
|
|
8aa0828701 | ||
|
|
41ad9d9a28 | ||
|
|
6af8ccbf7a | ||
|
|
270a55f6b9 | ||
|
|
ad8f5491b5 | ||
|
|
79227258e8 | ||
|
|
80dc5b54e8 | ||
|
|
57f85768eb | ||
|
|
aa995e845a | ||
|
|
a15cb298a0 | ||
|
|
ea05a47086 | ||
|
|
0f535881a4 | ||
|
|
96918b50ca |
@ -1,63 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "mtas-tv-frontend"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"../node_modules/semantic-ui-css/semantic.css",
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
"../node_modules/marked/lib/marked.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "css",
|
||||
"component": {}
|
||||
}
|
||||
}
|
||||
156
angular.json
Executable file
156
angular.json
Executable file
@ -0,0 +1,156 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"mtas-tv-frontend": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/semantic-ui-css/semantic.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/marked/lib/marked.js"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"staging": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.stg.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "mtas-tv-frontend:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "mtas-tv-frontend:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "mtas-tv-frontend:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"karmaConfig": "./karma.conf.js",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"scripts": [
|
||||
"node_modules/marked/lib/marked.js"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/semantic-ui-css/semantic.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mtas-tv-frontend-e2e": {
|
||||
"root": "e2e",
|
||||
"sourceRoot": "e2e",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "./protractor.conf.js",
|
||||
"devServerTarget": "mtas-tv-frontend:serve"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"e2e/tsconfig.e2e.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "mtas-tv-frontend",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"prefix": "app",
|
||||
"styleext": "css"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"prefix": "app"
|
||||
}
|
||||
}
|
||||
}
|
||||
34
deploy.php
Normal file → Executable file
34
deploy.php
Normal file → Executable file
@ -15,21 +15,29 @@ set('writable_dirs', []);
|
||||
set('keep_releases', 3);
|
||||
set('default_stage', 'production');
|
||||
|
||||
// Servers
|
||||
// Servers - mtas : esekivws5222a.rnd.ki.sw.ericsson.se
|
||||
host('mtas')
|
||||
->stage('production')
|
||||
->user('edvidan')
|
||||
->forwardAgent()
|
||||
->set('ng_basehref', '/mtastv/')
|
||||
->set('ng_configuration', 'production')
|
||||
->set('env_vars', 'NODE_ENV=production')
|
||||
->set('deploy_path', '/proj/webdocs/mtoolbox/root/mtastv-inst/frontend');
|
||||
|
||||
host('vasgyuro.tsp')
|
||||
->stage('production')
|
||||
->user('edvidan')
|
||||
->forwardAgent()
|
||||
->set('ng_basehref', '/mtas-tv/')
|
||||
->set('ng_target', 'production')
|
||||
->set('ng_environment', 'prod')
|
||||
->set('env_vars', 'NODE_ENV=production')
|
||||
->set('deploy_path', '/home/edvidan/applications/mtas-tv');
|
||||
->stage('staging')
|
||||
->user('edvidan')
|
||||
->forwardAgent()
|
||||
->set('ng_basehref', '/mtas-tv/')
|
||||
->set('ng_configuration', 'staging')
|
||||
->set('env_vars', 'NODE_ENV=production')
|
||||
->set('deploy_path', '/home/edvidan/applications/mtas-tv');
|
||||
|
||||
// Tasks
|
||||
desc('Prepare release');
|
||||
task('deploy:ng-prepare', function() {
|
||||
runLocally("ng build --base-href={{ng_basehref}} --target={{ng_target}} --environment={{ng_environment}}");
|
||||
desc('Build release');
|
||||
task('deploy:ng-build', function() {
|
||||
runLocally("ng build --base-href={{ng_basehref}} --configuration={{ng_configuration}}");
|
||||
runLocally("tar -cJf dist.tar.xz dist");
|
||||
});
|
||||
|
||||
@ -52,7 +60,7 @@ task('deploy', [
|
||||
'deploy:prepare',
|
||||
'deploy:lock',
|
||||
'deploy:release',
|
||||
'deploy:ng-prepare',
|
||||
'deploy:ng-build',
|
||||
'deploy:ng-upload',
|
||||
'deploy:shared',
|
||||
'deploy:clear_paths',
|
||||
|
||||
12
htaccess
12
htaccess
@ -16,12 +16,12 @@ RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
|
||||
RewriteRule ^(.*) - [E=BASE:%1]
|
||||
RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]
|
||||
|
||||
<Limit GET POST PUT DELETE HEAD OPTIONS>
|
||||
Require all granted
|
||||
</Limit>
|
||||
<LimitExcept GET POST PUT DELETE HEAD OPTIONS>
|
||||
Require all denied
|
||||
</LimitExcept>
|
||||
#<Limit GET POST PUT DELETE HEAD OPTIONS>
|
||||
# Require all granted
|
||||
#</Limit>
|
||||
#<LimitExcept GET POST PUT DELETE HEAD OPTIONS>
|
||||
# Require all denied
|
||||
#</LimitExcept>
|
||||
|
||||
<Files revision.json>
|
||||
FileETag None
|
||||
|
||||
@ -4,24 +4,22 @@
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular/cli'],
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular/cli/plugins/karma')
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client:{
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
reports: [ 'html', 'lcovonly' ],
|
||||
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
angularCli: {
|
||||
environment: 'dev'
|
||||
},
|
||||
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
|
||||
11211
package-lock.json
generated
11211
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@ -1,52 +1,54 @@
|
||||
{
|
||||
"name": "mtas-tv-frontend",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build --prod",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^5.2.0",
|
||||
"@angular/common": "^5.2.0",
|
||||
"@angular/compiler": "^5.2.0",
|
||||
"@angular/core": "^5.2.0",
|
||||
"@angular/forms": "^5.2.0",
|
||||
"@angular/http": "^5.2.0",
|
||||
"@angular/platform-browser": "^5.2.0",
|
||||
"@angular/platform-browser-dynamic": "^5.2.0",
|
||||
"@angular/router": "^5.2.0",
|
||||
"@types/marked": "^0.3.0",
|
||||
"core-js": "^2.4.1",
|
||||
"marked": "^0.3.19",
|
||||
"@angular/animations": "^6.1.7",
|
||||
"@angular/common": "^6.1.7",
|
||||
"@angular/compiler": "^6.1.7",
|
||||
"@angular/core": "^6.1.7",
|
||||
"@angular/forms": "^6.1.7",
|
||||
"@angular/http": "^6.1.7",
|
||||
"@angular/platform-browser": "^6.1.7",
|
||||
"@angular/platform-browser-dynamic": "^6.1.7",
|
||||
"@angular/router": "^6.1.7",
|
||||
"@types/date-fns": "^2.6.0",
|
||||
"@types/marked": "^0.4.1",
|
||||
"core-js": "^2.5.4",
|
||||
"date-fns": "^1.29.0",
|
||||
"marked": "^0.5.0",
|
||||
"ng2-semantic-ui": "^0.9.7",
|
||||
"rxjs": "^5.5.6",
|
||||
"semantic-ui-css": "^2.3.1",
|
||||
"zone.js": "^0.8.19"
|
||||
"rxjs": "^6.3.2",
|
||||
"semantic-ui-css": "^2.3.3",
|
||||
"zone.js": "~0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "~1.7.3",
|
||||
"@angular/compiler-cli": "^5.2.0",
|
||||
"@angular/language-service": "^5.2.0",
|
||||
"@types/jasmine": "~2.8.3",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "^4.0.1",
|
||||
"jasmine-core": "~2.8.0",
|
||||
"@angular-devkit/build-angular": "~0.7.0",
|
||||
"@angular/cli": "~6.1.5",
|
||||
"@angular/compiler-cli": "^6.1.7",
|
||||
"@angular/language-service": "^6.1.7",
|
||||
"@types/jasmine": "^2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "~8.9.4",
|
||||
"codelyzer": "~4.2.1",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~2.0.0",
|
||||
"karma": "~1.7.1",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-coverage-istanbul-reporter": "^2.0.4",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.1.2",
|
||||
"ts-node": "~4.1.0",
|
||||
"protractor": "^5.4.1",
|
||||
"ts-node": "~5.0.1",
|
||||
"tslint": "~5.9.1",
|
||||
"typescript": "~2.5.3"
|
||||
"typescript": "~2.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
23
src/app/admin/admin-routing.module.ts
Normal file → Executable file
23
src/app/admin/admin-routing.module.ts
Normal file → Executable file
@ -11,63 +11,56 @@ import { SlideResolverService } from './slide-resolver.service';
|
||||
import { SlideService } from '../shared/service/slide.service';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'admin',
|
||||
redirectTo: '/dashboard',
|
||||
pathMatch: 'full'
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
pathMatch: 'full',
|
||||
}, {
|
||||
path: 'dashboard',
|
||||
component: DashboardComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
}, {
|
||||
path: 'admin/teams',
|
||||
component: TeamListComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
teams: TeamService,
|
||||
}
|
||||
}, {
|
||||
path: 'admin/team/new',
|
||||
component: TeamEditorComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
}, {
|
||||
path: 'admin/team/edit/:id',
|
||||
component: TeamEditorComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
team: TeamResolverService,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
path: 'admin/slides',
|
||||
component: SlideListComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
slides: SlideService,
|
||||
teams: TeamService,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
path: 'admin/slide/new',
|
||||
component: SlideEditorComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
teams: TeamService,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
path: 'admin/slide/edit/:id',
|
||||
component: SlideEditorComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
slide: SlideResolverService,
|
||||
teams: TeamService,
|
||||
}
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AdminRoutingModule {
|
||||
}
|
||||
export class AdminRoutingModule {}
|
||||
|
||||
2
src/app/admin/dashboard/dashboard.component.html
Normal file → Executable file
2
src/app/admin/dashboard/dashboard.component.html
Normal file → Executable file
@ -2,7 +2,7 @@
|
||||
<h1 class="ui dividing header">Dashboard</h1>
|
||||
|
||||
<div class="ui four cards">
|
||||
<a class="ui raised yellow card" [routerLink]="['/commit-tracker']">
|
||||
<a class="ui raised yellow card" (click)="startSlideShow()" *ngIf="hasTeamSelected">
|
||||
<div class="content">
|
||||
<div class="header">Start slideshow</div>
|
||||
<div class="meta">
|
||||
|
||||
13
src/app/admin/dashboard/dashboard.component.ts
Normal file → Executable file
13
src/app/admin/dashboard/dashboard.component.ts
Normal file → Executable file
@ -1,5 +1,7 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import {SettingsService} from '../../shared/service/settings.service';
|
||||
import {SlideShowService} from '../../display/slide-show.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
@ -8,10 +10,19 @@ import { Title } from '@angular/platform-browser';
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
||||
constructor(private titleService: Title) { }
|
||||
constructor(private titleService: Title,
|
||||
private settingService: SettingsService,
|
||||
private slideShowService: SlideShowService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('Dashboard : MTAStv');
|
||||
}
|
||||
|
||||
get hasTeamSelected(): boolean {
|
||||
return this.settingService.team.id !== null;
|
||||
}
|
||||
|
||||
public startSlideShow() {
|
||||
this.slideShowService.startWithFirstSlide();
|
||||
}
|
||||
}
|
||||
|
||||
56
src/app/admin/slide-editor/slide-editor.component.html
Normal file → Executable file
56
src/app/admin/slide-editor/slide-editor.component.html
Normal file → Executable file
@ -2,31 +2,55 @@
|
||||
<h1 class="ui dividing header">Slide editor</h1>
|
||||
<form class="ui form" #slideEditorForm (ngSubmit)="saveSlide()">
|
||||
<div class="two fields">
|
||||
<div class="eight wide field">
|
||||
<div class="six wide field">
|
||||
<label for="slide_name">Slide title</label>
|
||||
<input id="slide_name" type="text" name="slide_name" [(ngModel)]="slide.title">
|
||||
</div>
|
||||
|
||||
<div class="two wide field">
|
||||
<label>Visibility</label>
|
||||
<div class="field">
|
||||
<sui-radio-button name="slide_visibility" value="public" [(ngModel)]="slide.visibility">Public</sui-radio-button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<sui-radio-button name="slide_visibility" value="team" [(ngModel)]="slide.visibility">Team</sui-radio-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="eight wide field">
|
||||
<label for="team">Visible to this team</label>
|
||||
<sui-select class="selection"
|
||||
id="team"
|
||||
name="team"
|
||||
[(ngModel)]="slide.team"
|
||||
<label for="teams">Visible to this team</label>
|
||||
<sui-multi-select class="selection"
|
||||
id="teams"
|
||||
name="teams"
|
||||
[(ngModel)]="slide.teams"
|
||||
[isDisabled]="slide.visibility=='public'"
|
||||
labelField="name"
|
||||
[isSearchable]="true"
|
||||
#select>
|
||||
<sui-select-option [value]="emptyTeam"></sui-select-option>
|
||||
<sui-select-option *ngFor="let team of teams" [value]="team"></sui-select-option>
|
||||
</sui-select>
|
||||
</sui-multi-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="slide_data">Slide data</label>
|
||||
<textarea id="slide_data" rows="30" name="slide_data" [(ngModel)]="slide.slideData"></textarea>
|
||||
<div class="inline fields">
|
||||
<label>Slide type</label>
|
||||
<div class="field">
|
||||
<sui-radio-button name="slide_type" value="markdown" [(ngModel)]="slide.type">Markdown</sui-radio-button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<sui-radio-button name="slide_type" value="iframe" [(ngModel)]="slide.type">Iframe</sui-radio-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field" *ngIf="isMarkdown">
|
||||
<label for="slide_data">Slide data</label>
|
||||
<textarea id="slide_data" rows="30" name="slide_data" [(ngModel)]="slide.slideData"></textarea>
|
||||
</div>
|
||||
<div class="field" *ngIf="isIframe" [class.error]="checkError(slideUrl)">
|
||||
<label for="slide_url">Slide url</label>
|
||||
<input id="slide_url" type="url" name="slide_url"
|
||||
[(ngModel)]="slide.slideUrl" #slideUrl="ngModel">
|
||||
</div>
|
||||
|
||||
<div class="five wide field">
|
||||
<div class="ui checkbox">
|
||||
@ -43,7 +67,8 @@
|
||||
[class.primary]="canPreview"
|
||||
[class.disabled]="!canPreview"
|
||||
(click)="preview()"><i class="search icon"></i>Preview</button>
|
||||
<a class="ui button orange"
|
||||
<a *ngIf="isMarkdown"
|
||||
class="ui button orange"
|
||||
href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"
|
||||
target="_blank"><i class="question circle outline icon"></i>MD howto</a>
|
||||
<a class="ui button"
|
||||
@ -54,5 +79,10 @@
|
||||
[data]="renderedPreview"
|
||||
[preview]="true"
|
||||
[(visible)]="previewVisible"
|
||||
*ngIf="previewVisible"></app-slide>
|
||||
*ngIf="previewVisible && isMarkdown"></app-slide>
|
||||
<app-slide-iframe class="preview"
|
||||
[data]="slide.slideUrl"
|
||||
[preview]="true"
|
||||
[(visible)]="previewVisible"
|
||||
*ngIf="previewVisible && isIframe"></app-slide-iframe>
|
||||
</div>
|
||||
|
||||
44
src/app/admin/slide-editor/slide-editor.component.ts
Normal file → Executable file
44
src/app/admin/slide-editor/slide-editor.component.ts
Normal file → Executable file
@ -1,11 +1,12 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
import * as marked from 'marked';
|
||||
import { Slide } from '../../shared/slide';
|
||||
import { SlideService } from '../../shared/service/slide.service';
|
||||
import { Team } from '../../shared/team';
|
||||
import {Slide, SlideType, SlideVisibility} from '../../shared/slide';
|
||||
import {SlideService} from '../../shared/service/slide.service';
|
||||
import {Team} from '../../shared/team';
|
||||
import {NgModel} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-slide-editor',
|
||||
@ -14,7 +15,6 @@ import { Team } from '../../shared/team';
|
||||
})
|
||||
export class SlideEditorComponent implements OnInit {
|
||||
private md;
|
||||
public emptyTeam: Team = new Team();
|
||||
public slide: Slide;
|
||||
public teams: Array<Team> = [];
|
||||
public renderedPreview: String = '';
|
||||
@ -25,7 +25,6 @@ export class SlideEditorComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private router: Router) {
|
||||
this.md = marked.setOptions({});
|
||||
this.emptyTeam.name = 'All teams';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -36,9 +35,11 @@ export class SlideEditorComponent implements OnInit {
|
||||
}) => {
|
||||
this.teams = data.teams;
|
||||
this.slide = data.slide ? data.slide : new Slide;
|
||||
this.slide.team = this.slide.team === null
|
||||
? this.emptyTeam
|
||||
: this.teams.find(team => team.id === this.slide.team.id);
|
||||
this.slide.teams = this.teams.filter(
|
||||
team => this.slide.teams.some(
|
||||
st => team.id === st.id
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,16 +54,31 @@ export class SlideEditorComponent implements OnInit {
|
||||
get canSave(): boolean {
|
||||
return [
|
||||
this.slide.title,
|
||||
this.slide.slideData
|
||||
].every(field => field.trim().length > 0);
|
||||
this.isMarkdown ? this.slide.slideData : this.slide.slideUrl
|
||||
].every(field => field.trim().length > 0) && (
|
||||
this.slide.visibility === SlideVisibility.Public ||
|
||||
this.slide.visibility === SlideVisibility.Team && this.slide.teams.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
get canPreview(): boolean {
|
||||
return this.slide.slideData.trim().length > 0;
|
||||
return this.isMarkdown && this.slide.slideData.trim().length > 0 || this.isIframe && this.slide.slideUrl.trim().length > 0;
|
||||
}
|
||||
|
||||
public preview() {
|
||||
this.previewVisible = true;
|
||||
this.renderedPreview = this.md.parse(this.slide.slideData);
|
||||
}
|
||||
|
||||
get isMarkdown(): boolean {
|
||||
return this.slide.type === SlideType.MarkDown;
|
||||
}
|
||||
|
||||
get isIframe(): boolean {
|
||||
return this.slide.type === SlideType.IFrame;
|
||||
}
|
||||
|
||||
public checkError(fieldModel: NgModel): boolean {
|
||||
return fieldModel.invalid && (fieldModel.dirty || fieldModel.touched);
|
||||
}
|
||||
}
|
||||
|
||||
6
src/app/admin/slide-list/slide-list.component.html
Normal file → Executable file
6
src/app/admin/slide-list/slide-list.component.html
Normal file → Executable file
@ -19,8 +19,8 @@
|
||||
<tr>
|
||||
<th class="collapsing"></th>
|
||||
<th><i class="large address book outline icon"></i>Slide title</th>
|
||||
<th class="collapsing"><i class="large users icon"></i>Owner team</th>
|
||||
<th class="collapsing"><i class="large check square outline icon"></i>Visible</th>
|
||||
<th class="collapsing"><i class="large users icon"></i>Visible to</th>
|
||||
<th class="collapsing"><i class="large check square outline icon"></i>Active</th>
|
||||
<th class="collapsing"><i class="large arrows alternate vertical icon"></i>Order</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -33,7 +33,7 @@
|
||||
class="large link red fitted trash alternate outline icon"></i></a>
|
||||
</td>
|
||||
<td>{{slide.title}}</td>
|
||||
<td class="collapsing">{{slideTeam(slide.team)}}</td>
|
||||
<td class="collapsing">{{slideTeam(slide)}}</td>
|
||||
<td class="center aligned"><i class="large icon" [ngClass]="visibleClass(slide)"></i></td>
|
||||
<td class="center aligned">
|
||||
<a title="Up" (click)="moveUp(slide)"><i
|
||||
|
||||
27
src/app/admin/slide-list/slide-list.component.ts
Normal file → Executable file
27
src/app/admin/slide-list/slide-list.component.ts
Normal file → Executable file
@ -1,10 +1,10 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
import { SlideService } from '../../shared/service/slide.service';
|
||||
import { Slide } from '../../shared/slide';
|
||||
import { Team } from '../../shared/team';
|
||||
import {SlideService} from '../../shared/service/slide.service';
|
||||
import {Slide, SlideVisibility} from '../../shared/slide';
|
||||
import {Team} from '../../shared/team';
|
||||
|
||||
@Component({
|
||||
selector: 'app-slide-list',
|
||||
@ -38,7 +38,7 @@ export class SlideListComponent implements OnInit {
|
||||
return this.selectedTeam === this.emptyTeam
|
||||
? this.slideService.slides
|
||||
: this.slideService.slides.filter(
|
||||
slide => slide.team == null || slide.team.id === this.selectedTeam.id
|
||||
slide => slide.teams == null || slide.teams.some(s => s.id === this.selectedTeam.id)
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,8 +46,10 @@ export class SlideListComponent implements OnInit {
|
||||
this.slideService.slides = slides;
|
||||
}
|
||||
|
||||
public slideTeam(team: Team): String {
|
||||
return team === null ? 'All teams' : team.name;
|
||||
public slideTeam(slide: Slide): string {
|
||||
return slide.visibility === SlideVisibility.Team && slide.teams.length > 0
|
||||
? slide.teams.map(team => team.name).join(', ')
|
||||
: 'All teams';
|
||||
}
|
||||
|
||||
public moveUp(slide: Slide) {
|
||||
@ -106,4 +108,11 @@ export class SlideListComponent implements OnInit {
|
||||
'red times': !slide.isVisible
|
||||
};
|
||||
}
|
||||
|
||||
public visibilityIcon(slide: Slide) {
|
||||
return {
|
||||
'green globe': slide.visibility === SlideVisibility.Public,
|
||||
'green users': slide.visibility === SlideVisibility.Team
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
2
src/app/admin/slide-resolver.service.ts
Normal file → Executable file
2
src/app/admin/slide-resolver.service.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Slide } from '../shared/slide';
|
||||
|
||||
189
src/app/admin/team-editor/team-editor.component.html
Normal file → Executable file
189
src/app/admin/team-editor/team-editor.component.html
Normal file → Executable file
@ -1,20 +1,186 @@
|
||||
<div class="ui main container">
|
||||
<h1 class="ui dividing header">Team editor</h1>
|
||||
<form class="ui form" #teamEditorForm (ngSubmit)="saveTeam()">
|
||||
<div class="six wide field">
|
||||
<label for="team_name">Team name</label>
|
||||
<input id="team_name" type="text" name="team_name" [(ngModel)]="team.name">
|
||||
<form class="ui form" #teamEditorForm (ngSubmit)="saveTeam(f)" #f="ngForm">
|
||||
<div class="two inline fields">
|
||||
<div class="six wide field" [class.error]="checkError(teamName)">
|
||||
<label for="team_name">Team name</label>
|
||||
<input id="team_name" type="text" name="team_name"
|
||||
required [(ngModel)]="team.name" #teamName="ngModel">
|
||||
</div>
|
||||
<div class="six wide field">
|
||||
<label> </label>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="team_is_active" name="team_is_active"
|
||||
[(ngModel)]="team.isActive">
|
||||
<label for="team_is_active">Active</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="six wide field">
|
||||
|
||||
<h4 class="ui dividing header">Built-in slides</h4>
|
||||
<div class="three inline fields">
|
||||
<div class="three wide field">
|
||||
<label for="team_name"> </label>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="team_is_active" name="team_is_active"
|
||||
[(ngModel)]="team.isActive">
|
||||
<label for="team_is_active">Active</label>
|
||||
<input type="checkbox" id="kanban_enabled" name="kanban_enabled"
|
||||
[(ngModel)]="team.kanbanEnabled">
|
||||
<label for="kanban_enabled">Kanban board</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide field">
|
||||
<label for="team_name"> </label>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="commit_tracker_enabled" name="commit_tracker_enabled"
|
||||
[(ngModel)]="team.commitTrackerEnabled">
|
||||
<label for="commit_tracker_enabled">Commit-tracker</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide field">
|
||||
<label for="team_name"> </label>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="watched_enabled" name="watched_enabled"
|
||||
[(ngModel)]="team.watchedEnabled">
|
||||
<label for="watched_enabled">Watched</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="team.kanbanEnabled">
|
||||
<h3 class="ui dividing header">Kanban configuration</h3>
|
||||
<div class="six wide field" [class.error]="checkError(filterId)">
|
||||
<label for="filter_id">Jira filter id</label>
|
||||
<input id="filter_id" type="number" name="filter_id"
|
||||
required minlength="4" min="1" [required]="team.kanbanEnabled"
|
||||
[(ngModel)]="team.filterId" #filterId="ngModel">
|
||||
</div>
|
||||
<h5 class="ui dividing header">Daily standup timer</h5>
|
||||
<div class="three inline fields">
|
||||
<div class="two wide field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="daily_lock_timer" name="daily_lock_timer"
|
||||
[(ngModel)]="team.dailyLockEnabled">
|
||||
<label for="daily_lock_timer">Enabled</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="four wide field" [class.error]="checkError(startTime)">
|
||||
<label for="daily_start">Starts</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="time" id="daily_start" name="daily_start"
|
||||
min="9:00" max="15:00" [required]="team.dailyLockEnabled && team.kanbanEnabled"
|
||||
[(ngModel)]="team.dailyStartTime" #startTime="ngModel">
|
||||
<i class="time icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="four wide field" [class.error]="checkError(endTime)">
|
||||
<label for="daily_end">Ends</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="time" id="daily_end" name="daily_end"
|
||||
min="9:00" max="15:00" [required]="team.dailyLockEnabled && team.kanbanEnabled"
|
||||
[(ngModel)]="team.dailyEndTime" #endTime="ngModel">
|
||||
<i class="time icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="ui dividing header">Columns</h4>
|
||||
<div class="four fields">
|
||||
<div class="six wide field">
|
||||
<label>Jira status</label>
|
||||
<input type="text" name="column1_js"
|
||||
placeholder="Jira column name" [(ngModel)]="team.backlogColumn.jiraStatusName">
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<label>Display name</label>
|
||||
<input type="text" name="column1_l"
|
||||
placeholder="Kanban board header" [(ngModel)]="team.backlogColumn.label">
|
||||
</div>
|
||||
<div class="two wide field">
|
||||
<label>WIP limit</label>
|
||||
<input type="text" name="column1_wip"
|
||||
placeholder="WIP limit" [(ngModel)]="team.backlogColumn.wipLimit">
|
||||
</div>
|
||||
</div>
|
||||
<div class="four fields">
|
||||
<div class="six wide field">
|
||||
<input type="text" name="column2_js"
|
||||
placeholder="Jira column name" [(ngModel)]="team.inprogressColumn.jiraStatusName">
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<input type="text" name="column2_l"
|
||||
placeholder="Kanban board header" [(ngModel)]="team.inprogressColumn.label">
|
||||
</div>
|
||||
<div class="two wide field">
|
||||
<input type="text" name="column2_wip"
|
||||
placeholder="WIP limit" [(ngModel)]="team.inprogressColumn.wipLimit">
|
||||
</div>
|
||||
</div>
|
||||
<div class="four fields">
|
||||
<div class="six wide field">
|
||||
<input type="text" name="column3_js"
|
||||
placeholder="Jira column name" [(ngModel)]="team.verificationColumn.jiraStatusName">
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<input type="text" name="column3_l"
|
||||
placeholder="Kanban board header" [(ngModel)]="team.verificationColumn.label">
|
||||
</div>
|
||||
<div class="two wide field">
|
||||
<input type="text" name="column3_wip"
|
||||
placeholder="WIP limit" [(ngModel)]="team.verificationColumn.wipLimit">
|
||||
</div>
|
||||
</div>
|
||||
<div class="four fields">
|
||||
<div class="six wide field">
|
||||
<input type="text" name="column4_js"
|
||||
placeholder="Jira column name" [(ngModel)]="team.doneColumn.jiraStatusName">
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<input type="text" name="column4_l"
|
||||
placeholder="Kanban board header" [(ngModel)]="team.doneColumn.label">
|
||||
</div>
|
||||
<div class="two wide field">
|
||||
<input type="text" name="column4_wip"
|
||||
placeholder="WIP limit" [(ngModel)]="team.doneColumn.wipLimit">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="ui dividing header">Team members</h4>
|
||||
<h4 class="ui dividing header">Labels</h4>
|
||||
<div class="three inline fields">
|
||||
<div class="two wide field">
|
||||
<button type="button" class="ui fluid button"
|
||||
[class.positive]="canAddLabel"
|
||||
[class.disabled]="!canAddLabel"
|
||||
(keydown.enter)="handleEnter($event)"
|
||||
(click)="addLabel()">Add
|
||||
</button>
|
||||
</div>
|
||||
<div class="five wide field">
|
||||
<input type="text" #labelInput
|
||||
name="label_name"
|
||||
placeholder="Label text"
|
||||
(keydown.enter)="handleLabelEnter($event)"
|
||||
[(ngModel)]="label.name">
|
||||
</div>
|
||||
<ng-template let-option #optionTemplate>
|
||||
<span class="ui tiny {{option}} label">{{option}}</span>
|
||||
</ng-template>
|
||||
<sui-select class="ui right floated selection"
|
||||
id="label_color"
|
||||
name="label_color"
|
||||
[(ngModel)]="label.color"
|
||||
[optionTemplate]="optionTemplate"
|
||||
[isSearchable]="false"
|
||||
#labelSelect>
|
||||
<sui-select-option *ngFor="let labelColor of labelColors" [value]="labelColor"></sui-select-option>
|
||||
</sui-select>
|
||||
</div>
|
||||
<div>
|
||||
<span *ngFor="let label of team.labels"
|
||||
class="ui medium {{label.color}} label">{{label.name}}<i class="large delete icon"
|
||||
(click)="removeLabel(label)"></i></span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<h3 class="ui dividing header">Team members</h3>
|
||||
<div class="three inline fields">
|
||||
<div class="two wide field">
|
||||
<button type="button" class="ui fluid button"
|
||||
@ -39,6 +205,7 @@
|
||||
[(ngModel)]="member.name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="ui dividing header"></h4>
|
||||
<table class="ui celled definition table" *ngIf="team.members.length">
|
||||
<thead>
|
||||
@ -59,8 +226,8 @@
|
||||
</table>
|
||||
|
||||
<button type="submit" class="ui button"
|
||||
[class.positive]="canSave"
|
||||
[class.disabled]="!canSave"><i class="save outline icon"></i>Save changes
|
||||
[class.positive]="canSave(f)"
|
||||
[class.disabled]="!canSave(f)"><i class="save outline icon"></i>Save changes
|
||||
</button>
|
||||
<a class="ui button"
|
||||
[routerLink]="['/admin/teams']"><i class="left angle icon"></i>Back to teams list</a>
|
||||
|
||||
72
src/app/admin/team-editor/team-editor.component.ts
Normal file → Executable file
72
src/app/admin/team-editor/team-editor.component.ts
Normal file → Executable file
@ -1,11 +1,12 @@
|
||||
import { Component, ElementRef, HostBinding, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TeamService } from '../../shared/service/team.service';
|
||||
import { Team } from '../../shared/team';
|
||||
import { Member } from '../../shared/member';
|
||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
||||
import { Label } from '../../shared/label';
|
||||
import {NgForm, NgModel} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-team-editor',
|
||||
@ -13,8 +14,10 @@ import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
||||
styleUrls: ['./team-editor.component.css']
|
||||
})
|
||||
export class TeamEditorComponent implements OnInit {
|
||||
@ViewChild('labelInput') labelInputElement: ElementRef;
|
||||
@ViewChild('signumInput') signumInputElement: ElementRef;
|
||||
public team: Team;
|
||||
public label: Label = new Label();
|
||||
public member: Member = new Member();
|
||||
|
||||
constructor(private teamService: TeamService,
|
||||
@ -28,6 +31,57 @@ export class TeamEditorComponent implements OnInit {
|
||||
this.route.data.subscribe((data: { team: Team }) => this.team = data.team ? data.team : new Team());
|
||||
}
|
||||
|
||||
get labelColors(): Array<string> {
|
||||
return [
|
||||
'red',
|
||||
'orange',
|
||||
'yellow',
|
||||
'olive',
|
||||
'green',
|
||||
'teal',
|
||||
'blue',
|
||||
'violet',
|
||||
'purple',
|
||||
'pink',
|
||||
'brown',
|
||||
'grey',
|
||||
'black',
|
||||
'white'
|
||||
];
|
||||
}
|
||||
|
||||
get canAddLabel(): boolean {
|
||||
try {
|
||||
return [this.label.name, this.label.color].every(field => field.length !== 0)
|
||||
&& this.team.labels.every(label => label.name !== this.label.name);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public addLabel() {
|
||||
this.team.labels = this.team.labels
|
||||
.concat(Object.assign({}, this.label))
|
||||
.sort((a: Label, b: Label) => a.name < b.name ? -1 : 1);
|
||||
this.label = new Label();
|
||||
}
|
||||
|
||||
public removeLabel(label: Label) {
|
||||
this.team.labels = this.team.labels.filter(teamLabel => teamLabel !== label);
|
||||
}
|
||||
|
||||
public handleLabelEnter(ev: KeyboardEvent) {
|
||||
ev.preventDefault();
|
||||
if (this.canAddLabel) {
|
||||
this.addLabel();
|
||||
this.focusLabelField();
|
||||
}
|
||||
}
|
||||
|
||||
public focusLabelField() {
|
||||
this.labelInputElement.nativeElement.focus();
|
||||
}
|
||||
|
||||
get canAddMember(): boolean {
|
||||
try {
|
||||
return [this.member.name, this.member.signum].every(field => field.length !== 0)
|
||||
@ -62,18 +116,22 @@ export class TeamEditorComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
get canSave(): boolean {
|
||||
return [
|
||||
public canSave(form: NgForm): boolean {
|
||||
return form.valid && [
|
||||
this.team.name.trim(),
|
||||
this.team.members
|
||||
].every(field => field.length > 0);
|
||||
].every(field => field.length > 0) && this.team.filterId > 0;
|
||||
}
|
||||
|
||||
public saveTeam() {
|
||||
if (this.canSave) {
|
||||
public saveTeam(form: NgForm) {
|
||||
if (this.canSave(form)) {
|
||||
this.teamService.persist(this.team).subscribe(
|
||||
() => this.router.navigate(['/admin/teams'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public checkError(fieldModel: NgModel): boolean {
|
||||
return fieldModel.invalid && (fieldModel.dirty || fieldModel.touched);
|
||||
}
|
||||
}
|
||||
|
||||
2
src/app/admin/team-resolver.service.ts
Normal file → Executable file
2
src/app/admin/team-resolver.service.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Team } from '../shared/team';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
9
src/app/app.component.css
Normal file → Executable file
9
src/app/app.component.css
Normal file → Executable file
@ -0,0 +1,9 @@
|
||||
.pause-indicator {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
background-color: lightgrey;
|
||||
color: red;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
5
src/app/app.component.html
Normal file → Executable file
5
src/app/app.component.html
Normal file → Executable file
@ -1 +1,4 @@
|
||||
<router-outlet></router-outlet>
|
||||
<div class="pause-indicator" *ngIf="paused">Slideshow is paused</div>
|
||||
<main [@routerTransition]="getAnimationData(o)">
|
||||
<router-outlet #o="outlet"></router-outlet>
|
||||
</main>
|
||||
|
||||
51
src/app/app.component.ts
Normal file → Executable file
51
src/app/app.component.ts
Normal file → Executable file
@ -1,12 +1,57 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, HostListener, OnInit } from '@angular/core';
|
||||
import { TimerService } from './shared/service/timer.service';
|
||||
import { SlideShowService } from './display/slide-show.service';
|
||||
import { Router, RouterOutlet } from '@angular/router';
|
||||
import { slideInOutAnimation } from './shared/slide-in-out-animation';
|
||||
import { AnimationDirection, SettingsService } from './shared/service/settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
styleUrls: ['./app.component.css'],
|
||||
animations: [slideInOutAnimation],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(private timerService: TimerService) {}
|
||||
constructor(private timerService: TimerService,
|
||||
private slideShowService: SlideShowService,
|
||||
private settings: SettingsService,
|
||||
private router: Router) {}
|
||||
|
||||
public ngOnInit() {}
|
||||
|
||||
@HostListener('document:keyup', ['$event.key'])
|
||||
private keyPressed(key: string) {
|
||||
if (this.timerService.autoSwitch) {
|
||||
switch (key) {
|
||||
case 'Home':
|
||||
this.router.navigate(['/dashboard']);
|
||||
break;
|
||||
case ' ':
|
||||
this.timerService.togglePause();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
this.timerService.pause();
|
||||
this.slideShowService.prevSlide();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.timerService.pause();
|
||||
this.slideShowService.nextSlide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get paused(): boolean {
|
||||
return this.timerService.paused;
|
||||
}
|
||||
|
||||
public getAnimationData(outlet: RouterOutlet) {
|
||||
return {
|
||||
value: outlet.activatedRouteData.state ? outlet.activatedRouteData.state : false,
|
||||
params: {
|
||||
offsetEnter: this.settings.animationDirection === AnimationDirection.LEFT ? -100 : 100,
|
||||
offsetLeave: this.settings.animationDirection === AnimationDirection.LEFT ? 100 : -100,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
10
src/app/display/commit-tracker/commit-tracker.component.css
Normal file → Executable file
10
src/app/display/commit-tracker/commit-tracker.component.css
Normal file → Executable file
@ -1,5 +1,11 @@
|
||||
:host {
|
||||
background-color: #444;
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.ui.label.inprogress {
|
||||
@ -43,6 +49,8 @@
|
||||
|
||||
.ui.jira-avatar.image > img {
|
||||
border-radius: 4px;
|
||||
max-width: 45px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
@ -56,4 +64,4 @@
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
src/app/display/commit-tracker/commit-tracker.component.html
Normal file → Executable file
4
src/app/display/commit-tracker/commit-tracker.component.html
Normal file → Executable file
@ -1,5 +1,5 @@
|
||||
<div class="ui main wide-container dark">
|
||||
<table *ngIf="commits?.length" class="ui large padded inverted celled2 table">
|
||||
<table *ngIf="commits?.length" class="ui large padded inverted table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="collapsing"><i class="user icon"></i>Owner</th>
|
||||
@ -43,4 +43,4 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
30
src/app/display/commit-tracker/commit-tracker.component.ts
Normal file → Executable file
30
src/app/display/commit-tracker/commit-tracker.component.ts
Normal file → Executable file
@ -1,17 +1,15 @@
|
||||
import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { TimerObservable } from 'rxjs/observable/TimerObservable';
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Subscription, timer} from 'rxjs';
|
||||
|
||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
||||
import { CommitTrackerService } from '../../shared/service/commit-tracker.service';
|
||||
import { SettingsService } from '../../shared/service/settings.service';
|
||||
import { Commit } from '../../shared/commit';
|
||||
import { CommitStatus } from '../../shared/commit-status.enum';
|
||||
import { Result } from '../../shared/result.enum';
|
||||
import { Build } from '../../shared/build';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import {CommitTrackerService} from '../../shared/service/commit-tracker.service';
|
||||
import {SettingsService} from '../../shared/service/settings.service';
|
||||
import {Commit} from '../../shared/commit';
|
||||
import {CommitStatus} from '../../shared/commit-status.enum';
|
||||
import {Result} from '../../shared/result.enum';
|
||||
import {Build} from '../../shared/build';
|
||||
import {environment} from '../../../environments/environment';
|
||||
|
||||
const TIMER_COMMITTRACKER_REFRESH = 10000;
|
||||
const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||
@ -20,16 +18,12 @@ const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||
selector: 'app-commit-tracker',
|
||||
templateUrl: './commit-tracker.component.html',
|
||||
styleUrls: ['./commit-tracker.component.css'],
|
||||
animations: [slideInOutAnimation]
|
||||
})
|
||||
export class CommitTrackerComponent implements OnInit, OnDestroy {
|
||||
|
||||
public CommitStatus = CommitStatus;
|
||||
private refreshCommitTrackerTimer: Subscription;
|
||||
|
||||
@HostBinding('@slideInOutAnimation')
|
||||
slideIn = false;
|
||||
|
||||
constructor(private commitTrackerService: CommitTrackerService,
|
||||
private settings: SettingsService,
|
||||
private titleService: Title,
|
||||
@ -40,7 +34,7 @@ export class CommitTrackerComponent implements OnInit, OnDestroy {
|
||||
this.titleService.setTitle('Commit-tracker : MTAStv');
|
||||
this.route.data.subscribe((data: { commits: Array<Commit> }) => this.commits = data.commits);
|
||||
|
||||
const timerCT = TimerObservable.create(TIMER_COMMITTRACKER_REFRESH, TIMER_COMMITTRACKER_REFRESH);
|
||||
const timerCT = timer(TIMER_COMMITTRACKER_REFRESH, TIMER_COMMITTRACKER_REFRESH);
|
||||
this.refreshCommitTrackerTimer = timerCT.subscribe(() => {
|
||||
this.commitTrackerService.getTeamCommits(this.settings.team.members.map(member => member.signum))
|
||||
.subscribe(commits => this.commits = commits);
|
||||
|
||||
60
src/app/display/display-routing.module.ts
Normal file → Executable file
60
src/app/display/display-routing.module.ts
Normal file → Executable file
@ -9,87 +9,117 @@ import { SlideShowComponent } from './slide-show/slide-show.component';
|
||||
import { SlideResolverService } from '../admin/slide-resolver.service';
|
||||
import { KanbanBoardComponent } from './kanban-board/kanban-board.component';
|
||||
import { KanbanService } from './shared';
|
||||
import { WatchersComponent } from './watchers/watchers.component';
|
||||
import { WatcherService } from './shared/watcher.service';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'slideshow/:id',
|
||||
component: SlideShowComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
slide: SlideResolverService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: false
|
||||
autoSwitchable: false,
|
||||
state: 'slideshow',
|
||||
}
|
||||
}, {
|
||||
path: 'slideshow-odd/:id',
|
||||
component: SlideShowComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
slide: SlideResolverService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: true
|
||||
autoSwitchable: true,
|
||||
state: 'slideshow-odd',
|
||||
}
|
||||
}, {
|
||||
path: 'slideshow-even/:id',
|
||||
component: SlideShowComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
slide: SlideResolverService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: true
|
||||
autoSwitchable: true,
|
||||
state: 'slideshow-even',
|
||||
}
|
||||
}, {
|
||||
path: 'commit-tracker',
|
||||
component: CommitTrackerComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
commits: CommitTrackerService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: true
|
||||
autoSwitchable: true,
|
||||
state: 'commit-tracker',
|
||||
}
|
||||
}, {
|
||||
path: 'commit-tracker-fixed',
|
||||
component: CommitTrackerComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
commits: CommitTrackerService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: false,
|
||||
state: 'commit-tracker-fixed',
|
||||
}
|
||||
}, {
|
||||
path: 'kanban',
|
||||
component: KanbanBoardComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
kanbanBoard: KanbanService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: true
|
||||
autoSwitchable: true,
|
||||
state: 'kanban',
|
||||
}
|
||||
}, {
|
||||
path: 'kanban-fixed',
|
||||
component: KanbanBoardComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
kanbanBoard: KanbanService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: false
|
||||
autoSwitchable: false,
|
||||
state: 'kanban-fixed',
|
||||
}
|
||||
}, {
|
||||
path: 'watchers',
|
||||
component: WatchersComponent,
|
||||
resolve: {
|
||||
watchers: WatcherService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: true,
|
||||
state: 'watchers',
|
||||
}
|
||||
}, {
|
||||
path: 'watchers-fixed',
|
||||
component: WatchersComponent,
|
||||
resolve: {
|
||||
watchers: WatcherService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: false,
|
||||
state: 'watchers-fixed',
|
||||
}
|
||||
}, {
|
||||
path: 'settings',
|
||||
component: SettingsComponent,
|
||||
// canActivate: [AuthGuardService, RoleGuardService],
|
||||
resolve: {
|
||||
teams: TeamService,
|
||||
},
|
||||
data: {
|
||||
autoSwitchable: true,
|
||||
// state: 'settings',
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class DisplayRoutingModule { }
|
||||
export class DisplayRoutingModule {}
|
||||
|
||||
6
src/app/display/display.module.ts
Normal file → Executable file
6
src/app/display/display.module.ts
Normal file → Executable file
@ -16,6 +16,8 @@ import { PrefixJiraIdPipe } from './shared/prefix-jira-id.pipe';
|
||||
import { PriorityColorPipe } from './shared/priority-color.pipe';
|
||||
import { ShortenTextPipe } from './shared/shorten-text.pipe';
|
||||
import { KanbanService } from './shared';
|
||||
import { SlideIframeComponent } from './slide-iframe/slide-iframe.component';
|
||||
import { WatchersComponent } from './watchers/watchers.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -24,7 +26,7 @@ import { KanbanService } from './shared';
|
||||
SuiModule,
|
||||
DisplayRoutingModule
|
||||
],
|
||||
exports: [SlideComponent],
|
||||
exports: [SlideComponent, SlideIframeComponent],
|
||||
declarations: [
|
||||
CommitTrackerComponent,
|
||||
SettingsComponent,
|
||||
@ -37,6 +39,8 @@ import { KanbanService } from './shared';
|
||||
PrefixJiraIdPipe,
|
||||
PriorityColorPipe,
|
||||
ShortenTextPipe,
|
||||
SlideIframeComponent,
|
||||
WatchersComponent,
|
||||
],
|
||||
providers: [
|
||||
SlideShowService,
|
||||
|
||||
10
src/app/display/kanban-board/kanban-board.component.css
Normal file → Executable file
10
src/app/display/kanban-board/kanban-board.component.css
Normal file → Executable file
@ -1,7 +1,13 @@
|
||||
:host {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
background-color: #444;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
8
src/app/display/kanban-board/kanban-board.component.html
Normal file → Executable file
8
src/app/display/kanban-board/kanban-board.component.html
Normal file → Executable file
@ -1,18 +1,18 @@
|
||||
<div class="ui main fullwide-container">
|
||||
<div class="ui grid">
|
||||
<div app-kanban-entry-item class="four wide column"
|
||||
rowHeading="INBOX"
|
||||
[rowHeading]="backlogLabel"
|
||||
[kanbanEntries]="kanbanBoard.inbox"></div>
|
||||
<div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass"
|
||||
rowHeading="INPROGRESS"
|
||||
[rowHeading]="inProgressLabel"
|
||||
[wipLimit]="inprogressWipLimit" [wipCount]="inprogressWipCount"
|
||||
[kanbanEntries]="kanbanBoard.inProgress"></div>
|
||||
<div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass"
|
||||
rowHeading="VERIFICATION"
|
||||
[rowHeading]="verificationLabel"
|
||||
[wipLimit]="verificationWipLimit" [wipCount]="verificationWipCount"
|
||||
[kanbanEntries]="kanbanBoard.verification"></div>
|
||||
<div app-kanban-entry-item class="four wide column"
|
||||
rowHeading="DÖNER"
|
||||
[rowHeading]="doneLabel"
|
||||
[kanbanEntries]="kanbanBoard.done"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
46
src/app/display/kanban-board/kanban-board.component.ts
Normal file → Executable file
46
src/app/display/kanban-board/kanban-board.component.ts
Normal file → Executable file
@ -1,34 +1,28 @@
|
||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
|
||||
import { KanbanBoard, KanbanEntry, KanbanService, } from '../shared';
|
||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
||||
|
||||
const WIP_LIMIT_INPROGRESS = 12;
|
||||
const WIP_LIMIT_VERIFICATION = 8;
|
||||
import {KanbanBoard, KanbanEntry, KanbanService,} from '../shared';
|
||||
import {SettingsService} from '../../shared/service/settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-kanban-board',
|
||||
templateUrl: './kanban-board.component.html',
|
||||
styleUrls: ['./kanban-board.component.css'],
|
||||
animations: [slideInOutAnimation]
|
||||
})
|
||||
export class KanbanBoardComponent implements OnInit {
|
||||
|
||||
@HostBinding('@slideInOutAnimation')
|
||||
slideIn = true;
|
||||
|
||||
constructor(private titleService: Title,
|
||||
private route: ActivatedRoute,
|
||||
private kanbanService: KanbanService) {
|
||||
private kanbanService: KanbanService,
|
||||
private settings: SettingsService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set page title, and handle preloaded kanbanBoard data
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('TaurusXFT : Kanban board');
|
||||
this.titleService.setTitle(`${this.settings.team.name} : Kanban board`);
|
||||
this.route.data.subscribe((data: {
|
||||
kanbanBoard: KanbanBoard,
|
||||
}) => {
|
||||
@ -45,7 +39,7 @@ export class KanbanBoardComponent implements OnInit {
|
||||
}
|
||||
|
||||
get inprogressWipLimit(): number {
|
||||
return WIP_LIMIT_INPROGRESS;
|
||||
return this.settings.team.inprogressColumn.wipLimit;
|
||||
}
|
||||
|
||||
get inprogressWipCount(): number {
|
||||
@ -64,12 +58,12 @@ export class KanbanBoardComponent implements OnInit {
|
||||
*/
|
||||
get inprogressWipClass() {
|
||||
return {
|
||||
'over-wip': this.inprogressWipCount > WIP_LIMIT_INPROGRESS,
|
||||
'over-wip': this.inprogressWipCount > this.settings.team.inprogressColumn.wipLimit,
|
||||
};
|
||||
}
|
||||
|
||||
get verificationWipLimit(): number {
|
||||
return WIP_LIMIT_VERIFICATION;
|
||||
return this.settings.team.verificationColumn.wipLimit;
|
||||
}
|
||||
|
||||
get verificationWipCount(): number {
|
||||
@ -88,7 +82,23 @@ export class KanbanBoardComponent implements OnInit {
|
||||
*/
|
||||
get verificationWipClass() {
|
||||
return {
|
||||
'over-wip': this.verificationWipCount > WIP_LIMIT_VERIFICATION,
|
||||
'over-wip': this.verificationWipCount > this.settings.team.verificationColumn.wipLimit,
|
||||
};
|
||||
}
|
||||
|
||||
get backlogLabel(): string {
|
||||
return this.settings.team.backlogColumn.label;
|
||||
}
|
||||
|
||||
get inProgressLabel(): string {
|
||||
return this.settings.team.inprogressColumn.label;
|
||||
}
|
||||
|
||||
get verificationLabel(): string {
|
||||
return this.settings.team.verificationColumn.label;
|
||||
}
|
||||
|
||||
get doneLabel(): string {
|
||||
return this.settings.team.doneColumn.label;
|
||||
}
|
||||
}
|
||||
|
||||
8
src/app/display/kanban-entry-item/kanban-entry-item.component.html
Normal file → Executable file
8
src/app/display/kanban-entry-item/kanban-entry-item.component.html
Normal file → Executable file
@ -10,10 +10,10 @@
|
||||
[ngClass]="entryClass(kanbanEntry)">
|
||||
<div class="content">
|
||||
<div class="task-description">
|
||||
<ng-template [ngIf]="hasLabels(kanbanEntry)">
|
||||
<span *ngFor="let label of kanbanEntry.labels"
|
||||
class="ui mini {{labelClass(label)}} right floated label">{{label|uppercase|blockedDays:kanbanEntry.daysBlocked}}</span>
|
||||
</ng-template>
|
||||
<span *ngIf="kanbanEntry.epicName"
|
||||
class="ui mini olive right floated label">{{kanbanEntry.epicName}}</span>
|
||||
<span *ngFor="let label of filteredLabels(kanbanEntry)"
|
||||
class="ui mini {{labelClass(label)}} right floated label">{{label|blockedDays:kanbanEntry.daysBlocked}}</span>
|
||||
<span *ngIf="wasBlocked(kanbanEntry)" class="ui mini {{labelClass('blocked')}} right floated label">{{kanbanEntry.daysBlocked}}D</span>
|
||||
<div *ngIf="!hasMultiAssignee(kanbanEntry)" class="ui jira-avatar floated image">
|
||||
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}" [title]="kanbanEntry.assignee?.name">
|
||||
|
||||
53
src/app/display/kanban-entry-item/kanban-entry-item.component.ts
Normal file → Executable file
53
src/app/display/kanban-entry-item/kanban-entry-item.component.ts
Normal file → Executable file
@ -2,19 +2,10 @@ import { Component, Input } from '@angular/core';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { JiraAssignee, KanbanEntry } from '../shared';
|
||||
import { SettingsService } from '../../shared/service/settings.service';
|
||||
|
||||
const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||
const JIRA_BOARD_BASE_HREF = 'https://jirapducc.mo.ca.am.ericsson.se/browse/';
|
||||
|
||||
const labelColors = {
|
||||
TSP: 'teal',
|
||||
MTAS: 'orange',
|
||||
INTERNAL: 'yellow',
|
||||
TEAM: 'yellow',
|
||||
BLOCKED: 'red',
|
||||
SPIKE: 'purple',
|
||||
EXPEDITE: 'pink',
|
||||
};
|
||||
const JIRA_BOARD_BASE_HREF = 'https://cc-jira.rnd.ki.sw.ericsson.se/browse/';
|
||||
|
||||
@Component({
|
||||
selector: 'app-kanban-entry-item,[app-kanban-entry-item]',
|
||||
@ -27,8 +18,7 @@ export class KanbanEntryItemComponent {
|
||||
@Input() wipLimit = 0;
|
||||
@Input() wipCount = 0;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
constructor(private settingService: SettingsService) {}
|
||||
|
||||
/**
|
||||
* Returns the full url of the assignee avatar,
|
||||
@ -38,17 +28,9 @@ export class KanbanEntryItemComponent {
|
||||
* @returns {string}
|
||||
*/
|
||||
public avatarUrl(avatarPath: string): string {
|
||||
return environment.apiUrl + (avatarPath ? avatarPath : DEFAULT_AVATAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if issue has any labels attached
|
||||
*
|
||||
* @param {KanbanEntry} entry
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public hasLabels(entry: KanbanEntry): boolean {
|
||||
return entry.labels.length > 0;
|
||||
return environment.apiUrl + (avatarPath
|
||||
? avatarPath
|
||||
: DEFAULT_AVATAR);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,11 +40,16 @@ export class KanbanEntryItemComponent {
|
||||
* @returns {string}
|
||||
*/
|
||||
public labelClass(label: string): string {
|
||||
try {
|
||||
return labelColors[label.toUpperCase()];
|
||||
} catch (e) {
|
||||
return 'white';
|
||||
if (this.settingService.team.labels) {
|
||||
const color = this.settingService.team.labels.find(
|
||||
teamLabel => teamLabel.name.toLocaleLowerCase() === label.toLocaleLowerCase()
|
||||
).color;
|
||||
|
||||
if (color !== null) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
return 'white';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,6 +64,15 @@ export class KanbanEntryItemComponent {
|
||||
};
|
||||
}
|
||||
|
||||
public filteredLabels(kanbanEntry: KanbanEntry): Array<string> {
|
||||
if (this.settingService.team.labels) {
|
||||
return kanbanEntry.labels.filter(entryLabel => this.settingService.team.labels.some(
|
||||
teamLabel => teamLabel.name.toLocaleLowerCase() === entryLabel.toLocaleLowerCase()
|
||||
));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate jira issue href
|
||||
*
|
||||
@ -119,4 +115,5 @@ export class KanbanEntryItemComponent {
|
||||
public getAssignees(kanbanEntry: KanbanEntry): Array<JiraAssignee> {
|
||||
return [].concat([kanbanEntry.assignee], kanbanEntry.additionalAssignees);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
2
src/app/display/settings/settings.component.html
Normal file → Executable file
2
src/app/display/settings/settings.component.html
Normal file → Executable file
@ -18,7 +18,7 @@
|
||||
<div class="four wide field">
|
||||
<label for="slide_interval">Slide duration: {{slideInterval}}ms</label>
|
||||
<input id="slide_interval" name="slide_interval"
|
||||
type="range" min="1000" max="120000" step="100"
|
||||
type="range" min="5000" max="120000" step="250"
|
||||
[(ngModel)]="slideInterval">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
10
src/app/display/settings/settings.component.ts
Normal file → Executable file
10
src/app/display/settings/settings.component.ts
Normal file → Executable file
@ -15,20 +15,20 @@ export class SettingsComponent implements OnInit {
|
||||
public slideInterval: number;
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private settingsService: SettingsService,
|
||||
private settings: SettingsService,
|
||||
private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe((data: {teams: Array<Team>}) => {
|
||||
this.teams = data.teams;
|
||||
this.selectedTeam = this.teams.find(team => team.id === this.settingsService.team.id);
|
||||
this.slideInterval = this.settingsService.slideInterval;
|
||||
this.selectedTeam = this.teams.find(team => team.id === this.settings.team.id);
|
||||
this.slideInterval = this.settings.slideInterval;
|
||||
});
|
||||
}
|
||||
|
||||
public saveSettings() {
|
||||
this.settingsService.team = this.selectedTeam;
|
||||
this.settingsService.slideInterval = this.slideInterval;
|
||||
this.settings.team = this.selectedTeam;
|
||||
this.settings.slideInterval = this.slideInterval;
|
||||
this.router.navigate(['/admin']);
|
||||
}
|
||||
}
|
||||
|
||||
2
src/app/display/shared/kanban-board.model.ts
Normal file → Executable file
2
src/app/display/shared/kanban-board.model.ts
Normal file → Executable file
@ -1,4 +1,4 @@
|
||||
import {KanbanEntry} from "./kanban-entry.model";
|
||||
import {KanbanEntry} from './kanban-entry.model';
|
||||
|
||||
export class KanbanBoard {
|
||||
public inbox: Array<KanbanEntry>;
|
||||
|
||||
7
src/app/display/shared/kanban-entry.model.ts
Normal file → Executable file
7
src/app/display/shared/kanban-entry.model.ts
Normal file → Executable file
@ -1,12 +1,13 @@
|
||||
import {JiraIssueType} from "./jira-issue-type.model";
|
||||
import {JiraStatus} from "./jira-status.model";
|
||||
import {JiraAssignee} from "./jira-assignee.model";
|
||||
import {JiraIssueType} from './jira-issue-type.model';
|
||||
import {JiraStatus} from './jira-status.model';
|
||||
import {JiraAssignee} from './jira-assignee.model';
|
||||
|
||||
export class KanbanEntry {
|
||||
public id: number;
|
||||
public key: string;
|
||||
public summary: string;
|
||||
public issueType: JiraIssueType;
|
||||
public epicName: string;
|
||||
public status: JiraStatus;
|
||||
public assignee: JiraAssignee;
|
||||
public additionalAssignees: Array<JiraAssignee> = [];
|
||||
|
||||
14
src/app/display/shared/kanban.service.ts
Normal file → Executable file
14
src/app/display/shared/kanban.service.ts
Normal file → Executable file
@ -1,10 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { KanbanBoard } from './kanban-board.model';
|
||||
import { SettingsService } from '../../shared/service/settings.service';
|
||||
import { TeamService } from '../../shared/service/team.service';
|
||||
import { flatMap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class KanbanService {
|
||||
@ -12,16 +15,21 @@ export class KanbanService {
|
||||
|
||||
private cachedKanbanBoard: KanbanBoard = new KanbanBoard();
|
||||
|
||||
constructor(private httpService: HttpClient) {
|
||||
constructor(private httpService: HttpClient,
|
||||
private teamService: TeamService,
|
||||
private settingService: SettingsService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable instance to the kanban board api
|
||||
* Reloads team data before, to refresh team config
|
||||
*
|
||||
* @returns {Observable<KanbanBoard>}
|
||||
*/
|
||||
public getList(): Observable<KanbanBoard> {
|
||||
return this.httpService.get<KanbanBoard>(this.url);
|
||||
return this.teamService.get(this.settingService.team.id).pipe(
|
||||
flatMap(() => this.httpService.get<KanbanBoard>(`${this.url}/${this.settingService.team.id}`))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
13
src/app/display/shared/watched-issue.model.ts
Executable file
13
src/app/display/shared/watched-issue.model.ts
Executable file
@ -0,0 +1,13 @@
|
||||
export class WatchedIssue {
|
||||
public issue = '';
|
||||
public summary = '';
|
||||
public assignee = '';
|
||||
public comment: WatchedIssueComment;
|
||||
}
|
||||
|
||||
export class WatchedIssueComment {
|
||||
public signum = '';
|
||||
public name = '';
|
||||
public content = '';
|
||||
public date = '';
|
||||
}
|
||||
15
src/app/display/shared/watcher.service.spec.ts
Normal file
15
src/app/display/shared/watcher.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { WatcherService } from './watcher.service';
|
||||
|
||||
describe('WatcherService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [WatcherService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([WatcherService], (service: WatcherService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
62
src/app/display/shared/watcher.service.ts
Executable file
62
src/app/display/shared/watcher.service.ts
Executable file
@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { KanbanBoard } from './kanban-board.model';
|
||||
import { SettingsService } from '../../shared/service/settings.service';
|
||||
import { TeamService } from '../../shared/service/team.service';
|
||||
import { flatMap } from 'rxjs/operators';
|
||||
import {WatchedIssue} from './watched-issue.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WatcherService {
|
||||
private url = environment.apiUrl + '/api/watched';
|
||||
|
||||
private cachedWatchers: Array<WatchedIssue> = [];
|
||||
|
||||
constructor(private httpService: HttpClient,
|
||||
private teamService: TeamService,
|
||||
private settingService: SettingsService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable instance to the kanban board api
|
||||
* Reloads team data before, to refresh team config
|
||||
*
|
||||
* @returns {Observable<KanbanBoard>}
|
||||
*/
|
||||
public getList(): Observable<Array<WatchedIssue>> {
|
||||
return this.teamService.get(this.settingService.team.id).pipe(
|
||||
flatMap(() => this.httpService.get<Array<WatchedIssue>>(`${this.url}/${this.settingService.team.id}`))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Route preload resolver
|
||||
*
|
||||
* @param {ActivatedRouteSnapshot} route
|
||||
* @returns {Promise<KanbanBoard>}
|
||||
*/
|
||||
public resolve(route: ActivatedRouteSnapshot): Promise<Array<WatchedIssue> | boolean> {
|
||||
return this.getList().toPromise().then(result => result ? result : false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the board
|
||||
*/
|
||||
public reload() {
|
||||
this.getList().subscribe(result => this.cachedWatchers = result);
|
||||
}
|
||||
|
||||
get watchers(): Array<WatchedIssue> {
|
||||
return this.cachedWatchers;
|
||||
}
|
||||
|
||||
set watchers(kanbanBoard: Array<WatchedIssue>) {
|
||||
this.cachedWatchers = kanbanBoard;
|
||||
}
|
||||
}
|
||||
21
src/app/display/slide-iframe/slide-iframe.component.css
Executable file
21
src/app/display/slide-iframe/slide-iframe.component.css
Executable file
@ -0,0 +1,21 @@
|
||||
:host {
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 3px;
|
||||
background: #222;
|
||||
}
|
||||
:host.preview {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
padding: 30px;
|
||||
}
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
1
src/app/display/slide-iframe/slide-iframe.component.html
Executable file
1
src/app/display/slide-iframe/slide-iframe.component.html
Executable file
@ -0,0 +1 @@
|
||||
<iframe [src]="sanitizedUrl"></iframe>
|
||||
25
src/app/display/slide-iframe/slide-iframe.component.spec.ts
Normal file
25
src/app/display/slide-iframe/slide-iframe.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SlideIframeComponent } from './slide-iframe.component';
|
||||
|
||||
describe('SlideIframeComponent', () => {
|
||||
let component: SlideIframeComponent;
|
||||
let fixture: ComponentFixture<SlideIframeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SlideIframeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SlideIframeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
29
src/app/display/slide-iframe/slide-iframe.component.ts
Executable file
29
src/app/display/slide-iframe/slide-iframe.component.ts
Executable file
@ -0,0 +1,29 @@
|
||||
import {Component, EventEmitter, HostListener, Input, OnInit, Output} from '@angular/core';
|
||||
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-slide-iframe',
|
||||
templateUrl: './slide-iframe.component.html',
|
||||
styleUrls: ['./slide-iframe.component.css']
|
||||
})
|
||||
export class SlideIframeComponent implements OnInit {
|
||||
@Input() data = '';
|
||||
@Input() preview = false;
|
||||
|
||||
@Input() visible = true;
|
||||
@Output() visibleChange = new EventEmitter();
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) {}
|
||||
ngOnInit() {}
|
||||
|
||||
@HostListener('click')
|
||||
public hide() {
|
||||
if (this.preview) {
|
||||
this.visibleChange.emit(false);
|
||||
}
|
||||
}
|
||||
|
||||
get sanitizedUrl(): SafeResourceUrl {
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(this.data);
|
||||
}
|
||||
}
|
||||
88
src/app/display/slide-show.service.ts
Normal file → Executable file
88
src/app/display/slide-show.service.ts
Normal file → Executable file
@ -1,46 +1,86 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Slide } from '../shared/slide';
|
||||
import { Slide, SlideVisibility } from '../shared/slide';
|
||||
import { SlideService } from '../shared/service/slide.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { SettingsService } from '../shared/service/settings.service';
|
||||
import { AnimationDirection, SettingsService } from '../shared/service/settings.service';
|
||||
import { TwoWayLinkedList } from '../shared/two-way-linked-list';
|
||||
import { SlideWrapper, WrappedType } from '../shared/slide-wrapper';
|
||||
|
||||
@Injectable()
|
||||
export class SlideShowService {
|
||||
|
||||
private oddEven = false;
|
||||
private currentSlideIndex = -1;
|
||||
private slides: Array<Slide> = [];
|
||||
private cachedSlides: TwoWayLinkedList<SlideWrapper> = new TwoWayLinkedList<SlideWrapper>();
|
||||
|
||||
constructor(private slideService: SlideService,
|
||||
private settingsService: SettingsService,
|
||||
private settings: SettingsService,
|
||||
private router: Router) {
|
||||
this.reloadSlides();
|
||||
}
|
||||
|
||||
public nextSlide() {
|
||||
if (this.currentSlideIndex === this.slides.length - 1) {
|
||||
// this.currentSlideIndex++;
|
||||
// this.router.navigate(['/kanban']);
|
||||
// } else if (this.currentSlideIndex === this.slides.length) {
|
||||
this.currentSlideIndex = -1;
|
||||
this.reloadSlides();
|
||||
this.router.navigate(['/commit-tracker']);
|
||||
public startWithFirstSlide() {
|
||||
this.settings.animationDirection = AnimationDirection.RIGHT;
|
||||
this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.first));
|
||||
}
|
||||
|
||||
public prevSlide() {
|
||||
this.settings.animationDirection = AnimationDirection.LEFT;
|
||||
if (this.cachedSlides.isFirst()) {
|
||||
this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.last));
|
||||
} else {
|
||||
this.oddEven = !this.oddEven;
|
||||
this.currentSlideIndex++;
|
||||
this.router.navigate([
|
||||
this.oddEven ? '/slideshow-odd' : '/slideshow-even',
|
||||
this.slides[this.currentSlideIndex].id
|
||||
]);
|
||||
const prevSlide = this.cachedSlides.prev();
|
||||
this.switchToWrappedSlide(prevSlide);
|
||||
}
|
||||
}
|
||||
|
||||
private reloadSlides() {
|
||||
const team = this.settingsService.team;
|
||||
public nextSlide() {
|
||||
this.settings.animationDirection = AnimationDirection.RIGHT;
|
||||
if (this.cachedSlides.isLast()) {
|
||||
this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.first));
|
||||
} else {
|
||||
const nextSlide = this.cachedSlides.next();
|
||||
this.switchToWrappedSlide(nextSlide);
|
||||
}
|
||||
}
|
||||
|
||||
private switchToWrappedSlide(wrappedSlide: SlideWrapper) {
|
||||
if (WrappedType.BUILTIN === wrappedSlide.type) {
|
||||
this.router.navigate([wrappedSlide.slideRoute]);
|
||||
} else if (WrappedType.USER === wrappedSlide.type) {
|
||||
this.oddEven = !this.oddEven;
|
||||
this.router.navigate([
|
||||
this.oddEven ? '/slideshow-odd' : '/slideshow-even',
|
||||
wrappedSlide.slideData.id
|
||||
]);
|
||||
} else {
|
||||
throw Error(`Unknown slide type: ${wrappedSlide.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
private reloadSlides(onReloadFinish: () => void = null) {
|
||||
this.slideService.list().subscribe(
|
||||
slides => this.slides = slides.filter(
|
||||
slide => slide.team === null || slide.team.id === team.id
|
||||
)
|
||||
slides => {
|
||||
this.cachedSlides.clear();
|
||||
slides.filter(
|
||||
(slide: Slide) => slide.isVisible && (slide.visibility === SlideVisibility.Public || slide.teams.some(
|
||||
s => s.id === this.settings.team.id
|
||||
))
|
||||
).map(slide => this.cachedSlides.push(new SlideWrapper(WrappedType.USER, slide)));
|
||||
|
||||
if (this.settings.team.kanbanEnabled) {
|
||||
this.cachedSlides.push(new SlideWrapper(WrappedType.BUILTIN, '/kanban'));
|
||||
}
|
||||
if (this.settings.team.commitTrackerEnabled) {
|
||||
this.cachedSlides.push(new SlideWrapper(WrappedType.BUILTIN, '/commit-tracker'));
|
||||
}
|
||||
if (this.settings.team.watchedEnabled) {
|
||||
this.cachedSlides.push(new SlideWrapper(WrappedType.BUILTIN, '/watchers'));
|
||||
}
|
||||
|
||||
if (null !== onReloadFinish) {
|
||||
onReloadFinish();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
8
src/app/display/slide-show/slide-show.component.css
Normal file → Executable file
8
src/app/display/slide-show/slide-show.component.css
Normal file → Executable file
@ -0,0 +1,8 @@
|
||||
:host {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
3
src/app/display/slide-show/slide-show.component.html
Normal file → Executable file
3
src/app/display/slide-show/slide-show.component.html
Normal file → Executable file
@ -1 +1,2 @@
|
||||
<app-slide [data]="renderedSlide"></app-slide>
|
||||
<app-slide *ngIf="isMarkdown" [data]="renderedSlide"></app-slide>
|
||||
<app-slide-iframe *ngIf="isIframe" [data]="slide.slideUrl"></app-slide-iframe>
|
||||
|
||||
21
src/app/display/slide-show/slide-show.component.ts
Normal file → Executable file
21
src/app/display/slide-show/slide-show.component.ts
Normal file → Executable file
@ -1,24 +1,19 @@
|
||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
import * as marked from 'marked';
|
||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
||||
import { Slide } from '../../shared/slide';
|
||||
import {Slide, SlideType} from '../../shared/slide';
|
||||
|
||||
@Component({
|
||||
selector: 'app-slide-show',
|
||||
templateUrl: './slide-show.component.html',
|
||||
styleUrls: ['./slide-show.component.css'],
|
||||
animations: [slideInOutAnimation]
|
||||
})
|
||||
export class SlideShowComponent implements OnInit {
|
||||
private md;
|
||||
public slide: Slide;
|
||||
|
||||
@HostBinding('@slideInOutAnimation')
|
||||
slideIn = true;
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private titleService: Title) {
|
||||
this.md = marked.setOptions({});
|
||||
@ -34,4 +29,12 @@ export class SlideShowComponent implements OnInit {
|
||||
get renderedSlide(): String {
|
||||
return this.md.parse(this.slide.slideData);
|
||||
}
|
||||
|
||||
get isMarkdown(): boolean {
|
||||
return this.slide.type === SlideType.MarkDown;
|
||||
}
|
||||
|
||||
get isIframe(): boolean {
|
||||
return this.slide.type === SlideType.IFrame;
|
||||
}
|
||||
}
|
||||
|
||||
1
src/app/display/slide/slide.component.ts
Normal file → Executable file
1
src/app/display/slide/slide.component.ts
Normal file → Executable file
@ -12,7 +12,6 @@ export class SlideComponent implements OnInit {
|
||||
@Input() visible = true;
|
||||
@Output() visibleChange = new EventEmitter();
|
||||
|
||||
|
||||
constructor() {}
|
||||
ngOnInit() {}
|
||||
|
||||
|
||||
58
src/app/display/watchers/watchers.component.css
Executable file
58
src/app/display/watchers/watchers.component.css
Executable file
@ -0,0 +1,58 @@
|
||||
:host {
|
||||
background-color: #444;
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* avatar */
|
||||
.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;
|
||||
max-width: 45px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* items */
|
||||
.ui.items .item .header {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ui.items .item .meta {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
/* comments */
|
||||
.ui.comments .comment .metadata {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.ui.comments .comment .text {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ui.comments .comment .author {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* header */
|
||||
h1.massive {
|
||||
font-size: 72px;
|
||||
position: absolute;
|
||||
top: calc(50% - 157px);
|
||||
left: calc(50% - 541px);
|
||||
}
|
||||
|
||||
.ui.comments {
|
||||
max-width: initial;
|
||||
}
|
||||
48
src/app/display/watchers/watchers.component.html
Executable file
48
src/app/display/watchers/watchers.component.html
Executable file
@ -0,0 +1,48 @@
|
||||
<div class="ui main wide-container dark">
|
||||
<table class="ui large padded inverted celled2 table" *ngIf="watchers.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="collapsing"><i class="user icon"></i></th>
|
||||
<th><i class="tasks icon"></i>JIRA</th>
|
||||
<th><i class="envelope open outline icon"></i>Last comment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let issue of watchers">
|
||||
<td class="collapsing">
|
||||
<div class="ui jira-avatar image">
|
||||
<img src="{{avatarUrl(issue.assignee)}}" [title]="issue.assignee">
|
||||
</div>
|
||||
</td>
|
||||
<td class="five wide">
|
||||
<div class="ui items">
|
||||
<div class="item">
|
||||
<div class="content">
|
||||
<a class="header">{{issue.issue}}</a>
|
||||
<div class="meta">{{issue.summary}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="ui fluid comments">
|
||||
<div class="comment">
|
||||
<a class="avatar"><img src="{{avatarUrl(issue.comment.signum)}}"></a>
|
||||
<div class="content">
|
||||
<span class="author">{{issue.comment.name}}</span>
|
||||
<div class="metadata"><span class="date">{{issue.comment.date}}</span></div>
|
||||
<div class="text">{{issue.comment.content|shortenText:500}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div *ngIf="!watchers.length">
|
||||
<h1 class="ui massive green center aligned icon header">
|
||||
<i class="ui massive check circle outline icon"></i>
|
||||
<div class="content">No watched item needs attention.</div>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
25
src/app/display/watchers/watchers.component.spec.ts
Normal file
25
src/app/display/watchers/watchers.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WatchersComponent } from './watchers.component';
|
||||
|
||||
describe('WatchersComponent', () => {
|
||||
let component: WatchersComponent;
|
||||
let fixture: ComponentFixture<WatchersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ WatchersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(WatchersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
43
src/app/display/watchers/watchers.component.ts
Executable file
43
src/app/display/watchers/watchers.component.ts
Executable file
@ -0,0 +1,43 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {SettingsService} from '../../shared/service/settings.service';
|
||||
import {WatcherService} from '../shared/watcher.service';
|
||||
import {WatchedIssue} from '../shared/watched-issue.model';
|
||||
import {environment} from '../../../environments/environment';
|
||||
|
||||
const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||
|
||||
@Component({
|
||||
selector: 'app-watchers',
|
||||
templateUrl: './watchers.component.html',
|
||||
styleUrls: ['./watchers.component.css'],
|
||||
})
|
||||
export class WatchersComponent implements OnInit {
|
||||
|
||||
constructor(private titleService: Title,
|
||||
private route: ActivatedRoute,
|
||||
private watcherService: WatcherService,
|
||||
private settingService: SettingsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle(`${this.settingService.team.name} : Watched issue activity`);
|
||||
this.route.data.subscribe((data: {
|
||||
watchers: Array<WatchedIssue>,
|
||||
}) => {
|
||||
this.watchers = data.watchers;
|
||||
});
|
||||
}
|
||||
|
||||
get watchers(): Array<WatchedIssue> {
|
||||
return this.watcherService.watchers;
|
||||
}
|
||||
|
||||
set watchers(watchers: Array<WatchedIssue>) {
|
||||
this.watcherService.watchers = watchers;
|
||||
}
|
||||
|
||||
public avatarUrl(signum: string): string {
|
||||
return environment.apiUrl + (signum ? `/avatars/${signum}` : DEFAULT_AVATAR);
|
||||
}
|
||||
}
|
||||
5
src/app/shared/kanban-column.ts
Normal file
5
src/app/shared/kanban-column.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class KanbanColumn {
|
||||
jiraStatusName = '';
|
||||
label = '';
|
||||
wipLimit = 0;
|
||||
}
|
||||
4
src/app/shared/label.ts
Executable file
4
src/app/shared/label.ts
Executable file
@ -0,0 +1,4 @@
|
||||
export class Label {
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
2
src/app/shared/service/commit-tracker.service.ts
Normal file → Executable file
2
src/app/shared/service/commit-tracker.service.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { Commit } from '../commit';
|
||||
|
||||
12
src/app/shared/service/self-updater.service.ts
Normal file → Executable file
12
src/app/shared/service/self-updater.service.ts
Normal file → Executable file
@ -1,12 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Location } from '@angular/common';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class SelfUpdaterService {
|
||||
|
||||
private appRevision: number = 0;
|
||||
private appRevision = 0;
|
||||
private initFailed = false;
|
||||
|
||||
constructor(private httpClient: HttpClient,
|
||||
@ -16,7 +16,7 @@ export class SelfUpdaterService {
|
||||
() => {
|
||||
console.log(
|
||||
'%c Couldn\'t load initial revision data from server. Self update disabled.',
|
||||
'background: #222; color: #bada55;'
|
||||
'background: #222; color: #FFC300;'
|
||||
);
|
||||
this.initFailed = true;
|
||||
}
|
||||
@ -31,7 +31,11 @@ export class SelfUpdaterService {
|
||||
if (!this.initFailed) {
|
||||
this.getDeployedRevision().subscribe(revision => {
|
||||
if (revision > this.appRevision) {
|
||||
this.locationService.go('/');
|
||||
console.log(
|
||||
`%c Version change detected (${this.appRevision}=>${revision}), reloading app.`,
|
||||
'background: #222; color: #BADA55;'
|
||||
);
|
||||
window.location.reload(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
10
src/app/shared/service/settings.service.ts
Normal file → Executable file
10
src/app/shared/service/settings.service.ts
Normal file → Executable file
@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { Team } from '../team';
|
||||
|
||||
const DEFAULT_SLIDE_INTERVAL = 30000;
|
||||
@ -14,6 +13,8 @@ export class SettingsService {
|
||||
private teamSubject: Subject<Team> = new Subject<Team>();
|
||||
private intervalSubject: Subject<number> = new Subject<number>();
|
||||
|
||||
public animationDirection: AnimationDirection = AnimationDirection.RIGHT;
|
||||
|
||||
constructor() {}
|
||||
|
||||
get team(): Team {
|
||||
@ -52,3 +53,8 @@ export class SettingsService {
|
||||
return this.teamSubject.asObservable();
|
||||
}
|
||||
}
|
||||
|
||||
export enum AnimationDirection {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
}
|
||||
|
||||
18
src/app/shared/service/slide.service.ts
Normal file → Executable file
18
src/app/shared/service/slide.service.ts
Normal file → Executable file
@ -1,10 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { Slide } from '../slide';
|
||||
import { TeamService } from './team.service';
|
||||
import { SettingsService } from './settings.service';
|
||||
import { flatMap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class SlideService implements Resolve<Array<Slide>>{
|
||||
@ -13,14 +16,14 @@ export class SlideService implements Resolve<Array<Slide>>{
|
||||
private apiEndPointPosition = environment.apiUrl + '/api/slide-position';
|
||||
private cachedSlides: Array<Slide> = [];
|
||||
|
||||
constructor(private httpClient: HttpClient) {}
|
||||
constructor(private httpClient: HttpClient,
|
||||
private teamService: TeamService,
|
||||
private settings: SettingsService) {}
|
||||
|
||||
private static prepareSlideData(slide: Slide) {
|
||||
const slideToSave = <any>Object.assign({}, slide);
|
||||
try {
|
||||
slideToSave.team = slideToSave.team.id === null
|
||||
? null
|
||||
: slideToSave.team.id;
|
||||
slideToSave.teams = slide.teams.map(team => team.id);
|
||||
} catch (e) {}
|
||||
return slideToSave;
|
||||
}
|
||||
@ -30,6 +33,11 @@ export class SlideService implements Resolve<Array<Slide>>{
|
||||
}
|
||||
|
||||
public list(): Observable<Array<Slide>> {
|
||||
if (this.settings.team.id) {
|
||||
return this.teamService.get(this.settings.team.id).pipe(
|
||||
flatMap( () => this.httpClient.get<Array<Slide>>(this.apiEndPoint))
|
||||
);
|
||||
}
|
||||
return this.httpClient.get<Array<Slide>>(this.apiEndPoint);
|
||||
}
|
||||
|
||||
|
||||
20
src/app/shared/service/team.service.ts
Normal file → Executable file
20
src/app/shared/service/team.service.ts
Normal file → Executable file
@ -1,10 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { Team } from '../team';
|
||||
import {SettingsService} from './settings.service';
|
||||
import {map} from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class TeamService implements Resolve<Array<Team>> {
|
||||
@ -12,7 +14,8 @@ export class TeamService implements Resolve<Array<Team>> {
|
||||
private apiEndPoint = environment.apiUrl + '/api/team';
|
||||
private cachedTeams: Array<Team> = [];
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
constructor(private httpClient: HttpClient,
|
||||
private settingsService: SettingsService) { }
|
||||
|
||||
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Team>> {
|
||||
return this.list().toPromise();
|
||||
@ -22,6 +25,12 @@ export class TeamService implements Resolve<Array<Team>> {
|
||||
return this.httpClient.get<Array<Team>>(this.apiEndPoint);
|
||||
}
|
||||
|
||||
public get(id: number): Observable<Team> {
|
||||
return this.httpClient.get<Team>(`${this.apiEndPoint}/${id}`).pipe(
|
||||
map(team => this.updateSettingsWhenSelected(team))
|
||||
);
|
||||
}
|
||||
|
||||
public persist(team: Team): Observable<Team> {
|
||||
return team.id === null
|
||||
? this.create(team)
|
||||
@ -47,4 +56,11 @@ export class TeamService implements Resolve<Array<Team>> {
|
||||
set teams(teams: Array<Team>) {
|
||||
this.cachedTeams = teams;
|
||||
}
|
||||
|
||||
private updateSettingsWhenSelected(team: Team): Team {
|
||||
if (this.settingsService.team.id === team.id) {
|
||||
this.settingsService.team = team;
|
||||
}
|
||||
return team;
|
||||
}
|
||||
}
|
||||
|
||||
59
src/app/shared/service/timer.service.ts
Normal file → Executable file
59
src/app/shared/service/timer.service.ts
Normal file → Executable file
@ -1,19 +1,21 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { SettingsService } from './settings.service';
|
||||
import { SelfUpdaterService } from './self-updater.service';
|
||||
import { TimerObservable } from 'rxjs/observable/TimerObservable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Subject, timer, Subscription } from 'rxjs';
|
||||
import { ActivationStart, Router } from '@angular/router';
|
||||
import { SlideShowService } from '../../display/slide-show.service';
|
||||
import 'rxjs/add/operator/filter';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import { filter, switchMap } from 'rxjs/operators';
|
||||
import * as isWithinRange from 'date-fns/is_within_range';
|
||||
import * as min from 'date-fns/min';
|
||||
import * as max from 'date-fns/max';
|
||||
|
||||
const TIMER_UPDATE_POLL_INTERVAL = 30000;
|
||||
const TIME_SEPARATOR = ':';
|
||||
|
||||
@Injectable()
|
||||
export class TimerService implements OnDestroy {
|
||||
private autoSwitch = false;
|
||||
public paused = false;
|
||||
public autoSwitch = false;
|
||||
private slideShowTimer: Subscription;
|
||||
private selfUpdateCheckerTimer: Subscription;
|
||||
private slideTimerSubject: Subject<number> = new Subject<number>();
|
||||
@ -24,18 +26,19 @@ export class TimerService implements OnDestroy {
|
||||
private router: Router,
|
||||
private slideShowService: SlideShowService) {
|
||||
|
||||
const timerSUC = TimerObservable.create(TIMER_UPDATE_POLL_INTERVAL, TIMER_UPDATE_POLL_INTERVAL);
|
||||
const timerSUC = timer(TIMER_UPDATE_POLL_INTERVAL, TIMER_UPDATE_POLL_INTERVAL);
|
||||
this.selfUpdateCheckerTimer = timerSUC.subscribe(() => {
|
||||
this.selfUpdaterService.checkAndReloadIfNecessary();
|
||||
});
|
||||
|
||||
this.slideShowTimer = this.slideTimerSubject.switchMap((period: number) => TimerObservable.create(period))
|
||||
this.slideShowTimer = this.slideTimerSubject
|
||||
.pipe(switchMap((period: number) => timer(period)))
|
||||
.subscribe(() => this.changeSlide());
|
||||
this.setSlideTimer(this.settings.slideInterval);
|
||||
|
||||
this.autoSwitch = false;
|
||||
this.router.events
|
||||
.filter(event => event instanceof ActivationStart)
|
||||
.pipe(filter(event => event instanceof ActivationStart))
|
||||
.subscribe((event: ActivationStart) => this.autoSwitch = !!event.snapshot.data.autoSwitchable);
|
||||
|
||||
this.slideIntervalSubscription = this.settings.slideIntervalChanged.subscribe(
|
||||
@ -50,13 +53,45 @@ export class TimerService implements OnDestroy {
|
||||
}
|
||||
|
||||
private changeSlide() {
|
||||
if (this.autoSwitch) {
|
||||
this.slideShowService.nextSlide();
|
||||
if (!this.paused) {
|
||||
if (this.autoSwitch && this.isDuringDailyStandup()) {
|
||||
this.router.navigate(['/kanban']);
|
||||
}
|
||||
if (this.autoSwitch && !this.isDuringDailyStandup()) {
|
||||
this.slideShowService.nextSlide();
|
||||
}
|
||||
this.setSlideTimer(this.settings.slideInterval);
|
||||
}
|
||||
this.setSlideTimer(this.settings.slideInterval);
|
||||
}
|
||||
|
||||
public setSlideTimer(delay: number) {
|
||||
this.slideTimerSubject.next(delay);
|
||||
}
|
||||
|
||||
public togglePause() {
|
||||
this.paused = !this.paused;
|
||||
if (!this.paused) {
|
||||
this.setSlideTimer(this.settings.slideInterval);
|
||||
}
|
||||
}
|
||||
|
||||
public pause() {
|
||||
this.paused = true;
|
||||
}
|
||||
|
||||
private isDuringDailyStandup(): boolean {
|
||||
if (this.settings.team.dailyLockEnabled) {
|
||||
const now = new Date();
|
||||
const startsParts = this.settings.team.dailyStartTime.split(TIME_SEPARATOR).map(part => +part);
|
||||
const endsParts = this.settings.team.dailyEndTime.split(TIME_SEPARATOR).map(part => +part);
|
||||
const times = [
|
||||
new Date(now.getFullYear(), now.getMonth(), now.getDate(), startsParts[0], startsParts[1]),
|
||||
new Date(now.getFullYear(), now.getMonth(), now.getDate(), endsParts[0], endsParts[1])
|
||||
];
|
||||
const startsAt = min(...times);
|
||||
const endsAt = max(...times);
|
||||
return isWithinRange(now, startsAt, endsAt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
64
src/app/shared/slide-in-out-animation.ts
Normal file → Executable file
64
src/app/shared/slide-in-out-animation.ts
Normal file → Executable file
@ -1,42 +1,34 @@
|
||||
// import the required animation functions from the angular animations module
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import {animate, group, query, state, style, transition, trigger} from '@angular/animations';
|
||||
|
||||
|
||||
export const slideInOutAnimation =
|
||||
// trigger name for attaching this animation to an element using the [@triggerName] syntax
|
||||
trigger('slideInOutAnimation', [
|
||||
|
||||
// end state styles for route container (host)
|
||||
state('*', style({
|
||||
// the view covers the whole screen with a semi tranparent background
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
})),
|
||||
|
||||
// route 'enter' transition
|
||||
transition(':enter', [
|
||||
// styles at start of transition
|
||||
style({
|
||||
// start with the content positioned off the right of the screen,
|
||||
// -400% is required instead of -100% because the negative position adds to the width of the element
|
||||
transform: 'translateX(100%)'
|
||||
trigger('routerTransition', [
|
||||
transition('* <=> *', [
|
||||
query(':enter, :leave', style({
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
}), {
|
||||
optional: true
|
||||
}),
|
||||
|
||||
// animation and styles at end of transition
|
||||
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
||||
// transition the right position to 0 which slides the content into view
|
||||
transform: 'translateX(0)'
|
||||
}))
|
||||
]),
|
||||
|
||||
// route 'leave' transition
|
||||
transition(':leave', [
|
||||
// animation and styles at end of transition
|
||||
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
||||
// transition the right position to -400% which slides the content out of view
|
||||
transform: 'translateX(-100%)'
|
||||
}))
|
||||
group([
|
||||
query(':enter', [
|
||||
style({ transform: 'translateX({{offsetEnter}}%)'}),
|
||||
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
||||
transform: 'translateX(0%)'
|
||||
}))
|
||||
], {
|
||||
optional: true
|
||||
}),
|
||||
query(':leave', [
|
||||
style({ transform: 'translateX(0%)' }),
|
||||
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
||||
transform: 'translateX({{offsetLeave}}%)'
|
||||
}))
|
||||
], { optional: true })
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
23
src/app/shared/slide-wrapper.ts
Executable file
23
src/app/shared/slide-wrapper.ts
Executable file
@ -0,0 +1,23 @@
|
||||
import { Slide } from './slide';
|
||||
|
||||
export class SlideWrapper {
|
||||
public type: WrappedType = null;
|
||||
public slideData: Slide = null;
|
||||
public slideRoute: string = null;
|
||||
|
||||
public constructor(type: WrappedType, data: string|Slide) {
|
||||
this.type = type;
|
||||
if (type === WrappedType.USER) {
|
||||
this.slideData = data as Slide;
|
||||
} else if (type === WrappedType.BUILTIN) {
|
||||
this.slideRoute = data as string;
|
||||
} else {
|
||||
throw new Error(`Unknown slide type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum WrappedType {
|
||||
BUILTIN,
|
||||
USER
|
||||
}
|
||||
23
src/app/shared/slide.ts
Normal file → Executable file
23
src/app/shared/slide.ts
Normal file → Executable file
@ -2,11 +2,24 @@ import { Team } from './team';
|
||||
|
||||
export class Slide {
|
||||
id: number = null;
|
||||
title: String = '';
|
||||
team: Team = null;
|
||||
slideData: String = '';
|
||||
title = '';
|
||||
type: SlideType = SlideType.MarkDown;
|
||||
visibility: SlideVisibility = SlideVisibility.Public;
|
||||
teams: Array<Team> = [];
|
||||
slideData = '';
|
||||
slideUrl = '';
|
||||
isVisible = true;
|
||||
createdAt: String = null;
|
||||
updatedAt: String = null;
|
||||
createdAt: string = null;
|
||||
updatedAt: string = null;
|
||||
position = 0;
|
||||
}
|
||||
|
||||
export enum SlideType {
|
||||
MarkDown = 'markdown',
|
||||
IFrame = 'iframe',
|
||||
}
|
||||
|
||||
export enum SlideVisibility {
|
||||
Public = 'public',
|
||||
Team = 'team',
|
||||
}
|
||||
|
||||
16
src/app/shared/team.ts
Normal file → Executable file
16
src/app/shared/team.ts
Normal file → Executable file
@ -1,10 +1,24 @@
|
||||
import { Member } from './member';
|
||||
import { KanbanColumn } from './kanban-column';
|
||||
import { Label } from './label';
|
||||
|
||||
export class Team {
|
||||
id: number = null;
|
||||
name: String = '';
|
||||
members: Array<Member> = [];
|
||||
isActive = false;
|
||||
filterId = 0;
|
||||
kanbanEnabled = true;
|
||||
commitTrackerEnabled = true;
|
||||
watchedEnabled = true;
|
||||
dailyLockEnabled = false;
|
||||
dailyStartTime: string;
|
||||
dailyEndTime: string;
|
||||
backlogColumn: KanbanColumn = new KanbanColumn();
|
||||
inprogressColumn: KanbanColumn = new KanbanColumn();
|
||||
verificationColumn: KanbanColumn = new KanbanColumn();
|
||||
doneColumn: KanbanColumn = new KanbanColumn();
|
||||
labels: Array<Label> = [];
|
||||
isActive = true;
|
||||
createdAt: String = null;
|
||||
updatedAt: String = null;
|
||||
}
|
||||
|
||||
116
src/app/shared/two-way-linked-list.ts
Executable file
116
src/app/shared/two-way-linked-list.ts
Executable file
@ -0,0 +1,116 @@
|
||||
|
||||
export class TwoWayLinkedList<T> {
|
||||
private count = 0;
|
||||
private entryNode: Node<T> = null;
|
||||
private lastNode: Node<T> = null;
|
||||
private nodePtr: Node<T> = null;
|
||||
|
||||
public push(newNode: T): TwoWayLinkedList<T> {
|
||||
if (null === this.lastNode) {
|
||||
this.entryNode = new Node<T>(newNode);
|
||||
this.lastNode = this.entryNode;
|
||||
this.nodePtr = this.entryNode;
|
||||
this.entryNode
|
||||
.setPrev(this.entryNode)
|
||||
.setNext(this.entryNode);
|
||||
} else {
|
||||
const prevPtr = this.lastNode;
|
||||
const nodeToAdd = new Node(newNode);
|
||||
nodeToAdd.setNext(this.entryNode).setPrev(prevPtr);
|
||||
prevPtr.setNext(nodeToAdd);
|
||||
this.lastNode = nodeToAdd;
|
||||
}
|
||||
this.count++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public pop(): T {
|
||||
if (null === this.lastNode) {
|
||||
throw new Error('No items in list');
|
||||
}
|
||||
|
||||
let returnData: T;
|
||||
if (this.entryNode === this.lastNode) {
|
||||
returnData = this.lastNode.getData();
|
||||
this.lastNode.setPrev(null).setNext(null);
|
||||
this.entryNode = null;
|
||||
this.lastNode = null;
|
||||
this.nodePtr = null;
|
||||
} else {
|
||||
const newLast = this.lastNode.getPrev();
|
||||
if (this.nodePtr === this.lastNode) {
|
||||
this.nodePtr = newLast;
|
||||
}
|
||||
newLast.setNext(this.entryNode);
|
||||
returnData = this.lastNode.getData();
|
||||
this.lastNode.setPrev(null).setNext(null);
|
||||
this.lastNode = newLast;
|
||||
}
|
||||
this.count--;
|
||||
return returnData;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
while (this.count > 0) {
|
||||
this.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public prev(): T {
|
||||
this.nodePtr = this.nodePtr.getPrev();
|
||||
return this.nodePtr.getData();
|
||||
}
|
||||
|
||||
public next(): T {
|
||||
this.nodePtr = this.nodePtr.getNext();
|
||||
return this.nodePtr.getData();
|
||||
}
|
||||
|
||||
public isFirst(): boolean {
|
||||
return this.nodePtr === this.lastNode;
|
||||
}
|
||||
|
||||
public isLast(): boolean {
|
||||
return this.nodePtr === this.lastNode;
|
||||
}
|
||||
|
||||
public get first(): T {
|
||||
return this.entryNode.getData();
|
||||
}
|
||||
|
||||
public get last(): T {
|
||||
return this.lastNode.getData();
|
||||
}
|
||||
}
|
||||
|
||||
export class Node<T> {
|
||||
private prev: Node<T> = null;
|
||||
private next: Node<T> = null;
|
||||
private readonly data: T = null;
|
||||
|
||||
constructor(data: T) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public getPrev(): Node<T> {
|
||||
return this.prev;
|
||||
}
|
||||
|
||||
public setPrev(node: Node<T>): Node<T> {
|
||||
this.prev = node;
|
||||
return this;
|
||||
}
|
||||
|
||||
public getNext(): Node<T> {
|
||||
return this.next;
|
||||
}
|
||||
|
||||
public setNext(node: Node<T>): Node<T> {
|
||||
this.next = node;
|
||||
return this;
|
||||
}
|
||||
|
||||
public getData(): T {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
2
src/environments/environment.prod.ts
Normal file → Executable file
2
src/environments/environment.prod.ts
Normal file → Executable file
@ -1,5 +1,5 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiUrl: 'http://ttt-api.tsp.eth.ericsson.se/mtastv-api',
|
||||
apiUrl: 'https://mtoolbox.rnd.ki.sw.ericsson.se/mtastv-api',
|
||||
commitTrackerApiUrl: 'https://mtas-trex.rnd.ki.sw.ericsson.se:8080/committracker/api/'
|
||||
};
|
||||
|
||||
5
src/environments/environment.stg.ts
Executable file
5
src/environments/environment.stg.ts
Executable file
@ -0,0 +1,5 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiUrl: 'http://ttt-api.tsp.eth.ericsson.se/mtastv-api',
|
||||
commitTrackerApiUrl: 'https://mtas-trex.rnd.ki.sw.ericsson.se:8080/committracker/api/'
|
||||
};
|
||||
@ -10,7 +10,8 @@
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"test.ts"
|
||||
"test.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
]
|
||||
],
|
||||
"module": "es2015",
|
||||
"baseUrl": "./"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,6 @@
|
||||
"forin": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs",
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"import-spacing": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user