* angular2 version uplift

* configurable kanban board added
* iframe slide type added
* many to many implementation of team-slide connection
This commit is contained in:
Dávid Danyi 2018-09-05 17:03:21 +02:00
parent 96918b50ca
commit 0f535881a4
39 changed files with 5093 additions and 6724 deletions

View File

@ -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": {}
}
}

138
angular.json Normal file
View File

@ -0,0 +1,138 @@
{
"$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"
}
]
}
}
},
"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"
}
}
}

View File

@ -4,24 +4,22 @@
module.exports = function (config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['jasmine', '@angular/cli'], frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [ plugins: [
require('karma-jasmine'), require('karma-jasmine'),
require('karma-chrome-launcher'), require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'), require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma') require('@angular-devkit/build-angular/plugins/karma')
], ],
client:{ client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ], dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true fixWebpackSourcePaths: true
}, },
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'], reporters: ['progress', 'kjhtml'],
port: 9876, port: 9876,
colors: true, colors: true,

11124
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,52 @@
{ {
"name": "mtas-tv-frontend", "name": "mtas-tv-frontend",
"version": "0.0.0", "version": "0.0.0",
"license": "MIT",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build --prod", "build": "ng build",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e" "e2e": "ng e2e"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^5.2.0", "@angular/animations": "^6.1.0",
"@angular/common": "^5.2.0", "@angular/common": "^6.1.0",
"@angular/compiler": "^5.2.0", "@angular/compiler": "^6.1.0",
"@angular/core": "^5.2.0", "@angular/core": "^6.1.0",
"@angular/forms": "^5.2.0", "@angular/forms": "^6.1.0",
"@angular/http": "^5.2.0", "@angular/http": "^6.1.0",
"@angular/platform-browser": "^5.2.0", "@angular/platform-browser": "^6.1.0",
"@angular/platform-browser-dynamic": "^5.2.0", "@angular/platform-browser-dynamic": "^6.1.0",
"@angular/router": "^5.2.0", "@angular/router": "^6.1.0",
"@types/marked": "^0.3.0", "@types/marked": "^0.4.1",
"core-js": "^2.4.1", "core-js": "^2.5.4",
"marked": "^0.3.19", "marked": "^0.5.0",
"ng2-semantic-ui": "^0.9.7", "ng2-semantic-ui": "^0.9.7",
"rxjs": "^5.5.6", "rxjs": "^6.0.0",
"semantic-ui-css": "^2.3.1", "semantic-ui-css": "^2.3.3",
"zone.js": "^0.8.19" "zone.js": "~0.8.26"
}, },
"devDependencies": { "devDependencies": {
"@angular/cli": "~1.7.3", "@angular-devkit/build-angular": "~0.7.0",
"@angular/compiler-cli": "^5.2.0", "@angular/cli": "~6.1.5",
"@angular/language-service": "^5.2.0", "@angular/compiler-cli": "^6.1.0",
"@types/jasmine": "~2.8.3", "@angular/language-service": "^6.1.0",
"@types/jasminewd2": "~2.0.2", "@types/jasmine": "~2.8.6",
"@types/node": "~6.0.60", "@types/jasminewd2": "~2.0.3",
"codelyzer": "^4.0.1", "@types/node": "~8.9.4",
"jasmine-core": "~2.8.0", "codelyzer": "~4.2.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0", "karma": "~1.7.1",
"karma-chrome-launcher": "~2.2.0", "karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1", "karma-coverage-istanbul-reporter": "~2.0.0",
"karma-jasmine": "~1.1.0", "karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2", "karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2", "protractor": "~5.4.0",
"ts-node": "~4.1.0", "ts-node": "~5.0.1",
"tslint": "~5.9.1", "tslint": "~5.9.1",
"typescript": "~2.5.3" "typescript": "~2.7.2"
} }
} }

2
src/app/admin/dashboard/dashboard.component.html Normal file → Executable file
View File

