Rewritten most of the frontend to use Redux.
Also, lots of code cleanup, especially in frontend.
This commit is contained in:
		
							parent
							
								
									bfdcb87cef
								
							
						
					
					
						commit
						0451cdbbed
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11,4 +11,4 @@ build/
 | 
				
			||||||
dist/
 | 
					dist/
 | 
				
			||||||
q3stats.egg-info/
 | 
					q3stats.egg-info/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tests_web_app/tmp/
 | 
					tests_web_app/tmp/tmp*
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,16 +24,22 @@ import React from "react";
 | 
				
			||||||
import ReactDOM from "react-dom";
 | 
					import ReactDOM from "react-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import DashboardView from "./views/DashboardView";
 | 
					import DashboardView from "./views/DashboardView";
 | 
				
			||||||
 | 
					import DataSource from "./lib/DataSource";
 | 
				
			||||||
import NavigationBarView from "./views/NavigationBarView";
 | 
					import NavigationBarView from "./views/NavigationBarView";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AppWindow extends React.Component {
 | 
					class AppWindow extends React.Component {
 | 
				
			||||||
 | 
					  componentWillMount () {
 | 
				
			||||||
 | 
					    DataSource.setRouter(this.props.router);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="q3stats-app-window">
 | 
					      <div className="q3stats-app-window">
 | 
				
			||||||
        <NavigationBarView router={this.props.router} />
 | 
					        <NavigationBarView
 | 
				
			||||||
 | 
					          location={this.props.location}
 | 
				
			||||||
 | 
					          router={this.props.router} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="q3stats-content-view">
 | 
					        <div className="q3stats-content-view">
 | 
				
			||||||
          {this.props.children || <DashboardView router={this.props.router} />}
 | 
					          {this.props.children || <DashboardView />}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="q3stats-footer">
 | 
					        <div className="q3stats-footer">
 | 
				
			||||||
| 
						 | 
					@ -49,8 +55,4 @@ class AppWindow extends React.Component {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AppWindow.propTypes = {
 | 
					 | 
				
			||||||
  errorCode: React.PropTypes.string
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AppWindow;
 | 
					export default AppWindow;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										70
									
								
								frontend/src/actions/dashboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								frontend/src/actions/dashboard.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,70 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 {DASHBOARD_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DataSource from "../lib/DataSource";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cleanup = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: DASHBOARD_ACTIONS.CLEANUP
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadData = (dispatch) => {
 | 
				
			||||||
 | 
					  DataSource.loadDashboardData().then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseData(dispatch, data));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: DASHBOARD_ACTIONS.LOAD_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseData = (dispatch, data) => {
 | 
				
			||||||
 | 
					  if (data.day) {
 | 
				
			||||||
 | 
					    dispatch(loadChartData(dispatch, data.day));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: DASHBOARD_ACTIONS.PARSE_DATA,
 | 
				
			||||||
 | 
					    data: data
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadChartData = (dispatch, day) => {
 | 
				
			||||||
 | 
					  DataSource.loadDayChartData(day).then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseChartData(data));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: DASHBOARD_ACTIONS.LOAD_CHART_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseChartData = (data) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: DASHBOARD_ACTIONS.PARSE_CHART_DATA,
 | 
				
			||||||
 | 
					    categories: data.maps,
 | 
				
			||||||
 | 
					    series: data.scores
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										67
									
								
								frontend/src/actions/navigationBar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								frontend/src/actions/navigationBar.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 {NAVIGATION_BAR_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DataSource from "../lib/DataSource";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cleanup = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NAVIGATION_BAR_ACTIONS.CLEANUP
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const expand = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NAVIGATION_BAR_ACTIONS.EXPAND
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const collapse = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NAVIGATION_BAR_ACTIONS.COLLAPSE
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const showAboutModal = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NAVIGATION_BAR_ACTIONS.SHOW_ABOUT_MODAL
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const hideAboutModal = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NAVIGATION_BAR_ACTIONS.HIDE_ABOUT_MODAL
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const showSettingsModal = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NAVIGATION_BAR_ACTIONS.SHOW_SETTINGS_MODAL
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const hideSettingsModal = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NAVIGATION_BAR_ACTIONS.HIDE_SETTINGS_MODAL
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										67
									
								
								frontend/src/actions/playerGame.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								frontend/src/actions/playerGame.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 {PLAYER_GAME_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DataSource from "../lib/DataSource";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cleanup = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_GAME_ACTIONS.CLEANUP
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadData = (dispatch, player, game) => {
 | 
				
			||||||
 | 
					  DataSource.loadPlayerGameData(player, game).then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseData(dispatch, data));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_GAME_ACTIONS.LOAD_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseData = (dispatch, data) => {
 | 
				
			||||||
 | 
					  dispatch(loadChartsData(dispatch, data.player, data.game));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_GAME_ACTIONS.PARSE_DATA,
 | 
				
			||||||
 | 
					    data: data
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadChartsData = (dispatch, player, game) => {
 | 
				
			||||||
 | 
					  DataSource.loadPlayerGameChartsData(player, game).then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseChartsData(data));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_GAME_ACTIONS.LOAD_CHARTS_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseChartsData = (data) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_GAME_ACTIONS.PARSE_CHARTS_DATA,
 | 
				
			||||||
 | 
					    data: data
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										57
									
								
								frontend/src/actions/playerStats.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								frontend/src/actions/playerStats.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 {PLAYER_STATS_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DataSource from "../lib/DataSource";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cleanup = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_STATS_ACTIONS.CLEANUP
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setMode = (mode) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_STATS_ACTIONS.SET_MODE,
 | 
				
			||||||
 | 
					    mode: mode
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadChartData = (dispatch, player, kind, mode) => {
 | 
				
			||||||
 | 
					  DataSource.loadPlayerStatsChartData(player, kind, mode).then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseChartData(data, player, kind, mode));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_STATS_ACTIONS.LOAD_CHART_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseChartData = (data, player, kind, mode) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYER_STATS_ACTIONS.PARSE_CHART_DATA,
 | 
				
			||||||
 | 
					    kind: kind,
 | 
				
			||||||
 | 
					    mode: mode,
 | 
				
			||||||
 | 
					    data: data
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										48
									
								
								frontend/src/actions/players.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								frontend/src/actions/players.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 {PLAYERS_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DataSource from "../lib/DataSource";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cleanup = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYERS_ACTIONS.CLEANUP
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadData = (dispatch) => {
 | 
				
			||||||
 | 
					  DataSource.loadPlayersData().then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseData(dispatch, data));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYERS_ACTIONS.LOAD_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseData = (dispatch, data) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: PLAYERS_ACTIONS.PARSE_DATA,
 | 
				
			||||||
 | 
					    data: data
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										81
									
								
								frontend/src/actions/sessions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								frontend/src/actions/sessions.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,81 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 {SESSIONS_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DataSource from "../lib/DataSource";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cleanup = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: SESSIONS_ACTIONS.CLEANUP
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setDay = (dispatch, day) => {
 | 
				
			||||||
 | 
					  let actualDay = day || "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dispatch(loadData(dispatch, actualDay));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: SESSIONS_ACTIONS.SET_DAY,
 | 
				
			||||||
 | 
					    day: actualDay
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadData = (dispatch, day) => {
 | 
				
			||||||
 | 
					  DataSource.loadSessionData(day).then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseData(dispatch, data));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: SESSIONS_ACTIONS.LOAD_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseData = (dispatch, data) => {
 | 
				
			||||||
 | 
					  if (data.day) {
 | 
				
			||||||
 | 
					    dispatch(loadChartData(dispatch, data.day));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: SESSIONS_ACTIONS.PARSE_DATA,
 | 
				
			||||||
 | 
					    data: data
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loadChartData = (dispatch, day) => {
 | 
				
			||||||
 | 
					  DataSource.loadDayChartData(day).then((data) => {
 | 
				
			||||||
 | 
					    dispatch(parseChartData(data));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: SESSIONS_ACTIONS.LOAD_CHART_DATA
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const parseChartData = (data) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: SESSIONS_ACTIONS.PARSE_CHART_DATA,
 | 
				
			||||||
 | 
					    categories: data.maps,
 | 
				
			||||||
 | 
					    series: data.scores
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -20,11 +20,17 @@
 | 
				
			||||||
 * IN THE SOFTWARE.
 | 
					 * IN THE SOFTWARE.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {ACTION_SETTINGS_SET_LAYOUT} from "../lib/defs";
 | 
					import {SETTINGS_ACTIONS, SETTINGS_KEYS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setLayout = (newLayout) => {
 | 
					export const setLayout = (newLayout) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    window.localStorage.setItem(SETTINGS_KEYS.LAYOUT, newLayout);
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    // pass
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: ACTION_SETTINGS_SET_LAYOUT,
 | 
					    type: SETTINGS_ACTIONS.SET_LAYOUT,
 | 
				
			||||||
    layout: newLayout
 | 
					    layout: newLayout
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ import ReactDOM from "react-dom";
 | 
				
			||||||
import {connect} from "react-redux";
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
import underscore from "underscore";
 | 
					import underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ChartComponent extends React.Component {
 | 
					class ChartComponent extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ const LAYOUT_TO_CLASSNAME = {};
 | 
				
			||||||
LAYOUT_TO_CLASSNAME[LAYOUT_WIDE] = "container-fluid";
 | 
					LAYOUT_TO_CLASSNAME[LAYOUT_WIDE] = "container-fluid";
 | 
				
			||||||
LAYOUT_TO_CLASSNAME[LAYOUT_NARROW] = "container";
 | 
					LAYOUT_TO_CLASSNAME[LAYOUT_NARROW] = "container";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ContainerComponent extends React.Component {
 | 
					class ContainerComponent extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let className = LAYOUT_TO_CLASSNAME[this.props.layout];
 | 
					    let className = LAYOUT_TO_CLASSNAME[this.props.layout];
 | 
				
			||||||
    if (!className) {
 | 
					    if (!className) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {ITEM_NAMES} from "../lib/defs";
 | 
					import {ITEM_NAMES} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ItemIconComponent extends React.Component {
 | 
					class ItemIconComponent extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let itemName = ITEM_NAMES[this.props.item] || "?";
 | 
					    let itemName = ITEM_NAMES[this.props.item] || "?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@
 | 
				
			||||||
import ClassName from "classnames";
 | 
					import ClassName from "classnames";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LoaderComponent extends React.Component {
 | 
					class LoaderComponent extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let className = ClassName("q3stats-loader", {
 | 
					    let className = ClassName("q3stats-loader", {
 | 
				
			||||||
      "visible": this.props.visible
 | 
					      "visible": this.props.visible
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {POWERUP_NAMES} from "../lib/defs";
 | 
					import {POWERUP_NAMES} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PowerupIconComponent extends React.Component {
 | 
					class PowerupIconComponent extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let powerupName = POWERUP_NAMES[this.props.powerup] || "?";
 | 
					    let powerupName = POWERUP_NAMES[this.props.powerup] || "?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {WEAPON_NAMES} from "../lib/defs";
 | 
					import {WEAPON_NAMES} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WeaponIconComponent extends React.Component {
 | 
					class WeaponIconComponent extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let weaponName = WEAPON_NAMES[this.props.weapon] || "?";
 | 
					    let weaponName = WEAPON_NAMES[this.props.weapon] || "?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										64
									
								
								frontend/src/lib/DataSource.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/src/lib/DataSource.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,64 @@
 | 
				
			||||||
 | 
					import underscore from "underscore";
 | 
				
			||||||
 | 
					import "whatwg-fetch";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_INIT = {
 | 
				
			||||||
 | 
					  credentials: "include"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DataSource {
 | 
				
			||||||
 | 
					  constructor () {
 | 
				
			||||||
 | 
					    this._router = null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  setRouter (router) {
 | 
				
			||||||
 | 
					    this._router = router;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  _fetch (requestOrURL, init) {
 | 
				
			||||||
 | 
					    init = init || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let actualInit = underscore.extendOwn(
 | 
				
			||||||
 | 
					      underscore.clone(DEFAULT_INIT), init
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return window.fetch(requestOrURL, actualInit).catch((error) => {
 | 
				
			||||||
 | 
					      this._router.push("/error/");
 | 
				
			||||||
 | 
					      throw new Error("FetchError: Unable to fetch");
 | 
				
			||||||
 | 
					    }).then((response) => {
 | 
				
			||||||
 | 
					      if (!response.ok) {
 | 
				
			||||||
 | 
					        let statusCode = response.status || "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this._router.push("/error/" + statusCode);
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					          "FetchError: '" + statusCode + "'" + " '" + response.statusText + "'"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return response.json();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  loadDashboardData () {
 | 
				
			||||||
 | 
					    return this._fetch("/api/v1/dashboard");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  loadDayChartData (day) {
 | 
				
			||||||
 | 
					    return this._fetch("/api/v1/charts/day/" + day);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  loadSessionData (day) {
 | 
				
			||||||
 | 
					    return this._fetch("/api/v1/sessions/" + day);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  loadPlayerGameData (player, game) {
 | 
				
			||||||
 | 
					    return this._fetch("/api/v1/players/" + player + "/game/" + game);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  loadPlayerGameChartsData (player, game) {
 | 
				
			||||||
 | 
					    return this._fetch("/api/v1/charts/player/" + player + "/game/" + game);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  loadPlayersData () {
 | 
				
			||||||
 | 
					    return this._fetch("/api/v1/players");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  loadPlayerStatsChartData (player, kind, mode) {
 | 
				
			||||||
 | 
					    return this._fetch(
 | 
				
			||||||
 | 
					      "/api/v1/charts/player/" + player + "/" + kind + "/" + mode
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let sharedDataSource = new DataSource();
 | 
				
			||||||
 | 
					export default sharedDataSource;
 | 
				
			||||||
| 
						 | 
					@ -30,9 +30,61 @@ export const LAYOUT_CHOICES = [
 | 
				
			||||||
  [LAYOUT_NARROW, "Narrow"]
 | 
					  [LAYOUT_NARROW, "Narrow"]
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SETTINGS_KEY_LAYOUT = "layout";
 | 
					export const SETTINGS_KEYS = {
 | 
				
			||||||
 | 
					  LAYOUT: "layout"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ACTION_SETTINGS_SET_LAYOUT = "ACTION_SETTINGS_SET_LAYOUT";
 | 
					export const DASHBOARD_ACTIONS = {
 | 
				
			||||||
 | 
					  CLEANUP: "ACTION_DASHBOARD_CLEANUP",
 | 
				
			||||||
 | 
					  LOAD_CHART_DATA: "ACTION_DASHBOARD_LOAD_CHART_DATA",
 | 
				
			||||||
 | 
					  LOAD_DATA: "ACTION_DASHBOARD_LOAD_DATA",
 | 
				
			||||||
 | 
					  PARSE_CHART_DATA: "ACTION_DASHBOARD_PARSE_CHART_DATA",
 | 
				
			||||||
 | 
					  PARSE_DATA: "ACTION_DASHBOARD_PARSE_DATA"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const NAVIGATION_BAR_ACTIONS = {
 | 
				
			||||||
 | 
					  CLEANUP: "ACTION_NAVIGATION_BAR_CLEANUP",
 | 
				
			||||||
 | 
					  EXPAND: "ACTION_NAVIGATION_BAR_EXPAND",
 | 
				
			||||||
 | 
					  COLLAPSE: "ACTION_NAVIGATION_BAR_COLLAPSE",
 | 
				
			||||||
 | 
					  SHOW_ABOUT_MODAL: "ACTION_NAVIGATION_BAR_SHOW_ABOUT_MODAL",
 | 
				
			||||||
 | 
					  HIDE_ABOUT_MODAL: "ACTION_NAVIGATION_BAR_HIDE_ABOUT_MODAL",
 | 
				
			||||||
 | 
					  SHOW_SETTINGS_MODAL: "ACTION_NAVIGATION_BAR_SHOW_SETTINGS_MODAL",
 | 
				
			||||||
 | 
					  HIDE_SETTINGS_MODAL: "ACTION_NAVIGATION_BAR_HIDE_SETTINGS_MODAL"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PLAYER_GAME_ACTIONS = {
 | 
				
			||||||
 | 
					  CLEANUP: "ACTION_PLAYER_GAME_CLEANUP",
 | 
				
			||||||
 | 
					  LOAD_CHARTS_DATA: "ACTION_PLAYER_GAME_LOAD_CHARTS_DATA",
 | 
				
			||||||
 | 
					  LOAD_DATA: "ACTION_PLAYER_GAME_LOAD_DATA",
 | 
				
			||||||
 | 
					  PARSE_CHARTS_DATA: "ACTION_PLAYER_GAME_PARSE_CHARTS",
 | 
				
			||||||
 | 
					  PARSE_DATA: "ACTION_PLAYER_GAME_PARSE_DATA"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PLAYER_STATS_ACTIONS = {
 | 
				
			||||||
 | 
					  CLEANUP: "ACTION_PLAYER_STATS_CLEANUP",
 | 
				
			||||||
 | 
					  SET_MODE: "ACTION_PLAYER_STATS_SET_MODE",
 | 
				
			||||||
 | 
					  LOAD_CHART_DATA: "ACTION_PLAYER_STATS_LOAD_CHART_DATA",
 | 
				
			||||||
 | 
					  PARSE_CHART_DATA: "ACTION_PLAYER_STATS_PARSE_CHART_DATA"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PLAYERS_ACTIONS = {
 | 
				
			||||||
 | 
					  CLEANUP: "ACTION_PLAYERS_CLEANUP",
 | 
				
			||||||
 | 
					  LOAD_DATA: "ACTION_PLAYERS_LOAD_DATA",
 | 
				
			||||||
 | 
					  PARSE_DATA: "ACTION_PLAYERS_PARSE_DATA"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SESSIONS_ACTIONS = {
 | 
				
			||||||
 | 
					  CLEANUP: "ACTION_SESSIONS_CLEANUP",
 | 
				
			||||||
 | 
					  LOAD_CHART_DATA: "ACTION_SESSIONS_LOAD_CHART_DATA",
 | 
				
			||||||
 | 
					  LOAD_DATA: "ACTION_SESSIONS_LOAD_DATA",
 | 
				
			||||||
 | 
					  PARSE_CHART_DATA: "ACTION_SESSIONS_PARSE_CHART_DATA",
 | 
				
			||||||
 | 
					  PARSE_DATA: "ACTION_SESSIONS_PARSE_DATA",
 | 
				
			||||||
 | 
					  SET_DAY: "ACTION_SESSIONS_SET_DAY"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SETTINGS_ACTIONS = {
 | 
				
			||||||
 | 
					  SET_LAYOUT: "ACTION_SETTINGS_SET_LAYOUT"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const WEAPON_NAMES = {
 | 
					export const WEAPON_NAMES = {
 | 
				
			||||||
  "BFG": "BFG10k",
 | 
					  "BFG": "BFG10k",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,6 @@ import React from "react";
 | 
				
			||||||
import ReactDOM from "react-dom";
 | 
					import ReactDOM from "react-dom";
 | 
				
			||||||
import {Router, Route, hashHistory} from "react-router";
 | 
					import {Router, Route, hashHistory} from "react-router";
 | 
				
			||||||
import {Provider} from "react-redux";
 | 
					import {Provider} from "react-redux";
 | 
				
			||||||
import "whatwg-fetch";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import AppWindow from "./AppWindow";
 | 
					import AppWindow from "./AppWindow";
 | 
				
			||||||
import SessionsView from "./views/SessionsView";
 | 
					import SessionsView from "./views/SessionsView";
 | 
				
			||||||
| 
						 | 
					@ -15,11 +14,11 @@ import PlayersView from "./views/PlayersView";
 | 
				
			||||||
import PlayerGameView from "./views/PlayerGameView";
 | 
					import PlayerGameView from "./views/PlayerGameView";
 | 
				
			||||||
import PlayerStatsView from "./views/PlayerStatsView";
 | 
					import PlayerStatsView from "./views/PlayerStatsView";
 | 
				
			||||||
import ErrorView from "./views/ErrorView";
 | 
					import ErrorView from "./views/ErrorView";
 | 
				
			||||||
import reactStore from "./store";
 | 
					import reduxStore from "./store";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.addEventListener("load", function () {
 | 
					window.addEventListener("load", function () {
 | 
				
			||||||
  ReactDOM.render(
 | 
					  ReactDOM.render(
 | 
				
			||||||
    <Provider store={reactStore}>
 | 
					    <Provider store={reduxStore}>
 | 
				
			||||||
      <Router history={hashHistory}>
 | 
					      <Router history={hashHistory}>
 | 
				
			||||||
        <Route path="/" component={AppWindow}>
 | 
					        <Route path="/" component={AppWindow}>
 | 
				
			||||||
          <Route path="/sessions/" component={SessionsView}>
 | 
					          <Route path="/sessions/" component={SessionsView}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										71
									
								
								frontend/src/reducers/dashboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								frontend/src/reducers/dashboard.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {DASHBOARD_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_STATE = {
 | 
				
			||||||
 | 
					  loading: true,
 | 
				
			||||||
 | 
					  day: "",
 | 
				
			||||||
 | 
					  emosOfTheMonth: [],
 | 
				
			||||||
 | 
					  fraggersOfTheMonth: [],
 | 
				
			||||||
 | 
					  categories: [],
 | 
				
			||||||
 | 
					  series: []
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dashboardReducer = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case DASHBOARD_ACTIONS.CLEANUP:
 | 
				
			||||||
 | 
					      return underscore.clone(DEFAULT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case DASHBOARD_ACTIONS.LOAD_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        loading: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case DASHBOARD_ACTIONS.PARSE_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        loading: false,
 | 
				
			||||||
 | 
					        day: action.data.day,
 | 
				
			||||||
 | 
					        emosOfTheMonth: action.data.eotm,
 | 
				
			||||||
 | 
					        fraggersOfTheMonth: action.data.fotm
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case DASHBOARD_ACTIONS.LOAD_CHART_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        categories: [],
 | 
				
			||||||
 | 
					        series: []
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case DASHBOARD_ACTIONS.PARSE_CHART_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        categories: action.categories,
 | 
				
			||||||
 | 
					        series: action.series
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default dashboardReducer;
 | 
				
			||||||
| 
						 | 
					@ -22,9 +22,21 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {combineReducers} from "redux";
 | 
					import {combineReducers} from "redux";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dashboardReducer from "./dashboard";
 | 
				
			||||||
 | 
					import navigationBarReducer from "./navigationBar";
 | 
				
			||||||
 | 
					import playerGameReducer from "./playerGame";
 | 
				
			||||||
 | 
					import playerStatsReducer from "./playerStats";
 | 
				
			||||||
 | 
					import playersReducer from "./players";
 | 
				
			||||||
 | 
					import sessionsReducer from "./sessions";
 | 
				
			||||||
import settingsReducer from "./settings";
 | 
					import settingsReducer from "./settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Q3StatsApp = combineReducers({
 | 
					const Q3StatsApp = combineReducers({
 | 
				
			||||||
 | 
					  dashboard: dashboardReducer,
 | 
				
			||||||
 | 
					  navigationBar: navigationBarReducer,
 | 
				
			||||||
 | 
					  playerGame: playerGameReducer,
 | 
				
			||||||
 | 
					  playerStats: playerStatsReducer,
 | 
				
			||||||
 | 
					  players: playersReducer,
 | 
				
			||||||
 | 
					  sessions: sessionsReducer,
 | 
				
			||||||
  settings: settingsReducer
 | 
					  settings: settingsReducer
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										75
									
								
								frontend/src/reducers/navigationBar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								frontend/src/reducers/navigationBar.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {NAVIGATION_BAR_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_STATE = {
 | 
				
			||||||
 | 
					  expanded: false,
 | 
				
			||||||
 | 
					  showAboutModal: false,
 | 
				
			||||||
 | 
					  showSettingsModal: false
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const navigationBarReducer = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case NAVIGATION_BAR_ACTIONS.CLEANUP:
 | 
				
			||||||
 | 
					      return underscore.clone(DEFAULT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case NAVIGATION_BAR_ACTIONS.EXPAND:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        expanded: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case NAVIGATION_BAR_ACTIONS.COLLAPSE:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        expanded: false
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case NAVIGATION_BAR_ACTIONS.SHOW_ABOUT_MODAL:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        expanded: false,
 | 
				
			||||||
 | 
					        showAboutModal: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case NAVIGATION_BAR_ACTIONS.HIDE_ABOUT_MODAL:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        showAboutModal: false
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case NAVIGATION_BAR_ACTIONS.SHOW_SETTINGS_MODAL:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        expanded: false,
 | 
				
			||||||
 | 
					        showSettingsModal: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case NAVIGATION_BAR_ACTIONS.HIDE_SETTINGS_MODAL:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        showSettingsModal: false
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default navigationBarReducer;
 | 
				
			||||||
							
								
								
									
										90
									
								
								frontend/src/reducers/playerGame.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								frontend/src/reducers/playerGame.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {PLAYER_GAME_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_STATE = {
 | 
				
			||||||
 | 
					  loading: true,
 | 
				
			||||||
 | 
					  map: "",
 | 
				
			||||||
 | 
					  weaponStats: {},
 | 
				
			||||||
 | 
					  itemStats: {},
 | 
				
			||||||
 | 
					  powerupStats: {},
 | 
				
			||||||
 | 
					  scoreChartSeries: [],
 | 
				
			||||||
 | 
					  damageChartSeries: [],
 | 
				
			||||||
 | 
					  totalsChartSeries: []
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processSerieData = (serie, name) => {
 | 
				
			||||||
 | 
					  if (!serie) {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "type": "pie",
 | 
				
			||||||
 | 
					      "name": name,
 | 
				
			||||||
 | 
					      "data": serie
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const playerGameReducer = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case PLAYER_GAME_ACTIONS.CLEANUP:
 | 
				
			||||||
 | 
					      return underscore.clone(DEFAULT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PLAYER_GAME_ACTIONS.LOAD_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        loading: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PLAYER_GAME_ACTIONS.PARSE_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        loading: false,
 | 
				
			||||||
 | 
					        map: action.data.map,
 | 
				
			||||||
 | 
					        weaponStats: action.data.weapons || {},
 | 
				
			||||||
 | 
					        itemStats: action.data.items || {},
 | 
				
			||||||
 | 
					        powerupStats: action.data.powerups || {}
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PLAYER_GAME_ACTIONS.LOAD_CHARTS_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        scoreChartSeries: [],
 | 
				
			||||||
 | 
					        damageChartSeries: [],
 | 
				
			||||||
 | 
					        totalsChartSeries: []
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PLAYER_GAME_ACTIONS.PARSE_CHARTS_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        scoreChartSeries: processSerieData(action.data.score),
 | 
				
			||||||
 | 
					        damageChartSeries: processSerieData(action.data.damage),
 | 
				
			||||||
 | 
					        totalsChartSeries: processSerieData(action.data.totals)
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default playerGameReducer;
 | 
				
			||||||
							
								
								
									
										101
									
								
								frontend/src/reducers/playerStats.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								frontend/src/reducers/playerStats.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,101 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {PLAYER_STATS_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_STATE = {
 | 
				
			||||||
 | 
					  mode: "session",
 | 
				
			||||||
 | 
					  sessionWinsChartData: {
 | 
				
			||||||
 | 
					    categories: [],
 | 
				
			||||||
 | 
					    series: []
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mapWinsChartData: {
 | 
				
			||||||
 | 
					    categories: [],
 | 
				
			||||||
 | 
					    series: []
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  sessionAccuracyChartData: {
 | 
				
			||||||
 | 
					    categories: [],
 | 
				
			||||||
 | 
					    series: []
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mapAccuracyChartData: {
 | 
				
			||||||
 | 
					    categories: [],
 | 
				
			||||||
 | 
					    series: []
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processChartData = (data, mode, kind) => {
 | 
				
			||||||
 | 
					  let parsedData = {
 | 
				
			||||||
 | 
					    categories: [],
 | 
				
			||||||
 | 
					    series: []
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (mode == "session") {
 | 
				
			||||||
 | 
					    parsedData.categories = data.dates;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    parsedData.categories = data.maps;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let key = mode;
 | 
				
			||||||
 | 
					  if (kind == "wins") {
 | 
				
			||||||
 | 
					    key = key + "WinsChartData";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parsedData.series = [
 | 
				
			||||||
 | 
					      {"name": "Wins", "data": data.wins},
 | 
				
			||||||
 | 
					      {"name": "Losses", "data": data.losses}
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					  } else if (kind == "accuracy") {
 | 
				
			||||||
 | 
					    key = key + "AccuracyChartData";
 | 
				
			||||||
 | 
					    parsedData.series = data.series;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let newState = {};
 | 
				
			||||||
 | 
					  newState[key] = parsedData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return newState;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const playerStatsReducer = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case PLAYER_STATS_ACTIONS.CLEANUP:
 | 
				
			||||||
 | 
					      return underscore.clone(DEFAULT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PLAYER_STATS_ACTIONS.SET_MODE:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        mode: action.mode
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PLAYER_STATS_ACTIONS.LOAD_CHART_DATA:
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case PLAYER_STATS_ACTIONS.PARSE_CHART_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn(
 | 
				
			||||||
 | 
					        {}, state, processChartData(action.data, action.mode, action.kind)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default playerStatsReducer;
 | 
				
			||||||
| 
						 | 
					@ -20,37 +20,34 @@
 | 
				
			||||||
 * IN THE SOFTWARE.
 | 
					 * IN THE SOFTWARE.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import underscore from "underscore";
 | 
					import underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_INIT = {
 | 
					import {PLAYERS_ACTIONS} from "../lib/defs";
 | 
				
			||||||
  credentials: "include"
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_STATE = {
 | 
				
			||||||
 | 
					  loading: true,
 | 
				
			||||||
 | 
					  players: []
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseView extends React.Component {
 | 
					const playersReducer = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
  fetch (requestOrURL, init) {
 | 
					  switch (action.type) {
 | 
				
			||||||
    init = init || {};
 | 
					    case PLAYERS_ACTIONS.CLEANUP:
 | 
				
			||||||
 | 
					      return underscore.clone(DEFAULT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let actualInit = underscore.extendOwn(
 | 
					    case PLAYERS_ACTIONS.LOAD_DATA:
 | 
				
			||||||
      underscore.clone(DEFAULT_INIT), init
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
    );
 | 
					        loading: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return window.fetch(requestOrURL, actualInit).catch((error) => {
 | 
					    case PLAYERS_ACTIONS.PARSE_DATA:
 | 
				
			||||||
      this.props.router.push("/error/");
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
      throw new Error("FetchError: Unable to fetch");
 | 
					        loading: false,
 | 
				
			||||||
    }).then((response) => {
 | 
					        players: action.data.players
 | 
				
			||||||
      if (!response.ok) {
 | 
					      });
 | 
				
			||||||
        let statusCode = response.status || "";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.props.router.push("/error/" + statusCode);
 | 
					    default:
 | 
				
			||||||
        throw new Error(
 | 
					      return state;
 | 
				
			||||||
          "FetchError: '" + statusCode + "'" + " '" + response.statusText + "'"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return response.json();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default BaseView;
 | 
					export default playersReducer;
 | 
				
			||||||
							
								
								
									
										79
									
								
								frontend/src/reducers/sessions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								frontend/src/reducers/sessions.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {SESSIONS_ACTIONS} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_STATE = {
 | 
				
			||||||
 | 
					  loading: true,
 | 
				
			||||||
 | 
					  day: "",
 | 
				
			||||||
 | 
					  previousDay: "",
 | 
				
			||||||
 | 
					  nextDay: "",
 | 
				
			||||||
 | 
					  games: [],
 | 
				
			||||||
 | 
					  categories: [],
 | 
				
			||||||
 | 
					  series: []
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sessionsReducer = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case SESSIONS_ACTIONS.CLEANUP:
 | 
				
			||||||
 | 
					      return underscore.clone(DEFAULT_STATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case SESSIONS_ACTIONS.SET_DAY:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        day: action.day,
 | 
				
			||||||
 | 
					        loading: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case SESSIONS_ACTIONS.LOAD_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        loading: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case SESSIONS_ACTIONS.PARSE_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        day: action.data.day,
 | 
				
			||||||
 | 
					        games: action.data.games,
 | 
				
			||||||
 | 
					        loading: false,
 | 
				
			||||||
 | 
					        nextDay: action.data.next_day || "",
 | 
				
			||||||
 | 
					        previousDay: action.data.previous_day || ""
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case SESSIONS_ACTIONS.LOAD_CHART_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        categories: [],
 | 
				
			||||||
 | 
					        series: []
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case SESSIONS_ACTIONS.PARSE_CHART_DATA:
 | 
				
			||||||
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
 | 
					        categories: action.categories,
 | 
				
			||||||
 | 
					        series: action.series
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default sessionsReducer;
 | 
				
			||||||
| 
						 | 
					@ -22,15 +22,13 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import underscore from "underscore";
 | 
					import underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {DEFAULT_LAYOUT, SETTINGS_ACTIONS, SETTINGS_KEYS} from "../lib/defs";
 | 
				
			||||||
  DEFAULT_LAYOUT, SETTINGS_KEY_LAYOUT, ACTION_SETTINGS_SET_LAYOUT
 | 
					 | 
				
			||||||
} from "../lib/defs";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_STATE = {};
 | 
					const DEFAULT_STATE = {};
 | 
				
			||||||
DEFAULT_STATE[SETTINGS_KEY_LAYOUT] = (function () {
 | 
					DEFAULT_STATE[SETTINGS_KEYS.LAYOUT] = (function () {
 | 
				
			||||||
  let result = null;
 | 
					  let result = null;
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    result = window.localStorage.getItem(SETTINGS_KEY_LAYOUT);
 | 
					    result = window.localStorage.getItem(SETTINGS_KEYS.LAYOUT);
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    // pass
 | 
					    // pass
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -38,9 +36,9 @@ DEFAULT_STATE[SETTINGS_KEY_LAYOUT] = (function () {
 | 
				
			||||||
  return result || DEFAULT_LAYOUT;
 | 
					  return result || DEFAULT_LAYOUT;
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const settings = (state = DEFAULT_STATE, action) => {
 | 
					const settingsReducer = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
  switch (action.type) {
 | 
					  switch (action.type) {
 | 
				
			||||||
    case ACTION_SETTINGS_SET_LAYOUT:
 | 
					    case SETTINGS_ACTIONS.SET_LAYOUT:
 | 
				
			||||||
      return underscore.extendOwn({}, state, {
 | 
					      return underscore.extendOwn({}, state, {
 | 
				
			||||||
        layout: action.layout
 | 
					        layout: action.layout
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
| 
						 | 
					@ -50,4 +48,4 @@ const settings = (state = DEFAULT_STATE, action) => {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default settings;
 | 
					export default settingsReducer;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ import Button from "react-bootstrap/lib/Button";
 | 
				
			||||||
import Modal from "react-bootstrap/lib/Modal";
 | 
					import Modal from "react-bootstrap/lib/Modal";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AboutModalView extends React.Component {
 | 
					class AboutModalView extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Modal show={this.props.show} onHide={this.props.onHide}>
 | 
					      <Modal show={this.props.show} onHide={this.props.onHide}>
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ class AboutModalView extends React.Component {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AboutModalView.PropTypes = {
 | 
					AboutModalView.propTypes = {
 | 
				
			||||||
  show: React.PropTypes.bool.isRequired,
 | 
					  show: React.PropTypes.bool.isRequired,
 | 
				
			||||||
  onHide: React.PropTypes.func.isRequired
 | 
					  onHide: React.PropTypes.func.isRequired
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,13 +21,14 @@
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import BaseView from "../lib/BaseView";
 | 
					 | 
				
			||||||
import ChartComponent from "../components/ChartComponent";
 | 
					import ChartComponent from "../components/ChartComponent";
 | 
				
			||||||
import ContainerComponent from "../components/ContainerComponent";
 | 
					import ContainerComponent from "../components/ContainerComponent";
 | 
				
			||||||
import LoaderComponent from "../components/LoaderComponent";
 | 
					import LoaderComponent from "../components/LoaderComponent";
 | 
				
			||||||
 | 
					import {cleanup, loadData} from "../actions/dashboard";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TopPlayersTableView extends React.Component {
 | 
					class TopPlayersTableView extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <table className="table table-striped table-bordered table-hover">
 | 
					      <table className="table table-striped table-bordered table-hover">
 | 
				
			||||||
| 
						 | 
					@ -64,7 +65,7 @@ TopPlayersTableView.propTypes = {
 | 
				
			||||||
  scores: React.PropTypes.array
 | 
					  scores: React.PropTypes.array
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DashboardView extends BaseView {
 | 
					class DashboardView extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,35 +92,14 @@ class DashboardView extends BaseView {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.state = {
 | 
					 | 
				
			||||||
      ready: false,
 | 
					 | 
				
			||||||
      day: "",
 | 
					 | 
				
			||||||
      emosOfTheMonth: [],
 | 
					 | 
				
			||||||
      fraggersOfTheMonth: [],
 | 
					 | 
				
			||||||
      categories: null,
 | 
					 | 
				
			||||||
      series: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentDidMount () {
 | 
					  componentDidMount () {
 | 
				
			||||||
    this.fetch("/api/v1/dashboard").then((data) => {
 | 
					    if (!this.props.day) {
 | 
				
			||||||
      this.setState({
 | 
					      this.props.dispatch(loadData(this.props.dispatch));
 | 
				
			||||||
        ready: true,
 | 
					    }
 | 
				
			||||||
        day: data.day,
 | 
					 | 
				
			||||||
        emosOfTheMonth: data.eotm,
 | 
					 | 
				
			||||||
        fraggersOfTheMonth: data.fotm
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.loadChartData();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  loadChartData () {
 | 
					  componentWillUnmount () {
 | 
				
			||||||
    this.fetch("/api/v1/charts/day/" + this.state.day).then((data) => {
 | 
					    this.props.dispatch(cleanup());
 | 
				
			||||||
      this.setState({
 | 
					 | 
				
			||||||
        categories: data.maps,
 | 
					 | 
				
			||||||
        series: data.scores
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
| 
						 | 
					@ -128,9 +108,9 @@ class DashboardView extends BaseView {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <ChartComponent
 | 
					        <ChartComponent
 | 
				
			||||||
          config={this.chartConfig}
 | 
					          config={this.chartConfig}
 | 
				
			||||||
          categories={this.state.categories}
 | 
					          categories={this.props.categories}
 | 
				
			||||||
          series={this.state.series}
 | 
					          series={this.props.series}
 | 
				
			||||||
          subtitle={this.state.day} />
 | 
					          subtitle={this.props.day} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="row">
 | 
					        <div className="row">
 | 
				
			||||||
          <div className="col-sm-6">
 | 
					          <div className="col-sm-6">
 | 
				
			||||||
| 
						 | 
					@ -138,7 +118,7 @@ class DashboardView extends BaseView {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <TopPlayersTableView
 | 
					            <TopPlayersTableView
 | 
				
			||||||
              scoreColumnTitle="Frags"
 | 
					              scoreColumnTitle="Frags"
 | 
				
			||||||
              scores={this.state.fraggersOfTheMonth} />
 | 
					              scores={this.props.fraggersOfTheMonth} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div className="col-sm-6">
 | 
					          <div className="col-sm-6">
 | 
				
			||||||
| 
						 | 
					@ -146,14 +126,29 @@ class DashboardView extends BaseView {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <TopPlayersTableView
 | 
					            <TopPlayersTableView
 | 
				
			||||||
              scoreColumnTitle="Suicides"
 | 
					              scoreColumnTitle="Suicides"
 | 
				
			||||||
              scores={this.state.emosOfTheMonth} />
 | 
					              scores={this.props.emosOfTheMonth} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <LoaderComponent visible={!this.state.ready} />
 | 
					        <LoaderComponent visible={this.props.loading} />
 | 
				
			||||||
      </ContainerComponent>
 | 
					      </ContainerComponent>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default DashboardView;
 | 
					DashboardView.propTypes = {
 | 
				
			||||||
 | 
					  loading: React.PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					  day: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  emosOfTheMonth: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  fraggersOfTheMonth: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  categories: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  series: React.PropTypes.array.isRequired
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mapStateToProps = (state, props) => {
 | 
				
			||||||
 | 
					  return state.dashboard;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reduxContainer = connect(mapStateToProps)(DashboardView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default reduxContainer;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ErrorView extends React.Component {
 | 
					class ErrorView extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="q3stats-error-view">
 | 
					      <div className="q3stats-error-view">
 | 
				
			||||||
| 
						 | 
					@ -44,4 +44,8 @@ class ErrorView extends React.Component {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ErrorView.propTypes = {
 | 
				
			||||||
 | 
					  params: React.PropTypes.object
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default ErrorView;
 | 
					export default ErrorView;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,15 +25,19 @@ import ContainerComponent from "../components/ContainerComponent";
 | 
				
			||||||
import Glyphicon from "react-bootstrap/lib/Glyphicon";
 | 
					import Glyphicon from "react-bootstrap/lib/Glyphicon";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import Navbar from "react-bootstrap/lib/Navbar";
 | 
					import Navbar from "react-bootstrap/lib/Navbar";
 | 
				
			||||||
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
import {Router, Link} from "react-router";
 | 
					import {Router, Link} from "react-router";
 | 
				
			||||||
import reduxStore from "../store";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  cleanup, expand, collapse, showAboutModal, hideAboutModal, showSettingsModal,
 | 
				
			||||||
 | 
					  hideSettingsModal
 | 
				
			||||||
 | 
					} from "../actions/navigationBar";
 | 
				
			||||||
import {LAYOUT_WIDE} from "../lib/defs";
 | 
					import {LAYOUT_WIDE} from "../lib/defs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import AboutModalView from "./AboutModalView";
 | 
					import AboutModalView from "./AboutModalView";
 | 
				
			||||||
import SettingsModalView from "./SettingsModalView";
 | 
					import SettingsModalView from "./SettingsModalView";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NavigationBarItemView extends React.Component {
 | 
					class NavigationBarItemView extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let className = ClassNames({
 | 
					    let className = ClassNames({
 | 
				
			||||||
      active: this.props.active
 | 
					      active: this.props.active
 | 
				
			||||||
| 
						 | 
					@ -56,66 +60,46 @@ NavigationBarItemView.propTypes = {
 | 
				
			||||||
  onClick: React.PropTypes.func.isRequired
 | 
					  onClick: React.PropTypes.func.isRequired
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NavigationBarView extends React.Component {
 | 
					class NavigationBarView extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
    this.onNavigationBarItemClick = this.onNavigationBarItemClick.bind(this);
 | 
					    this.onNavigationBarItemClick = this.onNavigationBarItemClick.bind(this);
 | 
				
			||||||
    this.onNavigationBarToggleExpanded = this.onNavigationBarToggleExpanded.bind(this);
 | 
					    this.onNavigationBarToggleExpanded = this.onNavigationBarToggleExpanded.bind(this);
 | 
				
			||||||
    this.onHideSettingsModal = this.onHideSettingsModal.bind(this);
 | 
					    this.onHideSettingsModal = this.onHideSettingsModal.bind(this);
 | 
				
			||||||
    this.onShowSettingsModalButtonClick = this.onShowSettingsModalButtonClick.bind(this);
 | 
					    this.onShowSettingsModalButtonClick = this.onShowSettingsModalButtonClick.bind(this);
 | 
				
			||||||
    this.onStoreChange = this.onStoreChange.bind(this);
 | 
					 | 
				
			||||||
    this.onHideAboutModal = this.onHideAboutModal.bind(this);
 | 
					    this.onHideAboutModal = this.onHideAboutModal.bind(this);
 | 
				
			||||||
    this.onShowAboutModalButtonClick = this.onShowAboutModalButtonClick.bind(this);
 | 
					    this.onShowAboutModalButtonClick = this.onShowAboutModalButtonClick.bind(this);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.state = {
 | 
					 | 
				
			||||||
      layout: reduxStore.getState().settings.layout,
 | 
					 | 
				
			||||||
      navBarExpanded: false,
 | 
					 | 
				
			||||||
      showAboutModal: false,
 | 
					 | 
				
			||||||
      showSettingsModal: false
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  componentDidMount () {
 | 
					 | 
				
			||||||
    this._storeUnsubscribe = reduxStore.subscribe(this.onStoreChange);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  componentWillUnmount () {
 | 
					 | 
				
			||||||
    this._storeUnsubscribe();
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  onNavigationBarItemClick (e) {
 | 
					  onNavigationBarItemClick (e) {
 | 
				
			||||||
    this.setState({navBarExpanded: false});
 | 
					    this.props.dispatch(collapse());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  onNavigationBarToggleExpanded (expanded) {
 | 
					  onNavigationBarToggleExpanded (expanded) {
 | 
				
			||||||
    this.setState({navBarExpanded: expanded});
 | 
					    if (expanded) {
 | 
				
			||||||
  }
 | 
					      this.props.dispatch(expand());
 | 
				
			||||||
  onHideSettingsModal () {
 | 
					    } else {
 | 
				
			||||||
    this.setState({showSettingsModal: false});
 | 
					      this.props.dispatch(collapse());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  onShowSettingsModalButtonClick () {
 | 
					  onShowSettingsModalButtonClick () {
 | 
				
			||||||
    this.setState({
 | 
					    this.props.dispatch(showSettingsModal());
 | 
				
			||||||
      navBarExpanded: false,
 | 
					 | 
				
			||||||
      showSettingsModal: true
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  onStoreChange () {
 | 
					  onHideSettingsModal () {
 | 
				
			||||||
    this.setState({layout: reduxStore.getState().settings.layout});
 | 
					    this.props.dispatch(hideSettingsModal());
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  onHideAboutModal () {
 | 
					 | 
				
			||||||
    this.setState({showAboutModal: false});
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  onShowAboutModalButtonClick () {
 | 
					  onShowAboutModalButtonClick () {
 | 
				
			||||||
    this.setState({
 | 
					    this.props.dispatch(showAboutModal());
 | 
				
			||||||
      navBarExpanded: false,
 | 
					  }
 | 
				
			||||||
      showAboutModal: true
 | 
					  onHideAboutModal () {
 | 
				
			||||||
    });
 | 
					    this.props.dispatch(hideAboutModal());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="q3stats-navigation-bar">
 | 
					      <div className="q3stats-navigation-bar">
 | 
				
			||||||
        <Navbar
 | 
					        <Navbar
 | 
				
			||||||
          ref="navbar"
 | 
					          ref="navbar"
 | 
				
			||||||
          collapseOnSelect
 | 
					          expanded={this.props.expanded}
 | 
				
			||||||
          expanded={this.state.navBarExpanded}
 | 
					 | 
				
			||||||
          fixedTop
 | 
					          fixedTop
 | 
				
			||||||
          fluid={this.state.layout == LAYOUT_WIDE}
 | 
					          fluid={this.props.layout == LAYOUT_WIDE}
 | 
				
			||||||
          onToggle={this.onNavigationBarToggleExpanded}>
 | 
					          onToggle={this.onNavigationBarToggleExpanded}>
 | 
				
			||||||
          <Navbar.Header>
 | 
					          <Navbar.Header>
 | 
				
			||||||
            <Navbar.Brand>
 | 
					            <Navbar.Brand>
 | 
				
			||||||
| 
						 | 
					@ -160,20 +144,38 @@ class NavigationBarView extends React.Component {
 | 
				
			||||||
          </Navbar.Collapse>
 | 
					          </Navbar.Collapse>
 | 
				
			||||||
        </Navbar>
 | 
					        </Navbar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <SettingsModalView
 | 
					 | 
				
			||||||
          show={this.state.showSettingsModal}
 | 
					 | 
				
			||||||
          onHide={this.onHideSettingsModal} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <AboutModalView
 | 
					        <AboutModalView
 | 
				
			||||||
          show={this.state.showAboutModal}
 | 
					          show={this.props.showAboutModal}
 | 
				
			||||||
          onHide={this.onHideAboutModal} />
 | 
					          onHide={this.onHideAboutModal} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <SettingsModalView
 | 
				
			||||||
 | 
					          show={this.props.showSettingsModal}
 | 
				
			||||||
 | 
					          onHide={this.onHideSettingsModal} />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NavigationBarView.PropTypes = {
 | 
					NavigationBarView.propTypes = {
 | 
				
			||||||
  router: React.PropTypes.instanceOf(Router).isRequired
 | 
					  expanded: React.PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					  layout: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  location: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  router: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  showAboutModal: React.PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					  showSettingsModal: React.PropTypes.bool.isRequired
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default NavigationBarView;
 | 
					const mapStateToProps = (state, props) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    expanded: state.navigationBar.expanded,
 | 
				
			||||||
 | 
					    layout: state.settings.layout,
 | 
				
			||||||
 | 
					    location: props.location,
 | 
				
			||||||
 | 
					    router: props.router,
 | 
				
			||||||
 | 
					    showAboutModal: state.navigationBar.showAboutModal,
 | 
				
			||||||
 | 
					    showSettingsModal: state.navigationBar.showSettingsModal
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reduxContainer = connect(mapStateToProps)(NavigationBarView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default reduxContainer;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,17 +21,18 @@
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
import underscore from "underscore";
 | 
					import underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import BaseView from "../lib/BaseView";
 | 
					 | 
				
			||||||
import ChartComponent from "../components/ChartComponent";
 | 
					import ChartComponent from "../components/ChartComponent";
 | 
				
			||||||
import ContainerComponent from "../components/ContainerComponent";
 | 
					import ContainerComponent from "../components/ContainerComponent";
 | 
				
			||||||
import ItemIconComponent from "../components/ItemIconComponent";
 | 
					import ItemIconComponent from "../components/ItemIconComponent";
 | 
				
			||||||
import LoaderComponent from "../components/LoaderComponent";
 | 
					import LoaderComponent from "../components/LoaderComponent";
 | 
				
			||||||
import PowerupIconComponent from "../components/PowerupIconComponent";
 | 
					import PowerupIconComponent from "../components/PowerupIconComponent";
 | 
				
			||||||
import WeaponIconComponent from "../components/WeaponIconComponent";
 | 
					import WeaponIconComponent from "../components/WeaponIconComponent";
 | 
				
			||||||
 | 
					import {cleanup, loadData} from "../actions/playerGame";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PlayerGameView extends BaseView {
 | 
					class PlayerGameView extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,87 +41,39 @@ class PlayerGameView extends BaseView {
 | 
				
			||||||
        "enabled": false
 | 
					        "enabled": false
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.state = {
 | 
					 | 
				
			||||||
      ready: false,
 | 
					 | 
				
			||||||
      map: "",
 | 
					 | 
				
			||||||
      weaponStats: {},
 | 
					 | 
				
			||||||
      itemStats: {},
 | 
					 | 
				
			||||||
      powerupStats: {},
 | 
					 | 
				
			||||||
      scoreChartSeries: [],
 | 
					 | 
				
			||||||
      damageChartSeries: [],
 | 
					 | 
				
			||||||
      totalsChartSeries: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentDidMount () {
 | 
					  componentDidMount () {
 | 
				
			||||||
    let url = (
 | 
					    this.props.dispatch(loadData(
 | 
				
			||||||
      "/api/v1/players/" + this.props.params.player + "/game/" +
 | 
					      this.props.dispatch, this.props.params.player, this.props.params.game
 | 
				
			||||||
      this.props.params.game
 | 
					    ));
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.fetch(url).then((data) => {
 | 
					 | 
				
			||||||
      this.setState({
 | 
					 | 
				
			||||||
        ready: true,
 | 
					 | 
				
			||||||
        map: data.map,
 | 
					 | 
				
			||||||
        weaponStats: data.weapons || {},
 | 
					 | 
				
			||||||
        itemStats: data.items || {},
 | 
					 | 
				
			||||||
        powerupStats: data.powerups || {}
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.loadChartsData();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  processSerieData (serie, name) {
 | 
					  componentWillUnmount () {
 | 
				
			||||||
    if (!serie) {
 | 
					    this.props.dispatch(cleanup());
 | 
				
			||||||
      return [];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "type": "pie",
 | 
					 | 
				
			||||||
        "name": name,
 | 
					 | 
				
			||||||
        "data": serie
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  loadChartsData () {
 | 
					 | 
				
			||||||
    let url = (
 | 
					 | 
				
			||||||
      "/api/v1/charts/player/" + this.props.params.player + "/game/" +
 | 
					 | 
				
			||||||
      this.props.params.game
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.fetch(url).then((data) => {
 | 
					 | 
				
			||||||
      this.setState({
 | 
					 | 
				
			||||||
        scoreChartSeries: this.processSerieData(data.score),
 | 
					 | 
				
			||||||
        damageChartSeries: this.processSerieData(data.damage),
 | 
					 | 
				
			||||||
        totalsChartSeries: this.processSerieData(data.totals)
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <ContainerComponent>
 | 
					      <ContainerComponent>
 | 
				
			||||||
        <h2>{this.props.params.player} stats on {this.state.map}</h2>
 | 
					        <h2>{this.props.params.player} stats on {this.props.map}</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="row">
 | 
					        <div className="row">
 | 
				
			||||||
          <div className="col-sm-4">
 | 
					          <div className="col-sm-4">
 | 
				
			||||||
            <ChartComponent
 | 
					            <ChartComponent
 | 
				
			||||||
              config={this.chartConfig}
 | 
					              config={this.chartConfig}
 | 
				
			||||||
              series={this.state.scoreChartSeries}
 | 
					              series={this.props.scoreChartSeries}
 | 
				
			||||||
              title="Score breakdown" />
 | 
					              title="Score breakdown" />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div className="col-sm-4">
 | 
					          <div className="col-sm-4">
 | 
				
			||||||
            <ChartComponent
 | 
					            <ChartComponent
 | 
				
			||||||
              config={this.chartConfig}
 | 
					              config={this.chartConfig}
 | 
				
			||||||
              series={this.state.damageChartSeries}
 | 
					              series={this.props.damageChartSeries}
 | 
				
			||||||
              title="Damage breakdown" />
 | 
					              title="Damage breakdown" />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div className="col-sm-4">
 | 
					          <div className="col-sm-4">
 | 
				
			||||||
            <ChartComponent
 | 
					            <ChartComponent
 | 
				
			||||||
              config={this.chartConfig}
 | 
					              config={this.chartConfig}
 | 
				
			||||||
              series={this.state.totalsChartSeries}
 | 
					              series={this.props.totalsChartSeries}
 | 
				
			||||||
              title="Health & armor" />
 | 
					              title="Health & armor" />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					@ -139,15 +92,15 @@ class PlayerGameView extends BaseView {
 | 
				
			||||||
          </thead>
 | 
					          </thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <tbody>
 | 
					          <tbody>
 | 
				
			||||||
            {underscore.keys(this.state.weaponStats).length == 0 &&
 | 
					            {underscore.keys(this.props.weaponStats).length == 0 &&
 | 
				
			||||||
              <tr>
 | 
					              <tr>
 | 
				
			||||||
                <td colSpan="5"><strong>No stats :(</strong></td>
 | 
					                <td colSpan="5"><strong>No stats :(</strong></td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {underscore.map(
 | 
					            {underscore.map(
 | 
				
			||||||
              underscore.keys(this.state.weaponStats), (key, index) => {
 | 
					              underscore.keys(this.props.weaponStats), (key, index) => {
 | 
				
			||||||
                let stats = this.state.weaponStats[key];
 | 
					                let stats = this.props.weaponStats[key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return (
 | 
					                return (
 | 
				
			||||||
                  <tr key={index}>
 | 
					                  <tr key={index}>
 | 
				
			||||||
| 
						 | 
					@ -176,15 +129,15 @@ class PlayerGameView extends BaseView {
 | 
				
			||||||
              </thead>
 | 
					              </thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <tbody>
 | 
					              <tbody>
 | 
				
			||||||
                {underscore.keys(this.state.itemStats).length == 0 &&
 | 
					                {underscore.keys(this.props.itemStats).length == 0 &&
 | 
				
			||||||
                  <tr>
 | 
					                  <tr>
 | 
				
			||||||
                    <td colSpan="2"><strong>No stats :(</strong></td>
 | 
					                    <td colSpan="2"><strong>No stats :(</strong></td>
 | 
				
			||||||
                  </tr>
 | 
					                  </tr>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                {underscore.map(
 | 
					                {underscore.map(
 | 
				
			||||||
                  underscore.keys(this.state.itemStats), (key, index) => {
 | 
					                  underscore.keys(this.props.itemStats), (key, index) => {
 | 
				
			||||||
                    let value = this.state.itemStats[key];
 | 
					                    let value = this.props.itemStats[key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return (
 | 
					                    return (
 | 
				
			||||||
                      <tr key={index}>
 | 
					                      <tr key={index}>
 | 
				
			||||||
| 
						 | 
					@ -211,15 +164,15 @@ class PlayerGameView extends BaseView {
 | 
				
			||||||
              </thead>
 | 
					              </thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <tbody>
 | 
					              <tbody>
 | 
				
			||||||
                {underscore.keys(this.state.powerupStats).length == 0 &&
 | 
					                {underscore.keys(this.props.powerupStats).length == 0 &&
 | 
				
			||||||
                  <tr>
 | 
					                  <tr>
 | 
				
			||||||
                    <td colSpan="3"><strong>No stats :(</strong></td>
 | 
					                    <td colSpan="3"><strong>No stats :(</strong></td>
 | 
				
			||||||
                  </tr>
 | 
					                  </tr>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                {underscore.map(
 | 
					                {underscore.map(
 | 
				
			||||||
                  underscore.keys(this.state.powerupStats), (key, index) => {
 | 
					                  underscore.keys(this.props.powerupStats), (key, index) => {
 | 
				
			||||||
                    let stats = this.state.powerupStats[key];
 | 
					                    let stats = this.props.powerupStats[key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return (
 | 
					                    return (
 | 
				
			||||||
                      <tr key={index}>
 | 
					                      <tr key={index}>
 | 
				
			||||||
| 
						 | 
					@ -235,10 +188,38 @@ class PlayerGameView extends BaseView {
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <LoaderComponent visible={!this.state.ready} />
 | 
					        <LoaderComponent visible={this.props.loading} />
 | 
				
			||||||
      </ContainerComponent>
 | 
					      </ContainerComponent>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default PlayerGameView;
 | 
					PlayerGameView.propTypes = {
 | 
				
			||||||
 | 
					  loading: React.PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					  map: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  weaponStats: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  itemStats: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  powerupStats: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  scoreChartSeries: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  damageChartSeries: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  totalsChartSeries: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  params: React.PropTypes.object.isRequired
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mapStateToProps = (state, props) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    loading: state.playerGame.loading,
 | 
				
			||||||
 | 
					    map: state.playerGame.map,
 | 
				
			||||||
 | 
					    weaponStats: state.playerGame.weaponStats,
 | 
				
			||||||
 | 
					    itemStats: state.playerGame.itemStats,
 | 
				
			||||||
 | 
					    powerupStats: state.playerGame.powerupStats,
 | 
				
			||||||
 | 
					    scoreChartSeries: state.playerGame.scoreChartSeries,
 | 
				
			||||||
 | 
					    damageChartSeries: state.playerGame.damageChartSeries,
 | 
				
			||||||
 | 
					    totalsChartSeries: state.playerGame.totalsChartSeries,
 | 
				
			||||||
 | 
					    params: props.params
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reduxContainer = connect(mapStateToProps)(PlayerGameView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default reduxContainer;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,16 +22,18 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ClassName from "classnames";
 | 
					import ClassName from "classnames";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
 | 
					import underscore from "underscore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import BaseView from "../lib/BaseView";
 | 
					 | 
				
			||||||
import ChartComponent from "../components/ChartComponent";
 | 
					import ChartComponent from "../components/ChartComponent";
 | 
				
			||||||
 | 
					import {cleanup, loadChartData, setMode} from "../actions/playerStats";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WINS_CHART_TITLE_SESSION = "Wins and losses by session";
 | 
					const WINS_CHART_TITLE_SESSION = "Wins and losses by session";
 | 
				
			||||||
const WINS_CHART_TITLE_MAP = "Wins and losses by map";
 | 
					const WINS_CHART_TITLE_MAP = "Wins and losses by map";
 | 
				
			||||||
const ACCURACY_CHART_TITLE_SESSION = "Average weapon accuracy by session";
 | 
					const ACCURACY_CHART_TITLE_SESSION = "Average weapon accuracy by session";
 | 
				
			||||||
const ACCURACY_CHART_TITLE_MAP = "Average weapon accuracy by map";
 | 
					const ACCURACY_CHART_TITLE_MAP = "Average weapon accuracy by map";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ModeLinkComponent extends React.Component {
 | 
					class ModeLinkComponent extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
    this.onClick = this.onClick.bind(this);
 | 
					    this.onClick = this.onClick.bind(this);
 | 
				
			||||||
| 
						 | 
					@ -62,80 +64,55 @@ ModeLinkComponent.propTypes = {
 | 
				
			||||||
  onSwitchMode: React.PropTypes.func.isRequired
 | 
					  onSwitchMode: React.PropTypes.func.isRequired
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseStatsChartComponent extends BaseView {
 | 
					class BaseStatsChartComponent extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.chartConfig = {};
 | 
					    this.chartConfig = {};
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.state = {
 | 
					 | 
				
			||||||
      sessionCategories: [],
 | 
					 | 
				
			||||||
      sessionSeries: [],
 | 
					 | 
				
			||||||
      mapCategories: [],
 | 
					 | 
				
			||||||
      mapSeries: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentDidMount () {
 | 
					  componentDidMount () {
 | 
				
			||||||
    this.load();
 | 
					    this.load();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentWillReceiveProps (nextProps) {
 | 
					 | 
				
			||||||
    if (nextProps.player != this.props.player) {
 | 
					 | 
				
			||||||
      this.setState({
 | 
					 | 
				
			||||||
        sessionCategories: [],
 | 
					 | 
				
			||||||
        sessionSeries: [],
 | 
					 | 
				
			||||||
        mapCategories: [],
 | 
					 | 
				
			||||||
        mapSeries: []
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  componentDidUpdate (prevProps, prevState) {
 | 
					  componentDidUpdate (prevProps, prevState) {
 | 
				
			||||||
    if (prevProps.player != this.props.player || prevProps.mode != this.props.mode) {
 | 
					    if (prevProps.player != this.props.player) {
 | 
				
			||||||
 | 
					      this.load(true);
 | 
				
			||||||
 | 
					    } else if (prevProps.mode != this.props.mode) {
 | 
				
			||||||
      this.load();
 | 
					      this.load();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  url () {
 | 
					  kind () {
 | 
				
			||||||
    throw new Error("Not Implemented");
 | 
					    throw new Error("Not Implemented");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  parse () {
 | 
					  load (force) {
 | 
				
			||||||
    throw new Error("Not Implemented");
 | 
					    if (underscore.isUndefined(force)) {
 | 
				
			||||||
  }
 | 
					      force = false;
 | 
				
			||||||
  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) {
 | 
					    let hasData = true;
 | 
				
			||||||
      this.fetch(this.url()).then((data) => {
 | 
					    if (this.props.mode == "session") {
 | 
				
			||||||
        let newState = {};
 | 
					      hasData = hasData && this.props.sessionData.categories.length > 0;
 | 
				
			||||||
        let parsedData = this.parse(data);
 | 
					      hasData = hasData && this.props.sessionData.series.length > 0;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      hasData = hasData && this.props.mapData.categories.length > 0;
 | 
				
			||||||
 | 
					      hasData = hasData && this.props.mapData.series.length > 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.props.mode == "session") {
 | 
					    if (force || !hasData) {
 | 
				
			||||||
          newState.sessionCategories = parsedData.categories;
 | 
					      this.props.dispatch(loadChartData(
 | 
				
			||||||
          newState.sessionSeries = parsedData.series;
 | 
					        this.props.dispatch, this.props.player, this.kind(), this.props.mode
 | 
				
			||||||
        } else {
 | 
					      ));
 | 
				
			||||||
          newState.mapCategories = parsedData.categories;
 | 
					 | 
				
			||||||
          newState.mapSeries = parsedData.series;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.setState(newState);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  title () {
 | 
					  title () {
 | 
				
			||||||
    throw new Error("Not Implemented");
 | 
					    throw new Error("Not Implemented");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let categories = this.state.sessionCategories;
 | 
					    let categories = this.props.sessionData.categories;
 | 
				
			||||||
    let series = this.state.sessionSeries;
 | 
					    let series = this.props.sessionData.series;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.props.mode == "map") {
 | 
					    if (this.props.mode == "map") {
 | 
				
			||||||
      categories = this.state.mapCategories;
 | 
					      categories = this.props.mapData.categories;
 | 
				
			||||||
      series = this.state.mapSeries;
 | 
					      series = this.props.mapData.series;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
| 
						 | 
					@ -188,27 +165,8 @@ class WinsChartComponent extends BaseStatsChartComponent {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  url () {
 | 
					  kind () {
 | 
				
			||||||
    return (
 | 
					    return "wins";
 | 
				
			||||||
      "/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 () {
 | 
					  title () {
 | 
				
			||||||
    if (this.props.mode == "map") {
 | 
					    if (this.props.mode == "map") {
 | 
				
			||||||
| 
						 | 
					@ -222,6 +180,13 @@ class WinsChartComponent extends BaseStatsChartComponent {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WinsChartComponent.propTypes = {
 | 
				
			||||||
 | 
					  player: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  mode: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  sessionData: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  mapData: React.PropTypes.object.isRequired
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AccuracyChartComponent extends BaseStatsChartComponent {
 | 
					class AccuracyChartComponent extends BaseStatsChartComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
| 
						 | 
					@ -241,25 +206,8 @@ class AccuracyChartComponent extends BaseStatsChartComponent {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  url () {
 | 
					  kind () {
 | 
				
			||||||
    return (
 | 
					    return "accuracy";
 | 
				
			||||||
      "/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 () {
 | 
					  title () {
 | 
				
			||||||
    if (this.props.mode == "map") {
 | 
					    if (this.props.mode == "map") {
 | 
				
			||||||
| 
						 | 
					@ -273,25 +221,28 @@ class AccuracyChartComponent extends BaseStatsChartComponent {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WinsChartComponent.propTypes = {
 | 
					AccuracyChartComponent.propTypes = {
 | 
				
			||||||
  player: React.PropTypes.string.isRequired,
 | 
					  player: React.PropTypes.string.isRequired,
 | 
				
			||||||
  mode: React.PropTypes.string.isRequired
 | 
					  mode: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  sessionData: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  mapData: React.PropTypes.object.isRequired
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PlayerStatsView extends React.Component {
 | 
					class PlayerStatsView extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
    this.onSwitchMode = this.onSwitchMode.bind(this);
 | 
					    this.onSwitchMode = this.onSwitchMode.bind(this);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.state = {
 | 
					 | 
				
			||||||
      mode: "session"
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentWillReceiveProps (nextProps) {
 | 
					  componentWillReceiveProps (nextProps) {
 | 
				
			||||||
    this.setState({mode: "session"});
 | 
					    if (nextProps.params.player != this.props.params.player) {
 | 
				
			||||||
 | 
					      this.props.dispatch(cleanup());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  onSwitchMode (mode) {
 | 
					  onSwitchMode (mode) {
 | 
				
			||||||
    this.setState({mode: mode});
 | 
					    this.props.dispatch(setMode(mode));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  componentWillUnmount () {
 | 
				
			||||||
 | 
					    this.props.dispatch(cleanup());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
| 
						 | 
					@ -300,13 +251,13 @@ class PlayerStatsView extends React.Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <ul className="nav nav-tabs">
 | 
					        <ul className="nav nav-tabs">
 | 
				
			||||||
          <ModeLinkComponent
 | 
					          <ModeLinkComponent
 | 
				
			||||||
            mode={this.state.mode}
 | 
					            mode={this.props.mode}
 | 
				
			||||||
            name="By session"
 | 
					            name="By session"
 | 
				
			||||||
            code="session"
 | 
					            code="session"
 | 
				
			||||||
            onSwitchMode={this.onSwitchMode} />
 | 
					            onSwitchMode={this.onSwitchMode} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <ModeLinkComponent
 | 
					          <ModeLinkComponent
 | 
				
			||||||
            mode={this.state.mode}
 | 
					            mode={this.props.mode}
 | 
				
			||||||
            name="By map"
 | 
					            name="By map"
 | 
				
			||||||
            code="map"
 | 
					            code="map"
 | 
				
			||||||
            onSwitchMode={this.onSwitchMode} />
 | 
					            onSwitchMode={this.onSwitchMode} />
 | 
				
			||||||
| 
						 | 
					@ -314,16 +265,42 @@ class PlayerStatsView extends React.Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <WinsChartComponent
 | 
					        <WinsChartComponent
 | 
				
			||||||
          player={this.props.params.player}
 | 
					          player={this.props.params.player}
 | 
				
			||||||
          mode={this.state.mode}
 | 
					          mode={this.props.mode}
 | 
				
			||||||
          router={this.props.router} />
 | 
					          sessionData={this.props.sessionWinsChartData}
 | 
				
			||||||
 | 
					          mapData={this.props.mapWinsChartData}
 | 
				
			||||||
 | 
					          dispatch={this.props.dispatch} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <AccuracyChartComponent
 | 
					        <AccuracyChartComponent
 | 
				
			||||||
          player={this.props.params.player}
 | 
					          player={this.props.params.player}
 | 
				
			||||||
          mode={this.state.mode}
 | 
					          mode={this.props.mode}
 | 
				
			||||||
          router={this.props.router} />
 | 
					          sessionData={this.props.sessionAccuracyChartData}
 | 
				
			||||||
 | 
					          mapData={this.props.mapAccuracyChartData}
 | 
				
			||||||
 | 
					          dispatch={this.props.dispatch} />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default PlayerStatsView;
 | 
					PlayerStatsView.propTypes = {
 | 
				
			||||||
 | 
					  mode: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  sessionWinsChartData: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  mapWinsChartData: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  sessionAccuracyChartData: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  mapAccuracyChartData: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  params: React.PropTypes.object.isRequired
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mapStateToProps = (state, props) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    mode: state.playerStats.mode,
 | 
				
			||||||
 | 
					    sessionWinsChartData: state.playerStats.sessionWinsChartData,
 | 
				
			||||||
 | 
					    mapWinsChartData: state.playerStats.mapWinsChartData,
 | 
				
			||||||
 | 
					    sessionAccuracyChartData: state.playerStats.sessionAccuracyChartData,
 | 
				
			||||||
 | 
					    mapAccuracyChartData: state.playerStats.mapAccuracyChartData,
 | 
				
			||||||
 | 
					    params: props.params
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reduxContainer = connect(mapStateToProps)(PlayerStatsView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default reduxContainer;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,13 +23,14 @@
 | 
				
			||||||
import ClassName from "classnames";
 | 
					import ClassName from "classnames";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import {Link} from "react-router";
 | 
					import {Link} from "react-router";
 | 
				
			||||||
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import BaseView from "../lib/BaseView";
 | 
					 | 
				
			||||||
import ContainerComponent from "../components/ContainerComponent";
 | 
					import ContainerComponent from "../components/ContainerComponent";
 | 
				
			||||||
import LoaderComponent from "../components/LoaderComponent";
 | 
					import LoaderComponent from "../components/LoaderComponent";
 | 
				
			||||||
import PlayerStatsView from "./PlayerStatsView";
 | 
					import PlayerStatsView from "./PlayerStatsView";
 | 
				
			||||||
 | 
					import {cleanup, loadData} from "../actions/players";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PlayerLinkComponent extends React.Component {
 | 
					class PlayerLinkComponent extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let className = ClassName({
 | 
					    let className = ClassName({
 | 
				
			||||||
      active: this.props.active
 | 
					      active: this.props.active
 | 
				
			||||||
| 
						 | 
					@ -48,22 +49,12 @@ PlayerLinkComponent.propTypes = {
 | 
				
			||||||
  player: React.PropTypes.string.isRequired
 | 
					  player: React.PropTypes.string.isRequired
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PlayersView extends BaseView {
 | 
					class PlayersView extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					 | 
				
			||||||
    super(props);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.state = {
 | 
					 | 
				
			||||||
      ready: false,
 | 
					 | 
				
			||||||
      players: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  componentDidMount () {
 | 
					  componentDidMount () {
 | 
				
			||||||
    this.fetch("/api/v1/players").then((data) => {
 | 
					    this.props.dispatch(loadData(this.props.dispatch));
 | 
				
			||||||
      this.setState({
 | 
					  }
 | 
				
			||||||
        ready: true,
 | 
					  componentWillUnmount () {
 | 
				
			||||||
        players: data.players
 | 
					    this.props.dispatch(cleanup());
 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
| 
						 | 
					@ -72,7 +63,7 @@ class PlayersView extends BaseView {
 | 
				
			||||||
          <div className="col-sm-3">
 | 
					          <div className="col-sm-3">
 | 
				
			||||||
            <h2>Players</h2>
 | 
					            <h2>Players</h2>
 | 
				
			||||||
            <ul className="nav nav-pills nav-stacked">
 | 
					            <ul className="nav nav-pills nav-stacked">
 | 
				
			||||||
              {this.state.players.map((item, index) => {
 | 
					              {this.props.players.map((item, index) => {
 | 
				
			||||||
                return (
 | 
					                return (
 | 
				
			||||||
                  <PlayerLinkComponent
 | 
					                  <PlayerLinkComponent
 | 
				
			||||||
                    key={index}
 | 
					                    key={index}
 | 
				
			||||||
| 
						 | 
					@ -93,10 +84,27 @@ class PlayersView extends BaseView {
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <LoaderComponent visible={!this.state.ready} />
 | 
					        <LoaderComponent visible={this.props.loading} />
 | 
				
			||||||
      </ContainerComponent>
 | 
					      </ContainerComponent>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default PlayersView;
 | 
					PlayersView.propTypes = {
 | 
				
			||||||
 | 
					  loading: React.PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					  players: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  params: React.PropTypes.object.isRequired
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mapStateToProps = (state, props) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    loading: state.players.loading,
 | 
				
			||||||
 | 
					    players: state.players.players,
 | 
				
			||||||
 | 
					    params: props.params
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reduxContainer = connect(mapStateToProps)(PlayersView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default reduxContainer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,15 +22,16 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ClassName from "classnames";
 | 
					import ClassName from "classnames";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
import {Link} from "react-router";
 | 
					import {Link} from "react-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import BaseView from "../lib/BaseView";
 | 
					 | 
				
			||||||
import ChartComponent from "../components/ChartComponent";
 | 
					import ChartComponent from "../components/ChartComponent";
 | 
				
			||||||
import ContainerComponent from "../components/ContainerComponent";
 | 
					import ContainerComponent from "../components/ContainerComponent";
 | 
				
			||||||
import LoaderComponent from "../components/LoaderComponent";
 | 
					import LoaderComponent from "../components/LoaderComponent";
 | 
				
			||||||
import WeaponIconComponent from "../components/WeaponIconComponent";
 | 
					import WeaponIconComponent from "../components/WeaponIconComponent";
 | 
				
			||||||
 | 
					import {cleanup, setDay} from "../actions/sessions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NavComponent extends React.Component {
 | 
					class NavComponent extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    let previousClassName = ClassName("previous", {
 | 
					    let previousClassName = ClassName("previous", {
 | 
				
			||||||
      disabled: !this.props.previousDay
 | 
					      disabled: !this.props.previousDay
 | 
				
			||||||
| 
						 | 
					@ -64,7 +65,7 @@ NavComponent.propTypes = {
 | 
				
			||||||
  nextDay: React.PropTypes.string
 | 
					  nextDay: React.PropTypes.string
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GamesTableView extends React.Component {
 | 
					class GamesTableView extends React.PureComponent {
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <table className="table table-striped table-bordered table-hover">
 | 
					      <table className="table table-striped table-bordered table-hover">
 | 
				
			||||||
| 
						 | 
					@ -136,7 +137,7 @@ GamesTableView.propTypes = {
 | 
				
			||||||
  games: React.PropTypes.array.isRequired
 | 
					  games: React.PropTypes.array.isRequired
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SessionsView extends BaseView {
 | 
					class SessionsView extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -163,96 +164,80 @@ class SessionsView extends BaseView {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
    this.state = {
 | 
					  setDay (day) {
 | 
				
			||||||
      shouldReload: false,
 | 
					    this.props.dispatch(setDay(this.props.dispatch, day));
 | 
				
			||||||
      ready: false,
 | 
					 | 
				
			||||||
      day: "",
 | 
					 | 
				
			||||||
      previousDay: "",
 | 
					 | 
				
			||||||
      nextDay: "",
 | 
					 | 
				
			||||||
      games: [],
 | 
					 | 
				
			||||||
      categories: null,
 | 
					 | 
				
			||||||
      series: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentDidMount () {
 | 
					  componentDidMount () {
 | 
				
			||||||
    this.loadData();
 | 
					    this.setDay(this.props.params.day);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  componentWillUnmount () {
 | 
				
			||||||
 | 
					    this.props.dispatch(cleanup());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentDidUpdate (prevProps) {
 | 
					  componentDidUpdate (prevProps) {
 | 
				
			||||||
    if ((this.state.shouldReload) && (!this.state.ready)) {
 | 
					    if (prevProps.params.day != this.props.params.day) {
 | 
				
			||||||
      this.setState({shouldReload: false});
 | 
					      this.setDay(this.props.params.day);
 | 
				
			||||||
      this.loadData();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  componentWillReceiveProps (nextProps) {
 | 
					 | 
				
			||||||
    if (nextProps.params.day != this.props.params.day) {
 | 
					 | 
				
			||||||
      this.setState({
 | 
					 | 
				
			||||||
        shouldReload: true,
 | 
					 | 
				
			||||||
        ready: false,
 | 
					 | 
				
			||||||
        day: "",
 | 
					 | 
				
			||||||
        previousDay: "",
 | 
					 | 
				
			||||||
        nextDay: "",
 | 
					 | 
				
			||||||
        games: [],
 | 
					 | 
				
			||||||
        categories: null,
 | 
					 | 
				
			||||||
        series: []
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  loadData () {
 | 
					 | 
				
			||||||
    this.fetch("/api/v1/sessions/" + (this.props.params.day || "")).then((data) => {
 | 
					 | 
				
			||||||
      this.setState({
 | 
					 | 
				
			||||||
        shouldReload: false,
 | 
					 | 
				
			||||||
        ready: true,
 | 
					 | 
				
			||||||
        day: data.day,
 | 
					 | 
				
			||||||
        previousDay: data.previous_day,
 | 
					 | 
				
			||||||
        nextDay: data.next_day,
 | 
					 | 
				
			||||||
        games: data.games
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.loadChartData();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  loadChartData () {
 | 
					 | 
				
			||||||
    this.fetch("/api/v1/charts/day/" + this.state.day).then((data) => {
 | 
					 | 
				
			||||||
      this.setState({
 | 
					 | 
				
			||||||
        categories: data.maps,
 | 
					 | 
				
			||||||
        series: data.scores
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <ContainerComponent>
 | 
					      <ContainerComponent>
 | 
				
			||||||
        {this.state.ready &&
 | 
					        {!this.props.loading &&
 | 
				
			||||||
          <div>
 | 
					          <div>
 | 
				
			||||||
            <NavComponent
 | 
					            <NavComponent
 | 
				
			||||||
              previousDay={this.state.previousDay}
 | 
					              previousDay={this.props.previousDay}
 | 
				
			||||||
              nextDay={this.state.nextDay} />
 | 
					              nextDay={this.props.nextDay} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <h2>Results</h2>
 | 
					            <h2>Results</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ChartComponent
 | 
					            <ChartComponent
 | 
				
			||||||
              config={this.chartConfig}
 | 
					              config={this.chartConfig}
 | 
				
			||||||
              categories={this.state.categories}
 | 
					              categories={this.props.categories}
 | 
				
			||||||
              series={this.state.series}
 | 
					              series={this.props.series}
 | 
				
			||||||
              subtitle={this.state.day} />
 | 
					              subtitle={this.props.day} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <h2>Stats</h2>
 | 
					            <h2>Stats</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <GamesTableView
 | 
					            <GamesTableView
 | 
				
			||||||
              games={this.state.games} />
 | 
					              games={this.props.games} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <NavComponent
 | 
					            <NavComponent
 | 
				
			||||||
              onNavigateToDay={this.onNavigateToDay}
 | 
					              onNavigateToDay={this.onNavigateToDay}
 | 
				
			||||||
              previousDay={this.state.previousDay}
 | 
					              previousDay={this.props.previousDay}
 | 
				
			||||||
              nextDay={this.state.nextDay} />
 | 
					              nextDay={this.props.nextDay} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <LoaderComponent visible={!this.state.ready} />
 | 
					        <LoaderComponent visible={this.props.loading} />
 | 
				
			||||||
      </ContainerComponent>
 | 
					      </ContainerComponent>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default SessionsView;
 | 
					SessionsView.propTypes = {
 | 
				
			||||||
 | 
					  categories: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  day: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  games: React.PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					  loading: React.PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					  nextDay: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  params: React.PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  previousDay: React.PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  series: React.PropTypes.array.isRequired
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mapStateToProps = (state, props) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    loading: state.sessions.loading,
 | 
				
			||||||
 | 
					    day: state.sessions.day,
 | 
				
			||||||
 | 
					    previousDay: state.sessions.previousDay,
 | 
				
			||||||
 | 
					    nextDay: state.sessions.nextDay,
 | 
				
			||||||
 | 
					    games: state.sessions.games,
 | 
				
			||||||
 | 
					    categories: state.sessions.categories,
 | 
				
			||||||
 | 
					    series: state.sessions.series,
 | 
				
			||||||
 | 
					    params: props.params
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reduxContainer = connect(mapStateToProps)(SessionsView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default reduxContainer;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,10 +29,12 @@ import Modal from "react-bootstrap/lib/Modal";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import {connect} from "react-redux";
 | 
					import {connect} from "react-redux";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {DEFAULT_LAYOUT, LAYOUT_CHOICES, SETTINGS_KEY_LAYOUT} from "../lib/defs";
 | 
					import {
 | 
				
			||||||
 | 
					  DEFAULT_LAYOUT, LAYOUT_CHOICES, SETTINGS_KEY_LAYOUT
 | 
				
			||||||
 | 
					} from "../lib/defs";
 | 
				
			||||||
import {setLayout} from "../actions/settings";
 | 
					import {setLayout} from "../actions/settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SettingsModalView extends React.Component {
 | 
					class SettingsModalView extends React.PureComponent {
 | 
				
			||||||
  constructor (props) {
 | 
					  constructor (props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
    this.onSaveButtonClick = this.onSaveButtonClick.bind(this);
 | 
					    this.onSaveButtonClick = this.onSaveButtonClick.bind(this);
 | 
				
			||||||
| 
						 | 
					@ -42,16 +44,15 @@ class SettingsModalView extends React.Component {
 | 
				
			||||||
      layout: this.props.settings.layout
 | 
					      layout: this.props.settings.layout
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  componentWillReceiveProps (nextProps) {
 | 
				
			||||||
 | 
					    if (!this.props.show && nextProps.show) {
 | 
				
			||||||
 | 
					      this.setState({layout: this.props.settings.layout});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  onSaveButtonClick (e) {
 | 
					  onSaveButtonClick (e) {
 | 
				
			||||||
    e.stopPropagation();
 | 
					    e.stopPropagation();
 | 
				
			||||||
    e.preventDefault();
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      window.localStorage.setItem(SETTINGS_KEY_LAYOUT, this.state.layout);
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      // pass
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.props.dispatch(setLayout(this.state.layout));
 | 
					    this.props.dispatch(setLayout(this.state.layout));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.props.onHide();
 | 
					    this.props.onHide();
 | 
				
			||||||
| 
						 | 
					@ -95,7 +96,6 @@ class SettingsModalView extends React.Component {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SettingsModalView.PropTypes = {
 | 
					SettingsModalView.PropTypes = {
 | 
				
			||||||
  dispatch: React.PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  settings: React.PropTypes.object.isRequired,
 | 
					  settings: React.PropTypes.object.isRequired,
 | 
				
			||||||
  show: React.PropTypes.bool.isRequired,
 | 
					  show: React.PropTypes.bool.isRequired,
 | 
				
			||||||
  onHide: React.PropTypes.func.isRequired
 | 
					  onHide: React.PropTypes.func.isRequired
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -92,6 +92,8 @@ def get_api_v1_player_game(player, game_uuid):
 | 
				
			||||||
        abort(404)
 | 
					        abort(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    result = {
 | 
					    result = {
 | 
				
			||||||
 | 
					        "game": game_uuid,
 | 
				
			||||||
 | 
					        "player": player,
 | 
				
			||||||
        "map": game.map,
 | 
					        "map": game.map,
 | 
				
			||||||
        "items": score.items,
 | 
					        "items": score.items,
 | 
				
			||||||
        "weapons": _process_weapons(score.weapons),
 | 
					        "weapons": _process_weapons(score.weapons),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,6 +86,8 @@ class Test_GetAPIv1Players(BaseQ3StatsWebAppTestCase):
 | 
				
			||||||
            rsp = self.client.get('/api/v1/players/Player 1/game/game1')
 | 
					            rsp = self.client.get('/api/v1/players/Player 1/game/game1')
 | 
				
			||||||
            assert rsp.status_code == 200
 | 
					            assert rsp.status_code == 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            assert rsp.json['game'] == 'game1'
 | 
				
			||||||
 | 
					            assert rsp.json['player'] == 'Player 1'
 | 
				
			||||||
            assert rsp.json['map'] == 'Q3DM7'
 | 
					            assert rsp.json['map'] == 'Q3DM7'
 | 
				
			||||||
            assert rsp.json['items'] == {'YA': 1}
 | 
					            assert rsp.json['items'] == {'YA': 1}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										0
									
								
								tests_web_app/tmp/.placeholder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests_web_app/tmp/.placeholder
									
									
									
									
									
										Normal file
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user