Merge branch 'release/v1.7.4' of hardi/opt-cc into master

pull/35/head
hardi 2018-05-05 10:01:04 +02:00 committed by Gogs
commit 639d7d0aa8
69 changed files with 805 additions and 915 deletions

View File

@ -1,2 +1,3 @@
{"_id":{"$oid":"5abd55ea9e30a76bfef747d6"},"title":"Ein Kessel Buntes","timestamp":{"$date":"2018-03-29T21:08:58.123Z"},"updatedAt":{"$date":"2018-03-29T21:08:58.123Z"},"__v":0} {"_id":{"$oid":"5abd55ea9e30a76bfef747d6"},"title":"Ein Kessel Buntes","timestamp":{"$date":"2018-03-29T21:08:58.123Z"},"updatedAt":{"$date":"2018-03-29T21:08:58.123Z"},"__v":0}
{"_id":{"$oid":"5abd58989e30a76bfef747e6"},"title":"This Is The End","timestamp":{"$date":"2018-03-29T21:20:24.558Z"},"updatedAt":{"$date":"2018-03-29T21:20:24.558Z"},"__v":0} {"_id":{"$oid":"5abd58989e30a76bfef747e6"},"title":"This Is The End","timestamp":{"$date":"2018-03-29T21:20:24.558Z"},"updatedAt":{"$date":"2018-03-29T21:20:24.558Z"},"__v":0}
{"_id":{"$oid":"5abd55ea9e32a76afef777d6"},"title":"Placeholder","timestamp":{"$date":"2018-03-30T21:08:58.123Z"},"updatedAt":{"$date":"2018-03-30T21:08:58.123Z"},"__v":0}

View File

@ -1,2 +1,3 @@
{"_id":{"$oid":"5abf65d83fc5fa349ffd5cbb"},"playersBlufor":4,"playersOpfor":4,"budgetBlufor":4535500,"budgetOpfor":4221250,"endBudgetBlufor":997000,"endBudgetOpfor":512000,"title":"Battle #1","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-19T23:00:00.000Z"},"endDate":{"$date":"2018-03-20T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-31T10:41:28.451Z"},"updatedAt":{"$date":"2018-03-31T10:41:28.451Z"},"__v":0} {"_id":{"$oid":"5abf65d83fc5fa349ffd5cbb"},"playersBlufor":4,"playersOpfor":4,"budgetBlufor":4535500,"budgetOpfor":4221250,"endBudgetBlufor":997000,"endBudgetOpfor":512000,"title":"Battle #1","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-19T23:00:00.000Z"},"endDate":{"$date":"2018-03-20T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-31T10:41:28.451Z"},"updatedAt":{"$date":"2018-03-31T10:41:28.451Z"},"__v":0}
{"_id":{"$oid":"5abf65ae3fc5fa349ffd5ca3"},"playersBlufor":4,"playersOpfor":4,"budgetBlufor":4535500,"budgetOpfor":4221250,"endBudgetBlufor":997000,"endBudgetOpfor":512000,"title":"Test Battle #0","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-19T23:00:00.000Z"},"endDate":{"$date":"2018-03-20T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-31T10:40:46.695Z"},"updatedAt":{"$date":"2018-03-31T10:40:46.695Z"},"__v":0} {"_id":{"$oid":"5abf65ae3fc5fa349ffd5ca3"},"playersBlufor":4,"playersOpfor":4,"budgetBlufor":4535500,"budgetOpfor":4221250,"endBudgetBlufor":997000,"endBudgetOpfor":512000,"title":"Test Battle #0","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-19T23:00:00.000Z"},"endDate":{"$date":"2018-03-20T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-31T10:40:46.695Z"},"updatedAt":{"$date":"2018-03-31T10:40:46.695Z"},"__v":0}
{"_id":{"$oid":"5abf65ae2df5fa349ffd5ca3"},"playersBlufor":20,"playersOpfor":20,"budgetBlufor":4535500,"budgetOpfor":4535500,"endBudgetBlufor":0,"endBudgetOpfor":20000,"title":"Test Battle XY","campaign":{"$oid":"5abd55ea9e30a76bfef747d6"},"date":{"$date":"2018-03-02T23:00:00.000Z"},"endDate":{"$date":"2018-03-02T01:30:00.000Z"},"ptBlufor":34,"ptOpfor":25,"timestamp":{"$date":"2018-03-02T10:40:46.695Z"},"updatedAt":{"$date":"2018-03-02T10:40:46.695Z"},"__v":0}

View File

@ -4,7 +4,7 @@
###-------------------------------------------------------------### ###-------------------------------------------------------------###
###------------------- HOW TO USE THIS FILE --------------------### ###------------------- HOW TO USE THIS FILE --------------------###
###-------------------------------------------------------------### ###-------------------------------------------------------------###
### 1. start express server with `npm run start-test` ### ### 1. start express server with `npm run start-api-test` ###
### 2. import data by executing script: `./populate-data.sh` ### ### 2. import data by executing script: `./populate-data.sh` ###
### 3. change data in app as you need for tests ### ### 3. change data in app as you need for tests ###
### 4. export data state with: `./populate-data.sh -m save` ### ### 4. export data state with: `./populate-data.sh -m save` ###

View File

@ -24,6 +24,25 @@ Create a new campaign
+ Attributes (Campaign, fixed-type) + Attributes (Campaign, fixed-type)
### Update Campaign [PATCH /campaigns/{id}]
Update a campaign, identified by its id
**Permission: 3**
+ Parameters
+ id: `5abd55ea9e32a76afef777d6` (string, required) - unique id of campaign
+ Request (application/json)
+ Attributes
+ _id: `5abd55ea9e32a76afef777d6` (string, required) - unique id of campaign
+ title: `Operation Pandora` (string, optional) - display name of the campaign
+ Response 200 (application/json; charset=utf-8)
+ Attributes (Campaign, fixed-type)
### Delete Campaign [DELETE /campaigns/{id}] ### Delete Campaign [DELETE /campaigns/{id}]
Delete a campaign Delete a campaign

View File

@ -76,6 +76,35 @@ Create a new war
+ Attributes (War, fixed-type) + Attributes (War, fixed-type)
### Update War [PATCH /wars/{id}]
Update a war, identified by its id
**Permission: 3**
+ Parameters
+ id: `5abf65ae2df5fa349ffd5ca3` (string, required) - unique id of campaign
+ Request (application/json)
+ Attributes
+ _id: `5abf65ae2df5fa349ffd5ca3` (string, required) - unique id of war
+ title: `Final Touchdown` (string, optional) - display name of the war
+ date: `2017-05-11T20:00:00.471Z` (string, optional) - starting date
+ endDate: `2017-05-12T00:30:32.471Z` (string, optional) - end date
+ ptBlufor: 5 (number, optional) - points Blufor
+ ptOpfor: 5 (number, optional) - points Opfor
+ playersBlufor: 20 (number, optional) - number of players Blufor
+ playersOpfor: 20 (number, optional) - number of players Opfor
+ budgetBlufor: 3000000 (number, optional) - start budget Blufor
+ budgetOpfor: 3000000 (number, optional) - start budget Opfor
+ endBudgetBlufor: 10000 (number, optional) - end budget Blufor
+ endBudgetOpfor: 12000 (number, optional) - end budget Opfor
+ Response 200 (application/json; charset=utf-8)
+ Attributes (War, fixed-type)
### Delete War [DELETE /wars/{id}] ### Delete War [DELETE /wars/{id}]
Delete a war Delete a war

53
api/routes/_generic.js Normal file
View File

@ -0,0 +1,53 @@
'use strict';
// HTTP status codes by name
const codes = require('./http-codes');
const genericGetById = (req, res, next, modelClass) => {
modelClass.findById(req.params.id, (err, item) => {
if (err) {
err.status = codes.servererror;
return next(err);
} else if (!item) {
err = new Error('item not found');
err.status = codes.notfound;
return next(err);
}
res.locals.items = item;
return next();
});
};
const genericPatch = (req, res, next, modelClass) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) {
// little bit different as in PUT. :id does not need to be in data, but if the _id and url id must match
const err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + ' ' +
req.body._id);
err.status = codes.notfound;
next(err);
return; // prevent node to process this function further after next() has finished.
}
req.body.updatedAt = new Date();
req.body.$inc = {__v: 1};
if (req.body.hasOwnProperty('__v')) {
delete req.body.__v;
}
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
// reset attributes that are missing.
modelClass.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) {
err.status = codes.wrongrequest;
} else if (!item) {
err = new Error('item not found');
err.status = codes.notfound;
} else {
res.locals.items = item;
}
next(err);
});
};
exports.genericGetById = genericGetById;
exports.genericPatch = genericPatch;