@ -2,7 +2,7 @@
<h1 class="ui dividing header">Dashboard</h1> <h1 class="ui dividing header">Dashboard</h1>
<div class="ui four cards"> <div class="ui four cards">
<a class="ui raised yellow card" [routerLink]="['/commit-tracker']"> <a class="ui raised yellow card" [routerLink]="['/kanban']">
<div class="content"> <div class="content">
<div class="header">Start slideshow</div> <div class="header">Start slideshow</div>
<div class="meta"> <div class="meta">

51
src/app/admin/slide-editor/slide-editor.component.html Normal file → Executable file
View File

@ -2,31 +2,54 @@
<h1 class="ui dividing header">Slide editor</h1> <h1 class="ui dividing header">Slide editor</h1>
<form class="ui form" #slideEditorForm (ngSubmit)="saveSlide()"> <form class="ui form" #slideEditorForm (ngSubmit)="saveSlide()">
<div class="two fields"> <div class="two fields">
<div class="eight wide field"> <div class="six wide field">
<label for="slide_name">Slide title</label> <label for="slide_name">Slide title</label>
<input id="slide_name" type="text" name="slide_name" [(ngModel)]="slide.title"> <input id="slide_name" type="text" name="slide_name" [(ngModel)]="slide.title">
</div> </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"> <div class="eight wide field">
<label for="team">Visible to this team</label> <label for="teams">Visible to this team</label>
<sui-select class="selection" <sui-multi-select class="selection"
id="team" id="teams"
name="team" name="teams"
[(ngModel)]="slide.team" [(ngModel)]="slide.teams"
[isDisabled]="slide.visibility=='public'"
labelField="name" labelField="name"
[isSearchable]="true" [isSearchable]="true"
#select> #select>
<sui-select-option [value]="emptyTeam"></sui-select-option>
<sui-select-option *ngFor="let team of teams" [value]="team"></sui-select-option> <sui-select-option *ngFor="let team of teams" [value]="team"></sui-select-option>
</sui-select> </sui-multi-select>
</div> </div>
</div> </div>
<div class="inline fields">
<label>Slide type</label>
<div class="field"> <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> <label for="slide_data">Slide data</label>
<textarea id="slide_data" rows="30" name="slide_data" [(ngModel)]="slide.slideData"></textarea> <textarea id="slide_data" rows="30" name="slide_data" [(ngModel)]="slide.slideData"></textarea>
</div> </div>
<div class="field" *ngIf="isIframe">
<label for="slide_url">Slide url</label>
<input id="slide_url" type="text" name="slide_data" [(ngModel)]="slide.slideUrl">
</div>
<div class="five wide field"> <div class="five wide field">
<div class="ui checkbox"> <div class="ui checkbox">
@ -43,7 +66,8 @@
[class.primary]="canPreview" [class.primary]="canPreview"
[class.disabled]="!canPreview" [class.disabled]="!canPreview"
(click)="preview()"><i class="search icon"></i>Preview</button> (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" href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"
target="_blank"><i class="question circle outline icon"></i>MD howto</a> target="_blank"><i class="question circle outline icon"></i>MD howto</a>
<a class="ui button" <a class="ui button"
@ -54,5 +78,10 @@
[data]="renderedPreview" [data]="renderedPreview"
[preview]="true" [preview]="true"
[(visible)]="previewVisible" [(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> </div>

39
src/app/admin/slide-editor/slide-editor.component.ts Normal file → Executable file
View File

@ -1,11 +1,11 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import { Title } from '@angular/platform-browser'; import {Title} from '@angular/platform-browser';
import * as marked from 'marked'; import * as marked from 'marked';
import { Slide } from '../../shared/slide'; import {Slide, SlideType, SlideVisibility} from '../../shared/slide';
import { SlideService } from '../../shared/service/slide.service'; import {SlideService} from '../../shared/service/slide.service';
import { Team } from '../../shared/team'; import {Team} from '../../shared/team';
@Component({ @Component({
selector: 'app-slide-editor', selector: 'app-slide-editor',
@ -14,7 +14,6 @@ import { Team } from '../../shared/team';
}) })
export class SlideEditorComponent implements OnInit { export class SlideEditorComponent implements OnInit {
private md; private md;
public emptyTeam: Team = new Team();
public slide: Slide; public slide: Slide;
public teams: Array<Team> = []; public teams: Array<Team> = [];
public renderedPreview: String = ''; public renderedPreview: String = '';
@ -25,7 +24,6 @@ export class SlideEditorComponent implements OnInit {
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router) { private router: Router) {
this.md = marked.setOptions({}); this.md = marked.setOptions({});
this.emptyTeam.name = 'All teams';
} }
ngOnInit() { ngOnInit() {
@ -36,9 +34,11 @@ export class SlideEditorComponent implements OnInit {
}) => { }) => {
this.teams = data.teams; this.teams = data.teams;
this.slide = data.slide ? data.slide : new Slide; this.slide = data.slide ? data.slide : new Slide;
this.slide.team = this.slide.team === null this.slide.teams = this.teams.filter(
? this.emptyTeam team => this.slide.teams.some(
: this.teams.find(team => team.id === this.slide.team.id); st => team.id === st.id
)
);
}); });
} }
@ -53,16 +53,27 @@ export class SlideEditorComponent implements OnInit {
get canSave(): boolean { get canSave(): boolean {
return [ return [
this.slide.title, this.slide.title,
this.slide.slideData this.isMarkdown ? this.slide.slideData : this.slide.slideUrl
].every(field => field.trim().length > 0); ].every(field => field.trim().length > 0) && (
this.slide.visibility === SlideVisibility.Public ||
this.slide.visibility === SlideVisibility.Team && this.slide.teams.length > 0
);
} }
get canPreview(): boolean { 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() { public preview() {
this.previewVisible = true; this.previewVisible = true;
this.renderedPreview = this.md.parse(this.slide.slideData); 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;
}
} }

6
src/app/admin/slide-list/slide-list.component.html Normal file → Executable file
View File

@ -19,8 +19,8 @@
<tr> <tr>
<th class="collapsing"></th> <th class="collapsing"></th>
<th><i class="large address book outline icon"></i>Slide title</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 users icon"></i>Visible to</th>
<th class="collapsing"><i class="large check square outline icon"></i>Visible</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> <th class="collapsing"><i class="large arrows alternate vertical icon"></i>Order</th>
</tr> </tr>
</thead> </thead>
@ -33,7 +33,7 @@
class="large link red fitted trash alternate outline icon"></i></a> class="large link red fitted trash alternate outline icon"></i></a>
</td> </td>
<td>{{slide.title}}</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"><i class="large icon" [ngClass]="visibleClass(slide)"></i></td>
<td class="center aligned"> <td class="center aligned">
<a title="Up" (click)="moveUp(slide)"><i <a title="Up" (click)="moveUp(slide)"><i

27
src/app/admin/slide-list/slide-list.component.ts Normal file → Executable file
View File

@ -1,10 +1,10 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import { Title } from '@angular/platform-browser'; import {Title} from '@angular/platform-browser';
import { SlideService } from '../../shared/service/slide.service'; import {SlideService} from '../../shared/service/slide.service';
import { Slide } from '../../shared/slide'; import {Slide, SlideVisibility} from '../../shared/slide';
import { Team } from '../../shared/team'; import {Team} from '../../shared/team';
@Component({ @Component({
selector: 'app-slide-list', selector: 'app-slide-list',
@ -38,7 +38,7 @@ export class SlideListComponent implements OnInit {
return this.selectedTeam === this.emptyTeam return this.selectedTeam === this.emptyTeam
? this.slideService.slides ? this.slideService.slides
: this.slideService.slides.filter( : 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; this.slideService.slides = slides;
} }
public slideTeam(team: Team): String { public slideTeam(slide: Slide): string {
return team === null ? 'All teams' : team.name; return slide.visibility === SlideVisibility.Team && slide.teams.length > 0
? slide.teams.map(team => team.name).join(', ')
: 'All teams';
} }
public moveUp(slide: Slide) { public moveUp(slide: Slide) {
@ -106,4 +108,11 @@ export class SlideListComponent implements OnInit {
'red times': !slide.isVisible '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
View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { Slide } from '../shared/slide'; import { Slide } from '../shared/slide';

2
src/app/admin/team-resolver.service.ts Normal file → Executable file
View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs';
import { Team } from '../shared/team'; import { Team } from '../shared/team';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';

View File

@ -43,6 +43,8 @@
.ui.jira-avatar.image > img { .ui.jira-avatar.image > img {
border-radius: 4px; border-radius: 4px;
max-width: 45px;
height: auto;
} }

View File

@ -1,8 +1,7 @@
import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core'; import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Subscription'; import { Subscription, timer } from 'rxjs';
import { TimerObservable } from 'rxjs/observable/TimerObservable';
import { slideInOutAnimation } from '../../shared/slide-in-out-animation'; import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
import { CommitTrackerService } from '../../shared/service/commit-tracker.service'; import { CommitTrackerService } from '../../shared/service/commit-tracker.service';
@ -40,7 +39,7 @@ export class CommitTrackerComponent implements OnInit, OnDestroy {
this.titleService.setTitle('Commit-tracker : MTAStv'); this.titleService.setTitle('Commit-tracker : MTAStv');
this.route.data.subscribe((data: { commits: Array<Commit> }) => this.commits = data.commits); 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.refreshCommitTrackerTimer = timerCT.subscribe(() => {
this.commitTrackerService.getTeamCommits(this.settings.team.members.map(member => member.signum)) this.commitTrackerService.getTeamCommits(this.settings.team.members.map(member => member.signum))
.subscribe(commits => this.commits = commits); .subscribe(commits => this.commits = commits);

4
src/app/display/display.module.ts Normal file → Executable file
View File

@ -16,6 +16,7 @@ import { PrefixJiraIdPipe } from './shared/prefix-jira-id.pipe';
import { PriorityColorPipe } from './shared/priority-color.pipe'; import { PriorityColorPipe } from './shared/priority-color.pipe';
import { ShortenTextPipe } from './shared/shorten-text.pipe'; import { ShortenTextPipe } from './shared/shorten-text.pipe';
import { KanbanService } from './shared'; import { KanbanService } from './shared';
import { SlideIframeComponent } from './slide-iframe/slide-iframe.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -24,7 +25,7 @@ import { KanbanService } from './shared';
SuiModule, SuiModule,
DisplayRoutingModule DisplayRoutingModule
], ],
exports: [SlideComponent], exports: [SlideComponent, SlideIframeComponent],
declarations: [ declarations: [
CommitTrackerComponent, CommitTrackerComponent,
SettingsComponent, SettingsComponent,
@ -37,6 +38,7 @@ import { KanbanService } from './shared';
PrefixJiraIdPipe, PrefixJiraIdPipe,
PriorityColorPipe, PriorityColorPipe,
ShortenTextPipe, ShortenTextPipe,
SlideIframeComponent,
], ],
providers: [ providers: [
SlideShowService, SlideShowService,

View File

@ -1,18 +1,18 @@
<div class="ui main fullwide-container"> <div class="ui main fullwide-container">
<div class="ui grid"> <div class="ui grid">
<div app-kanban-entry-item class="four wide column" <div app-kanban-entry-item class="four wide column"
rowHeading="INBOX" [rowHeading]="backlogLabel"
[kanbanEntries]="kanbanBoard.inbox"></div> [kanbanEntries]="kanbanBoard.inbox"></div>
<div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass" <div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass"
rowHeading="INPROGRESS" [rowHeading]="inProgressLabel"
[wipLimit]="inprogressWipLimit" [wipCount]="inprogressWipCount" [wipLimit]="inprogressWipLimit" [wipCount]="inprogressWipCount"
[kanbanEntries]="kanbanBoard.inProgress"></div> [kanbanEntries]="kanbanBoard.inProgress"></div>
<div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass" <div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass"
rowHeading="VERIFICATION" [rowHeading]="verificationLabel"
[wipLimit]="verificationWipLimit" [wipCount]="verificationWipCount" [wipLimit]="verificationWipLimit" [wipCount]="verificationWipCount"
[kanbanEntries]="kanbanBoard.verification"></div> [kanbanEntries]="kanbanBoard.verification"></div>
<div app-kanban-entry-item class="four wide column" <div app-kanban-entry-item class="four wide column"
rowHeading="DÖNER" [rowHeading]="doneLabel"
[kanbanEntries]="kanbanBoard.done"></div> [kanbanEntries]="kanbanBoard.done"></div>
</div> </div>
</div> </div>

31
src/app/display/kanban-board/kanban-board.component.ts Normal file → Executable file
View File

@ -4,9 +4,7 @@ import { ActivatedRoute } from '@angular/router';
import { KanbanBoard, KanbanEntry, KanbanService, } from '../shared'; import { KanbanBoard, KanbanEntry, KanbanService, } from '../shared';
import { slideInOutAnimation } from '../../shared/slide-in-out-animation'; import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
import { SettingsService } from '../../shared/service/settings.service';
const WIP_LIMIT_INPROGRESS = 12;
const WIP_LIMIT_VERIFICATION = 8;
@Component({ @Component({
selector: 'app-kanban-board', selector: 'app-kanban-board',
@ -21,7 +19,8 @@ export class KanbanBoardComponent implements OnInit {
constructor(private titleService: Title, constructor(private titleService: Title,
private route: ActivatedRoute, private route: ActivatedRoute,
private kanbanService: KanbanService) { private kanbanService: KanbanService,
private settingService: SettingsService) {
} }
/** /**
@ -45,7 +44,7 @@ export class KanbanBoardComponent implements OnInit {
} }
get inprogressWipLimit(): number { get inprogressWipLimit(): number {
return WIP_LIMIT_INPROGRESS; return this.settingService.team.inprogressColumn.wipLimit;
} }
get inprogressWipCount(): number { get inprogressWipCount(): number {
@ -64,12 +63,12 @@ export class KanbanBoardComponent implements OnInit {
*/ */
get inprogressWipClass() { get inprogressWipClass() {
return { return {
'over-wip': this.inprogressWipCount > WIP_LIMIT_INPROGRESS, 'over-wip': this.inprogressWipCount > this.settingService.team.inprogressColumn.wipLimit,
}; };
} }
get verificationWipLimit(): number { get verificationWipLimit(): number {
return WIP_LIMIT_VERIFICATION; return this.settingService.team.verificationColumn.wipLimit;
} }
get verificationWipCount(): number { get verificationWipCount(): number {
@ -88,7 +87,23 @@ export class KanbanBoardComponent implements OnInit {
*/ */
get verificationWipClass() { get verificationWipClass() {
return { return {
'over-wip': this.verificationWipCount > WIP_LIMIT_VERIFICATION, 'over-wip': this.verificationWipCount > this.settingService.team.verificationColumn.wipLimit,
}; };
} }
get backlogLabel(): string {
return this.settingService.team.backlogColumn.label;
}
get inProgressLabel(): string {
return this.settingService.team.inprogressColumn.label;
}
get verificationLabel(): string {
return this.settingService.team.verificationColumn.label;
}
get doneLabel(): string {
return this.settingService.team.doneColumn.label;
}
} }

View File

@ -10,6 +10,8 @@
[ngClass]="entryClass(kanbanEntry)"> [ngClass]="entryClass(kanbanEntry)">
<div class="content"> <div class="content">
<div class="task-description"> <div class="task-description">
<span *ngIf="kanbanEntry.epicName"
class="ui mini olive right floated label">{{kanbanEntry.epicName}}</span>
<ng-template [ngIf]="hasLabels(kanbanEntry)"> <ng-template [ngIf]="hasLabels(kanbanEntry)">
<span *ngFor="let label of kanbanEntry.labels" <span *ngFor="let label of kanbanEntry.labels"
class="ui mini {{labelClass(label)}} right floated label">{{label|uppercase|blockedDays:kanbanEntry.daysBlocked}}</span> class="ui mini {{labelClass(label)}} right floated label">{{label|uppercase|blockedDays:kanbanEntry.daysBlocked}}</span>

View File

@ -4,7 +4,7 @@ import { environment } from '../../../environments/environment';
import { JiraAssignee, KanbanEntry } from '../shared'; import { JiraAssignee, KanbanEntry } from '../shared';
const DEFAULT_AVATAR = '/assets/riddler.png'; const DEFAULT_AVATAR = '/assets/riddler.png';
const JIRA_BOARD_BASE_HREF = 'https://jirapducc.mo.ca.am.ericsson.se/browse/'; const JIRA_BOARD_BASE_HREF = 'https://cc-jira.rnd.ki.sw.ericsson.se/browse/';
const labelColors = { const labelColors = {
TSP: 'teal', TSP: 'teal',
@ -14,6 +14,9 @@ const labelColors = {
BLOCKED: 'red', BLOCKED: 'red',
SPIKE: 'purple', SPIKE: 'purple',
EXPEDITE: 'pink', EXPEDITE: 'pink',
'MTAS-GUARDIAN': 'pink',
'MTAS-GUARDIANACTIVE': 'yellow',
}; };
@Component({ @Component({

7
src/app/display/shared/kanban-entry.model.ts Normal file → Executable file
View File

@ -1,12 +1,13 @@
import {JiraIssueType} from "./jira-issue-type.model"; import {JiraIssueType} from './jira-issue-type.model';
import {JiraStatus} from "./jira-status.model"; import {JiraStatus} from './jira-status.model';
import {JiraAssignee} from "./jira-assignee.model"; import {JiraAssignee} from './jira-assignee.model';
export class KanbanEntry { export class KanbanEntry {
public id: number; public id: number;
public key: string; public key: string;
public summary: string; public summary: string;
public issueType: JiraIssueType; public issueType: JiraIssueType;
public epicName: string;
public status: JiraStatus; public status: JiraStatus;
public assignee: JiraAssignee; public assignee: JiraAssignee;
public additionalAssignees: Array<JiraAssignee> = []; public additionalAssignees: Array<JiraAssignee> = [];

14
src/app/display/shared/kanban.service.ts Normal file → Executable file
View File

@ -1,10 +1,13 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ActivatedRouteSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { KanbanBoard } from './kanban-board.model'; 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() @Injectable()
export class KanbanService { export class KanbanService {
@ -12,16 +15,21 @@ export class KanbanService {
private cachedKanbanBoard: KanbanBoard = new KanbanBoard(); 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 * Returns an observable instance to the kanban board api
* Reloads team data before, to refresh team config
* *
* @returns {Observable<KanbanBoard>} * @returns {Observable<KanbanBoard>}
*/ */
public getList(): 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}`))
);
} }
/** /**

View File

@ -0,0 +1,20 @@
: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;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}

View File

@ -0,0 +1 @@
<iframe [src]="sanitizedUrl"></iframe>

View 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();
});
});

View 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);
}
}

8
src/app/display/slide-show.service.ts Normal file → Executable file
View File

@ -19,9 +19,9 @@ export class SlideShowService {
public nextSlide() { public nextSlide() {
if (this.currentSlideIndex === this.slides.length - 1) { if (this.currentSlideIndex === this.slides.length - 1) {
// this.currentSlideIndex++; this.currentSlideIndex++;
// this.router.navigate(['/kanban']); this.router.navigate(['/kanban']);
// } else if (this.currentSlideIndex === this.slides.length) { } else if (this.currentSlideIndex === this.slides.length) {
this.currentSlideIndex = -1; this.currentSlideIndex = -1;
this.reloadSlides(); this.reloadSlides();
this.router.navigate(['/commit-tracker']); this.router.navigate(['/commit-tracker']);
@ -39,7 +39,7 @@ export class SlideShowService {
const team = this.settingsService.team; const team = this.settingsService.team;
this.slideService.list().subscribe( this.slideService.list().subscribe(
slides => this.slides = slides.filter( slides => this.slides = slides.filter(
slide => slide.team === null || slide.team.id === team.id slide => slide.teams === null || slide.teams.some(s => s.id === team.id) && slide.isVisible
) )
); );
} }

3
src/app/display/slide-show/slide-show.component.html Normal file → Executable file
View 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>

18
src/app/display/slide-show/slide-show.component.ts Normal file → Executable file
View File

@ -1,10 +1,10 @@
import { Component, HostBinding, OnInit } from '@angular/core'; import {Component, HostBinding, OnInit} from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import { Title } from '@angular/platform-browser'; import {Title} from '@angular/platform-browser';
import * as marked from 'marked'; import * as marked from 'marked';
import { slideInOutAnimation } from '../../shared/slide-in-out-animation'; import {slideInOutAnimation} from '../../shared/slide-in-out-animation';
import { Slide } from '../../shared/slide'; import {Slide, SlideType} from '../../shared/slide';
@Component({ @Component({
selector: 'app-slide-show', selector: 'app-slide-show',
@ -34,4 +34,12 @@ export class SlideShowComponent implements OnInit {
get renderedSlide(): String { get renderedSlide(): String {
return this.md.parse(this.slide.slideData); 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
View File

@ -12,7 +12,6 @@ export class SlideComponent implements OnInit {
@Input() visible = true; @Input() visible = true;
@Output() visibleChange = new EventEmitter(); @Output() visibleChange = new EventEmitter();
constructor() {} constructor() {}
ngOnInit() {} ngOnInit() {}

2
src/app/shared/service/commit-tracker.service.ts Normal file → Executable file
View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { Commit } from '../commit'; import { Commit } from '../commit';

2
src/app/shared/service/self-updater.service.ts Normal file → Executable file
View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
@Injectable() @Injectable()

3
src/app/shared/service/settings.service.ts Normal file → Executable file
View File

@ -1,6 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable, Subject } from 'rxjs';
import { Subject } from 'rxjs/Subject';
import { Team } from '../team'; import { Team } from '../team';
const DEFAULT_SLIDE_INTERVAL = 30000; const DEFAULT_SLIDE_INTERVAL = 30000;

6
src/app/shared/service/slide.service.ts Normal file → Executable file
View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { Slide } from '../slide'; import { Slide } from '../slide';
@ -18,9 +18,7 @@ export class SlideService implements Resolve<Array<Slide>>{
private static prepareSlideData(slide: Slide) { private static prepareSlideData(slide: Slide) {
const slideToSave = <any>Object.assign({}, slide); const slideToSave = <any>Object.assign({}, slide);
try { try {
slideToSave.team = slideToSave.team.id === null slideToSave.teams = slide.teams.map(team => team.id);
? null
: slideToSave.team.id;
} catch (e) {} } catch (e) {}
return slideToSave; return slideToSave;
} }

20
src/app/shared/service/team.service.ts Normal file → Executable file
View File

@ -1,10 +1,12 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { Team } from '../team'; import { Team } from '../team';
import {SettingsService} from './settings.service';
import {map} from 'rxjs/operators';
@Injectable() @Injectable()
export class TeamService implements Resolve<Array<Team>> { 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 apiEndPoint = environment.apiUrl + '/api/team';
private cachedTeams: Array<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>> { public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Team>> {
return this.list().toPromise(); return this.list().toPromise();
@ -22,6 +25,12 @@ export class TeamService implements Resolve<Array<Team>> {
return this.httpClient.get<Array<Team>>(this.apiEndPoint); 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> { public persist(team: Team): Observable<Team> {
return team.id === null return team.id === null
? this.create(team) ? this.create(team)
@ -47,4 +56,11 @@ export class TeamService implements Resolve<Array<Team>> {
set teams(teams: Array<Team>) { set teams(teams: Array<Team>) {
this.cachedTeams = teams; this.cachedTeams = teams;
} }
private updateSettingsWhenSelected(team: Team): Team {
if (this.settingsService.team.id === team.id) {
this.settingsService.team = team;
}
return team;
}
} }

14
src/app/shared/service/timer.service.ts Normal file → Executable file
View File

@ -1,13 +1,10 @@
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { SettingsService } from './settings.service'; import { SettingsService } from './settings.service';
import { SelfUpdaterService } from './self-updater.service'; import { SelfUpdaterService } from './self-updater.service';
import { TimerObservable } from 'rxjs/observable/TimerObservable'; import { Subject, timer, Subscription } from 'rxjs';
import { Subscription } from 'rxjs/Subscription';
import { ActivationStart, Router } from '@angular/router'; import { ActivationStart, Router } from '@angular/router';
import { SlideShowService } from '../../display/slide-show.service'; import { SlideShowService } from '../../display/slide-show.service';
import 'rxjs/add/operator/filter'; import { filter, switchMap } from 'rxjs/operators';
import 'rxjs/add/operator/switchMap';
const TIMER_UPDATE_POLL_INTERVAL = 30000; const TIMER_UPDATE_POLL_INTERVAL = 30000;
@ -24,18 +21,19 @@ export class TimerService implements OnDestroy {
private router: Router, private router: Router,
private slideShowService: SlideShowService) { 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.selfUpdateCheckerTimer = timerSUC.subscribe(() => {
this.selfUpdaterService.checkAndReloadIfNecessary(); 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()); .subscribe(() => this.changeSlide());
this.setSlideTimer(this.settings.slideInterval); this.setSlideTimer(this.settings.slideInterval);
this.autoSwitch = false; this.autoSwitch = false;
this.router.events this.router.events
.filter(event => event instanceof ActivationStart) .pipe(filter(event => event instanceof ActivationStart))
.subscribe((event: ActivationStart) => this.autoSwitch = !!event.snapshot.data.autoSwitchable); .subscribe((event: ActivationStart) => this.autoSwitch = !!event.snapshot.data.autoSwitchable);
this.slideIntervalSubscription = this.settings.slideIntervalChanged.subscribe( this.slideIntervalSubscription = this.settings.slideIntervalChanged.subscribe(

23
src/app/shared/slide.ts Normal file → Executable file
View File

@ -2,11 +2,24 @@ import { Team } from './team';
export class Slide { export class Slide {
id: number = null; id: number = null;
title: String = ''; title = '';
team: Team = null; type: SlideType = SlideType.MarkDown;
slideData: String = ''; visibility: SlideVisibility = SlideVisibility.Public;
teams: Array<Team> = [];
slideData = '';
slideUrl = '';
isVisible = true; isVisible = true;
createdAt: String = null; createdAt: string = null;
updatedAt: String = null; updatedAt: string = null;
position = 0; position = 0;
} }
export enum SlideType {
MarkDown = 'markdown',
IFrame = 'iframe',
}
export enum SlideVisibility {
Public = 'public',
Team = 'team',
}

View File

@ -10,7 +10,8 @@
] ]
}, },
"files": [ "files": [
"test.ts" "test.ts",
"polyfills.ts"
], ],
"include": [ "include": [
"**/*.spec.ts", "**/*.spec.ts",

View File

@ -14,6 +14,8 @@
"lib": [ "lib": [
"es2017", "es2017",
"dom" "dom"
] ],
"module": "es2015",
"baseUrl": "./"
} }
} }

View File

@ -18,7 +18,6 @@
"forin": true, "forin": true,
"import-blacklist": [ "import-blacklist": [
true, true,
"rxjs",
"rxjs/Rx" "rxjs/Rx"
], ],
"import-spacing": true, "import-spacing": true,