aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/web/src/gui/core
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy/web/src/gui/core')
-rw-r--r--diplomacy/web/src/gui/core/action.jsx (renamed from diplomacy/web/src/gui/core/widgets.jsx)50
-rw-r--r--diplomacy/web/src/gui/core/button.jsx52
-rw-r--r--diplomacy/web/src/gui/core/content.jsx51
-rw-r--r--diplomacy/web/src/gui/core/fancybox.jsx2
-rw-r--r--diplomacy/web/src/gui/core/forms.jsx2
-rw-r--r--diplomacy/web/src/gui/core/page.jsx277
-rw-r--r--diplomacy/web/src/gui/core/tab.jsx29
-rw-r--r--diplomacy/web/src/gui/core/tabs.jsx29
8 files changed, 159 insertions, 333 deletions
diff --git a/diplomacy/web/src/gui/core/widgets.jsx b/diplomacy/web/src/gui/core/action.jsx
index 62a5eb4..73fe8cb 100644
--- a/diplomacy/web/src/gui/core/widgets.jsx
+++ b/diplomacy/web/src/gui/core/action.jsx
@@ -17,56 +17,6 @@
import React from "react";
import PropTypes from 'prop-types';
-export class Button extends React.Component {
- /** Bootstrap button.
- * Bootstrap classes:
- * - btn
- * - btn-primary
- * - mx-1 (margin-left 1px, margin-right 1px)
- * Props: title (str), onClick (function).
- * **/
- // title
- // onClick
- // pickEvent = false
- // large = false
- // small = false
-
- constructor(props) {
- super(props);
- this.onClick = this.onClick.bind(this);
- }
-
- onClick(event) {
- if (this.props.onClick)
- this.props.onClick(this.props.pickEvent ? event : null);
- }
-
- render() {
- return (
- <button
- className={`btn btn-${this.props.color || 'secondary'}` + (this.props.large ? ' btn-block' : '') + (this.props.small ? ' btn-sm' : '')}
- disabled={this.props.disabled}
- onClick={this.onClick}>
- <strong>{this.props.title}</strong>
- </button>
- );
- }
-}
-
-Button.propTypes = {
- title: PropTypes.string.isRequired,
- onClick: PropTypes.func.isRequired,
- color: PropTypes.string,
- large: PropTypes.bool,
- small: PropTypes.bool,
- pickEvent: PropTypes.bool,
- disabled: PropTypes.bool
-};
-
-Button.defaultPropTypes = {
- disabled: false
-};
-
export class Action extends React.Component {
// title
diff --git a/diplomacy/web/src/gui/core/button.jsx b/diplomacy/web/src/gui/core/button.jsx
new file mode 100644
index 0000000..0d5dadd
--- /dev/null
+++ b/diplomacy/web/src/gui/core/button.jsx
@@ -0,0 +1,52 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+export class Button extends React.Component {
+ /** Bootstrap button.
+ * Bootstrap classes:
+ * - btn
+ * - btn-primary
+ * - mx-1 (margin-left 1px, margin-right 1px)
+ * Props: title (str), onClick (function).
+ * **/
+ // title
+ // onClick
+ // pickEvent = false
+ // large = false
+ // small = false
+
+ constructor(props) {
+ super(props);
+ this.onClick = this.onClick.bind(this);
+ }
+
+ onClick(event) {
+ if (this.props.onClick)
+ this.props.onClick(this.props.pickEvent ? event : null);
+ }
+
+ render() {
+ return (
+ <button
+ className={`btn btn-${this.props.color || 'secondary'}` + (this.props.large ? ' btn-block' : '') + (this.props.small ? ' btn-sm' : '')}
+ disabled={this.props.disabled}
+ onClick={this.onClick}>
+ <strong>{this.props.title}</strong>
+ </button>
+ );
+ }
+}
+
+Button.propTypes = {
+ title: PropTypes.string.isRequired,
+ onClick: PropTypes.func.isRequired,
+ color: PropTypes.string,
+ large: PropTypes.bool,
+ small: PropTypes.bool,
+ pickEvent: PropTypes.bool,
+ disabled: PropTypes.bool
+};
+
+Button.defaultPropTypes = {
+ disabled: false
+};
diff --git a/diplomacy/web/src/gui/core/content.jsx b/diplomacy/web/src/gui/core/content.jsx
deleted file mode 100644
index 416ba9e..0000000
--- a/diplomacy/web/src/gui/core/content.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// ==============================================================================
-// Copyright (C) 2019 - Philip Paquette, Steven Bocco
-//
-// This program is free software: you can redistribute it and/or modify it under
-// the terms of the GNU Affero General Public License as published by the Free
-// Software Foundation, either version 3 of the License, or (at your option) any
-// later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-// details.
-//
-// You should have received a copy of the GNU Affero General Public License along
-// with this program. If not, see <https://www.gnu.org/licenses/>.
-// ==============================================================================
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export class Content extends React.Component {
- // PROPERTIES:
- // page: pointer to parent Page object
- // data: data for current content
-
- // Each derived class must implement this static method.
- static builder(page, data) {
- return {
- // page title (string)
- title: `${data ? 'with data' : 'without data'}`,
- // page navigation links: array of couples
- // (navigation title, navigation callback ( onClick=() => callback() ))
- navigation: [],
- // page content: React component (e.g. <MyComponent/>, or <div class="content">...</div>, etc).
- component: null
- };
- }
-
- getPage() {
- return this.props.page;
- }
-
- componentDidMount() {
- window.scrollTo(0, 0);
- }
-}
-
-
-Content.propTypes = {
- page: PropTypes.object.isRequired,
- data: PropTypes.object
-};
diff --git a/diplomacy/web/src/gui/core/fancybox.jsx b/diplomacy/web/src/gui/core/fancybox.jsx
index 4d1013d..66a1efe 100644
--- a/diplomacy/web/src/gui/core/fancybox.jsx
+++ b/diplomacy/web/src/gui/core/fancybox.jsx
@@ -15,8 +15,8 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Button} from "./widgets";
import PropTypes from 'prop-types';
+import {Button} from "./button";
const TIMES = '\u00D7';
diff --git a/diplomacy/web/src/gui/core/forms.jsx b/diplomacy/web/src/gui/core/forms.jsx
index 76d188c..da7250d 100644
--- a/diplomacy/web/src/gui/core/forms.jsx
+++ b/diplomacy/web/src/gui/core/forms.jsx
@@ -15,8 +15,8 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from "react";
-import {Button} from "./widgets";
import {UTILS} from "../../diplomacy/utils/utils";
+import {Button} from "./button";
export class Forms {
static createOnChangeCallback(component, callback) {
diff --git a/diplomacy/web/src/gui/core/page.jsx b/diplomacy/web/src/gui/core/page.jsx
index 5ca09fd..ad830f1 100644
--- a/diplomacy/web/src/gui/core/page.jsx
+++ b/diplomacy/web/src/gui/core/page.jsx
@@ -18,22 +18,14 @@
import React from "react";
import {ContentConnection} from "../diplomacy/contents/content_connection";
-import {ContentGames} from "../diplomacy/contents/content_games";
-import {ContentGame} from "../diplomacy/contents/content_game";
import {UTILS} from "../../diplomacy/utils/utils";
import {Diplog} from "../../diplomacy/utils/diplog";
-import {STRINGS} from "../../diplomacy/utils/strings";
-import {Game} from "../../diplomacy/engine/game";
-import Octicon, {Person} from '@githubprimer/octicons-react';
-import $ from "jquery";
import {FancyBox} from "./fancybox";
import {DipStorage} from "../diplomacy/utils/dipStorage";
-
-const CONTENTS = {
- connection: ContentConnection,
- games: ContentGames,
- game: ContentGame
-};
+import {PageContext} from "../diplomacy/widgets/page_context";
+import {ContentGames} from "../diplomacy/contents/content_games";
+import {loadGameFromDisk} from "../diplomacy/utils/load_game_from_disk";
+import {ContentGame} from "../diplomacy/contents/content_game";
export class Page extends React.Component {
@@ -50,23 +42,18 @@ export class Page extends React.Component {
error: null,
info: null,
success: null,
- title: null,
// Page content parameters
- contentName: 'connection',
- contentData: null,
+ name: null,
+ body: null,
// Games.
games: {}, // Games found.
myGames: {} // Games locally stored.
};
- this.loadPage = this.loadPage.bind(this);
- this.loadConnection = this.loadConnection.bind(this);
- this.loadGames = this.loadGames.bind(this);
- this.loadGame = this.loadGame.bind(this);
- this.loadGameFromDisk = this.loadGameFromDisk.bind(this);
- this.logout = this.logout.bind(this);
this.error = this.error.bind(this);
this.info = this.info.bind(this);
this.success = this.success.bind(this);
+ this.logout = this.logout.bind(this);
+ this.loadGameFromDisk = this.loadGameFromDisk.bind(this);
this.unloadFancyBox = this.unloadFancyBox.bind(this);
}
@@ -80,26 +67,8 @@ export class Page extends React.Component {
return games;
}
- copyState(updatedFields) {
- return Object.assign({}, this.state, updatedFields || {});
- }
-
- //// Methods to check page type.
-
- __page_is(contentName, contentData) {
- return this.state.contentName === contentName && (!contentData || this.state.contentData === contentData);
- }
-
- pageIsConnection(contentData) {
- return this.__page_is('connection', contentData);
- }
-
- pageIsGames(contentData) {
- return this.__page_is('games', contentData);
- }
-
- pageIsGame(contentData) {
- return this.__page_is('game', contentData);
+ static defaultPage() {
+ return <ContentConnection/>;
}
//// Methods to load a global fancybox.
@@ -114,91 +83,39 @@ export class Page extends React.Component {
//// Methods to load a page.
- loadPage(contentName, contentData, messages) {
- messages = messages || {};
- messages.error = Page.wrapMessage(messages.error);
- messages.info = Page.wrapMessage(messages.info);
- messages.success = Page.wrapMessage(messages.success);
- Diplog.printMessages(messages);
- this.setState(this.copyState({
- error: messages.error,
- info: messages.info,
- success: messages.success,
- contentName: contentName,
- contentData: contentData,
- title: null,
- fancyTitle: null,
- onFancyBox: null
- }));
- }
-
- loadConnection(contentData, messages) {
- this.loadPage('connection', contentData, messages);
+ load(name, body, messages) {
+ const newState = {};
+ if (messages) {
+ for (let key of ['error', 'info', 'success'])
+ newState[key] = Page.wrapMessage(messages[key]);
+ }
+ Diplog.printMessages(newState);
+ newState.name = name;
+ newState.body = body;
+ this.setState(newState);
}
- loadGames(contentData, messages) {
- this.loadPage('games', contentData, messages);
+ loadGames(messages) {
+ this.load(
+ 'games',
+ <ContentGames myGames={this.getMyGames()} gamesFound={this.getGamesFound()}/>,
+ messages
+ );
}
- loadGame(gameInfo, messages) {
- this.loadPage('game', gameInfo, messages);
+ loadGameFromDisk() {
+ loadGameFromDisk(
+ (game) => this.load(
+ `game: ${game.game_id}`,
+ <ContentGame data={game}/>,
+ {success: `Game loaded from disk: ${game.game_id}`}
+ ),
+ this.error
+ );
}
- loadGameFromDisk() {
- const input = $(document.createElement('input'));
- input.attr("type", "file");
- input.trigger('click');
- input.change(event => {
- const file = event.target.files[0];
- if (!file.name.match(/\.json$/i)) {
- this.error(`Invalid JSON filename ${file.name}`);
- } else {
- const reader = new FileReader();
- reader.onload = () => {
- const savedData = JSON.parse(reader.result);
- const gameObject = {};
- gameObject.game_id = `(local) ${savedData.id}`;
- gameObject.map_name = savedData.map;
- gameObject.rules = savedData.rules;
- const state_history = {};
- const message_history = {};
- const order_history = {};
- const result_history = {};
- for (let savedPhase of savedData.phases) {
- const gameState = savedPhase.state;
- const phaseOrders = savedPhase.orders || {};
- const phaseResults = savedPhase.results || {};
- const phaseMessages = {};
- if (savedPhase.messages) {
- for (let message of savedPhase.messages) {
- phaseMessages[message.time_sent] = message;
- }
- }
- if (!gameState.name)
- gameState.name = savedPhase.name;
- state_history[gameState.name] = gameState;
- order_history[gameState.name] = phaseOrders;
- message_history[gameState.name] = phaseMessages;
- result_history[gameState.name] = phaseResults;
- }
- gameObject.state_history = state_history;
- gameObject.message_history = message_history;
- gameObject.order_history = order_history;
- gameObject.state_history = state_history;
- gameObject.result_history = result_history;
- gameObject.messages = [];
- gameObject.role = STRINGS.OBSERVER_TYPE;
- gameObject.status = STRINGS.COMPLETED;
- gameObject.timestamp_created = 0;
- gameObject.deadline = 0;
- gameObject.n_controls = 0;
- gameObject.registration_password = '';
- const game = new Game(gameObject);
- this.loadGame(game);
- };
- reader.readAsText(file);
- }
- });
+ getName() {
+ return this.state.name;
}
//// Methods to sign out channel and go back to connection page.
@@ -211,16 +128,16 @@ export class Page extends React.Component {
this.availableMaps = null;
const message = Page.wrapMessage(`Disconnected from channel and server.`);
Diplog.success(message);
- this.setState(this.copyState({
+ this.setState({
error: null,
info: null,
success: message,
- contentName: 'connection',
- contentData: null,
+ name: null,
+ body: null,
// When disconnected, remove all games previously loaded.
games: {},
myGames: {}
- }));
+ });
}
logout() {
@@ -236,10 +153,6 @@ export class Page extends React.Component {
//// Methods to be used to set page title and messages.
- setTitle(title) {
- this.setState({title: title});
- }
-
error(message) {
message = Page.wrapMessage(message);
Diplog.error(message);
@@ -306,11 +219,14 @@ export class Page extends React.Component {
if (game.client) {
game.client.leave()
.then(() => {
- this.disconnectGame(gameID);
- this.loadGames(null, {info: `Game ${gameID} left.`});
+ this.disconnectGame(gameID).then(() => {
+ this.loadGames({info: `Game ${gameID} left.`});
+ });
})
.catch(error => this.error(`Error when leaving game ${gameID}: ${error.toString()}`));
}
+ } else {
+ this.loadGames({info: `No game to left.`});
}
}
@@ -319,12 +235,13 @@ export class Page extends React.Component {
const game = this.state.myGames[gameID];
if (game.client)
game.client.clearAllCallbacks();
- this.channel.getGamesInfo({games: [gameID]})
+ return this.channel.getGamesInfo({games: [gameID]})
.then(gamesInfo => {
this.updateMyGames(gamesInfo);
})
.catch(error => this.error(`Error while leaving game ${gameID}: ${error.toString()}`));
}
+ return null;
}
addToMyGames(game) {
@@ -335,7 +252,7 @@ export class Page extends React.Component {
if (gamesFound.hasOwnProperty(game.game_id))
gamesFound[game.game_id] = game;
DipStorage.addUserGame(this.channel.username, game.game_id);
- this.setState({myGames: myGames, games: gamesFound});
+ this.setState({myGames: myGames, games: gamesFound}, () => this.loadGames());
}
removeFromMyGames(gameID) {
@@ -343,7 +260,7 @@ export class Page extends React.Component {
const games = Object.assign({}, this.state.myGames);
delete games[gameID];
DipStorage.removeUserGame(this.channel.username, gameID);
- this.setState({myGames: games});
+ this.setState({myGames: games}, () => this.loadGames());
}
}
@@ -354,81 +271,37 @@ export class Page extends React.Component {
//// Render method.
render() {
- const content = CONTENTS[this.state.contentName].builder(this, this.state.contentData);
- const hasNavigation = UTILS.javascript.hasArray(content.navigation);
-
- // NB: I currently don't find a better way to update document title from content details.
const successMessage = this.state.success || '-';
const infoMessage = this.state.info || '-';
const errorMessage = this.state.error || '-';
- const title = this.state.title || content.title;
- document.title = title + ' | Diplomacy';
-
return (
- <div className="page container-fluid" id={this.state.contentName}>
- <div className={'top-msg row'}>
- <div title={successMessage !== '-' ? successMessage : ''}
- className={'col-sm-4 msg success ' + (this.state.success ? 'with-msg' : 'no-msg')}
- onClick={() => this.success()}>
- {successMessage}
- </div>
- <div title={infoMessage !== '-' ? infoMessage : ''}
- className={'col-sm-4 msg info ' + (this.state.info ? 'with-msg' : 'no-msg')}
- onClick={() => this.info()}>
- {infoMessage}
- </div>
- <div title={errorMessage !== '-' ? errorMessage : ''}
- className={'col-sm-4 msg error ' + (this.state.error ? 'with-msg' : 'no-msg')}
- onClick={() => this.error()}>
- {errorMessage}
- </div>
- </div>
- {((hasNavigation || this.channel) && (
- <div className={'title row'}>
- <div className={'col align-self-center'}><strong>{title}</strong></div>
- <div className={'col-sm-1'}>
- {(!hasNavigation && (
- <div className={'float-right'}>
- <strong>
- <u className={'mr-2'}>{this.channel.username}</u>
- <Octicon icon={Person}/>
- </strong>
- </div>
- )) || (
- <div className="dropdown float-right">
- <button className="btn btn-secondary dropdown-toggle" type="button"
- id="dropdownMenuButton" data-toggle="dropdown"
- aria-haspopup="true" aria-expanded="false">
- {(this.channel && this.channel.username && (
- <span>
- <u className={'mr-2'}>{this.channel.username}</u>
- <Octicon icon={Person}/>
- </span>
- )) || 'Menu'}
- </button>
- <div className="dropdown-menu dropdown-menu-right"
- aria-labelledby="dropdownMenuButton">
- {content.navigation.map((nav, index) => {
- const navTitle = nav[0];
- const navAction = nav[1];
- return <a key={index} className="dropdown-item"
- onClick={navAction}>{navTitle}</a>;
- })}
- </div>
- </div>
- )}
+ <PageContext.Provider value={this}>
+ <div className="page container-fluid" id={this.state.contentName}>
+ <div className={'top-msg row'}>
+ <div title={successMessage !== '-' ? successMessage : ''}
+ className={'col-sm-4 msg success ' + (this.state.success ? 'with-msg' : 'no-msg')}
+ onClick={() => this.success()}>
+ {successMessage}
+ </div>
+ <div title={infoMessage !== '-' ? infoMessage : ''}
+ className={'col-sm-4 msg info ' + (this.state.info ? 'with-msg' : 'no-msg')}
+ onClick={() => this.info()}>
+ {infoMessage}
+ </div>
+ <div title={errorMessage !== '-' ? errorMessage : ''}
+ className={'col-sm-4 msg error ' + (this.state.error ? 'with-msg' : 'no-msg')}
+ onClick={() => this.error()}>
+ {errorMessage}
</div>
</div>
- )) || (
- <div className={'title'}><strong>{title}</strong></div>
- )}
- {content.component}
- {this.state.onFancyBox && (
- <FancyBox title={this.state.fancyTitle} onClose={this.unloadFancyBox}>
- {this.state.onFancyBox()}
- </FancyBox>
- )}
- </div>
+ {this.state.body || Page.defaultPage()}
+ {this.state.onFancyBox && (
+ <FancyBox title={this.state.fancyTitle} onClose={this.unloadFancyBox}>
+ {this.state.onFancyBox()}
+ </FancyBox>
+ )}
+ </div>
+ </PageContext.Provider>
);
}
}
diff --git a/diplomacy/web/src/gui/core/tab.jsx b/diplomacy/web/src/gui/core/tab.jsx
new file mode 100644
index 0000000..f1ad4aa
--- /dev/null
+++ b/diplomacy/web/src/gui/core/tab.jsx
@@ -0,0 +1,29 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+export class Tab extends React.Component {
+ render() {
+ const style = {
+ display: this.props.display ? 'block' : 'none'
+ };
+ const id = this.props.id ? {id: this.props.id} : {};
+ return (
+ <div className={'tab mb-4 ' + this.props.className} style={style} {...id}>
+ {this.props.children}
+ </div>
+ );
+ }
+}
+
+Tab.propTypes = {
+ display: PropTypes.bool,
+ className: PropTypes.string,
+ id: PropTypes.string,
+ children: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
+};
+
+Tab.defaultProps = {
+ display: false,
+ className: '',
+ id: ''
+};
diff --git a/diplomacy/web/src/gui/core/tabs.jsx b/diplomacy/web/src/gui/core/tabs.jsx
index 6123219..a3f6b9b 100644
--- a/diplomacy/web/src/gui/core/tabs.jsx
+++ b/diplomacy/web/src/gui/core/tabs.jsx
@@ -15,36 +15,9 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from "react";
-import {Action} from "./widgets";
+import {Action} from "./action";
import PropTypes from 'prop-types';
-export class Tab extends React.Component {
- render() {
- const style = {
- display: this.props.display ? 'block' : 'none'
- };
- const id = this.props.id ? {id: this.props.id} : {};
- return (
- <div className={'tab mb-4 ' + this.props.className} style={style} {...id}>
- {this.props.children}
- </div>
- );
- }
-}
-
-Tab.propTypes = {
- display: PropTypes.bool,
- className: PropTypes.string,
- id: PropTypes.string,
- children: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
-};
-
-Tab.defaultProps = {
- display: false,
- className: '',
- id: ''
-};
-
export class Tabs extends React.Component {
/** PROPERTIES
* active: index of active menu (must be > menu.length).