View File

@ -16,12 +16,14 @@ const idValidator = require('../middleware/validators').idValidator;
const CampaignModel = require('../models/campaign'); const CampaignModel = require('../models/campaign');
const WarModel = require('../models/war'); const WarModel = require('../models/war');
// util
const genericGetById = require('./_generic').genericGetById;
const genericPatch = require('./_generic').genericPatch;
const campaigns = new express.Router(); const campaigns = new express.Router();
// routes ********************** // routes **********************
campaigns.route('/') campaigns.route('/')
.post(apiAuthenticationMiddleware, checkMT, (req, res, next) => { .post(apiAuthenticationMiddleware, checkMT, (req, res, next) => {
const campaign = new CampaignModel(req.body); const campaign = new CampaignModel(req.body);
// timestamp and default are set automatically by Mongoose Schema Validation // timestamp and default are set automatically by Mongoose Schema Validation
@ -43,18 +45,11 @@ campaigns.route('/')
campaigns.route('/:id') campaigns.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
CampaignModel.findById(req.params.id, (err, item) => { return genericGetById(req, res, next, CampaignModel);
if (err) { })
err.status = codes.servererror;
return next(err); .patch(apiAuthenticationMiddleware, checkMT, (req, res, next) => {
} else if (!item) { return genericPatch(req, res, next, CampaignModel);
err = new Error('item not found');
err.status = codes.notfound;
return next(err);
}
res.locals.items = item;
return next();
});
}) })
.delete((req, res, next) => { .delete((req, res, next) => {

View File

@ -21,6 +21,9 @@ const resourceLocation = require('../middleware/resource-location').resourceLoca
const DecorationModel = require('../models/decoration'); const DecorationModel = require('../models/decoration');
const AwardingsModel = require('../models/awarding'); const AwardingsModel = require('../models/awarding');
// util
const genericGetById = require('./_generic').genericGetById;
const decoration = new express.Router(); const decoration = new express.Router();
// routes ********************** // routes **********************
@ -81,18 +84,7 @@ decoration.route('/')
decoration.route('/:id') decoration.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
DecorationModel.findById(req.params.id, (err, item) => { return genericGetById(req, res, next, DecorationModel);
if (err) {
err.status = codes.servererror;
return next(err);
} else if (!item) {
err = new Error('item not found');
err.status = codes.notfound;
return next(err);
}
res.locals.items = item;
next();
});
}) })
.patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {

View File

@ -20,6 +20,9 @@ const resourceLocation = require('../middleware/resource-location').resourceLoca
// Mongoose Model using mongoDB // Mongoose Model using mongoDB
const RankModel = require('../models/rank'); const RankModel = require('../models/rank');
// util
const genericGetById = require('./_generic').genericGetById;
const ranks = new express.Router(); const ranks = new express.Router();
// routes ********************** // routes **********************
@ -73,18 +76,7 @@ ranks.route('/')
ranks.route('/:id') ranks.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
RankModel.findById(req.params.id, (err, item) => { return genericGetById(req, res, next, RankModel);
if (err) {
err.status = codes.servererror;
return next(err);
} else if (!item) {
err = new Error('item not found');
err.status = codes.notfound;
return next(err);
}
res.locals.items = item;
next();
});
}) })
.patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {

View File

@ -20,6 +20,9 @@ const resourceLocation = require('../middleware/resource-location').resourceLoca
// Mongoose Model using mongoDB // Mongoose Model using mongoDB
const SquadModel = require('../models/squad'); const SquadModel = require('../models/squad');
// util
const genericGetById = require('./_generic').genericGetById;
const squads = new express.Router(); const squads = new express.Router();
// routes ********************** // routes **********************
@ -77,18 +80,7 @@ squads.route('/')
squads.route('/:id') squads.route('/:id')
.get(idValidator, (req, res, next) => { .get(idValidator, (req, res, next) => {
SquadModel.findById(req.params.id, (err, item) => { return genericGetById(req, res, next, SquadModel);
if (err) {
err.status = codes.servererror;
return next(err);
} else if (!item) {
err = new Error('item not found');
err.status = codes.notfound;
return next(err);
}
res.locals.items = item;
next();
});
}) })
.patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => {

View File

@ -35,6 +35,9 @@ const LogFlagModel = require('../models/logs/flag');
const LogBudgetModel = require('../models/logs/budget'); const LogBudgetModel = require('../models/logs/budget');
const LogPointsModel = require('../models/logs/points'); const LogPointsModel = require('../models/logs/points');
// util
const genericPatch = require('./_generic').genericPatch;
const wars = new express.Router(); const wars = new express.Router();
// routes ********************** // routes **********************
@ -168,6 +171,10 @@ wars.route('/:id')
}); });
}) })
.patch(apiAuthenticationMiddleware, checkMT, (req, res, next) => {
return genericPatch(req, res, next, WarModel);
})
.delete(apiAuthenticationMiddleware, checkMT, (req, res, next) => { .delete(apiAuthenticationMiddleware, checkMT, (req, res, next) => {
WarModel.findByIdAndRemove(req.params.id, (err, item) => { WarModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) { if (err) {

View File

@ -106,6 +106,12 @@ const parseWarLog = (lineArray, war) => {
stats.war['endBudgetBlufor'] = transformMoneyString(budg[9]); stats.war['endBudgetBlufor'] = transformMoneyString(budg[9]);
stats.war['endBudgetOpfor'] = transformMoneyString(budg[12].slice(0, -2)); stats.war['endBudgetOpfor'] = transformMoneyString(budg[12].slice(0, -2));
stats.war.endDate = getFullTimeDate(war.date, budg[5]); stats.war.endDate = getFullTimeDate(war.date, budg[5]);
} else if (line.includes('Respawn von ')) {
/**
* RESPAWN
*/
const playerName = line.substring(line.lastIndexOf('Respawn von ') + 12, line.lastIndexOf('"'));
stats.respawn.push(getRespawnEntry(budg, playerName, war._id, war.date));
} else { } else {
stats.budget.push(getBudgetEntry(budg, war._id, war.date)); stats.budget.push(getBudgetEntry(budg, war._id, war.date));
} }
@ -140,13 +146,6 @@ const parseWarLog = (lineArray, war) => {
} else { } else {
stats.points.push(getPointsEntry(pt, line, war._id, war.date)); stats.points.push(getPointsEntry(pt, line, war._id, war.date));
} }
} else if (line.includes('(Respawn)')) {
/**
* RESPAWN
*/
const resp = line.split(WHITESPACE);
const playerName = line.substring(line.lastIndexOf('Spieler:') + 9, line.lastIndexOf('- Kosten') - 1);
stats.respawn.push(getRespawnEntry(resp, playerName, war._id, war.date));
} else if (line.includes('(Revive)')) { } else if (line.includes('(Revive)')) {
/** /**
* REVIVE * REVIVE

View File

@ -137,48 +137,7 @@ let addDecorationsAndSave = (userId, loadedImage, res, next) => {
return next(err); return next(err);
} }
if (awardings.length > 0) { if (awardings.length > 0) {
// TODO: simplify this sorting hell awardings.sort((a1, a2) => sortAwardingsForSignature(a1, a2));
awardings.sort((a1, a2) => {
if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) {
if (a1.decorationId.fraction === a2.decorationId.fraction) {
if (a1.date !== a2.date) {
if (a1.date < a2.date) {
return -1;
}
if (a1.date > a2.date) {
return 1;
}
}
return 0;
} else {
if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') {
return -1;
}
if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') {
return 1;
}
}
}
if (a1.decorationId.isMedal !== a2.decorationId.isMedal) {
if (a1.decorationId.isMedal && !a2.decorationId.isMedal) {
return 1;
}
if (!a1.decorationId.isMedal && a2.decorationId.isMedal) {
return -1;
}
}
if (a1.decorationId.isMedal && a2.decorationId.isMedal) {
if (a1.date !== a2.date) {
if (a1.date < a2.date) {
return -1;
}
if (a1.date > a2.date) {
return 1;
}
}
return 0;
}
});
// use synchronized call to keep correct order of decorations // use synchronized call to keep correct order of decorations
async.eachSeries(awardings, (award, callback) => { async.eachSeries(awardings, (award, callback) => {
@ -262,5 +221,47 @@ let saveJimpImageAndCompress = (image, userId, res, next) => {
}, 3000); }, 3000);
}; };
const sortAwardingsForSignature = (a1, a2) => {
if (!a1.decorationId.isMedal && !a2.decorationId.isMedal) {
if (a1.decorationId.fraction === a2.decorationId.fraction) {
if (a1.date !== a2.date) {
if (a1.date < a2.date) {
return -1;
}
if (a1.date > a2.date) {
return 1;
}
}
return 0;
} else {
if (a1.decorationId.fraction === 'GLOBAL' && a2.decorationId.fraction !== 'GLOBAL') {
return -1;
}
if (a2.decorationId.fraction === 'GLOBAL' && a1.decorationId.fraction !== 'GLOBAL') {
return 1;
}
}
}
if (a1.decorationId.isMedal !== a2.decorationId.isMedal) {
if (a1.decorationId.isMedal && !a2.decorationId.isMedal) {
return 1;
}
if (!a1.decorationId.isMedal && a2.decorationId.isMedal) {
return -1;
}
}
if (a1.decorationId.isMedal && a2.decorationId.isMedal) {
if (a1.date !== a2.date) {
if (a1.date < a2.date) {
return -1;
}
if (a1.date > a2.date) {
return 1;
}
}
return 0;
}
};
module.exports = createSignature; module.exports = createSignature;

View File

@ -39,7 +39,6 @@ const timeStringToDecimal = (timeString) => {
return hour + (sek / 3600); return hour + (sek / 3600);
}; };
const decimalToTimeString = (decimal) => { const decimalToTimeString = (decimal) => {
const hours = parseInt(decimal.toString().split('.')[0]); const hours = parseInt(decimal.toString().split('.')[0]);
const minutesFloat = ((decimal % 1) * 3600) / 60; const minutesFloat = ((decimal % 1) * 3600) / 60;

View File

@ -10,6 +10,7 @@
<option name="RIGHT_MARGIN" value="120" /> <option name="RIGHT_MARGIN" value="120" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" /> <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" /> <option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="SPACE_WITHIN_BRACKETS" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" /> <option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" /> <option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" /> <option name="EXTENDS_KEYWORD_WRAP" value="1" />

View File

@ -1,6 +1,6 @@
{ {
"name": "opt-cc", "name": "opt-cc",
"version": "1.7.3", "version": "1.7.4",
"author": "Florian Hartwich <hardi@noarch.de>", "author": "Florian Hartwich <hardi@noarch.de>",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@ -1,7 +1,6 @@
import {RouterModule, Routes} from '@angular/router'; import {RouterModule, Routes} from '@angular/router';
import {LoginComponent} from './login/index'; import {NotFoundComponent} from './common/not-found';
import {NotFoundComponent} from './common/not-found/not-found.component'; import {LoginComponent, LoginGuardAdmin, LoginGuardHL, LoginGuardMT} from './login';
import {LoginGuardAdmin, LoginGuardHL} from './login/login.guard';
import {armyRoutes, armyRoutingComponents} from './army/army.routing'; import {armyRoutes, armyRoutingComponents} from './army/army.routing';
import {SignupComponent} from './login/signup.component'; import {SignupComponent} from './login/signup.component';
import {RouteConfig} from './app.config'; import {RouteConfig} from './app.config';
@ -72,4 +71,4 @@ export const appRouting = RouterModule.forRoot(appRoutes);
export const routingComponents = [...armyRoutingComponents, LoginComponent, SignupComponent, NotFoundComponent]; export const routingComponents = [...armyRoutingComponents, LoginComponent, SignupComponent, NotFoundComponent];
export const routingProviders = [LoginGuardHL, LoginGuardAdmin]; export const routingProviders = [LoginGuardHL, LoginGuardMT, LoginGuardAdmin];

View File

@ -5,7 +5,7 @@
<p>Dieses Formular nur ausfüllen wenn du einer <b>HL</b> angehörst oder <b>SQL</b> bist. Dabei den Nutzernamen aus <p>Dieses Formular nur ausfüllen wenn du einer <b>HL</b> angehörst oder <b>SQL</b> bist. Dabei den Nutzernamen aus
dem OPT Forum verwenden! dem OPT Forum verwenden!
Im Forum eine Nachricht an <a href="https://opt-dev.de/dashboard/index.php?conversation-add/&userID=9" Im Forum eine Nachricht an <a href="https://operation-pandora.com/dashboard/index.php?conversation-add/&userID=9"
target="_blank">HardiReady</a> target="_blank">HardiReady</a>
senden, in welcher der 'geheime Text' steht, den du bei der Registrierung nutzt.<br> senden, in welcher der 'geheime Text' steht, den du bei der Registrierung nutzt.<br>
Dabei kann es sich um irgend eine willkürliche Zeichenfolge oder einen Satz handeln - dient nur dem Abgleich. Dabei kann es sich um irgend eine willkürliche Zeichenfolge oder einen Satz handeln - dient nur dem Abgleich.

View File

@ -18,8 +18,13 @@ export class CampaignService {
} }
submitCampaign(campaign: Campaign) { submitCampaign(campaign: Campaign) {
return this.http.post(this.config.apiCampaignPath, campaign) if (campaign._id) {
.map(res => res.json()); return this.http.patch(this.config.apiCampaignPath + '/' + campaign._id, campaign)
.map(res => res.json());
} else {
return this.http.post(this.config.apiCampaignPath, campaign)
.map(res => res.json());
}
} }
deleteCampaign(id: string) { deleteCampaign(id: string) {
@ -27,5 +32,9 @@ export class CampaignService {
.map(res => res.json()); .map(res => res.json());
} }
getCampaign(id: string) {
return this.http.get(this.config.apiCampaignPath + '/' + id)
.map(res => res.json());
}
} }

View File

@ -38,5 +38,9 @@ export class WarService {
.map(res => res.json()); .map(res => res.json());
} }
updateWar(war: War) {
return this.http.patch(this.config.apiWarPath + '/' + war._id, war)
.map(res => res.json());
}
} }

View File

@ -1,224 +0,0 @@
<div class="fade-in player-campaign-detail-container" xmlns="http://www.w3.org/1999/html">
<h2 class="pull-left">Kampagnendetails - {{campaignPlayer.name}}</h2>
<h2 class="pull-right">{{campaignPlayer.campaign.title}} Kampagne</h2>
<span class="btn btn-default btn-back" (click)="navigateBack()">< Zurück</span>
<div class="sum-container">
<div class="gauge-container pull-left">
<ngx-charts-linear-gauge
[view]="[200, 100]"
[scheme]="colorScheme"
[value]="kdRatio"
[previousValue]="1"
[max]="maxKd"
[min]="0"
[units]="'Kill/Death'">
</ngx-charts-linear-gauge>
<span style="height: 150px; display: block;"></span>
<ngx-charts-linear-gauge
[view]="[200, 100]"
[scheme]="colorScheme"
[value]="respawnDeathRatio"
[previousValue]="0.5"
[max]="maxRespawnDeathRatio"
[min]="0"
[units]="'Respawn/Death'">
</ngx-charts-linear-gauge>
</div>
<div class="sum-bar-container pull-left">
<ngx-charts-bar-horizontal
[scheme]="colorSchemeBar"
[results]="sumData"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel">
</ngx-charts-bar-horizontal>
</div>
</div>
<div class="charts-parent">
<div class="chart-container">
<ngx-charts-line-chart
[results]="killData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="killRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisKill"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="friendlyFireData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="friendlyFireRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisFriendlyFire"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="deathData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="deathRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisDeath"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="respawnData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="respawnRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisRespawn"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="reviveData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="reviveRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisRevive"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="captureData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="captureRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisCapture"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="vehicleLightData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="killRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisVehicleLight"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="vehicleHeavyData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="killRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisVehicleHeavy"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="vehicleAirData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="killRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisVehicleAir"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
</div>
</div>

View File

@ -1,223 +0,0 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {CampaignPlayer} from '../../models/model-interfaces';
import {PlayerService} from '../../services/logs/player.service';
import {ChartUtils} from '../../utils/chart-utils';
@Component({
selector: 'campaign-player-detail',
templateUrl: './campaign-player-detail.component.html',
styleUrls: ['./campaign-player-detail.component.css', '../../style/list-entry.css',
'../../style/hide-scrollbar.css', '../../style/overview.css']
})
export class CampaignPlayerDetailComponent implements OnInit {
@Input() campaignId: string;
@Input() playerName: string;
@Output() switchTab = new EventEmitter();
campaignPlayer: CampaignPlayer = {campaign: {}, players: []};
sumData: any[] = [];
killData: any[] = [];
friendlyFireData: any[] = [];
vehicleLightData: any[] = [];
vehicleHeavyData: any[] = [];
vehicleAirData: any[] = [];
deathData: any[] = [];
respawnData: any[] = [];
reviveData: any[] = [];
captureData: any[] = [];
yAxisKill = 'Kills';
yAxisFriendlyFire = 'FriendlyFire';
yAxisVehicleLight = 'Fahrzeug (Light)';
yAxisVehicleHeavy = 'Fahrzeug (Heavy)';
yAxisVehicleAir = 'Fahrzeug (Air)';
yAxisDeath = 'Tode';
yAxisRespawn = 'Respawn';
yAxisRevive = 'Revive';
yAxisCapture = 'Eroberungen';
avgLabel = 'Durchschnitt';
colorScheme = {
domain: ['#00ce12']
};
colorSchemeBar = {
domain: [
'#2d5a00', '#455600', '#00561f', '#3f3b00', '#003c19', '#083c00'
]
};
showRefLines = true;
showRefLabels = true;
killRefLines = [];
vehicleLightRefLines = [];
vehicleHeavyRefLines = [];
vehicleAirRefLines = [];
deathRefLines = [];
captureRefLines = [];
friendlyFireRefLines = [];
reviveRefLines = [];
respawnRefLines = [];
gradient = false;
xAxis = true;
yAxis = true;
legend = false;
showXAxisLabel = true;
showYAxisLabel = true;
autoscale = false;
timeline = false;
roundDomains = true;
totalKills;
totalFriendlyFire;
totalVehicleLight;
totalVehicleHeavy;
totalVehicleAir;
totalDeath;
totalRespawn;
totalRevive;
totalCapture;
kdRatio = 0;
maxKd = 1.7;
respawnDeathRatio = 0;
maxRespawnDeathRatio = 1;
constructor(private playerService: PlayerService) {
}
ngOnInit() {
this.playerService.getCampaignPlayer(this.campaignId, encodeURIComponent(this.playerName))
.subscribe(campaignPlayer => {
this.campaignPlayer = campaignPlayer;
this.killData = this.assignData(this.yAxisKill, 'kill');
this.friendlyFireData = this.assignData(this.yAxisFriendlyFire, 'friendlyFire');
this.deathData = this.assignData(this.yAxisDeath, 'death');
this.respawnData = this.assignData(this.yAxisRespawn, 'respawn');
this.reviveData = this.assignData(this.yAxisRevive, 'revive');
this.captureData = this.assignData(this.yAxisCapture, 'flagTouch');
this.vehicleLightData = this.assignData(this.yAxisVehicleLight, 'vehicleLight');
this.vehicleHeavyData = this.assignData(this.yAxisVehicleHeavy, 'vehicleHeavy');
this.vehicleAirData = this.assignData(this.yAxisVehicleAir, 'vehicleAir');
const totalDeathDiv = this.totalDeath === 0 ? 1 : this.totalDeath;
this.kdRatio = parseFloat((this.totalKills / totalDeathDiv).toFixed(2));
if (this.kdRatio > 1) {
this.maxKd = this.kdRatio * 1.7;
}
this.respawnDeathRatio = parseFloat((this.totalRespawn / totalDeathDiv).toFixed(2));
this.sumData = [
{
name: this.yAxisKill,
value: this.totalKills
},
{
name: this.yAxisFriendlyFire,
value: this.totalFriendlyFire
},
{
name: this.yAxisDeath,
value: this.totalDeath
},
{
name: this.yAxisRespawn,
value: this.totalRespawn
},
{
name: this.yAxisRevive,
value: this.totalRevive
},
{
name: this.yAxisCapture,
value: this.totalCapture
},
{
name: this.yAxisVehicleLight,
value: this.totalVehicleLight
},
{
name: this.yAxisVehicleHeavy,
value: this.totalVehicleHeavy
},
{
name: this.yAxisVehicleAir,
value: this.totalVehicleAir
},
];
Object.assign(this, [this.sumData, this.killData, this.friendlyFireData, this.vehicleLightData,
this.vehicleHeavyData, this.vehicleAirData, this.deathData, this.respawnData, this.reviveData,
this.captureData]);
});
}
private assignData(label, field) {
const killObj = {
name: label,
series: []
};
const playerLength = this.campaignPlayer.players.length;
let total = 0;
for (let i = 0; i < playerLength; i++) {
const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date);
const value = this.campaignPlayer.players[i][field];
killObj.series.push({
name: warDateString,
value: value
});
total += value;
}
switch (field) {
case 'kill':
this.killRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalKills = total;
break;
case 'friendlyFire':
this.friendlyFireRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalFriendlyFire = total;
break;
case 'death':
this.deathRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalDeath = total;
break;
case 'respawn':
this.respawnRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalRespawn = total;
break;
case 'revive':
this.reviveRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalRevive = total;
break;
case 'flagTouch':
this.captureRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalCapture = total;
break;
case 'vehicleLight':
this.vehicleLightRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalVehicleLight = total;
break;
case 'vehicleHeavy':
this.vehicleHeavyRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalVehicleHeavy = total;
break;
case 'vehicleAir':
this.vehicleAirRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalVehicleAir = total;
break;
}
return [killObj];
}
navigateBack() {
this.switchTab.emit(0);
}
}

View File

@ -0,0 +1,64 @@
<div class="fade-in player-campaign-detail-container" xmlns="http://www.w3.org/1999/html">
<h2 class="pull-left">Kampagnendetails - {{campaignPlayer.name}}</h2>
<h2 class="pull-right">{{campaignPlayer.campaign.title}} Kampagne</h2>
<span class="btn btn-default btn-back" (click)="navigateBack()">< Zurück</span>
<div class="sum-container">
<div class="gauge-container pull-left">
<ngx-charts-linear-gauge
[view]="[200, 100]"
[scheme]="colorScheme"
[value]="kdRatio"
[previousValue]="1"
[max]="maxKd"
[min]="0"
[units]="'Kill/Death'">
</ngx-charts-linear-gauge>
<span style="height: 150px; display: block;"></span>
<ngx-charts-linear-gauge
[view]="[200, 100]"
[scheme]="colorScheme"
[value]="respawnDeathRatio"
[previousValue]="0.5"
[max]="maxRespawnDeathRatio"
[min]="0"
[units]="'Respawn/Death'">
</ngx-charts-linear-gauge>
</div>
<div class="sum-bar-container pull-left">
<ngx-charts-bar-horizontal
[scheme]="colorSchemeBar"
[results]="sumData"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel">
</ngx-charts-bar-horizontal>
</div>
</div>
<div class="charts-parent">
<div class="chart-container" *ngFor="let category of graphData">
<ngx-charts-line-chart
[results]="category.data"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="category.refLine"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="category.label"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
</div>
</div>

View File

@ -0,0 +1,117 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {CampaignPlayer} from '../../../models/model-interfaces';
import {PlayerService} from '../../../services/logs/player.service';
import {ChartUtils} from '../../../utils/chart-utils';
import {PlayerUtils} from '../../../utils/player-utils';
@Component({
selector: 'campaign-player-detail',
templateUrl: './campaign-player-detail.component.html',
styleUrls: ['./campaign-player-detail.component.css', '../../../style/list-entry.css',
'../../../style/hide-scrollbar.css', '../../../style/overview.css']
})
export class CampaignPlayerDetailComponent implements OnInit {
@Input() campaignId: string;
@Input() playerName: string;
@Output() switchTab = new EventEmitter();
campaignPlayer: CampaignPlayer = {campaign: {}, players: []};
graphData: any[] = [];
sumData: any[] = [];
avgLabel = 'Durchschnitt';
colorScheme = {
domain: ['#00ce12']
};
colorSchemeBar = {
domain: [
'#2d5a00', '#455600', '#00561f', '#3f3b00', '#003c19', '#083c00'
]
};
showRefLines = true;
showRefLabels = true;
gradient = false;
xAxis = true;
yAxis = true;
legend = false;
showXAxisLabel = true;
showYAxisLabel = true;
autoscale = false;
timeline = false;
roundDomains = true;
kdRatio = 0;
maxKd = 1.7;
respawnDeathRatio = 0;
maxRespawnDeathRatio = 1;
playerAttributeNameMap = PlayerUtils.attributeDisplayNames.slice(2, PlayerUtils.attributeDisplayNames.length);
constructor(private playerService: PlayerService) {
}
ngOnInit() {
this.playerService.getCampaignPlayer(this.campaignId, encodeURIComponent(this.playerName))
.subscribe(campaignPlayer => {
this.campaignPlayer = campaignPlayer;
for (let i = 0; i < this.playerAttributeNameMap.length; i++) {
const attr = this.playerAttributeNameMap[i];
this.graphData.push({key: attr.prop, label: attr.head});
}
this.initDataArray();
const totalDeathDiv = this.graphData[7].total === 0 ? 1 : this.graphData[7].total;
this.kdRatio = parseFloat((this.graphData[0].total / totalDeathDiv).toFixed(2));
if (this.kdRatio > 1) {
this.maxKd = this.kdRatio * 1.7;
}
this.respawnDeathRatio = parseFloat((this.graphData[8].total / totalDeathDiv).toFixed(2));
// we can not directly push to target array, since only full reference changes trigger the refresh of data
const tmpSumData = [];
this.graphData.forEach(dataSet => {
tmpSumData.push({name: dataSet.label, value: dataSet.total});
});
this.sumData = tmpSumData;
});
}
private initDataArray() {
this.graphData.forEach(dataSet => {
const killObj = {
name: dataSet.label,
series: []
};
const playerLength = this.campaignPlayer.players.length;
let total = 0;
for (let i = 0; i < playerLength; i++) {
const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date);
const value = this.campaignPlayer.players[i][dataSet.key];
killObj.series.push({
name: warDateString,
value: value
});
total += value;
}
dataSet.data = [killObj];
dataSet.refLine = [{value: total / playerLength, name: this.avgLabel}];
dataSet.total = total;
});
}
navigateBack() {
this.switchTab.emit(0);
}
}

View File

@ -1,5 +1,6 @@
<form #form="ngForm" class="overview"> <form #form="ngForm" class="overview">
<h3>Kampagne hinzufügen</h3> <h3 *ngIf="campaign._id">Kampagne editieren</h3>
<h3 *ngIf="!campaign._id">Neue Kampagne hinzufügen</h3>
<div class="form-group"> <div class="form-group">
<label for="title">Titel</label> <label for="title">Titel</label>

View File

@ -1,14 +1,15 @@
import {Component, ViewChild} from '@angular/core'; import {Component, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {NgForm} from '@angular/forms'; import {NgForm} from '@angular/forms';
import {Campaign} from '../../models/model-interfaces'; import {Subscription} from 'rxjs/Subscription';
import {CampaignService} from '../../services/logs/campaign.service'; import {Campaign} from '../../../models/model-interfaces';
import {CampaignService} from '../../../services/logs/campaign.service';
@Component({ @Component({
selector: 'campaign-submit', selector: 'campaign-submit',
templateUrl: './campaign-submit.component.html', templateUrl: './campaign-submit.component.html',
styleUrls: ['./campaign-submit.component.css', '../../style/entry-form.css', '../../style/overview.css'] styleUrls: ['./campaign-submit.component.css', '../../../style/entry-form.css', '../../../style/overview.css']
}) })
export class CampaignSubmitComponent { export class CampaignSubmitComponent {
@ -18,17 +19,30 @@ export class CampaignSubmitComponent {
error; error;
subscription: Subscription;
@ViewChild(NgForm) form: NgForm; @ViewChild(NgForm) form: NgForm;
constructor(private route: ActivatedRoute, constructor(private route: ActivatedRoute,
private router: Router, private router: Router,
private campaignService: CampaignService) { private campaignService: CampaignService) {
this.subscription = this.route.params
.map(params => params['id'])
.filter(id => id !== undefined)
.flatMap(id => this.campaignService.getCampaign(id))
.subscribe(campaign => {
this.campaign = campaign;
});
} }
saveCampaign() { saveCampaign() {
this.campaignService.submitCampaign(this.campaign) this.campaignService.submitCampaign(this.campaign)
.subscribe(campaign => { .subscribe(campaign => {
this.router.navigate(['../overview/' + campaign._id], {relativeTo: this.route}); let redirectSuccessUrl = '../overview/';
if (this.campaign._id) {
redirectSuccessUrl = '../' + redirectSuccessUrl;
}
this.router.navigate([redirectSuccessUrl + campaign._id], {relativeTo: this.route});
}, },
error => { error => {
this.error = error._body.error.message; this.error = error._body.error.message;
@ -40,5 +54,4 @@ export class CampaignSubmitComponent {
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
return false; return false;
} }
} }

View File

@ -57,6 +57,10 @@ ngx-datatable {
background-color: #f7f7f7; background-color: #f7f7f7;
} }
:host /deep/ .ngx-datatable.fixed-header .datatable-header .datatable-header-inner .datatable-header-cell {
margin: auto;
}
/* Table Scrollbar BEGIN */ /* Table Scrollbar BEGIN */
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar { :host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar {

View File

@ -0,0 +1,56 @@
<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>
<div *ngFor="let attributeMap of playerAttributeDisplayNames">
<ngx-datatable
[rows]="players[attributeMap.prop]"
[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" prop="{{attributeMap.prop}}">
<ng-template ngx-datatable-header-template let-sort="sortFn">
<span class="datatable-header-cell-wrapper">
<span class="datatable-header-cell-label text-truncate" title="{{attributeMap.head}}" (click)="sort()">
<img src="../../../../assets/scoreboard/{{attributeMap.prop}}.png" alt="{{attributeMap.head}}">
</span>
</span>
</ng-template>
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span style="padding-left: 7px;">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</div>
</div>

View File

@ -1,17 +1,18 @@
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {PlayerService} from '../../services/logs/player.service'; import {PlayerService} from '../../../services/logs/player.service';
import {CampaignService} from '../../services/logs/campaign.service'; import {CampaignService} from '../../../services/logs/campaign.service';
import {Fraction} from '../../utils/fraction.enum'; import {Fraction} from '../../../utils/fraction.enum';
import {FormControl} from '@angular/forms'; import {FormControl} from '@angular/forms';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {Player} from '../../models/model-interfaces'; import {Player} from '../../../models/model-interfaces';
import {PlayerUtils} from '../../../utils/player-utils';
@Component({ @Component({
selector: 'stats-highscore', selector: 'stats-highscore',
templateUrl: './highscore.component.html', templateUrl: './highscore.component.html',
styleUrls: ['./highscore.component.css', '../../style/list-entry.css', '../../style/overview.css'] styleUrls: ['./highscore.component.css', '../../../style/list-entry.css', '../../../style/overview.css']
}) })
export class StatisticHighScoreComponent implements OnInit { export class StatisticHighScoreComponent implements OnInit {
@ -27,13 +28,15 @@ export class StatisticHighScoreComponent implements OnInit {
playersStored = {}; playersStored = {};
playerAttributeDisplayNames = PlayerUtils.attributeDisplayNames.slice(2, PlayerUtils.attributeDisplayNames.length);
cellHeight = 40; cellHeight = 40;
numberColWidth = 60; numberColWidth = 60;
nameColWidth = 210; nameColWidth = 210;
valueColWidth = 110; valueColWidth = 55;
emptyMessage = {emptyMessage: 'Keine Einträge'}; emptyMessage = {emptyMessage: 'Keine Einträge'};
@ -84,17 +87,11 @@ export class StatisticHighScoreComponent implements OnInit {
if (!this.searchTerm.value || this.searchTerm.value === '') { if (!this.searchTerm.value || this.searchTerm.value === '') {
this.players = this.playersStored; this.players = this.playersStored;
} else { } else {
this.players = { this.players = {};
kill: this.filterPlayerAttribute('kill'), for (let i = 0; i < this.playerAttributeDisplayNames.length; i++) {
friendlyFire: this.filterPlayerAttribute('friendlyFire'), const attributeProperty = this.playerAttributeDisplayNames[i].prop;
vehicleLight: this.filterPlayerAttribute('vehicleLight'), this.players[attributeProperty] = this.filterPlayerAttribute(attributeProperty);
vehicleHeavy: this.filterPlayerAttribute('vehicleLight'), }
vehicleAir: this.filterPlayerAttribute('vehicleLight'),
death: this.filterPlayerAttribute('death'),
respawn: this.filterPlayerAttribute('respawn'),
revive: this.filterPlayerAttribute('revive'),
flagTouch: this.filterPlayerAttribute('flagTouch')
};
} }
} }

View File

@ -1,14 +1,14 @@
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {CampaignService} from '../../services/logs/campaign.service'; import {CampaignService} from '../../../services/logs/campaign.service';
import {ChartUtils} from '../../utils/chart-utils'; import {ChartUtils} from '../../../utils/chart-utils';
import {Fraction} from '../../utils/fraction.enum'; import {Fraction} from '../../../utils/fraction.enum';
@Component({ @Component({
selector: 'stats-overview', selector: 'stats-overview',
templateUrl: './stats-overview.component.html', templateUrl: './stats-overview.component.html',
styleUrls: ['./stats-overview.component.css', '../../style/list-entry.css', '../../style/overview.css'] styleUrls: ['./stats-overview.component.css', '../../../style/list-entry.css', '../../../style/overview.css']
}) })
export class StatisticOverviewComponent implements OnInit { export class StatisticOverviewComponent implements OnInit {

View File

@ -1,208 +0,0 @@
<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.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.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.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>
<ngx-datatable
[rows]="players.vehicleLight"
[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="Fzg (Light)" prop="vehicleLight"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.vehicleHeavy"
[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="Fzg (Heavy)" prop="vehicleHeavy"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.vehicleAir"
[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="Fzg (Air)" prop="vehicleAir"></ngx-datatable-column>
</ngx-datatable>
</div>

View File

@ -1,16 +1,18 @@
import {RouterModule, Routes} from '@angular/router'; import {RouterModule, Routes} from '@angular/router';
import {StatisticComponent} from './stats.component'; import {StatisticComponent} from './stats.component';
import {WarListComponent} from './war-list/war-list.component'; import {WarListComponent} from './war/war-list/war-list.component';
import {StatisticOverviewComponent} from './overview/stats-overview.component'; import {StatisticOverviewComponent} from './campaign/overview/stats-overview.component';
import {WarItemComponent} from './war-list/war-item.component'; import {WarItemComponent} from './war/war-list/war-item.component';
import {ModuleWithProviders} from '@angular/core'; import {ModuleWithProviders} from '@angular/core';
import {CampaignSubmitComponent} from './campaign-submit/campaign-submit.component'; import {CampaignSubmitComponent} from './campaign/campaign-submit/campaign-submit.component';
import {CampaignPlayerDetailComponent} from './campaign-player-detail/campaign-player-detail.component'; import {CampaignPlayerDetailComponent} from './campaign/campaign-player-detail/campaign-player-detail.component';
import {WarDetailComponent} from './war-detail/war-detail.component'; import {ScoreboardComponent} from './war/scoreboard/scoreboard.component';
import {ScoreboardComponent} from './war-detail/scoreboard/scoreboard.component'; import {WarSubmitComponent} from './war/war-submit/war-submit.component';
import {WarSubmitComponent} from './war-submit/war-submit.component'; import {FractionStatsComponent} from './war/fraction-stats/fraction-stats.component';
import {FractionStatsComponent} from './war-detail/fraction-stats/fraction-stats.component'; import {StatisticHighScoreComponent} from './campaign/highscore/highscore.component';
import {StatisticHighScoreComponent} from './highscore/highscore.component'; import {WarHeaderComponent} from './war/war-header/war-header.component';
import {WarEditComponent} from './war/war-edit/war-edit.component';
import {LoginGuardMT} from '../login';
export const statsRoutes: Routes = [{ export const statsRoutes: Routes = [{
@ -32,30 +34,44 @@ export const statsRoutes: Routes = [{
component: StatisticHighScoreComponent, component: StatisticHighScoreComponent,
outlet: 'right' outlet: 'right'
}, },
{
path: 'new-campaign',
component: CampaignSubmitComponent,
outlet: 'right'
},
{
path: 'new',
component: WarSubmitComponent,
outlet: 'right'
},
{ {
path: 'war/:id', path: 'war/:id',
component: WarDetailComponent, component: WarHeaderComponent,
outlet: 'right' outlet: 'right'
}, },
{ {
path: 'campaign-player/:id/:playerName', path: 'campaign-player/:id/:playerName',
component: CampaignPlayerDetailComponent, component: CampaignPlayerDetailComponent,
outlet: 'right' outlet: 'right'
},
{
path: 'campaign',
component: CampaignSubmitComponent,
outlet: 'right',
canActivate: [LoginGuardMT]
},
{
path: 'campaign/:id',
component: CampaignSubmitComponent,
outlet: 'right',
canActivate: [LoginGuardMT]
},
{
path: 'submit-war',
component: WarSubmitComponent,
outlet: 'right',
canActivate: [LoginGuardMT]
},
{
path: 'submit-war/:id',
component: WarEditComponent,
outlet: 'right',
canActivate: [LoginGuardMT]
}]; }];
export const statsRouterModule: ModuleWithProviders = RouterModule.forChild(statsRoutes); export const statsRouterModule: ModuleWithProviders = RouterModule.forChild(statsRoutes);
export const statsRoutingComponents = [StatisticComponent, StatisticOverviewComponent, StatisticHighScoreComponent, export const statsRoutingComponents = [StatisticComponent, StatisticOverviewComponent, StatisticHighScoreComponent,
CampaignSubmitComponent, WarListComponent, WarSubmitComponent, WarDetailComponent, ScoreboardComponent, CampaignSubmitComponent, WarListComponent, WarSubmitComponent, WarEditComponent, WarHeaderComponent,
FractionStatsComponent, CampaignPlayerDetailComponent, WarItemComponent]; ScoreboardComponent, FractionStatsComponent, CampaignPlayerDetailComponent, WarItemComponent];

View File

@ -1,49 +0,0 @@
<div class="fade-in" style="overflow-x: auto" xmlns="http://www.w3.org/1999/html">
<ngx-datatable
[rows]="rows"
[sorts]="[{prop: 'kill', dir: 'desc'}]"
[reorderable]="reorderable"
[messages]="{emptyMessage: 'Loading...'}"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column name="{{tableHead[0]}}" prop="name" [width]="210" 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 name="{{tableHead[1]}}" prop="fraction" [width]="100">
<ng-template ngx-datatable-cell-template let-value="value">
{{value === 'BLUFOR' ? fraction.BLUFOR : fraction.OPFOR}}
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[2]}}" prop="kill"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[3]}}" prop="friendlyFire"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[4]}}" prop="vehicleLight"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[5]}}" prop="vehicleHeavy"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[6]}}" prop="vehicleAir"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[7]}}" prop="revive"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[8]}}" prop="flagTouch"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[9]}}" prop="death"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="{{tableHead[10]}}" prop="respawn"></ngx-datatable-column>
<!--<ngx-datatable-column [width]="80" name="" prop="name">-->
<!--<ng-template ngx-datatable-cell-template let-value="value">-->
<!--<span class="btn btn-sm btn-default in-table-btn disabled">Detail</span>-->
<!--</ng-template>-->
<!--</ngx-datatable-column>-->
<ngx-datatable-column [width]="80">
<ng-template ngx-datatable-cell-template let-row="row">
<span class="btn btn-sm btn-default in-table-btn"
(click)="selectPlayerDetail(1, isSteamUUID(row['steamUUID']) ? row['steamUUID'] : row['name'])">
Gesamt
</span>
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</div>

View File

@ -45,9 +45,9 @@ export class FractionStatsComponent implements OnInit, OnChanges {
labelPoints = 'Punkte'; labelPoints = 'Punkte';
labelBudget = 'Budget'; labelBudget = 'Budget';
labelKill = 'Kills'; labelKill = 'Abschüsse';
labelFriendlyFire = 'FriendlyFire'; labelFriendlyFire = 'Friendly Fire';
labelVehicle = 'Fahrzeug-Kills'; labelVehicle = 'Fahrzeug Abschüsse';
labelTransport = 'Lufttransport'; labelTransport = 'Lufttransport';
labelRevive = 'Revive'; labelRevive = 'Revive';
labelStabilize = 'Stabilisiert'; labelStabilize = 'Stabilisiert';

View File

@ -5,7 +5,7 @@
/* ########### DATATABLE ########### */ /* ########### DATATABLE ########### */
ngx-datatable { ngx-datatable {
width: 1040px; width: 940px;
margin: auto; margin: auto;
height: 68vh; height: 68vh;
} }
@ -43,6 +43,10 @@ ngx-datatable {
background-color: #f7f7f7; background-color: #f7f7f7;
} }
:host /deep/ .ngx-datatable.fixed-header .datatable-header .datatable-header-inner .datatable-header-cell {
margin: auto;
}
/* Table Scrollbar BEGIN */ /* Table Scrollbar BEGIN */
:host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar { :host /deep/ .ngx-datatable.scroll-vertical .datatable-body::-webkit-scrollbar {
width: 12px; width: 12px;

View File

@ -0,0 +1,47 @@
<div class="fade-in" style="overflow-x: auto" xmlns="http://www.w3.org/1999/html">
<ngx-datatable
[rows]="rows"
[sorts]="[{prop: 'kill', dir: 'desc'}]"
[reorderable]="reorderable"
[messages]="{emptyMessage: 'Loading...'}"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column name="{{tableHead[0].head}}" prop="name" [width]="210" 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 name="{{tableHead[1].head}}" prop="fraction" [width]="90">
<ng-template ngx-datatable-cell-template let-value="value">
{{value === 'BLUFOR' ? fraction.BLUFOR : fraction.OPFOR}}
</ng-template>
</ngx-datatable-column>
<div *ngFor="let column of tableHead.slice(2, tableHead.length) ">
<ngx-datatable-column [width]="columnWidth" prop="{{column.prop}}">
<ng-template ngx-datatable-header-template let-sort="sortFn">
<span class="datatable-header-cell-wrapper">
<span class="datatable-header-cell-label text-truncate" title="{{column.head}}" (click)="sort()">
<img src="../../../../assets/scoreboard/{{column.prop}}.png" alt="{{column.head}}">
</span>
</span>
</ng-template>
</ngx-datatable-column>
</div>
<ngx-datatable-column [width]="80">
<ng-template ngx-datatable-cell-template let-row="row">
<span class="btn btn-sm btn-default in-table-btn"
(click)="selectPlayerDetail(1, isSteamUUID(row['steamUUID']) ? row['steamUUID'] : row['name'])">
Gesamt
</span>
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</div>

View File

@ -19,13 +19,14 @@ export class ScoreboardComponent implements OnChanges {
@Output() playerTabSwitch = new EventEmitter(); @Output() playerTabSwitch = new EventEmitter();
tableHead = ['Spieler', 'Fraktion', 'Kills', 'FF', 'Fzg(L)', 'Fzg(H)', 'Fzg(A)', tableHead = PlayerUtils.attributeDisplayNames;
'Revive', 'Flagge', 'Tod', 'Respawn'];
isSteamUUID = PlayerUtils.isSteamUUID; isSteamUUID = PlayerUtils.isSteamUUID;
cellHeight = 40; cellHeight = 40;
columnWidth = 65;
rows = []; rows = [];
reorderable = false; reorderable = false;

View File

@ -0,0 +1,85 @@
<form #form="ngForm" class="overview">
<h3>Schlacht bearbeiten</h3>
<div class="form-group">
<label for="title">Titel</label>
<input type="text" class="form-control"
[(ngModel)]="war.title"
name="title"
id="title"
required maxlength="50"/>
<show-error displayName="Name" controlPath="title"></show-error>
</div>
<div class="form-group">
<label for="campaign">Kampagne</label>
<select class="form-control"
name="campaign"
id="campaign"
[(ngModel)]="war.campaign"
required>
<option *ngFor="let campaign of campaignService.campaigns" [ngValue]="campaign._id">
{{campaign.title}}
</option>
</select>
</div>
<div class="form-group">
<label for="ptBlufor">Punkte NATO</label>
<input type="number" class="form-control"
[(ngModel)]="war.ptBlufor"
name="ptBlufor"
id="ptBlufor"
required min="0"/>
<show-error displayName="Name" controlPath="ptBlufor"></show-error>
</div>
<div class="form-group">
<label for="ptOpfor">Punkte CSAT</label>
<input type="number" class="form-control"
[(ngModel)]="war.ptOpfor"
name="ptOpfor"
id="ptOpfor"
required min="0"/>
<show-error displayName="Name" controlPath="ptOpfor"></show-error>
</div>
<div class="form-group">
<label for="endBudgetBlufor">Endbudget NATO</label>
<input type="number" class="form-control"
[(ngModel)]="war.endBudgetBlufor"
name="endBudgetBlufor"
id="endBudgetBlufor"
required/>
<show-error displayName="Name" controlPath="endBudgetBlufor"></show-error>
</div>
<div class="form-group">
<label for="endBudgetOpfor">Endbudget CSAT</label>
<input type="number" class="form-control"
[(ngModel)]="war.endBudgetOpfor"
name="endBudgetOpfor"
id="endBudgetOpfor"
required/>
<show-error displayName="Name" controlPath="endBudgetOpfor"></show-error>
</div>
<button id="cancel"
(click)="cancel()"
class="btn btn-default">
Abbrechen
</button>
<button id="save"
(click)="updateWar()"
class="btn btn-default"
[disabled]="!form.valid">
Bestätigen
</button>
<span *ngIf="showErrorLabel"
class="center-block label label-danger" style="font-size: medium; padding: 2px; margin-top: 2px">
{{error}}
</span>
</form>

View File

@ -0,0 +1,56 @@
import {Component, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {NgForm} from '@angular/forms';
import {WarService} from '../../../services/logs/war.service';
import {War} from '../../../models/model-interfaces';
import {CampaignService} from '../../../services/logs/campaign.service';
import {Subscription} from 'rxjs/Subscription';
@Component({
selector: 'war-edit',
templateUrl: './war-edit.component.html',
styleUrls: ['./war-edit.component.css', '../../../style/load-indicator.css',
'../../../style/entry-form.css', '../../../style/overview.css']
})
export class WarEditComponent {
war: War = {players: []};
subscription: Subscription;
showErrorLabel = false;
error;
@ViewChild(NgForm) form: NgForm;
constructor(private route: ActivatedRoute,
private router: Router,
private warService: WarService,
public campaignService: CampaignService) {
this.subscription = this.route.params
.map(params => params['id'])
.filter(id => id !== undefined)
.flatMap(id => this.warService.getWar(id))
.subscribe(war => {
this.war = war;
});
}
updateWar() {
this.warService.updateWar(this.war)
.subscribe(war => {
this.router.navigate(['../../war/' + war._id], {relativeTo: this.route});
},
error => {
this.error = error._body.error.message;
this.showErrorLabel = true;
});
}
cancel() {
this.router.navigate(['..'], {relativeTo: this.route});
return false;
}
}

View File

@ -28,7 +28,7 @@ span.tab-control {
} }
.nav-tabs { .nav-tabs {
width: 980px; width: 920px;
margin: auto; margin: auto;
clear: both; clear: both;
border-bottom: 0; border-bottom: 0;

View File

@ -1,19 +1,19 @@
import {Component, OnInit, ViewChild} from '@angular/core'; import {Component, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {WarService} from '../../services/logs/war.service'; import {WarService} from '../../../services/logs/war.service';
import {War} from '../../models/model-interfaces'; import {War} from '../../../models/model-interfaces';
import {ChartUtils} from '../../utils/chart-utils'; import {ChartUtils} from '../../../utils/chart-utils';
import {Fraction} from '../../utils/fraction.enum'; import {Fraction} from '../../../utils/fraction.enum';
import {LogsService} from '../../services/logs/logs.service'; import {LogsService} from '../../../services/logs/logs.service';
import {ScoreboardComponent} from './scoreboard/scoreboard.component'; import {ScoreboardComponent} from '../scoreboard/scoreboard.component';
@Component({ @Component({
selector: 'war-detail', selector: 'war-detail',
templateUrl: './war-detail.component.html', templateUrl: './war-header.component.html',
styleUrls: ['./war-detail.component.css', '../../style/list-entry.css', '../../style/hide-scrollbar.css'] styleUrls: ['./war-header.component.css', '../../../style/list-entry.css', '../../../style/hide-scrollbar.css']
}) })
export class WarDetailComponent implements OnInit { export class WarHeaderComponent implements OnInit {
readonly fraction = Fraction; readonly fraction = Fraction;

View File

@ -13,6 +13,10 @@
<span (click)="delete(); $event.stopPropagation()" <span (click)="delete(); $event.stopPropagation()"
title="Löschen" title="Löschen"
class="glyphicon glyphicon-trash trash"></span> class="glyphicon glyphicon-trash trash"></span>
<span (click)="edit(); $event.stopPropagation()"
style="padding-right: 10px;"
title="Bearbeiten"
class="glyphicon glyphicon-edit trash"></span>
</div> </div>
</div> </div>

View File

@ -1,11 +1,11 @@
import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {War} from '../../models/model-interfaces'; import {War} from '../../../models/model-interfaces';
import {LoginService} from '../../services/app-user-service/login-service'; import {LoginService} from '../../../services/app-user-service/login-service';
@Component({ @Component({
selector: 'pjm-war-item', selector: 'pjm-war-item',
templateUrl: './war-item.component.html', templateUrl: './war-item.component.html',
styleUrls: ['./war-item.component.css', '../../style/list-entry.css'], styleUrls: ['./war-item.component.css', '../../../style/list-entry.css'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class WarItemComponent implements OnInit { export class WarItemComponent implements OnInit {
@ -16,6 +16,8 @@ export class WarItemComponent implements OnInit {
@Output() warSelected = new EventEmitter(); @Output() warSelected = new EventEmitter();
@Output() warEdit = new EventEmitter();
@Output() warDelete = new EventEmitter(); @Output() warDelete = new EventEmitter();
constructor(public loginService: LoginService) { constructor(public loginService: LoginService) {
@ -28,6 +30,10 @@ export class WarItemComponent implements OnInit {
this.warSelected.emit(this.war._id); this.warSelected.emit(this.war._id);
} }
edit() {
this.warEdit.emit(this.war._id);
}
delete() { delete() {
this.warDelete.emit(this.war); this.warDelete.emit(this.war);
} }

View File

@ -11,13 +11,18 @@
<accordion *ngFor="let campaign of campaigns; let isFirstRow=first"> <accordion *ngFor="let campaign of campaigns; let isFirstRow=first">
<accordion-group [isOpen]="isFirstRow"> <accordion-group [isOpen]="isFirstRow">
<div accordion-heading> <div accordion-heading>
{{campaign.title}} <span (click)="editCampaign(campaign); $event.stopPropagation()" title="Bearbeiten"
<span class="pull-right"></span> style="color: whitesmoke; padding-top: 0; padding-right: 10px;"
<span (click)="deleteCampaign(campaign); $event.stopPropagation()" title="Löschen" class="glyphicon glyphicon-edit trash pull-left"
style="color: whitesmoke; padding-top: 0; padding-right: 20px;"
class="glyphicon glyphicon-trash trash pull-right"
*ngIf="loginService.hasPermission(3)"> *ngIf="loginService.hasPermission(3)">
</span> </span>
<span (click)="deleteCampaign(campaign); $event.stopPropagation()" title="Löschen"
style="color: whitesmoke; padding-top: 0; padding-right: 10px;"
class="glyphicon glyphicon-trash trash pull-left"
*ngIf="loginService.hasPermission(3)">
</span>
{{campaign.title}}
<span class="pull-right"></span>
</div> </div>
<div class="fade-in list-entry top-list-entry" <div class="fade-in list-entry top-list-entry"
@ -42,6 +47,7 @@
<div *ngFor="let war of campaign.wars"> <div *ngFor="let war of campaign.wars">
<pjm-war-item <pjm-war-item
[war]="war" [war]="war"
(warEdit)="editWar($event)"
(warDelete)="deleteWar(war)" (warDelete)="deleteWar(war)"
(warSelected)="selectWar($event)" (warSelected)="selectWar($event)"
[selected]="war._id == selectedWarId"> [selected]="war._id == selectedWarId">

View File

@ -1,15 +1,15 @@
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 {Campaign, War} from '../../models/model-interfaces'; import {Campaign, War} from '../../../models/model-interfaces';
import {WarService} from '../../services/logs/war.service'; import {WarService} from '../../../services/logs/war.service';
import {LoginService} from '../../services/app-user-service/login-service'; import {LoginService} from '../../../services/app-user-service/login-service';
import {CampaignService} from '../../services/logs/campaign.service'; import {CampaignService} from '../../../services/logs/campaign.service';
import {RouteConfig} from '../../app.config'; import {RouteConfig} from '../../../app.config';
@Component({ @Component({
selector: 'war-list', selector: 'war-list',
templateUrl: './war-list.component.html', templateUrl: './war-list.component.html',
styleUrls: ['./war-list.component.css', '../../style/list-entry.css', '../../style/select-list.css'] styleUrls: ['./war-list.component.css', '../../../style/list-entry.css', '../../../style/select-list.css']
}) })
export class WarListComponent implements OnInit { export class WarListComponent implements OnInit {
@ -46,12 +46,7 @@ export class WarListComponent implements OnInit {
selectNewCampaign() { selectNewCampaign() {
this.selectedWarId = null; this.selectedWarId = null;
this.router.navigate([{outlets: {'right': ['new-campaign']}}], {relativeTo: this.route}); this.router.navigate([{outlets: {'right': ['campaign']}}], {relativeTo: this.route});
}
selectNewWar() {
this.selectedWarId = null;
this.router.navigate([{outlets: {'right': ['new']}}], {relativeTo: this.route});
} }
selectWar(warId) { selectWar(warId) {
@ -75,6 +70,16 @@ export class WarListComponent implements OnInit {
} }
} }
selectNewWar() {
this.selectedWarId = null;
this.router.navigate([{outlets: {'right': ['submit-war']}}], {relativeTo: this.route});
}
editWar(warId) {
this.selectedWarId = warId;
this.router.navigate([{outlets: {'right': ['submit-war', warId]}}], {relativeTo: this.route});
}
deleteWar(war: War) { deleteWar(war: War) {
if (confirm('Soll die Schlacht ' + war.title + ' wirklich gelöscht werden?')) { if (confirm('Soll die Schlacht ' + war.title + ' wirklich gelöscht werden?')) {
this.warService.deleteWar(war._id) this.warService.deleteWar(war._id)
@ -82,7 +87,9 @@ export class WarListComponent implements OnInit {
if (this.selectedWarId === war._id) { if (this.selectedWarId === war._id) {
this.selectOverview('all'); this.selectOverview('all');
} }
this.campaigns.splice(this.campaigns.indexOf(war), 1); this.campaigns.forEach(campaign => {
campaign.wars.splice(campaign.wars.indexOf(war), 1);
});
}); });
} }
} }
@ -99,4 +106,7 @@ export class WarListComponent implements OnInit {
} }
} }
editCampaign(selectCampaign) {
this.router.navigate([{outlets: {'right': ['campaign', selectCampaign._id]}}], {relativeTo: this.route});
}
} }

View File

@ -29,7 +29,7 @@
<label for="log">Logfile</label> <label for="log">Logfile</label>
<input id="log" name="log" class="ui-button form-control" type="file" <input id="log" name="log" class="ui-button form-control" type="file"
(change)="fileChange($event)"> (change)="fileChange($event)">
<span class="label label-bg label-danger center-block" style="font-size:small" *ngIf="showImageError"> <span class="label label-bg label-danger center-block" style="font-size:small" *ngIf="showFileError">
Logfile muss im Format RPT, LOG oder TXT vorliegen Logfile muss im Format RPT, LOG oder TXT vorliegen
</span> </span>
</div> </div>

View File

@ -1,16 +1,16 @@
import {Component, ViewChild} from '@angular/core'; import {Component, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {NgForm} from '@angular/forms'; import {NgForm} from '@angular/forms';
import {WarService} from '../../services/logs/war.service'; import {WarService} from '../../../services/logs/war.service';
import {War} from '../../models/model-interfaces'; import {War} from '../../../models/model-interfaces';
import {CampaignService} from '../../services/logs/campaign.service'; import {CampaignService} from '../../../services/logs/campaign.service';
@Component({ @Component({
selector: 'war-submit', selector: 'war-submit',
templateUrl: './war-submit.component.html', templateUrl: './war-submit.component.html',
styleUrls: ['./war-submit.component.css', '../../style/load-indicator.css', styleUrls: ['./war-submit.component.css', '../../../style/load-indicator.css',
'../../style/entry-form.css', '../../style/overview.css'] '../../../style/entry-form.css', '../../../style/overview.css']
}) })
export class WarSubmitComponent { export class WarSubmitComponent {
@ -18,7 +18,9 @@ export class WarSubmitComponent {
fileList: FileList; fileList: FileList;
showImageError = false; readonly validExtensions = ['.rpt', '.log', '.txt'];
showFileError = false;
showErrorLabel = false; showErrorLabel = false;
@ -35,14 +37,13 @@ export class WarSubmitComponent {
} }
fileChange(event) { fileChange(event) {
if (!event.target.files[0].name.endsWith('.rpt') if (this.validExtensions.filter(ext => event.target.files[0] &&
&& !event.target.files[0].name.endsWith('.log') event.target.files[0].name.endsWith(ext)).length === 1) {
&& !event.target.files[0].name.endsWith('.txt')) { this.showFileError = false;
this.showImageError = true;
this.fileList = undefined;
} else {
this.showImageError = false;
this.fileList = event.target.files; this.fileList = event.target.files;
} else {
this.showFileError = true;
this.fileList = undefined;
} }
} }
@ -69,5 +70,4 @@ export class WarSubmitComponent {
this.router.navigate(['..'], {relativeTo: this.route}); this.router.navigate(['..'], {relativeTo: this.route});
return false; return false;
} }
} }

View File

@ -1,5 +1,19 @@
export class PlayerUtils { export class PlayerUtils {
public static readonly attributeDisplayNames = [
{prop: 'name', head: 'Spieler'},
{prop: 'fraction', head: 'Fraktion'},
{prop: 'kill', head: 'Abschüsse'},
{prop: 'friendlyFire', head: 'Friendly Fire'},
{prop: 'revive', head: 'Revive'},
{prop: 'flagTouch', head: 'Eroberungen'},
{prop: 'vehicleLight', head: 'Fahrzeug (leicht)'},
{prop: 'vehicleHeavy', head: 'Fahrzeug (schwer)'},
{prop: 'vehicleAir', head: 'Fahrzeug (Luft)'},
{prop: 'death', head: 'Tode'},
{prop: 'respawn', head: 'Respawn'}
];
public static isSteamUUID(input: string): boolean { public static isSteamUUID(input: string): boolean {
const steamUIDPattern = new RegExp('[0-9]{17}'); const steamUIDPattern = new RegExp('[0-9]{17}');
return steamUIDPattern.test(input); return steamUIDPattern.test(input);

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

BIN
static/src/assets/tank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B