330 lines
8.0 KiB
JavaScript
330 lines
8.0 KiB
JavaScript
|
/**
|
||
|
* Copyright (c) 2017 Tomek Wójcik <tomek@bthlabs.pl>
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to
|
||
|
* deal in the Software without restriction, including without limitation the
|
||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||
|
* IN THE SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
import ClassName from "classnames";
|
||
|
import React from "react";
|
||
|
|
||
|
import BaseView from "../lib/BaseView";
|
||
|
import ChartComponent from "../components/ChartComponent";
|
||
|
|
||
|
const WINS_CHART_TITLE_SESSION = "Wins and losses by session";
|
||
|
const WINS_CHART_TITLE_MAP = "Wins and losses by map";
|
||
|
const ACCURACY_CHART_TITLE_SESSION = "Average weapon accuracy by session";
|
||
|
const ACCURACY_CHART_TITLE_MAP = "Average weapon accuracy by map";
|
||
|
|
||
|
class ModeLinkComponent extends React.Component {
|
||
|
constructor (props) {
|
||
|
super(props);
|
||
|
this.onClick = this.onClick.bind(this);
|
||
|
}
|
||
|
onClick (event) {
|
||
|
event.stopPropagation();
|
||
|
event.preventDefault();
|
||
|
|
||
|
this.props.onSwitchMode(this.props.code);
|
||
|
}
|
||
|
render () {
|
||
|
let className = ClassName({
|
||
|
active: (this.props.mode == this.props.code)
|
||
|
});
|
||
|
|
||
|
return (
|
||
|
<li className={className}>
|
||
|
<a onClick={this.onClick} href="#">{this.props.name}</a>
|
||
|
</li>
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ModeLinkComponent.propTypes = {
|
||
|
mode: React.PropTypes.string.isRequired,
|
||
|
name: React.PropTypes.string.isRequired,
|
||
|
code: React.PropTypes.string.isRequired,
|
||
|
onSwitchMode: React.PropTypes.func.isRequired
|
||
|
};
|
||
|
|
||
|
class BaseStatsChartComponent extends BaseView {
|
||
|
constructor (props) {
|
||
|
super(props);
|
||
|
|
||
|
this.chartConfig = {};
|
||
|
|
||
|
this.state = {
|
||
|
sessionCategories: [],
|
||
|
sessionSeries: [],
|
||
|
mapCategories: [],
|
||
|
mapSeries: []
|
||
|
};
|
||
|
}
|
||
|
componentDidMount () {
|
||
|
this.load();
|
||
|
}
|
||
|
componentWillReceiveProps (nextProps) {
|
||
|
if (nextProps.player != this.props.player) {
|
||
|
this.setState({
|
||
|
sessionCategories: [],
|
||
|
sessionSeries: [],
|
||
|
mapCategories: [],
|
||
|
mapSeries: []
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
componentDidUpdate (prevProps, prevState) {
|
||
|
if (prevProps.player != this.props.player || prevProps.mode != this.props.mode) {
|
||
|
this.load();
|
||
|
}
|
||
|
}
|
||
|
url () {
|
||
|
throw new Error("Not Implemented");
|
||
|
}
|
||
|
parse () {
|
||
|
throw new Error("Not Implemented");
|
||
|
}
|
||
|
load () {
|
||
|
let hasData = true;
|
||
|
if (this.props.mode == "session") {
|
||
|
hasData = hasData && this.state.sessionCategories.length > 0;
|
||
|
hasData = hasData && this.state.sessionSeries.length > 0;
|
||
|
} else {
|
||
|
hasData = hasData && this.state.mapCategories.length > 0;
|
||
|
hasData = hasData && this.state.mapSeries.length > 0;
|
||
|
}
|
||
|
|
||
|
if (!hasData) {
|
||
|
this.fetch(this.url()).then((data) => {
|
||
|
let newState = {};
|
||
|
let parsedData = this.parse(data);
|
||
|
|
||
|
if (this.props.mode == "session") {
|
||
|
newState.sessionCategories = parsedData.categories;
|
||
|
newState.sessionSeries = parsedData.series;
|
||
|
} else {
|
||
|
newState.mapCategories = parsedData.categories;
|
||
|
newState.mapSeries = parsedData.series;
|
||
|
}
|
||
|
|
||
|
this.setState(newState);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
title () {
|
||
|
throw new Error("Not Implemented");
|
||
|
}
|
||
|
render () {
|
||
|
let categories = this.state.sessionCategories;
|
||
|
let series = this.state.sessionSeries;
|
||
|
|
||
|
if (this.props.mode == "map") {
|
||
|
categories = this.state.mapCategories;
|
||
|
series = this.state.mapSeries;
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<ChartComponent
|
||
|
config={this.chartConfig}
|
||
|
categories={categories}
|
||
|
series={series}
|
||
|
title={this.title()} />
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class WinsChartComponent extends BaseStatsChartComponent {
|
||
|
constructor (props) {
|
||
|
super(props);
|
||
|
|
||
|
this.chartConfig = {
|
||
|
"chart": {
|
||
|
"type": "column"
|
||
|
},
|
||
|
"credits": {
|
||
|
"enabled": false
|
||
|
},
|
||
|
"yAxis": {
|
||
|
"title": {
|
||
|
"text": "Score"
|
||
|
}
|
||
|
},
|
||
|
"plotOptions": {
|
||
|
"column": {
|
||
|
"pointPadding": 0.2,
|
||
|
"borderWidth": 0,
|
||
|
"stacking": "normal",
|
||
|
"dataLabels": {
|
||
|
"enabled": true,
|
||
|
"color": "white",
|
||
|
"style": {
|
||
|
"textShadow": "0 0 3px black"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
"tooltip": {
|
||
|
"formatter": function () {
|
||
|
return (
|
||
|
"<b>" + this.x + "</b><br/>" + this.series.name + ": " +
|
||
|
this.y + "<br/>" + "Total: " + this.point.stackTotal
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
url () {
|
||
|
return (
|
||
|
"/api/v1/charts/player/" + this.props.player + "/wins/" + this.props.mode
|
||
|
);
|
||
|
}
|
||
|
parse (data) {
|
||
|
let result = {
|
||
|
categories: [],
|
||
|
series: [
|
||
|
{"name": "Wins", "data": data.wins},
|
||
|
{"name": "Losses", "data": data.losses}
|
||
|
]
|
||
|
};
|
||
|
|
||
|
if (this.props.mode == "session") {
|
||
|
result.categories = data.dates;
|
||
|
} else {
|
||
|
result.categories = data.maps;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
title () {
|
||
|
if (this.props.mode == "map") {
|
||
|
return WINS_CHART_TITLE_MAP;
|
||
|
}
|
||
|
|
||
|
return WINS_CHART_TITLE_SESSION;
|
||
|
}
|
||
|
render () {
|
||
|
return super.render();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class AccuracyChartComponent extends BaseStatsChartComponent {
|
||
|
constructor (props) {
|
||
|
super(props);
|
||
|
|
||
|
this.chartConfig = {
|
||
|
"credits": {
|
||
|
"enabled": false
|
||
|
},
|
||
|
"tooltip": {
|
||
|
"valueDecimals": 2,
|
||
|
"valueSuffix": "%"
|
||
|
},
|
||
|
"yAxis": {
|
||
|
"title": {
|
||
|
"text": "Avg accuracy [%]"
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
url () {
|
||
|
return (
|
||
|
"/api/v1/charts/player/" + this.props.player + "/accuracy/" +
|
||
|
this.props.mode
|
||
|
);
|
||
|
}
|
||
|
parse (data) {
|
||
|
let result = {
|
||
|
categories: [],
|
||
|
series: data.series
|
||
|
};
|
||
|
|
||
|
if (this.props.mode == "session") {
|
||
|
result.categories = data.dates;
|
||
|
} else {
|
||
|
result.categories = data.maps;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
title () {
|
||
|
if (this.props.mode == "map") {
|
||
|
return ACCURACY_CHART_TITLE_MAP;
|
||
|
}
|
||
|
|
||
|
return ACCURACY_CHART_TITLE_SESSION;
|
||
|
}
|
||
|
render () {
|
||
|
return super.render();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WinsChartComponent.propTypes = {
|
||
|
player: React.PropTypes.string.isRequired,
|
||
|
mode: React.PropTypes.string.isRequired
|
||
|
};
|
||
|
|
||
|
class PlayerStatsView extends React.Component {
|
||
|
constructor (props) {
|
||
|
super(props);
|
||
|
this.onSwitchMode = this.onSwitchMode.bind(this);
|
||
|
|
||
|
this.state = {
|
||
|
mode: "session"
|
||
|
};
|
||
|
}
|
||
|
componentWillReceiveProps (nextProps) {
|
||
|
this.setState({mode: "session"});
|
||
|
}
|
||
|
onSwitchMode (mode) {
|
||
|
this.setState({mode: mode});
|
||
|
}
|
||
|
render () {
|
||
|
return (
|
||
|
<div>
|
||
|
<h2>{this.props.params.player} stats</h2>
|
||
|
|
||
|
<ul className="nav nav-tabs">
|
||
|
<ModeLinkComponent
|
||
|
mode={this.state.mode}
|
||
|
name="By session"
|
||
|
code="session"
|
||
|
onSwitchMode={this.onSwitchMode} />
|
||
|
|
||
|
<ModeLinkComponent
|
||
|
mode={this.state.mode}
|
||
|
name="By map"
|
||
|
code="map"
|
||
|
onSwitchMode={this.onSwitchMode} />
|
||
|
</ul>
|
||
|
|
||
|
<WinsChartComponent
|
||
|
player={this.props.params.player}
|
||
|
mode={this.state.mode}
|
||
|
router={this.props.router} />
|
||
|
|
||
|
<AccuracyChartComponent
|
||
|
player={this.props.params.player}
|
||
|
mode={this.state.mode}
|
||
|
router={this.props.router} />
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default PlayerStatsView;
|