diff --git a/.gitignore b/.gitignore index 1217825..33bef2c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,8 @@ Thumbs.db .directory # Internal Data -/public +public/ +mongodb-data/ resource/ backup/ diff --git a/package.json b/package.json index 346f498..4ee7696 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opt-cc", - "version": "1.6.3", + "version": "1.6.4", "license": "MIT", "author": "Florian Hartwich ", "private": true, diff --git a/static/src/app/app.component.html b/static/src/app/app.component.html index c3cc9d9..6d4433a 100644 --- a/static/src/app/app.component.html +++ b/static/src/app/app.component.html @@ -96,6 +96,6 @@ diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css index a525771..58a0d9c 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css @@ -1,3 +1,8 @@ +.player-campaign-detail-container { + padding: 0 1% 0 1%; + border-top: thin solid lightgrey; +} + h2 { padding: 10px; } diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html index 8507339..3f0e0e8 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html @@ -1,10 +1,9 @@ -
-

Spielerstatistik - {{campaignPlayer.name}}

+
+

Kampagnendetails - {{campaignPlayer.name}}

{{campaignPlayer.campaign.title}} Kampagne

< Zurück -
[params['id'], params['playerName']]) - .flatMap(id => this.playerService.getCampaignPlayer(id[0], encodeURIComponent(id[1]))) + this.playerService.getCampaignPlayer(this.campaignId, encodeURIComponent(this.playerName)) .subscribe(campaignPlayer => { this.campaignPlayer = campaignPlayer; this.killData = this.assignData(this.yAxisKill, "kill"); @@ -91,14 +93,10 @@ export class CampaignPlayerDetailComponent { this.reviveData = this.assignData(this.yAxisRevive, "revive"); this.captureData = this.assignData(this.yAxisCapture, "flagTouch"); - if (this.totalDeath === 0) { - // avoid infinite or NaN with no death - this.totalDeath = 1; - } - this.kdRatio = parseFloat((this.totalKills / this.totalDeath).toFixed(2)); + this.kdRatio = parseFloat((this.totalKills / (this.totalDeath === 0 ? 1 : this.totalDeath)).toFixed(2)); if (this.kdRatio > 1) this.maxKd = this.kdRatio * 1.7; - this.respawnDeathRatio = parseFloat((this.totalRespawn / this.totalDeath).toFixed(2)); + this.respawnDeathRatio = parseFloat((this.totalRespawn / (this.totalDeath === 0 ? 1 : this.totalDeath)).toFixed(2)); this.sumData = [ { @@ -177,7 +175,7 @@ export class CampaignPlayerDetailComponent { } navigateBack() { - this.location.back(); + this.switchTab.emit(0); } } diff --git a/static/src/app/statistic/stats.module.ts b/static/src/app/statistic/stats.module.ts index 5be86ae..27c7bb5 100644 --- a/static/src/app/statistic/stats.module.ts +++ b/static/src/app/statistic/stats.module.ts @@ -4,7 +4,7 @@ import {SharedModule} from "../shared.module"; import {statsRouterModule, statsRoutingComponents} from "./stats.routing"; import {WarService} from "../services/logs/war.service"; import {NgxChartsModule} from "@swimlane/ngx-charts"; -import {AccordionModule, BsDropdownModule, ButtonsModule, TabsModule} from "ngx-bootstrap"; +import {AccordionModule, ButtonsModule} from "ngx-bootstrap"; import {CampaignService} from "../services/logs/campaign.service"; import {NgxDatatableModule} from "@swimlane/ngx-datatable"; import {PlayerService} from "../services/logs/player.service"; @@ -13,7 +13,7 @@ import {LogsService} from "../services/logs/logs.service"; @NgModule({ declarations: statsRoutingComponents, imports: [CommonModule, SharedModule, statsRouterModule, NgxChartsModule, - AccordionModule.forRoot(), BsDropdownModule.forRoot(), ButtonsModule.forRoot(), TabsModule.forRoot(), NgxDatatableModule], + AccordionModule.forRoot(), ButtonsModule.forRoot(), NgxDatatableModule], providers: [WarService, CampaignService, PlayerService, LogsService] }) export class StatsModule { diff --git a/static/src/app/statistic/stats.routing.ts b/static/src/app/statistic/stats.routing.ts index dbc6951..db263b9 100644 --- a/static/src/app/statistic/stats.routing.ts +++ b/static/src/app/statistic/stats.routing.ts @@ -1,13 +1,15 @@ import {RouterModule, Routes} from "@angular/router"; import {StatisticComponent} from "./stats.component"; -import {WarDetailComponent} from "./war-detail/war-detail.component"; -import {WarSubmitComponent} from "./war-submit/war-submit.component"; import {WarListComponent} from "./war-list/war-list.component"; import {StatisticOverviewComponent} from "./overview/stats-overview.component"; import {WarItemComponent} from "./war-list/war-item.component"; import {ModuleWithProviders} from "@angular/core"; import {CampaignSubmitComponent} from "./campaign-submit/campaign-submit.component"; import {CampaignPlayerDetailComponent} from "./campaign-player-detail/campaign-player-detail.component"; +import {WarDetailComponent} from "./war-detail/war-detail.component"; +import {ScoreboardComponent} from "./war-detail/scoreboard/scoreboard.component"; +import {WarSubmitComponent} from "./war-submit/war-submit.component"; +import {FractionStatsComponent} from "./war-detail/fraction-stats/fraction-stats.component"; export const statsRoutes: Routes = [{ @@ -48,5 +50,6 @@ export const statsRoutes: Routes = [{ export const statsRouterModule: ModuleWithProviders = RouterModule.forChild(statsRoutes); export const statsRoutingComponents = [StatisticComponent, StatisticOverviewComponent, CampaignSubmitComponent, - WarListComponent, WarSubmitComponent, WarDetailComponent, CampaignPlayerDetailComponent, WarItemComponent]; + WarListComponent, WarSubmitComponent, WarDetailComponent, ScoreboardComponent, FractionStatsComponent, + CampaignPlayerDetailComponent, WarItemComponent]; diff --git a/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.css b/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.css new file mode 100644 index 0000000..eff3b2f --- /dev/null +++ b/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.css @@ -0,0 +1,32 @@ +.btn-dark { + background: #4b4b4b; + color: #f5f5f5; + border-color: #000; +} + +.btn-dark:hover { + background: #afafaf; + color: #f5f5f5; +} + +.btn-dark.active { + background: #222222; +} + +.chart-container { + width: 95%; + margin: 2%; + min-width: 900px; + height: 600px; + padding: 15px; + float: left; +} + +.chart-select-group { + width: 980px; + margin: auto; +} + +/*.dropdown-menu > li > a {*/ +/*cursor: pointer;*/ +/*}*/ diff --git a/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.html b/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.html new file mode 100644 index 0000000..dec5e6c --- /dev/null +++ b/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.html @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + +
+
+ +
+ + +
+ +
+ + +
+
diff --git a/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.ts b/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.ts new file mode 100644 index 0000000..58756a7 --- /dev/null +++ b/static/src/app/statistic/war-detail/fraction-stats/fraction-stats.component.ts @@ -0,0 +1,339 @@ +import {Component, ElementRef, SimpleChanges, ViewChild} from "@angular/core"; +import * as d3 from "d3"; +import {ChartUtils} from "../../../utils/chart-utils"; +import {Fraction} from "../../../utils/fraction.enum"; +import {War} from "../../../models/model-interfaces"; + + +@Component({ + selector: 'war-detail-fraction', + templateUrl: './fraction-stats.component.html', + inputs: ['war', 'logData'], + styleUrls: ['./fraction-stats.component.css', '../../../style/list-entry.css', '../../../style/hide-scrollbar.css'] +}) +export class FractionStatsComponent { + + readonly fraction = Fraction; + + @ViewChild('overview') private overviewContainer: ElementRef; + + war: War; + + logData: any; + + startDateObj: Date; + + initialized: any; + + public chartSelectModel: string; + + lineChartData: any[] = []; + areaChartData: any[] = []; + + tmpPointData; + tmpBudgetData; + tmpKillData; + tmpFrienlyFireData; + tmpTransportData; + tmpReviveData; + tmpStabilizeData; + tmpFlagCaptureData; + + colorScheme = { + domain: [Fraction.COLOR_BLUFOR, Fraction.COLOR_OPFOR] + }; + + labelPoints = 'Punkte'; + labelBudget = 'Budget'; + labelKill = 'Kills'; + labelFriendlyFire = 'FriendlyFire'; + labelTransport = 'Lufttransport'; + labelRevive = 'Revive'; + labelStabilize = 'Stabilisiert'; + labelFlag = 'Flaggenbesitz'; + lineChartLabel: string = this.labelPoints; + + showLineChart = true; + stepCurve = d3.curveStepAfter; + gradient = false; + yAxis = true; + xAxis = true; + legend = false; + legendTitle = false; + showXAxisLabel = false; + showYAxisLabel = true; + autoscale = true; + timeline = false; + roundDomains = true; + + constructor() { + } + + ngOnInit() { + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.war || changes.logData) { + this.initialized = { + budget: false, + kill: false, + revive: false, + transport: false, + flag: false + }; + Object.assign(this, [this.lineChartData, this.areaChartData]); + this.chartSelectModel = this.labelPoints; + + this.startDateObj = new Date(this.war.date); + this.startDateObj.setHours(0); + this.startDateObj.setMinutes(1); + this.loadFractionData(); + } + } + + selectChart() { + if (this.chartSelectModel !== this.labelFlag) { + this.showLineChart = true; + this.lineChartLabel = this.chartSelectModel; + switch (this.chartSelectModel) { + case this.labelPoints: + this.lineChartData = this.tmpPointData; + break; + case this.labelBudget: + this.initBudgetData(); + this.lineChartData = this.tmpBudgetData; + break; + case this.labelKill: + this.initKillData(); + this.lineChartData = this.tmpKillData; + break; + case this.labelFriendlyFire: + this.initKillData(); + this.lineChartData = this.tmpFrienlyFireData; + break; + case this.labelRevive: + this.initRevive(); + this.lineChartData = this.tmpReviveData; + break; + case this.labelStabilize: + this.initRevive(); + this.lineChartData = this.tmpStabilizeData; + break; + case this.labelTransport: + this.initTransportData(); + this.lineChartData = this.tmpTransportData; + break; + } + } else { + this.initFlagHoldData(); + this.showLineChart = false; + this.areaChartData = this.tmpFlagCaptureData; + } + } + + loadFractionData() { + this.initializeTempCollections(); + this.initPointData(); + this.showLineChart = true; + this.lineChartLabel = this.labelPoints; + this.lineChartData = this.tmpPointData; + } + + initPointData() { + this.logData.points.forEach(pointEntry => { + this.tmpPointData[0].series.push(ChartUtils.getSeriesEntry(new Date(pointEntry.time), pointEntry.ptBlufor)); + this.tmpPointData[1].series.push(ChartUtils.getSeriesEntry(new Date(pointEntry.time), pointEntry.ptOpfor)); + }); + this.addFinalTimeData(this.tmpPointData); + } + + initBudgetData() { + if (this.initialized.budget) { + return; + } + this.logData.budget.forEach(budgetEntry => { + const budgetEntryDate = new Date(budgetEntry.time); + const fractionChange = budgetEntry.fraction === 'BLUFOR' ? 0 : 1; + const fractionOld = budgetEntry.fraction !== 'BLUFOR' ? 0 : 1; + + if (this.isTwoMinutesAhead(budgetEntryDate, this.tmpBudgetData)) { + this.tmpBudgetData[fractionChange].series.push(ChartUtils.getSeriesEntry(new Date(budgetEntry.time), budgetEntry.newBudget)); + this.tmpBudgetData[fractionOld].series.push(ChartUtils.getSeriesEntry(new Date(budgetEntry.time), + this.tmpBudgetData[fractionOld].series[this.tmpBudgetData[fractionOld].series.length - 1].value)); + } + }); + this.addFinalTimeData(this.tmpBudgetData); + this.initialized.budget = true; + } + + initKillData() { + if (this.initialized.kill) { + return; + } + let killCountBlufor = 0, killCountOpfor = 0, ffKillCountBlufor = 0, ffKillCountOpfor = 0; + + for (const {killEntry, index} of this.logData.kill.map((killEntry, index) => ({killEntry, index}))) { + const killEntryDate = new Date(killEntry.time); + if (killEntry.friendlyFire === false) { + if (killEntry.fraction === 'BLUFOR') { + killCountBlufor++; + } + if (killEntry.fraction === 'OPFOR') { + killCountOpfor++; + } + if (this.isTwoMinutesAhead(killEntryDate, this.tmpKillData)) { + this.tmpKillData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountBlufor)); + this.tmpKillData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountOpfor)); + } + } else { + if (killEntry.fraction === 'BLUFOR') { + ffKillCountBlufor++; + } + if (killEntry.fraction === 'OPFOR') { + ffKillCountOpfor++; + } + if (this.isTwoMinutesAhead(killEntryDate, this.tmpFrienlyFireData)) { + this.tmpFrienlyFireData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountBlufor)); + this.tmpFrienlyFireData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountOpfor)); + } + } + if (index === this.logData.kill.length - 1) { + this.tmpKillData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountBlufor)); + this.tmpKillData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountOpfor)); + this.tmpFrienlyFireData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountBlufor)); + this.tmpFrienlyFireData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountOpfor)); + } + } + + this.addFinalTimeData(this.tmpKillData); + this.addFinalTimeData(this.tmpFrienlyFireData) + this.initialized.kill = true; + } + + initRevive() { + if (this.initialized.revive) { + return; + } + let reviveCountBlufor = 0, reviveCountOpfor = 0, stabilizeCountBlufor = 0, stabilizeCountOpfor = 0; + for (const {reviveEntry, index} of this.logData.revive.map((reviveEntry, index) => ({reviveEntry, index}))) { + const reviveEntryDate = new Date(reviveEntry.time); + if (reviveEntry.stabilized === false) { + if (reviveEntry.fraction === 'BLUFOR') { + reviveCountBlufor++; + } else { + reviveCountOpfor++; + } + if (this.isTwoMinutesAhead(reviveEntryDate, this.tmpReviveData)) { + this.tmpReviveData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountBlufor)); + this.tmpReviveData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountOpfor)); + } + } else { + if (reviveEntry.fraction === 'BLUFOR') { + stabilizeCountBlufor++; + } else { + stabilizeCountOpfor++; + } + if (this.isTwoMinutesAhead(reviveEntryDate, this.tmpStabilizeData)) { + this.tmpStabilizeData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountBlufor)); + this.tmpStabilizeData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountOpfor)); + } + } + if (index === this.logData.revive.length - 1) { + this.tmpReviveData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountBlufor)); + this.tmpReviveData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountOpfor)); + this.tmpStabilizeData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountBlufor)); + this.tmpStabilizeData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountOpfor)); + } + } + this.addFinalTimeData(this.tmpReviveData); + this.addFinalTimeData(this.tmpStabilizeData); + this.initialized.revive = true; + } + + initTransportData() { + if (this.initialized.transport) { + return; + } + let transportCountBlufor = 0, transportCountOpfor = 0; + for (const {transportEntry, index} of this.logData.transport.map((transportEntry, index) => ({ + transportEntry, + index + }))) { + const transportEntryDate = new Date(transportEntry.time); + if (transportEntry.fraction === 'BLUFOR') { + transportCountBlufor++; + } else { + transportCountOpfor++; + } + if (this.isTwoMinutesAhead(transportEntryDate, this.tmpTransportData) || index === this.logData.transport.length - 1) { + this.tmpTransportData[0].series.push(ChartUtils.getSeriesEntry(transportEntryDate, transportCountBlufor)); + this.tmpTransportData[1].series.push(ChartUtils.getSeriesEntry(transportEntryDate, transportCountOpfor)); + } + } + this.addFinalTimeData(this.tmpTransportData); + this.initialized.transport = true; + + } + + initFlagHoldData() { + if (this.initialized.flag) { + return; + } + let flagStatusBlufor = true; + let flagStatusOpfor = true; + this.tmpFlagCaptureData[0].series.push(ChartUtils.getSeriesEntry(this.startDateObj, flagStatusBlufor)); + this.tmpFlagCaptureData[1].series.push(ChartUtils.getSeriesEntry(this.startDateObj, flagStatusOpfor)); + + this.logData.flag.forEach(flagEntry => { + if (flagEntry.flagFraction === 'BLUFOR') { + flagStatusBlufor = !flagEntry.capture; + } else { + flagStatusOpfor = !flagEntry.capture; + } + this.tmpFlagCaptureData[flagEntry.flagFraction === 'BLUFOR' ? 0 : 1].series.push( + ChartUtils.getSeriesEntry(new Date(flagEntry.time), flagEntry.flagFraction === 'BLUFOR' ? flagStatusBlufor : flagStatusOpfor) + ) + }); + + this.addFinalTimeData(this.tmpFlagCaptureData); + this.initialized.flag = true; + } + + protected isTwoMinutesAhead(entryDate: Date, tmpData: any): boolean { + return entryDate.getTime() >= tmpData[0].series[tmpData[0].series.length - 1].name.getTime() + (1.5 * 60000) + } + + initializeTempCollections() { + this.tmpPointData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + this.tmpBudgetData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + this.tmpKillData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + this.tmpFrienlyFireData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + this.tmpTransportData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + this.tmpReviveData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + this.tmpStabilizeData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + this.tmpFlagCaptureData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); + + [this.tmpKillData, this.tmpFrienlyFireData, this.tmpReviveData, this.tmpStabilizeData, this.tmpTransportData].forEach(tmp => { + [0, 1].forEach(index => { + tmp[index].series.push(ChartUtils.getSeriesEntry(this.startDateObj, 0)); + }) + }); + this.tmpBudgetData[0].series.push(ChartUtils.getSeriesEntry(this.startDateObj, this.war.budgetBlufor)); + this.tmpBudgetData[1].series.push(ChartUtils.getSeriesEntry(this.startDateObj, this.war.budgetOpfor)); + } + + addFinalTimeData(tmpCollection) { + const endDate = new Date(this.war.endDate); + if (tmpCollection === this.tmpBudgetData) { + this.tmpBudgetData[0].series.push(ChartUtils.getSeriesEntry(endDate, this.war.endBudgetBlufor)); + this.tmpBudgetData[1].series.push(ChartUtils.getSeriesEntry(endDate, this.war.endBudgetOpfor)); + } else { + for (let j in [0, 1]) { + if (tmpCollection[j].series[tmpCollection[j].series.length - 1].name < endDate) { + tmpCollection[j].series.push(ChartUtils.getSeriesEntry(endDate, tmpCollection[j].series[tmpCollection[j].series.length - 1].value)); + } + } + } + } + +} diff --git a/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.css b/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.css new file mode 100644 index 0000000..856b24a --- /dev/null +++ b/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.css @@ -0,0 +1,42 @@ +.player-name { + font-weight: bold; +} + +/* ########### DATATABLE ########### */ + +:host /deep/ .datatable-header { + background: #222222; + font-weight: 700; + border-radius: 10px 10px 0 0; + color: white; +} + +:host /deep/ span.datatable-header-cell-label, :host /deep/ div.datatable-body-cell-label { + padding-left: 8px; +} + +:host /deep/ .ngx-datatable .datatable-header { + /*vertical center alignment*/ + display: table-cell; + vertical-align: middle; +} + +:host /deep/ .ngx-datatable .datatable-body .datatable-body-row > div { + /*vertical alignment*/ + position: relative; + top: 10px; +} + +:host /deep/ .datatable-body-row { + color: #222222; + border-bottom: 1px solid grey; +} + +:host /deep/ .datatable-body-row:hover { + background-color: #f7f7f7; +} + +.in-table-btn { + position: absolute; + margin-top: -5px; +} diff --git a/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.html b/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.html new file mode 100644 index 0000000..4b0020e --- /dev/null +++ b/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.html @@ -0,0 +1,42 @@ +
+ + + + + {{value}} + + + + + + {{value === 'BLUFOR' ? fraction.BLUFOR : fraction.OPFOR}} + + + + + + + + + + + Detail + + + + + Gesamt + + + +
diff --git a/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.ts b/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.ts new file mode 100644 index 0000000..4d9b059 --- /dev/null +++ b/static/src/app/statistic/war-detail/scoreboard/scoreboard.component.ts @@ -0,0 +1,62 @@ +import {Component, ElementRef, EventEmitter, SimpleChanges, ViewChild} from "@angular/core"; +import {War} from "../../../models/model-interfaces"; +import {Fraction} from "../../../utils/fraction.enum"; + +@Component({ + selector: 'scoreboard', + templateUrl: './scoreboard.component.html', + inputs: ['war', 'fractionFilterSelected'], + outputs: ['playerTabSwitch'], + styleUrls: ['./scoreboard.component.css', '../../../style/list-entry.css', '../../../style/hide-scrollbar.css'] +}) +export class ScoreboardComponent { + + readonly fraction = Fraction; + + playerTabSwitch = new EventEmitter(); + + war: War; + + fractionFilterSelected: string; + + cellHeight = 40; + + rows = []; + + reorderable: boolean = false; + + customClasses = { + sortAscending: 'glyphicon glyphicon-triangle-top', + sortDescending: 'glyphicon glyphicon-triangle-bottom', + }; + + constructor() { + } + + ngOnInit() { + } + + selectPlayerDetail(view: number, playerName: string) { + this.playerTabSwitch.emit({view: view, player: playerName}) + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.war) { + this.rows = changes.war.currentValue.players; + } + if (changes.fractionFilterSelected) { + this.filterPlayersByFraction(this.fractionFilterSelected) + } + } + + filterPlayersByFraction(fraction?: string) { + if (fraction) { + this.rows = this.war.players.filter((player) => { + return player.fraction === fraction; + }) + } else { + this.rows = this.war.players; + } + } + +} diff --git a/static/src/app/statistic/war-detail/war-detail.component.css b/static/src/app/statistic/war-detail/war-detail.component.css index ac69342..9f2dc98 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.css +++ b/static/src/app/statistic/war-detail/war-detail.component.css @@ -1,7 +1,7 @@ -.vertical-spacer { - height: 205px; - float: left; - width: 4%; +.war-header-container { + width: 920px; + min-height: 205px; + margin: auto; } .head-field { @@ -10,120 +10,32 @@ margin-bottom: 10px; } -@media screen and (min-width: 1500px) { - .vertical-spacer { - width: 15%; - } +.war-header { + border-bottom: thin solid lightgrey; } -@media screen and (min-width: 2000px) { - .vertical-spacer { - width: 20%; - } -} - -.overview { - overflow-y: scroll; - overflow-x: hidden; - border-left: thin solid lightgrey; - bottom: 20px; - height: 100vh; -} - -.player-name { - font-weight: bold; -} - -/* ########### TABS ########### */ - -:host /deep/ .nav-tabs { - padding-left: 35% !important; -} - -:host /deep/ .nav-link { - background: #4b4b4b; - color: white; -} - -:host /deep/ .nav-link:hover { - background: #afafaf; - color: #000; -} - -:host /deep/ .nav-tabs > li.active > a { +.nav-tabs > li.active > a { background: #222222; - color: white; } -/* ########### DATATABLE ########### */ - -:host /deep/ .datatable-header { - background: #222222; - font-weight: 700; - border-radius: 10px 10px 0 0; - color: white; +.nav-tabs { + border-bottom: 0; } -:host /deep/ span.datatable-header-cell-label, :host /deep/ div.datatable-body-cell-label { - padding-left: 8px; -} - -:host /deep/ .ngx-datatable .datatable-header { - /*vertical center alignment*/ - display: table-cell; - vertical-align: middle; -} - -:host /deep/ .ngx-datatable .datatable-body .datatable-body-row > div { - /*vertical alignment*/ - position: relative; - top: 10px; -} - -:host /deep/ .datatable-body-row { - color: #222222; - border-bottom: 1px solid grey; - cursor: pointer; -} - -:host /deep/ .datatable-body-row:hover { - background-color: #f7f7f7; -} - -/* ########### CHART-TAB ######## */ - -.btn-dark { +.nav-tabs > li > a { background: #4b4b4b; - color: #f5f5f5; - border-color: #000; } -.btn-dark:hover { +.nav-tabs > li:not(.active) > a:hover { background: #afafaf; color: #f5f5f5; } -.btn-dark.active { - background: #222222; +.nav-link { + cursor: pointer !important; + color: #FFF !important; } -.chart-container { - width: 95%; - margin: 2%; - min-width: 900px; - height: 600px; - padding: 15px; - float: left; +.nav-tabs > li.deactivated > a.nav-link { + cursor: not-allowed !important; } - -.chart-select-group { - width: 50%; - margin: auto; - position: inherit; - display: block; - vertical-align: middle; -} - -/*.dropdown-menu > li > a {*/ - /*cursor: pointer;*/ -/*}*/ diff --git a/static/src/app/statistic/war-detail/war-detail.component.html b/static/src/app/statistic/war-detail/war-detail.component.html index 306a641..ea77b1b 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.html +++ b/static/src/app/statistic/war-detail/war-detail.component.html @@ -1,175 +1,85 @@ -
-
-
-
-

{{war.title}} - vom {{war.date | date: 'dd.MM.yyyy'}}

-
-

Endpunktestand:

- {{fraction.BLUFOR}} {{war.ptBlufor}} - | - {{war.ptOpfor}} {{fraction.OPFOR}} -
+
+
+

{{war.title}} - vom {{war.date | date: 'dd.MM.yyyy'}}

+
+

Endpunktestand:

+ {{fraction.BLUFOR}} {{war.ptBlufor}} + | + {{war.ptOpfor}} {{fraction.OPFOR}} +
-
-

Teilnehmer:

- - -
+
+

Teilnehmer:

+ + +
-
- Logfile anzeigen -
- - - -
-
-
+
+ Logfile + anzeigen +
+ + + +
+
- - - - Scoreboard - -
- - - - - {{value}} - - - - - - {{value === 'BLUFOR' ? fraction.BLUFOR : fraction.OPFOR}} - - - - - - - - - -
+ - - - Fraktionen - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - -
- -
- - -
- -
- - -
-
- - - - - - -
+ +
+ + + + + + +
diff --git a/static/src/app/statistic/war-detail/war-detail.component.ts b/static/src/app/statistic/war-detail/war-detail.component.ts index 99b39d2..8f22cec 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.ts +++ b/static/src/app/statistic/war-detail/war-detail.component.ts @@ -1,13 +1,10 @@ -/// -import {Component, ElementRef, ViewChild} from "@angular/core"; -import {ActivatedRoute, Router} from "@angular/router"; +import {Component} from "@angular/core"; +import {ActivatedRoute} from "@angular/router"; import {WarService} from "../../services/logs/war.service"; import {War} from "../../models/model-interfaces"; -import {LogsService} from "../../services/logs/logs.service"; -import {TabsetComponent} from "ngx-bootstrap"; -import * as d3 from "d3"; import {ChartUtils} from "../../utils/chart-utils"; import {Fraction} from "../../utils/fraction.enum"; +import {LogsService} from "../../services/logs/logs.service"; @Component({ @@ -19,76 +16,27 @@ export class WarDetailComponent { readonly fraction = Fraction; - @ViewChild('overview') private overviewContainer: ElementRef; + war: War; - @ViewChild('staticTabs') staticTabs: TabsetComponent; + logData; - war: War = {players: []}; + singlePlayerView: number; - logData: any; + playerDetailName: string; - initialized: any; + tab: number; - startDateObj: Date; + fractionStatsInitialized: boolean; - public chartSelectModel: string; - - fractionRadioSelect: string; + fractionFilterSelected: string; playerChart: any[] = []; - cellHeight = 40; - - rows = []; - - reorderable: boolean = false; - - customClasses = { - sortAscending: 'glyphicon glyphicon-triangle-top', - sortDescending: 'glyphicon glyphicon-triangle-bottom', - }; - - lineChartData: any[] = []; - areaChartData: any[] = []; - - tmpPointData; - tmpBudgetData; - tmpKillData; - tmpFrienlyFireData; - tmpTransportData; - tmpReviveData; - tmpStabilizeData; - tmpFlagCaptureData; - colorScheme = { - domain: [Fraction.COLOR_BLUFOR, Fraction.COLOR_OPFOR] + domain: [Fraction.COLOR_OPFOR, Fraction.COLOR_BLUFOR] }; - labelPoints = 'Punkte'; - labelBudget = 'Budget'; - labelKill = 'Kills'; - labelFriendlyFire = 'FriendlyFire'; - labelTransport = 'Lufttransport'; - labelRevive = 'Revive'; - labelStabilize = 'Stabilisiert'; - labelFlag = 'Flaggenbesitz'; - lineChartLabel: string = this.labelPoints; - - showLineChart = true; - stepCurve = d3.curveStepAfter; - gradient = false; - yAxis = true; - xAxis = true; - legend = false; - legendTitle = false; - showXAxisLabel = false; - showYAxisLabel = true; - autoscale = true; - timeline = false; - roundDomains = true; - constructor(private route: ActivatedRoute, - private router: Router, private warService: WarService, private logsService: LogsService) { } @@ -100,306 +48,39 @@ export class WarDetailComponent { .flatMap(id => this.warService.getWar(id)) .subscribe(war => { this.war = war; - this.rows = war.players; + + this.switchTab(0); + this.fractionStatsInitialized = false; + this.fractionFilterSelected = undefined; + this.playerChart = ChartUtils.getSingleDataArray(Fraction.OPFOR, war.playersOpfor, Fraction.BLUFOR, war.playersBlufor); + Object.assign(this, [this.playerChart]); + }) + } - this.initialized = { - basic: false, - budget: false, - kill: false, - revive: false, - transport: false, - flag: false - }; - Object.assign(this, [this.playerChart, this.lineChartData, this.areaChartData]); - this.chartSelectModel = this.labelPoints; - - this.startDateObj = new Date(this.war.date); - this.startDateObj.setHours(0); - this.startDateObj.setMinutes(1); - - this.fractionRadioSelect = undefined; - this.staticTabs.tabs[0].active = true; - this.scrollOverviewTop(); + switchTab(index: number) { + this.tab = index; + if (index === 1 && !this.fractionStatsInitialized) { + this.logsService.getFullLog(this.war._id).subscribe(log => { + this.logData = log; + this.fractionStatsInitialized = true; }); + } + window.scroll({left: 0, top: 0, behavior: 'smooth'}); + } + + /** + * called by EventEmitter, + * @param event with fields: 'view' (0 = war-detail, 1 = campaign-detail); 'player' = player name + */ + switchToPlayerTab(event) { + this.singlePlayerView = event.view; + this.playerDetailName = event.player; + this.switchTab(2); } filterPlayersByFraction(fraction?: string) { - if (fraction) { - this.rows = this.war.players.filter((player) => { - return player.fraction === fraction; - }) - } else { - this.rows = this.war.players; - } - } - - selectChart() { - if (this.chartSelectModel !== this.labelFlag) { - this.showLineChart = true; - this.lineChartLabel = this.chartSelectModel; - switch (this.chartSelectModel) { - case this.labelPoints: - this.lineChartData = this.tmpPointData; - break; - case this.labelBudget: - this.initBudgetData(); - this.lineChartData = this.tmpBudgetData; - break; - case this.labelKill: - this.initKillData(); - this.lineChartData = this.tmpKillData; - break; - case this.labelFriendlyFire: - this.initKillData(); - this.lineChartData = this.tmpFrienlyFireData; - break; - case this.labelRevive: - this.initRevive(); - this.lineChartData = this.tmpReviveData; - break; - case this.labelStabilize: - this.initRevive(); - this.lineChartData = this.tmpStabilizeData; - break; - case this.labelTransport: - this.initTransportData(); - this.lineChartData = this.tmpTransportData; - break; - } - } else { - this.initFlagHoldData(); - this.showLineChart = false; - this.areaChartData = this.tmpFlagCaptureData; - } - } - - selectPlayerDetail(player) { - if (player && player.selected && player.selected.length > 0) { - this.router.navigate(['../../campaign-player/' + this.war.campaign + '/' + player.selected[0].name], - {relativeTo: this.route}); - } - } - - scrollOverviewTop() { - try { - this.overviewContainer.nativeElement.scrollTop = 0; - } catch (err) { - } - } - - loadFractionData() { - if (this.initialized.basic) { - return; - } - - this.initializeTempCollections(); - - this.logsService.getFullLog(this.war._id).subscribe((data) => { - this.logData = data; - this.initPointData(); - this.showLineChart = true; - this.lineChartLabel = this.labelPoints; - this.lineChartData = this.tmpPointData; - this.initialized.basic = true; - }); - } - - initPointData() { - this.logData.points.forEach(pointEntry => { - this.tmpPointData[0].series.push(ChartUtils.getSeriesEntry(new Date(pointEntry.time), pointEntry.ptBlufor)); - this.tmpPointData[1].series.push(ChartUtils.getSeriesEntry(new Date(pointEntry.time), pointEntry.ptOpfor)); - }); - this.addFinalTimeData(this.tmpPointData); - } - - initBudgetData() { - if (this.initialized.budget) { - return; - } - this.logData.budget.forEach(budgetEntry => { - const budgetEntryDate = new Date(budgetEntry.time); - const fractionChange = budgetEntry.fraction === 'BLUFOR' ? 0 : 1; - const fractionOld = budgetEntry.fraction !== 'BLUFOR' ? 0 : 1; - - if (WarDetailComponent.isTwoMinutesAhead(budgetEntryDate, this.tmpBudgetData)) { - this.tmpBudgetData[fractionChange].series.push(ChartUtils.getSeriesEntry(new Date(budgetEntry.time), budgetEntry.newBudget)); - this.tmpBudgetData[fractionOld].series.push(ChartUtils.getSeriesEntry(new Date(budgetEntry.time), - this.tmpBudgetData[fractionOld].series[this.tmpBudgetData[fractionOld].series.length - 1].value)); - } - }); - this.addFinalTimeData(this.tmpBudgetData); - this.initialized.budget = true; - } - - initKillData() { - if (this.initialized.kill) { - return; - } - let killCountBlufor = 0, killCountOpfor = 0, ffKillCountBlufor = 0, ffKillCountOpfor = 0; - - for (const {killEntry, index} of this.logData.kill.map((killEntry, index) => ({killEntry, index}))) { - const killEntryDate = new Date(killEntry.time); - if (killEntry.friendlyFire === false) { - if (killEntry.fraction === 'BLUFOR') { - killCountBlufor++; - } - if (killEntry.fraction === 'OPFOR') { - killCountOpfor++; - } - if (WarDetailComponent.isTwoMinutesAhead(killEntryDate, this.tmpKillData)) { - this.tmpKillData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountBlufor)); - this.tmpKillData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountOpfor)); - } - } else { - if (killEntry.fraction === 'BLUFOR') { - ffKillCountBlufor++; - } - if (killEntry.fraction === 'OPFOR') { - ffKillCountOpfor++; - } - if (WarDetailComponent.isTwoMinutesAhead(killEntryDate, this.tmpFrienlyFireData)) { - this.tmpFrienlyFireData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountBlufor)); - this.tmpFrienlyFireData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountOpfor)); - } - } - if (index === this.logData.kill.length - 1) { - this.tmpKillData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountBlufor)); - this.tmpKillData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountOpfor)); - this.tmpFrienlyFireData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountBlufor)); - this.tmpFrienlyFireData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountOpfor)); - } - } - - this.addFinalTimeData(this.tmpKillData); - this.addFinalTimeData(this.tmpFrienlyFireData) - this.initialized.kill = true; - } - - initRevive() { - if (this.initialized.revive) { - return; - } - let reviveCountBlufor = 0, reviveCountOpfor = 0, stabilizeCountBlufor = 0, stabilizeCountOpfor = 0; - for (const {reviveEntry, index} of this.logData.revive.map((reviveEntry, index) => ({reviveEntry, index}))) { - const reviveEntryDate = new Date(reviveEntry.time); - if (reviveEntry.stabilized === false) { - if (reviveEntry.fraction === 'BLUFOR') { - reviveCountBlufor++; - } else { - reviveCountOpfor++; - } - if (WarDetailComponent.isTwoMinutesAhead(reviveEntryDate, this.tmpReviveData)) { - this.tmpReviveData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountBlufor)); - this.tmpReviveData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountOpfor)); - } - } else { - if (reviveEntry.fraction === 'BLUFOR') { - stabilizeCountBlufor++; - } else { - stabilizeCountOpfor++; - } - if (WarDetailComponent.isTwoMinutesAhead(reviveEntryDate, this.tmpStabilizeData)) { - this.tmpStabilizeData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountBlufor)); - this.tmpStabilizeData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountOpfor)); - } - } - if (index === this.logData.revive.length - 1) { - this.tmpReviveData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountBlufor)); - this.tmpReviveData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountOpfor)); - this.tmpStabilizeData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountBlufor)); - this.tmpStabilizeData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountOpfor)); - } - } - this.addFinalTimeData(this.tmpReviveData); - this.addFinalTimeData(this.tmpStabilizeData); - this.initialized.revive = true; - } - - initTransportData() { - if (this.initialized.transport) { - return; - } - let transportCountBlufor = 0, transportCountOpfor = 0; - for (const {transportEntry, index} of this.logData.transport.map((transportEntry, index) => ({ - transportEntry, - index - }))) { - const transportEntryDate = new Date(transportEntry.time); - if (transportEntry.fraction === 'BLUFOR') { - transportCountBlufor++; - } else { - transportCountOpfor++; - } - if (WarDetailComponent.isTwoMinutesAhead(transportEntryDate, this.tmpTransportData) || index === this.logData.transport.length - 1) { - this.tmpTransportData[0].series.push(ChartUtils.getSeriesEntry(transportEntryDate, transportCountBlufor)); - this.tmpTransportData[1].series.push(ChartUtils.getSeriesEntry(transportEntryDate, transportCountOpfor)); - } - } - this.addFinalTimeData(this.tmpTransportData); - this.initialized.transport = true; - - } - - initFlagHoldData() { - if (this.initialized.flag) { - return; - } - let flagStatusBlufor = true; - let flagStatusOpfor = true; - this.tmpFlagCaptureData[0].series.push(ChartUtils.getSeriesEntry(this.startDateObj, flagStatusBlufor)); - this.tmpFlagCaptureData[1].series.push(ChartUtils.getSeriesEntry(this.startDateObj, flagStatusOpfor)); - - this.logData.flag.forEach(flagEntry => { - if (flagEntry.flagFraction === 'BLUFOR') { - flagStatusBlufor = !flagEntry.capture; - } else { - flagStatusOpfor = !flagEntry.capture; - } - this.tmpFlagCaptureData[flagEntry.flagFraction === 'BLUFOR' ? 0 : 1].series.push( - ChartUtils.getSeriesEntry(new Date(flagEntry.time), flagEntry.flagFraction === 'BLUFOR' ? flagStatusBlufor : flagStatusOpfor) - ) - }); - - this.addFinalTimeData(this.tmpFlagCaptureData); - this.initialized.flag = true; - } - - private static isTwoMinutesAhead(entryDate: Date, tmpData: any): boolean { - return entryDate.getTime() >= tmpData[0].series[tmpData[0].series.length - 1].name.getTime() + (1.5 * 60000) - } - - initializeTempCollections() { - this.tmpPointData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - this.tmpBudgetData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - this.tmpKillData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - this.tmpFrienlyFireData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - this.tmpTransportData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - this.tmpReviveData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - this.tmpStabilizeData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - this.tmpFlagCaptureData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); - - [this.tmpKillData, this.tmpFrienlyFireData, this.tmpReviveData, this.tmpStabilizeData, this.tmpTransportData].forEach(tmp => { - [0, 1].forEach(index => { - tmp[index].series.push(ChartUtils.getSeriesEntry(this.startDateObj, 0)); - }) - }); - this.tmpBudgetData[0].series.push(ChartUtils.getSeriesEntry(this.startDateObj, this.war.budgetBlufor)); - this.tmpBudgetData[1].series.push(ChartUtils.getSeriesEntry(this.startDateObj, this.war.budgetOpfor)); - } - - addFinalTimeData(tmpCollection) { - const endDate = new Date(this.war.endDate); - if (tmpCollection === this.tmpBudgetData) { - this.tmpBudgetData[0].series.push(ChartUtils.getSeriesEntry(endDate, this.war.endBudgetBlufor)); - this.tmpBudgetData[1].series.push(ChartUtils.getSeriesEntry(endDate, this.war.endBudgetOpfor)); - } else { - for (let j in [0, 1]) { - if (tmpCollection[j].series[tmpCollection[j].series.length - 1].name < endDate) { - tmpCollection[j].series.push(ChartUtils.getSeriesEntry(endDate, tmpCollection[j].series[tmpCollection[j].series.length - 1].value)); - } - } - } + this.fractionFilterSelected = fraction; } } diff --git a/static/src/app/statistic/war-list/war-list.component.ts b/static/src/app/statistic/war-list/war-list.component.ts index f109160..4f6eb9a 100644 --- a/static/src/app/statistic/war-list/war-list.component.ts +++ b/static/src/app/statistic/war-list/war-list.component.ts @@ -82,7 +82,6 @@ export class WarListComponent implements OnInit { if (confirm('Soll die Kampagne ' + campaign.title + ' wirklich gelöscht werden?')) { this.campaignService.deleteCampaign(campaign._id) .subscribe((res) => { - console.log(res) if (this.selectedWarId === campaign._id) { this.selectOverview('all'); } diff --git a/static/src/app/users/user-list/user-list.component.html b/static/src/app/users/user-list/user-list.component.html index 952dd50..0d664f9 100644 --- a/static/src/app/users/user-list/user-list.component.html +++ b/static/src/app/users/user-list/user-list.component.html @@ -31,7 +31,7 @@ [user]="user" (userDelete)="deleteUser(user)" (userSelected)="selectUser($event)" - (userAward)="awardUser($event, $event)" + (userAward)="awardUser($event)" [selected]="user._id == selectedUserId">
diff --git a/static/src/main.ts b/static/src/main.ts index 50ed715..57d0769 100644 --- a/static/src/main.ts +++ b/static/src/main.ts @@ -4,29 +4,6 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppModule} from './app/app.module'; import {environment} from './environments/environment'; - -import 'rxjs/add/observable/of'; -import 'rxjs/add/operator/retryWhen'; -import 'rxjs/add/observable/fromEvent'; -import 'rxjs/add/observable/from'; -import 'rxjs/add/observable/range'; -import 'rxjs/add/observable/timer'; -import 'rxjs/add/observable/merge'; -import 'rxjs/add/observable/interval'; - -import 'rxjs/add/operator/filter'; -import 'rxjs/add/operator/debounceTime'; -import 'rxjs/add/operator/distinctUntilChanged'; -import 'rxjs/add/operator/mergeMap'; -import 'rxjs/add/operator/switchMap'; -import 'rxjs/add/operator/do'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/retry'; -import 'rxjs/add/operator/bufferCount'; -import 'rxjs/add/operator/bufferTime'; -import 'rxjs/add/operator/take'; -import 'rxjs/add/operator/delay'; - if (environment.production) { enableProdMode(); }