Merge branch 'release/1.6.7' of hardi/opt-cc into master

pull/23/head
hardi 2017-12-27 20:37:45 +01:00 committed by HardiReady
commit 3e78921372
15 changed files with 469 additions and 47 deletions

View File

@ -2,7 +2,7 @@
// modules
const express = require('express');
const logger = require('debug')('cc:wars');
const logger = require('debug')('cc:players');
// HTTP status codes by name
const codes = require('./http-codes');
@ -17,7 +17,65 @@ const WarModel = require('../models/war');
const campaignPlayer = express.Router();
// routes **********************
campaignPlayer.route('/:campaignId/:playerName')
campaignPlayer.route('/ranking/:campaignId')
.get((req, res, next) => {
WarModel.find({campaign: req.params.campaignId}, '_id', (err, wars) => {
if (err) return next(err);
const warIds = wars.map((obj) => {
return obj._id;
});
PlayerModel.find({warId: {"$in": warIds}}, (err, items) => {
if (err) return next(err);
if (!items || items.length === 0) {
const err = new Error('No players for given campaignId');
err.status = codes.notfound;
return next(err)
}
const rankingItems = [];
new Set(items.map(x => x.name)).forEach(playerName => {
const playerInstances = items.filter(p => p.name === playerName);
const resItem = {name: playerName, kill: 0, death: 0, friendlyFire: 0, revive: 0, respawn: 0, flagTouch: 0};
for (let i = 0; i < playerInstances.length; i++) {
resItem.kill += playerInstances[i].kill;
resItem.death += playerInstances[i].death;
resItem.friendlyFire += playerInstances[i].friendlyFire;
resItem.revive += playerInstances[i].revive;
resItem.respawn += playerInstances[i].respawn;
resItem.flagTouch += playerInstances[i].flagTouch;
}
resItem.fraction = playerInstances[playerInstances.length - 1].fraction;
rankingItems.push(resItem);
});
function getSortedField(fieldName) {
let num = 1;
rankingItems.sort((a, b) => b[fieldName] - a[fieldName])
const res = JSON.parse(JSON.stringify(rankingItems));
for (const entity of res) {
entity.num = num++;
}
return res;
}
res.locals.items = {
kill: getSortedField('kill'),
death: getSortedField('death'),
friendlyFire: getSortedField('friendlyFire'),
revive: getSortedField('revive'),
respawn: getSortedField('respawn'),
flagTouch: getSortedField('flagTouch')
};
next();
})
})
})
.all(
routerHandling.httpMethodNotAllowed
);
campaignPlayer.route('/single/:campaignId/:playerName')
.get((req, res, next) => {
CampaignModel.findById(req.params.campaignId, (err, campaign) => {
if (err) return next(err);
@ -31,7 +89,7 @@ campaignPlayer.route('/:campaignId/:playerName')
.exec((err, items) => {
if (err) return next(err);
if (!items || items.length === 0) {
const err = new Error('unknown player name');
const err = new Error('Unknown player name');
err.status = codes.notfound;
return next(err)
}

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "opt-cc",
"version": "1.6.3",
"version": "1.6.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "opt-cc",
"version": "1.6.6",
"version": "1.6.7",
"license": "MIT",
"author": "Florian Hartwich <hardi@noarch.de>",
"private": true,
@ -8,7 +8,7 @@
"start": "npm run deploy-static-prod && npm start --prefix ./api",
"dev": "npm run deploy-static && npm run dev --prefix ./api",
"deploy-static": "cd ./static && $(npm bin)/ng build && ln -s ../api/resource/ ../public/resource",
"deploy-static-prod": "cd ./static && $(npm bin)/ng build --prod --aot && ln -s ../api/resource/ ../public/resource",
"deploy-static:prod": "cd ./static && $(npm bin)/ng build --prod --aot && ln -s ../api/resource/ ../public/resource",
"postinstall": "npm install --prefix ./static && npm install --prefix ./api",
"mongodb": "mkdir -p mongodb-data && mongod --dbpath ./mongodb-data",
"test": "npm test --prefix ./api",

View File

@ -35,4 +35,4 @@ export const RouteConfig = {
requestPromotionPath: 'promotion',
confirmAwardPath: 'confirm-award',
confirmPromotionPath: 'confirm-promotion'
}
};

View File

@ -10,7 +10,12 @@ export class PlayerService {
}
getCampaignPlayer(campaignId: string, playerName: string) {
return this.http.get(this.config.apiPlayersPath + '/' + campaignId + '/' + playerName)
return this.http.get(this.config.apiPlayersPath + '/single/' + campaignId + '/' + playerName)
.map(res => res.json())
}
getCampaignHighscore(campaignId: string) {
return this.http.get(this.config.apiPlayersPath + '/ranking/' + campaignId)
.map(res => res.json())
}

View File

@ -0,0 +1,71 @@
h2 {
margin-left: 10%;
}
.player-name {
font-weight: bold;
}
.search-field {
width: 30%;
margin: 20px 0 0 10%;
}
ngx-datatable {
width: 345px;
margin: 3% 5% 0 5%;
height: 310px;
float: left;
border: solid #dfdfdf 1px;
border-radius: 10px 10px 2px 2px;
}
: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;
}
/* Table Scrollbar BEGIN */
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar {
width: 12px;
}
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #4b4b4b;
-webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.5);
}
/* Table Scrollbar END */

View File

@ -0,0 +1,145 @@
<div class="highscore-container fade-in">
<h2>{{title}} ⟶ Highscore</h2>
<div class="input-group search-field">
<input id="search-tasks"
placeholder="Spielername (mehrere mit '&' trennen)"
type="text" #query class="form-control"
(keyup.enter)="filterPlayers()"
[formControl]="searchTerm">
<span class="input-group-btn">
<button class="btn btn-default" type="button"
(click)="filterPlayers()">
Filter
</button>
</span>
</div>
<ngx-datatable
[rows]="players.kill"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="Kills" prop="kill"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.death"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="Tode" prop="death"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.respawn"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="Respawn" prop="respawn"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.friendlyFire"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.revive"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="Revive" prop="revive"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.flagTouch"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="Eroberung" prop="flagTouch"></ngx-datatable-column>
</ngx-datatable>
</div>

View File

@ -0,0 +1,111 @@
import {Component} from "@angular/core";
import {ActivatedRoute} from "@angular/router";
import {PlayerService} from "../../services/logs/player.service";
import {CampaignService} from "../../services/logs/campaign.service";
import {Fraction} from "../../utils/fraction.enum";
import {FormControl} from "@angular/forms";
import {Observable} from "rxjs/Observable";
@Component({
selector: 'stats-highscore',
templateUrl: './highscore.component.html',
styleUrls: ['./highscore.component.css', '../../style/list-entry.css', '../../style/overview.css'],
inputs: ['campaigns']
})
export class StatisticHighScoreComponent {
id = '';
title = '';
searchTerm = new FormControl();
players = {};
playersStored = {};
cellHeight = 40;
numberColWidth = 60;
nameColWidth = 210;
valueColWidth = 110;
emptyMessage = {emptyMessage: 'Keine Einträge'};
reorderable = false;
customClasses = {
sortAscending: 'glyphicon glyphicon-triangle-top',
sortDescending: 'glyphicon glyphicon-triangle-bottom',
};
readonly fraction = Fraction;
constructor(private route: ActivatedRoute,
private playerService: PlayerService,
private campaignService: CampaignService) {
}
ngOnInit() {
this.route.params
.map(params => params['id'])
.subscribe((id) => {
this.id = id;
if (this.campaignService.campaigns) {
this.initData();
} else {
this.campaignService.getAllCampaigns().subscribe(campaigns => {
this.initData()
})
}
});
const searchTermStream = this.searchTerm.valueChanges.debounceTime(400);
Observable.merge(searchTermStream)
.distinctUntilChanged().map(query => this.filterPlayers())
.subscribe();
}
initData() {
this.title = this.campaignService.campaigns
.filter(camp => camp._id === this.id).pop().title;
this.playerService.getCampaignHighscore(this.id).subscribe(players => {
this.players = players;
this.playersStored = players;
})
}
filterPlayers() {
if (!this.searchTerm.value || this.searchTerm.value === '') {
this.players = this.playersStored;
} else {
this.players = {
kill: this.filterPlayerAttribute('kill'),
friendlyFire: this.filterPlayerAttribute('friendlyFire'),
death: this.filterPlayerAttribute('death'),
respawn: this.filterPlayerAttribute('respawn'),
revive: this.filterPlayerAttribute('revive'),
flagTouch: this.filterPlayerAttribute('flagTouch')
}
}
}
private filterPlayerAttribute(attribute) {
const query = this.searchTerm.value.toLowerCase().split('&');
return this.playersStored[attribute].filter(player => {
for (let i = 0; i < query.length; i++) {
if (query[i].trim() != '' && player.name.toLowerCase().includes(query[i].trim())) {
return true;
}
}
return false;
})
}
}

View File

@ -1,6 +1,5 @@
import {Component} from "@angular/core";
import {ActivatedRoute} from "@angular/router";
import {CarouselConfig} from "ngx-bootstrap";
import {CampaignService} from "../../services/logs/campaign.service";
import {ChartUtils} from "../../utils/chart-utils";
import {Fraction} from "../../utils/fraction.enum";
@ -10,8 +9,7 @@ import {Fraction} from "../../utils/fraction.enum";
selector: 'stats-overview',
templateUrl: './stats-overview.component.html',
styleUrls: ['./stats-overview.component.css', '../../style/list-entry.css', '../../style/overview.css'],
inputs: ['campaigns'],
providers: [{provide: CarouselConfig, useValue: {interval: false}}]
inputs: ['campaigns']
})
export class StatisticOverviewComponent {

View File

@ -10,6 +10,7 @@ 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";
import {StatisticHighScoreComponent} from "./highscore/highscore.component";
export const statsRoutes: Routes = [{
@ -26,6 +27,11 @@ export const statsRoutes: Routes = [{
component: StatisticOverviewComponent,
outlet: 'right'
},
{
path: 'highscore/:id',
component: StatisticHighScoreComponent,
outlet: 'right'
},
{
path: 'new-campaign',
component: CampaignSubmitComponent,
@ -49,7 +55,7 @@ export const statsRoutes: Routes = [{
export const statsRouterModule: ModuleWithProviders = RouterModule.forChild(statsRoutes);
export const statsRoutingComponents = [StatisticComponent, StatisticOverviewComponent, CampaignSubmitComponent,
WarListComponent, WarSubmitComponent, WarDetailComponent, ScoreboardComponent, FractionStatsComponent,
CampaignPlayerDetailComponent, WarItemComponent];
export const statsRoutingComponents = [StatisticComponent, StatisticOverviewComponent, StatisticHighScoreComponent,
CampaignSubmitComponent, WarListComponent, WarSubmitComponent, WarDetailComponent, ScoreboardComponent,
FractionStatsComponent, CampaignPlayerDetailComponent, WarItemComponent];

View File

@ -42,6 +42,24 @@ ngx-datatable {
background-color: #f7f7f7;
}
/* Table Scrollbar BEGIN */
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar {
width: 12px;
}
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #4b4b4b;
-webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.5);
}
/* Table Scrollbar END */
.in-table-btn {
position: absolute;
margin-top: -5px;

View File

@ -39,18 +39,3 @@
.nav-tabs > li.deactivated > a.nav-link {
cursor: not-allowed !important;
}
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar {
width: 12px;
}
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
border-radius: 10px;
}
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #4b4b4b;
-webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.5);
}

View File

@ -5,3 +5,10 @@
color: white;
font-weight: 600;
}
.top-list-entry {
margin-top: -16px;
margin-bottom: 10px;
width: 50%;
float: left;
}

View File

@ -20,24 +20,33 @@
</span>
</div>
<div class="fade-in list-entry" style="margin-top: -16px; margin-bottom: 10px;"
<div class="fade-in list-entry top-list-entry"
[ngClass]="{selected : selectedWarId == campaign._id}" (click)="selectOverview(campaign._id)">
<div class="row">
<div class="col-xs-9">
<span style="margin:auto">
<a style="font-size: 22px;">Kampagnenübersicht</a>
</span>
</div>
<div style="margin: auto;">
<span style="margin:auto">
<a style="font-size: 22px;">Übersicht</a>
</span>
</div>
</div>
<div *ngFor="let war of campaign.wars">
<pjm-war-item
[war]="war"
(warDelete)="deleteWar(war)"
(warSelected)="selectWar($event)"
[selected]="war._id == selectedWarId">
</pjm-war-item>
<div class="fade-in list-entry top-list-entry"
[ngClass]="{selected : selectedWarId == campaign._id + highscore}" (click)="selectHighscore(campaign._id)">
<div style="margin: auto">
<span style="margin:auto">
<a style="font-size: 22px;">Highscore</a>
</span>
</div>
</div>
<div style="padding-top: 40px;">
<div *ngFor="let war of campaign.wars">
<pjm-war-item
[war]="war"
(warDelete)="deleteWar(war)"
(warSelected)="selectWar($event)"
[selected]="war._id == selectedWarId">
</pjm-war-item>
</div>
</div>
</accordion-group>
</accordion>

View File

@ -17,6 +17,8 @@ export class WarListComponent implements OnInit {
campaigns: Campaign[] = [];
public readonly highscore = 'HIGHSCORE';
constructor(private warService: WarService,
private campaignService: CampaignService,
public loginService: LoginService,
@ -59,10 +61,17 @@ export class WarListComponent implements OnInit {
}
}
selectOverview(overviewId) {
if (this.selectedWarId != overviewId) {
this.selectedWarId = overviewId;
this.router.navigate([{outlets: {'right': ['overview', overviewId]}}], {relativeTo: this.route});
selectOverview(campaignId) {
if (this.selectedWarId != campaignId) {
this.selectedWarId = campaignId;
this.router.navigate([{outlets: {'right': ['overview', campaignId]}}], {relativeTo: this.route});
}
}
selectHighscore(campaignId) {
if (this.selectedWarId != campaignId + this.highscore) {
this.selectedWarId = campaignId + this.highscore;
this.router.navigate([{outlets: {'right': ['highscore', campaignId]}}], {relativeTo: this.route});
}
}