aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/web
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy/web')
-rw-r--r--diplomacy/web/package-lock.json21
-rw-r--r--diplomacy/web/package.json3
-rw-r--r--diplomacy/web/src/diplomacy/utils/utils.js4
-rw-r--r--diplomacy/web/src/gui/components/fancyBox.js (renamed from diplomacy/web/src/gui/components/fancybox.jsx)32
-rw-r--r--diplomacy/web/src/gui/components/help.jsx30
-rw-r--r--diplomacy/web/src/gui/components/navigation.jsx2
-rw-r--r--diplomacy/web/src/gui/forms/create_form.jsx95
-rw-r--r--diplomacy/web/src/gui/forms/select_location_form.jsx19
-rw-r--r--diplomacy/web/src/gui/forms/select_via_form.jsx16
-rw-r--r--diplomacy/web/src/gui/pages/content_game.jsx74
-rw-r--r--diplomacy/web/src/gui/pages/content_games.jsx53
-rw-r--r--diplomacy/web/src/gui/pages/page.jsx33
-rw-r--r--diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js109
-rw-r--r--diplomacy/web/src/gui/wizards/gameCreation/mapList.js41
-rw-r--r--diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js94
-rw-r--r--diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js61
-rw-r--r--diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js62
-rw-r--r--diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js113
-rw-r--r--diplomacy/web/src/gui/wizards/gameCreation/panelList.js6
-rw-r--r--diplomacy/web/src/index.css34
l---------diplomacy/web/src/maps1
21 files changed, 662 insertions, 241 deletions
diff --git a/diplomacy/web/package-lock.json b/diplomacy/web/package-lock.json
index 6f65557..077f9fe 100644
--- a/diplomacy/web/package-lock.json
+++ b/diplomacy/web/package-lock.json
@@ -926,14 +926,6 @@
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-9.0.1.tgz",
"integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA=="
},
- "@githubprimer/octicons-react": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/@githubprimer/octicons-react/-/octicons-react-8.5.0.tgz",
- "integrity": "sha512-Tb9Nu4usHON3EGiyCnppNQoJmSJvhUw+iD1URpOsoc69YWv96jIcxUAU4/Tw/D6ydzOubf3H9kPdLKVqz8XOnQ==",
- "requires": {
- "prop-types": "^15.6.1"
- }
- },
"@hapi/address": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz",
@@ -1198,6 +1190,14 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
},
+ "@primer/octicons-react": {
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-9.1.1.tgz",
+ "integrity": "sha512-+ZgALoxUOYUeEnqqN6ZqSfRP6LDRgfmErhY4ZIuGlw5Ocjj7AI87J68dD/wYqWl4IW7xE6rmLvpC3kU3iGmAfQ==",
+ "requires": {
+ "prop-types": "^15.6.1"
+ }
+ },
"@svgr/babel-plugin-add-jsx-attribute": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
@@ -10079,6 +10079,11 @@
"whatwg-fetch": "3.0.0"
}
},
+ "react-confirm-alert": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/react-confirm-alert/-/react-confirm-alert-2.4.1.tgz",
+ "integrity": "sha512-Sc2N1paCTCS5HWEAhik2IQa9/vwSQLAoCT5uccjPH/VyTaBAkRPZPx9sUqFTy3q5VnnGwCPsoz7fnw54x79d/w=="
+ },
"react-dev-utils": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-9.0.1.tgz",
diff --git a/diplomacy/web/package.json b/diplomacy/web/package.json
index 5e1d505..aaf89c8 100644
--- a/diplomacy/web/package.json
+++ b/diplomacy/web/package.json
@@ -4,13 +4,14 @@
"homepage": ".",
"private": true,
"dependencies": {
- "@githubprimer/octicons-react": "^8.5.0",
+ "@primer/octicons-react": "^9.1.1",
"bootstrap": "^4.3.1",
"fancybox": "^3.0.1",
"jquery": "^3.4.1",
"popper.js": "^1.15.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
+ "react-confirm-alert": "^2.4.1",
"react-dom": "^16.8.6",
"react-helmet": "^5.2.1",
"react-inlinesvg": "^0.8.4",
diff --git a/diplomacy/web/src/diplomacy/utils/utils.js b/diplomacy/web/src/diplomacy/utils/utils.js
index f398cd2..8e54515 100644
--- a/diplomacy/web/src/diplomacy/utils/utils.js
+++ b/diplomacy/web/src/diplomacy/utils/utils.js
@@ -37,6 +37,10 @@ export const UTILS = {
return id;
},
+ createGameID: function (username) {
+ return `${username}_${new Date().getTime().toString(10)}`;
+ },
+
date: function () {
const d = new Date();
return d.toLocaleString() + '.' + d.getMilliseconds();
diff --git a/diplomacy/web/src/gui/components/fancybox.jsx b/diplomacy/web/src/gui/components/fancyBox.js
index 66a1efe..1e8c07c 100644
--- a/diplomacy/web/src/gui/components/fancybox.jsx
+++ b/diplomacy/web/src/gui/components/fancyBox.js
@@ -21,31 +21,19 @@ import {Button} from "./button";
const TIMES = '\u00D7';
export class FancyBox extends React.Component {
- // open-tag (<FancyBox></FancyBox>)
- // PROPERTIES
- // title
- // onClose
render() {
return (
- <div className={'fancy-wrapper'} onClick={this.props.onClose}>
- <div className={'fancy-box container'} onClick={(event) => {
- if (!event)
- event = window.event;
- if (event.hasOwnProperty('cancelBubble'))
- event.cancelBubble = true;
- if (event.stopPropagation)
- event.stopPropagation();
- }}>
- <div className={'row fancy-bar'}>
- <div className={'col-11 align-self-center fancy-title'}>{this.props.title}</div>
- <div className={'col-1 fancy-button'}>
- <Button title={TIMES} color={'danger'} onClick={this.props.onClose}/>
- </div>
- </div>
- <div className={'row'}>
- <div className={'col fancy-content'}>{this.props.children}</div>
+ <div className="fancy-box">
+ <div className="fancy-bar p-1 d-flex flex-row">
+ <div
+ className="flex-grow-1 fancy-title d-flex flex-column justify-content-center pr-0 pr-sm-1">{this.props.title}</div>
+ <div className="fancy-button">
+ <Button title={TIMES} color={'danger'} onClick={this.props.onClose}/>
</div>
</div>
+ <div>
+ <div className="col fancy-content">{this.props.children}</div>
+ </div>
</div>
);
}
@@ -55,5 +43,5 @@ export class FancyBox extends React.Component {
FancyBox.propTypes = {
title: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
- children: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
+ children: PropTypes.any.isRequired
};
diff --git a/diplomacy/web/src/gui/components/help.jsx b/diplomacy/web/src/gui/components/help.jsx
index 1ec1a54..f3f469f 100644
--- a/diplomacy/web/src/gui/components/help.jsx
+++ b/diplomacy/web/src/gui/components/help.jsx
@@ -1,13 +1,23 @@
import React from "react";
+import PropTypes from 'prop-types';
+import {FancyBox} from "./fancyBox";
-export function Help() {
- return (
- <div>
- <p>When building an order, press <strong>ESC</strong> to reset build.</p>
- <p>Press letter associated to an order type to start building an order of this type.
- <br/> Order type letter is indicated in order type name after order type radio button.
- </p>
- <p>In Phase History tab, use keyboard left and right arrows to navigate in past phases.</p>
- </div>
- );
+export class Help extends React.Component {
+ render() {
+ return (
+ <FancyBox title={'Help'} onClose={this.props.onClose}>
+ <div>
+ <p>When building an order, press <strong>ESC</strong> to reset build.</p>
+ <p>Press letter associated to an order type to start building an order of this type.
+ <br/> Order type letter is indicated in order type name after order type radio button.
+ </p>
+ <p>In Phase History tab, use keyboard left and right arrows to navigate in past phases.</p>
+ </div>
+ </FancyBox>
+ );
+ }
}
+
+Help.propTypes = {
+ onClose: PropTypes.func.isRequired
+};
diff --git a/diplomacy/web/src/gui/components/navigation.jsx b/diplomacy/web/src/gui/components/navigation.jsx
index 5d961bc..051f8ea 100644
--- a/diplomacy/web/src/gui/components/navigation.jsx
+++ b/diplomacy/web/src/gui/components/navigation.jsx
@@ -1,5 +1,5 @@
import React from "react";
-import Octicon, {Person} from "@githubprimer/octicons-react";
+import Octicon, {Person} from "@primer/octicons-react";
import PropTypes from "prop-types";
export class Navigation extends React.Component {
diff --git a/diplomacy/web/src/gui/forms/create_form.jsx b/diplomacy/web/src/gui/forms/create_form.jsx
deleted file mode 100644
index 6753519..0000000
--- a/diplomacy/web/src/gui/forms/create_form.jsx
+++ /dev/null
@@ -1,95 +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 {Forms} from "../components/forms";
-import {STRINGS} from "../../diplomacy/utils/strings";
-import PropTypes from "prop-types";
-
-export class CreateForm extends React.Component {
- constructor(props) {
- super(props);
- this.state = this.initState();
- }
-
- initState() {
- const state = {
- game_id: '',
- power_name: '',
- n_controls: 7,
- deadline: 300,
- registration_password: ''
- };
- for (let rule of STRINGS.PUBLIC_RULES)
- state[`rule_${rule.toLowerCase()}`] = false;
- return state;
- }
-
- render() {
- const onChange = Forms.createOnChangeCallback(this, this.props.onChange);
- const onSubmit = Forms.createOnSubmitCallback(this, this.props.onSubmit);
- return (
- <form>
- {Forms.createRow(
- Forms.createColLabel('game_id', 'Game ID (optional)'),
- <input id={'game_id'} className={'form-control'} type={'text'}
- value={Forms.getValue(this.state, 'game_id')} onChange={onChange}/>
- )}
- {Forms.createRow(
- Forms.createColLabel('power_name', 'power:'),
- <select id={'power_name'} className={'form-control custom-select'}
- value={Forms.getValue(this.state, 'power_name')} onChange={onChange}>
- {Forms.createSelectOptions(STRINGS.ALL_POWER_NAMES, true)}
- </select>
- )}
- {Forms.createRow(
- Forms.createColLabel('n_controls', 'number of required players:'),
- <input id={'n_controls'} className={'form-control'} type={'number'}
- value={Forms.getValue(this.state, 'n_controls')} onChange={onChange}/>
- )}
- {Forms.createRow(
- Forms.createColLabel('deadline', 'deadline (in seconds)'),
- <input id={'deadline'} className={'form-control'} type={'number'}
- value={Forms.getValue(this.state, 'deadline')}
- onChange={onChange}/>
- )}
- {Forms.createRow(
- Forms.createColLabel('registration_password', 'registration password'),
- <input id={'registration_password'} className={'form-control'} type={'password'}
- value={Forms.getValue(this.state, 'registration_password')} onChange={onChange}/>
- )}
- <div><strong>RULES:</strong></div>
- <div className={'mb-4'}>
- {STRINGS.PUBLIC_RULES.map((rule, index) => (
- <div key={index} className={'form-check-inline'}>
- {Forms.createCheckbox(
- `rule_${rule.toLowerCase()}`,
- rule,
- Forms.getValue(this.state, `rule_${rule.toLowerCase()}`),
- onChange)}
- </div>
- ))}
- </div>
- {Forms.createRow('', Forms.createSubmit('create a game', true, onSubmit))}
- </form>
- );
- }
-}
-
-CreateForm.propTypes = {
- onChange: PropTypes.func,
- onSubmit: PropTypes.func
-};
diff --git a/diplomacy/web/src/gui/forms/select_location_form.jsx b/diplomacy/web/src/gui/forms/select_location_form.jsx
index ca7be09..1725c9b 100644
--- a/diplomacy/web/src/gui/forms/select_location_form.jsx
+++ b/diplomacy/web/src/gui/forms/select_location_form.jsx
@@ -17,20 +17,27 @@
import React from "react";
import PropTypes from "prop-types";
import {Button} from "../components/button";
+import {FancyBox} from "../components/fancyBox";
export class SelectLocationForm extends React.Component {
render() {
+ const title = `Select location to continue building order: ${this.props.path.join(' ')}`;
return (
- <div>
- {this.props.locations.map((location, index) => (
- <Button key={index} title={location} large={true} onClick={() => this.props.onSelect(location)}/>
- ))}
- </div>
+ <FancyBox title={title} onClose={this.props.onClose}>
+ <div>
+ {this.props.locations.map((location, index) => (
+ <Button key={index} title={location} large={true}
+ onClick={() => this.props.onSelect(location)}/>
+ ))}
+ </div>
+ </FancyBox>
);
}
}
SelectLocationForm.propTypes = {
locations: PropTypes.arrayOf(PropTypes.string).isRequired,
- onSelect: PropTypes.func.isRequired // onSelect(location)
+ onSelect: PropTypes.func.isRequired, // onSelect(location)
+ onClose: PropTypes.func.isRequired,
+ path: PropTypes.array.isRequired
};
diff --git a/diplomacy/web/src/gui/forms/select_via_form.jsx b/diplomacy/web/src/gui/forms/select_via_form.jsx
index b779b8d..8b340af 100644
--- a/diplomacy/web/src/gui/forms/select_via_form.jsx
+++ b/diplomacy/web/src/gui/forms/select_via_form.jsx
@@ -17,19 +17,25 @@
import React from "react";
import PropTypes from "prop-types";
import {Button} from "../components/button";
+import {FancyBox} from "../components/fancyBox";
export class SelectViaForm extends React.Component {
render() {
return (
- <div>
- <Button title={'regular move (M)'} large={true} onClick={() => this.props.onSelect('M')}/>
- <Button title={'move via (V)'} large={true} onClick={() => this.props.onSelect('V')}/>
- </div>
+ <FancyBox title={`Select move type for move order: ${this.props.path.join(' ')}`}
+ onClose={this.props.onClose}>
+ <div>
+ <Button title={'regular move (M)'} large={true} onClick={() => this.props.onSelect('M')}/>
+ <Button title={'move via (V)'} large={true} onClick={() => this.props.onSelect('V')}/>
+ </div>
+ </FancyBox>
);
}
}
SelectViaForm.propTypes = {
- onSelect: PropTypes.func.isRequired
+ path: PropTypes.array.isRequired,
+ onSelect: PropTypes.func.isRequired,
+ onClose: PropTypes.func.isRequired
};
diff --git a/diplomacy/web/src/gui/pages/content_game.jsx b/diplomacy/web/src/gui/pages/content_game.jsx
index f37bdca..457d901 100644
--- a/diplomacy/web/src/gui/pages/content_game.jsx
+++ b/diplomacy/web/src/gui/pages/content_game.jsx
@@ -33,7 +33,6 @@ import {STRINGS} from "../../diplomacy/utils/strings";
import {Diplog} from "../../diplomacy/utils/diplog";
import {Table} from "../components/table";
import {PowerView} from "../utils/power_view";
-import {FancyBox} from "../components/fancybox";
import {DipStorage} from "../utils/dipStorage";
import Helmet from 'react-helmet';
import {Navigation} from "../components/navigation";
@@ -116,14 +115,11 @@ export class ContentGame extends React.Component {
orders: orders, // {power name => {loc => {local: bool, order: str}}}
power: null,
orderBuildingType: null,
- orderBuildingPath: [],
- fancy_title: null,
- fancy_function: null,
- on_fancy_close: null,
+ orderBuildingPath: []
};
// Bind some class methods to this instance.
- this.closeFancyBox = this.closeFancyBox.bind(this);
+ this.clearOrderBuildingPath = this.clearOrderBuildingPath.bind(this);
this.displayFirstPastPhase = this.displayFirstPastPhase.bind(this);
this.displayLastPastPhase = this.displayLastPastPhase.bind(this);
this.displayLocationOrders = this.displayLocationOrders.bind(this);
@@ -202,11 +198,8 @@ export class ContentGame extends React.Component {
return this.context;
}
- closeFancyBox() {
+ clearOrderBuildingPath() {
this.setState({
- fancy_title: null,
- fancy_function: null,
- on_fancy_close: null,
orderBuildingPath: []
});
}
@@ -220,11 +213,6 @@ export class ContentGame extends React.Component {
powerName, orderType, orderPath, location,
this.onOrderBuilding, this.onOrderBuilt, this.getPage().error
);
- this.setState({
- fancy_title: null,
- fancy_function: null,
- on_fancy_close: null
- });
}
setSelectedVia(moveType, powerName, orderPath, location) {
@@ -234,33 +222,35 @@ export class ContentGame extends React.Component {
powerName, moveType, orderPath, location,
this.onOrderBuilding, this.onOrderBuilt, this.getPage().error
);
- this.setState({
- fancy_title: null,
- fancy_function: null,
- on_fancy_close: null
- });
}
onSelectLocation(possibleLocations, powerName, orderType, orderPath) {
- const title = `Select location to continue building order: ${orderPath.join(' ')} ... (press ESC or close button to cancel building)`;
- const func = () => (<SelectLocationForm locations={possibleLocations}
- onSelect={(location) => this.setSelectedLocation(location, powerName, orderType, orderPath)}/>);
- this.setState({
- fancy_title: title,
- fancy_function: func,
- on_fancy_close: this.closeFancyBox
- });
+ this.getPage().dialog(onClose => (
+ <SelectLocationForm path={orderPath}
+ locations={possibleLocations}
+ onSelect={(location) => {
+ this.setSelectedLocation(location, powerName, orderType, orderPath);
+ onClose();
+ }}
+ onClose={() => {
+ this.clearOrderBuildingPath();
+ onClose();
+ }}/>
+ ));
}
onSelectVia(location, powerName, orderPath) {
- const title = `Select move type for move order: ${orderPath.join(' ')}`;
- const func = () => (
- <SelectViaForm onSelect={(moveType) => this.setSelectedVia(moveType, powerName, orderPath, location)}/>);
- this.setState({
- fancy_title: title,
- fancy_function: func,
- on_fancy_close: this.closeFancyBox
- });
+ this.getPage().dialog(onClose => (
+ <SelectViaForm path={orderPath}
+ onSelect={(moveType) => {
+ this.setSelectedVia(moveType, powerName, orderPath, location);
+ onClose();
+ }}
+ onClose={() => {
+ this.clearOrderBuildingPath();
+ onClose();
+ }}/>
+ ));
}
// ]
@@ -682,9 +672,6 @@ export class ContentGame extends React.Component {
onOrderBuilt(powerName, orderString) {
const state = Object.assign({}, this.state);
state.orderBuildingPath = [];
- state.fancy_title = null;
- state.fancy_function = null;
- state.on_fancy_close = null;
if (!orderString) {
Diplog.warn('No order built.');
this.setState(state);
@@ -712,9 +699,6 @@ export class ContentGame extends React.Component {
this.setState({
orderBuildingType: form.order_type,
orderBuildingPath: [],
- fancy_title: null,
- fancy_function: null,
- on_fancy_close: null
});
}
@@ -1162,7 +1146,7 @@ export class ContentGame extends React.Component {
const engine = this.props.data;
const title = ContentGame.gameTitle(engine);
const navigation = [
- ['Help', () => page.loadFancyBox('Help', () => <Help/>)],
+ ['Help', () => page.dialog(onClose => <Help onClose={onClose}/>)],
['Load a game from disk', page.loadGameFromDisk],
['Save game to disk', () => saveGameToDisk(engine, page.error)],
[`${UTILS.html.UNICODE_SMALL_LEFT_ARROW} Games`, () => page.loadGames()],
@@ -1292,10 +1276,6 @@ export class ContentGame extends React.Component {
currentTabOrderCreation
)) || ''}
</Tabs>
- {this.state.fancy_title && (
- <FancyBox title={this.state.fancy_title} onClose={this.state.on_fancy_close}>
- {this.state.fancy_function()}
- </FancyBox>)}
</main>
);
}
diff --git a/diplomacy/web/src/gui/pages/content_games.jsx b/diplomacy/web/src/gui/pages/content_games.jsx
index 31bd1af..5250f03 100644
--- a/diplomacy/web/src/gui/pages/content_games.jsx
+++ b/diplomacy/web/src/gui/pages/content_games.jsx
@@ -18,15 +18,15 @@ import React from "react";
import {Tabs} from "../components/tabs";
import {Table} from "../components/table";
import {FindForm} from "../forms/find_form";
-import {CreateForm} from "../forms/create_form";
import {InlineGameView} from "../utils/inline_game_view";
-import {STRINGS} from "../../diplomacy/utils/strings";
import {Helmet} from "react-helmet";
import {Navigation} from "../components/navigation";
import {PageContext} from "../components/page_context";
import {ContentGame} from "./content_game";
import PropTypes from 'prop-types';
import {Tab} from "../components/tab";
+import {GameCreationWizard} from "../wizards/gameCreation/gameCreationWizard";
+import {Diplog} from "../../diplomacy/utils/diplog";
const TABLE_LOCAL_GAMES = {
game_id: ['Game ID', 0],
@@ -71,23 +71,6 @@ export class ContentGames extends React.Component {
}
onCreate(form) {
- for (let key of Object.keys(form)) {
- if (form[key] === '')
- form[key] = null;
- }
- if (form.n_controls !== null)
- form.n_controls = parseInt(form.n_controls, 10);
- if (form.deadline !== null)
- form.deadline = parseInt(form.deadline, 10);
- form.rules = ['POWER_CHOICE'];
- for (let rule of STRINGS.PUBLIC_RULES) {
- const rule_id = `rule_${rule.toLowerCase()}`;
- if (form.hasOwnProperty(rule_id)) {
- if (form[rule_id])
- form.rules.push(rule);
- delete form[rule_id];
- }
- }
let networkGame = null;
this.getPage().channel.createGame(form)
.then((game) => {
@@ -116,6 +99,28 @@ export class ContentGames extends React.Component {
return new InlineGameView(this.getPage(), gameData);
}
+ gameCreationButton() {
+ return (
+ <button type="button"
+ className="btn btn-danger btn-sm mx-0 mx-sm-4"
+ onClick={() => this.getPage().dialog(onClose => (
+ <GameCreationWizard availableMaps={this.getPage().availableMaps}
+ onCancel={onClose}
+ username={this.getPage().channel.username}
+ onSubmit={(form) => {
+ onClose();
+ Diplog.info(`Creating game:`);
+ for (let entry of Object.entries(form)) {
+ Diplog.info(`${entry[0]}: ${entry[1] ? entry[1].toString() : entry[1]}`);
+ }
+ this.onCreate(form);
+ }}/>
+ ))}>
+ <strong>create a game</strong>
+ </button>
+ );
+ }
+
render() {
const title = 'Games';
const page = this.getPage();
@@ -133,14 +138,10 @@ export class ContentGames extends React.Component {
<Helmet>
<title>{title} | Diplomacy</title>
</Helmet>
- <Navigation title={title} username={page.channel.username} navigation={navigation}/>
- <Tabs menu={['create', 'find', 'my-games']} titles={['Create', 'Find', 'My Games']}
+ <Navigation title={title} afterTitle={this.gameCreationButton()}
+ username={page.channel.username} navigation={navigation}/>
+ <Tabs menu={['find', 'my-games']} titles={['Find', 'My Games']}
onChange={this.changeTab} active={tab}>
- {tab === 'create' ? (
- <Tab id="tab-games-create" display={true}>
- <CreateForm onSubmit={this.onCreate}/>
- </Tab>
- ) : ''}
{tab === 'find' ? (
<Tab id="tab-games-find" display={true}>
<FindForm onSubmit={this.onFind}/>
diff --git a/diplomacy/web/src/gui/pages/page.jsx b/diplomacy/web/src/gui/pages/page.jsx
index cd36f6c..a9ff9ac 100644
--- a/diplomacy/web/src/gui/pages/page.jsx
+++ b/diplomacy/web/src/gui/pages/page.jsx
@@ -20,12 +20,13 @@ import React from "react";
import {ContentConnection} from "./content_connection";
import {UTILS} from "../../diplomacy/utils/utils";
import {Diplog} from "../../diplomacy/utils/diplog";
-import {FancyBox} from "../components/fancybox";
import {DipStorage} from "../utils/dipStorage";
import {PageContext} from "../components/page_context";
import {ContentGames} from "./content_games";
import {loadGameFromDisk} from "../utils/load_game_from_disk";
import {ContentGame} from "./content_game";
+import {confirmAlert} from 'react-confirm-alert';
+import 'react-confirm-alert/src/react-confirm-alert.css';
export class Page extends React.Component {
@@ -35,9 +36,6 @@ export class Page extends React.Component {
this.channel = null;
this.availableMaps = null;
this.state = {
- // fancybox,
- fancyTitle: null,
- onFancyBox: null,
// Page messages
error: null,
info: null,
@@ -54,7 +52,6 @@ export class Page extends React.Component {
this.success = this.success.bind(this);
this.logout = this.logout.bind(this);
this.loadGameFromDisk = this.loadGameFromDisk.bind(this);
- this.unloadFancyBox = this.unloadFancyBox.bind(this);
this._post_remove = this._post_remove.bind(this);
this._add_to_my_games = this._add_to_my_games.bind(this);
this._remove_from_my_games = this._remove_from_my_games.bind(this);
@@ -80,14 +77,22 @@ export class Page extends React.Component {
this.__disconnect(error);
}
- //// Methods to load a global fancybox.
+ /**
+ * @callback OnClose
+ */
- loadFancyBox(title, callback) {
- this.setState({fancyTitle: title, onFancyBox: callback});
- }
+ /**
+ * @callback DialogBuilder
+ * @param {OnClose} onClose
+ */
- unloadFancyBox() {
- this.setState({fancyTitle: null, onFancyBox: null});
+ /**
+ * open a dialog box
+ * @param {DialogBuilder} builder - a callback to generate dialog GUI. Will be executed with a `onClose` callback
+ * parameter to call when dialog must be closed: `builder(onClose)`.
+ */
+ dialog(builder) {
+ confirmAlert({customUI: ({onClose}) => builder(onClose)});
}
//// Methods to load a page.
@@ -275,7 +280,6 @@ export class Page extends React.Component {
}
}
-
disconnectGame(gameID) {
const game = this.getGame(gameID);
if (game) {
@@ -363,11 +367,6 @@ export class Page extends React.Component {
</div>
</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/wizards/gameCreation/gameCreationWizard.js b/diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js
new file mode 100644
index 0000000..daaa461
--- /dev/null
+++ b/diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {Panels} from "./panelList";
+import {PanelChooseMap} from "./panelChooseMap";
+import {PanelChoosePlayers} from "./panelChoosePlayers";
+import {PanelChoosePower} from "./panelChoosePower";
+import {PanelChooseSettings} from "./panelChooseSettings";
+import {Maps} from "./mapList";
+import {UTILS} from "../../../diplomacy/utils/utils";
+
+export class GameCreationWizard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ panel: Panels.CHOOSE_MAP,
+ game_id: UTILS.createGameID(this.props.username),
+ power_name: null,
+ n_controls: -1,
+ deadline: 0,
+ registration_password: '',
+
+ map: Maps[0],
+ no_press: false
+ };
+ this.backward = this.backward.bind(this);
+ this.forward = this.forward.bind(this);
+ this.updateParams = this.updateParams.bind(this);
+ }
+
+ updateParams(params) {
+ this.setState(params);
+ }
+
+ goToPanel(panelID) {
+ if (panelID < Panels.CHOOSE_MAP)
+ this.props.onCancel();
+ else if (panelID > Panels.CHOOSE_SETTINGS) {
+ const rules = ['POWER_CHOICE'];
+ if (this.state.no_press)
+ rules.push('NO_PRESS');
+ if (!this.state.deadline) {
+ rules.push('NO_DEADLINE');
+ rules.push('REAL_TIME');
+ }
+ this.props.onSubmit({
+ game_id: this.state.game_id,
+ map_name: this.state.map.name,
+ power_name: this.state.power_name,
+ n_controls: this.state.n_controls,
+ deadline: this.state.deadline,
+ registration_password: this.state.registration_password || null,
+ rules: rules
+ });
+ } else
+ this.setState({panel: panelID, registration_password: ''});
+ }
+
+ backward(step) {
+ this.goToPanel(this.state.panel - (step ? step : 1));
+ }
+
+ forward(step) {
+ this.goToPanel(this.state.panel + (step ? step : 1));
+ }
+
+ renderPanel() {
+ switch (this.state.panel) {
+ case Panels.CHOOSE_MAP:
+ return <PanelChooseMap forward={this.forward}
+ params={this.state}
+ onUpdateParams={this.updateParams}
+ cancel={this.props.onCancel}/>;
+ case Panels.CHOOSE_PLAYERS:
+ return <PanelChoosePlayers backward={this.backward}
+ forward={this.forward}
+ onUpdateParams={this.updateParams}
+ nbPowers={this.props.availableMaps[this.state.map.name].powers.length}
+ cancel={this.props.onCancel}/>;
+ case Panels.CHOOSE_POWER:
+ return <PanelChoosePower backward={this.backward}
+ forward={this.forward}
+ onUpdateParams={this.updateParams}
+ powers={this.props.availableMaps[this.state.map.name].powers}
+ cancel={this.props.onCancel}/>;
+ case Panels.CHOOSE_SETTINGS:
+ return <PanelChooseSettings backward={this.backward}
+ forward={this.forward}
+ onUpdateParams={this.updateParams}
+ username={this.props.username}
+ params={this.state}
+ cancel={this.props.onCancel}/>;
+ default:
+ return '';
+ }
+ }
+
+ render() {
+ return (
+ <div className="game-creation-wizard">{this.renderPanel()}</div>
+ );
+ }
+}
+
+GameCreationWizard.propTypes = {
+ onCancel: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ availableMaps: PropTypes.object.isRequired,
+ username: PropTypes.string.isRequired
+};
diff --git a/diplomacy/web/src/gui/wizards/gameCreation/mapList.js b/diplomacy/web/src/gui/wizards/gameCreation/mapList.js
new file mode 100644
index 0000000..5d2c00a
--- /dev/null
+++ b/diplomacy/web/src/gui/wizards/gameCreation/mapList.js
@@ -0,0 +1,41 @@
+class VariantInfo {
+ constructor(variantName, variantTitle) {
+ this.name = variantName;
+ this.title = variantTitle;
+ this.map = null;
+ }
+
+ svgName() {
+ return this.map.name;
+ }
+}
+
+class MapInfo {
+ constructor(mapName, mapTitle, variants) {
+ this.name = mapName;
+ this.title = mapTitle;
+ this.variants = null;
+ if (variants) {
+ this.variants = [];
+ for (let variant of variants) {
+ variant.map = this;
+ this.variants.push(variant);
+ }
+ }
+ }
+
+ svgName() {
+ return this.name;
+ }
+}
+
+export const Maps = [
+ new MapInfo('standard', 'Standard', [
+ new VariantInfo('standard', 'Default'),
+ new VariantInfo('standard_age_of_empires', 'Age of empires'),
+ new VariantInfo('standard_age_of_empires_2', 'Age of empires II'),
+ new VariantInfo('standard_fleet_rome', 'Fleet at Rome'),
+ new VariantInfo('standard_france_austria', 'France VS Austria'),
+ new VariantInfo('standard_germany_italy', 'Germany VS Italy')
+ ]),
+];
diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js
new file mode 100644
index 0000000..5c40f1c
--- /dev/null
+++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js
@@ -0,0 +1,94 @@
+import React from "react";
+import {Maps} from "./mapList";
+import {FancyBox} from "../../components/fancyBox";
+import PropTypes from "prop-types";
+
+export class PanelChooseMap extends React.Component {
+ render() {
+ const mapImg = require(`../../../maps/svg/${this.props.params.map.svgName()}.svg`);
+ const mapEntries = [];
+ let count = 0;
+ for (let mapInfo of Maps) {
+ ++count;
+ if (!mapInfo.variants) {
+ mapEntries.push(
+ <div key={count} className="mb-1">
+ <button type="button"
+ className="btn btn-secondary btn-sm btn-block"
+ onMouseOver={() => this.props.onUpdateParams({map: mapInfo})}
+ onClick={() => this.props.forward()}>
+ {mapInfo.title}
+ </button>
+ </div>
+ );
+ } else {
+ const dropDownID = `collapse-${count}-${mapInfo.name}`;
+ const variants = mapInfo.variants.slice();
+ const defaultVariant = variants[0];
+ mapEntries.push(
+ <div key={count}>
+ <div className="mb-1 d-flex flex-row justify-content-center">
+ <button type="button"
+ className="btn btn-secondary btn-sm flex-grow-1 mr-1"
+ onMouseOver={() => this.props.onUpdateParams({map: defaultVariant})}
+ onClick={() => this.props.forward()}>
+ {mapInfo.title} ({defaultVariant.title})
+ </button>
+ <button type="button"
+ className="btn btn-outline-secondary btn-sm collapsed"
+ data-toggle="collapse"
+ data-target={`#${dropDownID}`}
+ aria-expanded={false}
+ aria-controls={dropDownID}>
+ <span className="unroll"><strong>+</strong></span>
+ <span className="roll"><strong>-</strong></span>
+ </button>
+ </div>
+ <div className="collapse" id={dropDownID}>
+ <div>
+ {(() => {
+ const views = [];
+ for (let i = 1; i < variants.length; ++i) {
+ const variantInfo = variants[i];
+ views.push(
+ <div key={variantInfo.name} className="mb-1">
+ <button type="button"
+ className="btn btn-outline-secondary btn-sm btn-block"
+ onMouseOver={() => this.props.onUpdateParams({map: variantInfo})}
+ onClick={() => this.props.forward()}>
+ {variantInfo.title}
+ </button>
+ </div>
+ );
+ }
+ return views;
+ })()}
+ </div>
+ </div>
+ </div>
+ );
+ }
+ }
+ return (
+ <FancyBox title={'Choose a map'} onClose={this.props.cancel}>
+ <div className="row panel-choose-map">
+ <div className="col-md">
+ <div className="map-list p-1 ml-0 ml-sm-1">
+ {mapEntries}
+ </div>
+ </div>
+ <div className="col-md">
+ <img className="img-fluid" src={mapImg} alt={this.props.params.map.title}/>
+ </div>
+ </div>
+ </FancyBox>
+ );
+ }
+}
+
+PanelChooseMap.propTypes = {
+ forward: PropTypes.func.isRequired,
+ cancel: PropTypes.func.isRequired,
+ params: PropTypes.object.isRequired,
+ onUpdateParams: PropTypes.func.isRequired
+};
diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js
new file mode 100644
index 0000000..84a47a0
--- /dev/null
+++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js
@@ -0,0 +1,61 @@
+import React from "react";
+import {FancyBox} from "../../components/fancyBox";
+import PropTypes from "prop-types";
+import Octicon, {ArrowLeft} from "@primer/octicons-react";
+
+export class PanelChoosePlayers extends React.Component {
+ render() {
+ return (
+ <FancyBox title={'Number of human players'} onClose={this.props.cancel}>
+ <div className="row">
+ <div className="col-sm">
+ <button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
+ this.props.onUpdateParams({n_controls: 0});
+ this.props.forward(2);
+ }}>None - just bots
+ </button>
+ </div>
+ <div className="col-sm">
+ <button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
+ this.props.onUpdateParams({n_controls: this.props.nbPowers});
+ this.props.forward();
+ }}>All humans - no bots
+ </button>
+ </div>
+ </div>
+ <div className="d-flex flex-row justify-content-center my-2">
+ {(() => {
+ const choice = [];
+ for (let i = 0; i < this.props.nbPowers; ++i) {
+ choice.push(
+ <button key={i} type="button"
+ className={`btn btn-secondary btn-sm flex-grow-1 ${i === 0 ? '' : 'ml-sm-1'}`}
+ onClick={() => {
+ this.props.onUpdateParams({n_controls: i + 1});
+ this.props.forward();
+ }}>
+ {i + 1}
+ </button>
+ );
+ }
+ return choice;
+ })()}
+ </div>
+ <div>
+ <button type="button" className="btn btn-secondary btn-sm px-3"
+ onClick={() => this.props.backward()}>
+ <Octicon icon={ArrowLeft}/>
+ </button>
+ </div>
+ </FancyBox>
+ );
+ }
+}
+
+PanelChoosePlayers.propTypes = {
+ backward: PropTypes.func.isRequired,
+ forward: PropTypes.func.isRequired,
+ cancel: PropTypes.func.isRequired,
+ onUpdateParams: PropTypes.func.isRequired,
+ nbPowers: PropTypes.number.isRequired
+};
diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js
new file mode 100644
index 0000000..dc400bd
--- /dev/null
+++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js
@@ -0,0 +1,62 @@
+import React from "react";
+import {FancyBox} from "../../components/fancyBox";
+import PropTypes from "prop-types";
+import Octicon, {ArrowLeft} from "@primer/octicons-react";
+
+export class PanelChoosePower extends React.Component {
+ render() {
+ return (
+ <FancyBox title={'Choose your power'} onClose={this.props.cancel}>
+ <div className="row">
+ <div className="col-sm">
+ <button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
+ this.props.onUpdateParams({power_name: null});
+ this.props.forward();
+ }}>I just want to observe
+ </button>
+ </div>
+ <div className="col-sm">
+ <button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
+ const powerName = this.props.powers[Math.floor(Math.random() * this.props.powers.length)];
+ this.props.onUpdateParams({power_name: powerName});
+ this.props.forward();
+ }}>Choose randomly for me
+ </button>
+ </div>
+ </div>
+ <div className="d-flex flex-row justify-content-center my-2">
+ {(() => {
+ const choice = [];
+ for (let i = 0; i < this.props.powers.length; ++i) {
+ choice.push(
+ <button key={i} type="button"
+ className={`btn btn-secondary btn-sm flex-grow-1 ${i === 0 ? '' : 'ml-sm-1'}`}
+ onClick={() => {
+ this.props.onUpdateParams({power_name: this.props.powers[i]});
+ this.props.forward();
+ }}>
+ {this.props.powers[i]}
+ </button>
+ );
+ }
+ return choice;
+ })()}
+ </div>
+ <div>
+ <button type="button" className="btn btn-secondary btn-sm px-3"
+ onClick={() => this.props.backward()}>
+ <Octicon icon={ArrowLeft}/>
+ </button>
+ </div>
+ </FancyBox>
+ );
+ }
+}
+
+PanelChoosePower.propTypes = {
+ backward: PropTypes.func.isRequired,
+ forward: PropTypes.func.isRequired,
+ cancel: PropTypes.func.isRequired,
+ onUpdateParams: PropTypes.func.isRequired,
+ powers: PropTypes.arrayOf(PropTypes.string).isRequired
+};
diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js
new file mode 100644
index 0000000..e509158
--- /dev/null
+++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js
@@ -0,0 +1,113 @@
+import React from "react";
+import {FancyBox} from "../../components/fancyBox";
+import PropTypes from "prop-types";
+import {UTILS} from "../../../diplomacy/utils/utils";
+import Octicon, {ArrowLeft} from "@primer/octicons-react";
+
+const DEADLINES = [
+ [0, '(no deadline)'],
+ [60, '1 min'],
+ [60 * 5, '5 min'],
+ [60 * 30, '30 min'],
+ [60 * 60 * 2, '2 hrs'],
+ [60 * 60 * 24, '24 hrs'],
+];
+
+export class PanelChooseSettings extends React.Component {
+ constructor(props) {
+ super(props);
+ this.onCheckNoPress = this.onCheckNoPress.bind(this);
+ this.onSelectDeadline = this.onSelectDeadline.bind(this);
+ this.onSetRegistrationPassword = this.onSetRegistrationPassword.bind(this);
+ this.onSetGameID = this.onSetGameID.bind(this);
+ }
+
+ onCheckNoPress(event) {
+ this.props.onUpdateParams({no_press: event.target.checked});
+ }
+
+ onSelectDeadline(event) {
+ this.props.onUpdateParams({deadline: parseInt(event.target.value)});
+ }
+
+ onSetRegistrationPassword(event) {
+ this.props.onUpdateParams({registration_password: event.target.value});
+ }
+
+ onSetGameID(event) {
+ let gameID = event.target.value;
+ if (!gameID)
+ gameID = UTILS.createGameID(this.props.username);
+ this.props.onUpdateParams({game_id: gameID});
+ }
+
+ render() {
+ return (
+ <FancyBox title={'Other settings'} onClose={this.props.cancel}>
+ <div>
+ <form>
+ <div className="form-group row align-items-center mb-2">
+ <label className="col-md col-form-label" htmlFor="deadline">Deadline</label>
+ <div className="col-md">
+ <select id="deadline" className="custom-select custom-select-sm"
+ value={this.props.params.deadline}
+ onChange={this.onSelectDeadline}>
+ {DEADLINES.map((deadline, index) => (
+ <option key={index} value={deadline[0]}>{deadline[1]}</option>
+ ))}
+ </select>
+ </div>
+ </div>
+ <div className="form-group row mb-2">
+ <label className="col-md col-form-label" htmlFor="registration-password">Login
+ password</label>
+ <div className="col-md">
+ <input type="password" className="form-control form-control-sm"
+ id="registration-password"
+ value={this.props.params.registration_password}
+ onChange={this.onSetRegistrationPassword} placeholder="(no password)"/>
+ </div>
+ </div>
+ <div className="form-group row mb-2">
+ <label className="col-md col-form-label" htmlFor="game-id">Game ID</label>
+ <div className="col-md">
+ <input type="text" className="form-control form-control-sm"
+ id="game-id"
+ value={this.props.params.game_id}
+ onChange={this.onSetGameID}/>
+ </div>
+ </div>
+ <div className="custom-control custom-checkbox mb-5">
+ <input type="checkbox" className="custom-control-input" id="no-press"
+ checked={this.props.params.no_press} onChange={this.onCheckNoPress}/>
+ <label className="custom-control-label" htmlFor="no-press">No messages allowed</label>
+ </div>
+ </form>
+ </div>
+ <div className="row">
+ <div className="col-sm">
+ <button type="button" className="btn btn-secondary btn-sm btn-block"
+ onClick={() => this.props.backward()}>
+ <Octicon icon={ArrowLeft}/>
+ </button>
+ </div>
+ <div className="col-sm">
+ <button type="button" className="btn btn-success btn-sm btn-block inline"
+ onClick={() => this.props.forward()}>
+ <strong>create the game</strong>
+ </button>
+ </div>
+ </div>
+ </FancyBox>
+ );
+ }
+}
+
+PanelChooseSettings.propTypes = {
+ backward: PropTypes.func.isRequired,
+ forward: PropTypes.func.isRequired,
+ cancel: PropTypes.func.isRequired,
+ params: PropTypes.object.isRequired,
+ onUpdateParams: PropTypes.func.isRequired,
+ username: PropTypes.string.isRequired
+};
diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelList.js b/diplomacy/web/src/gui/wizards/gameCreation/panelList.js
new file mode 100644
index 0000000..0b6100c
--- /dev/null
+++ b/diplomacy/web/src/gui/wizards/gameCreation/panelList.js
@@ -0,0 +1,6 @@
+export const Panels = {
+ CHOOSE_MAP: 0,
+ CHOOSE_PLAYERS: 1,
+ CHOOSE_POWER: 2,
+ CHOOSE_SETTINGS: 3
+};
diff --git a/diplomacy/web/src/index.css b/diplomacy/web/src/index.css
index f270135..8b14a97 100644
--- a/diplomacy/web/src/index.css
+++ b/diplomacy/web/src/index.css
@@ -261,12 +261,9 @@ span.power-name {
.fancy-box .fancy-bar {
background-color: rgb(240, 240, 240);
border-bottom: 1px solid silver;
- padding-top: 10px;
- padding-bottom: 10px;
}
.fancy-box .fancy-button {
- text-align: right;
}
.fancy-box .fancy-content {
@@ -419,6 +416,37 @@ span.power-name {
background-color: rgb(230, 230, 230);
}
+button .unroll {
+ display: none;
+}
+
+button .roll {
+ display: inline;
+}
+
+button.collapsed .unroll {
+ display: inline;
+}
+
+button.collapsed .roll {
+ display: none;
+}
+
+.panel-choose-map .map-list {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ overflow: auto;
+ border: 1px solid gray;
+}
+
+.inline {
+ white-space: nowrap;
+}
+
+
/** Page login. **/
/** Page games. **/
/** Page game. **/
diff --git a/diplomacy/web/src/maps b/diplomacy/web/src/maps
new file mode 120000
index 0000000..0aaf789
--- /dev/null
+++ b/diplomacy/web/src/maps
@@ -0,0 +1 @@
+../../maps/ \ No newline at end of file