aboutsummaryrefslogtreecommitdiff
path: root/diplomacy
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy')
-rw-r--r--diplomacy/web/src/diplomacy/engine/game.js24
-rw-r--r--diplomacy/web/src/diplomacy/utils/diplog.js2
-rw-r--r--diplomacy/web/src/gui/components/action.jsx (renamed from diplomacy/web/src/gui/core/action.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/button.jsx (renamed from diplomacy/web/src/gui/core/button.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/delete_button.jsx (renamed from diplomacy/web/src/gui/core/delete_button.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/fancybox.jsx (renamed from diplomacy/web/src/gui/core/fancybox.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/forms.jsx (renamed from diplomacy/web/src/gui/core/forms.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/help.jsx (renamed from diplomacy/web/src/gui/diplomacy/widgets/help.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/layouts.jsx (renamed from diplomacy/web/src/gui/core/layouts.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/message_view.jsx (renamed from diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/navigation.jsx (renamed from diplomacy/web/src/gui/diplomacy/widgets/navigation.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/page_context.jsx (renamed from diplomacy/web/src/gui/diplomacy/widgets/page_context.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/power_orders.jsx (renamed from diplomacy/web/src/gui/diplomacy/widgets/power_order.jsx)10
-rw-r--r--diplomacy/web/src/gui/components/power_orders_actions_bar.js26
-rw-r--r--diplomacy/web/src/gui/components/tab.jsx (renamed from diplomacy/web/src/gui/core/tab.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/table.jsx (renamed from diplomacy/web/src/gui/core/table.jsx)0
-rw-r--r--diplomacy/web/src/gui/components/tabs.jsx (renamed from diplomacy/web/src/gui/core/tabs.jsx)0
-rw-r--r--diplomacy/web/src/gui/forms/connection_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/connection_form.jsx)4
-rw-r--r--diplomacy/web/src/gui/forms/create_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/create_form.jsx)4
-rw-r--r--diplomacy/web/src/gui/forms/find_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/find_form.jsx)4
-rw-r--r--diplomacy/web/src/gui/forms/join_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/join_form.jsx)4
-rw-r--r--diplomacy/web/src/gui/forms/message_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/message_form.jsx)4
-rw-r--r--diplomacy/web/src/gui/forms/power_order_creation_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/power_actions_form.jsx)14
-rw-r--r--diplomacy/web/src/gui/forms/select_location_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/select_location_form.jsx)2
-rw-r--r--diplomacy/web/src/gui/forms/select_via_form.jsx (renamed from diplomacy/web/src/gui/diplomacy/forms/select_via_form.jsx)2
-rw-r--r--diplomacy/web/src/gui/map/dom_order_builder.js (renamed from diplomacy/web/src/gui/diplomacy/map/dom_order_builder.js)4
-rw-r--r--diplomacy/web/src/gui/map/dom_past_map.js (renamed from diplomacy/web/src/gui/diplomacy/map/dom_past_map.js)0
-rw-r--r--diplomacy/web/src/gui/map/map.jsx (renamed from diplomacy/web/src/gui/diplomacy/map/map.jsx)2
-rw-r--r--diplomacy/web/src/gui/map/renderer.js (renamed from diplomacy/web/src/gui/diplomacy/map/renderer.js)0
-rw-r--r--diplomacy/web/src/gui/pages/content_connection.jsx (renamed from diplomacy/web/src/gui/diplomacy/contents/content_connection.jsx)6
-rw-r--r--diplomacy/web/src/gui/pages/content_game.jsx (renamed from diplomacy/web/src/gui/diplomacy/contents/content_game.jsx)385
-rw-r--r--diplomacy/web/src/gui/pages/content_games.jsx (renamed from diplomacy/web/src/gui/diplomacy/contents/content_games.jsx)12
-rw-r--r--diplomacy/web/src/gui/pages/page.jsx (renamed from diplomacy/web/src/gui/core/page.jsx)14
-rw-r--r--diplomacy/web/src/gui/utils/dipStorage.jsx (renamed from diplomacy/web/src/gui/diplomacy/utils/dipStorage.jsx)0
-rw-r--r--diplomacy/web/src/gui/utils/inline_game_view.jsx (renamed from diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx)8
-rw-r--r--diplomacy/web/src/gui/utils/load_game_from_disk.js (renamed from diplomacy/web/src/gui/diplomacy/utils/load_game_from_disk.js)4
-rw-r--r--diplomacy/web/src/gui/utils/map_data.js (renamed from diplomacy/web/src/gui/diplomacy/utils/map_data.js)0
-rw-r--r--diplomacy/web/src/gui/utils/order.js (renamed from diplomacy/web/src/gui/diplomacy/utils/order.js)0
-rw-r--r--diplomacy/web/src/gui/utils/order_building.js (renamed from diplomacy/web/src/gui/diplomacy/utils/order_building.js)0
-rw-r--r--diplomacy/web/src/gui/utils/power_view.jsx (renamed from diplomacy/web/src/gui/diplomacy/utils/power_view.jsx)2
-rw-r--r--diplomacy/web/src/gui/utils/province.js (renamed from diplomacy/web/src/gui/diplomacy/utils/province.js)0
-rw-r--r--diplomacy/web/src/gui/utils/saveGameToDisk.js18
-rw-r--r--diplomacy/web/src/index.js2
43 files changed, 328 insertions, 229 deletions
diff --git a/diplomacy/web/src/diplomacy/engine/game.js b/diplomacy/web/src/diplomacy/engine/game.js
index 93da77c..e44fdf6 100644
--- a/diplomacy/web/src/diplomacy/engine/game.js
+++ b/diplomacy/web/src/diplomacy/engine/game.js
@@ -19,6 +19,7 @@ import {STRINGS} from "../utils/strings";
import {SortedDict} from "../utils/sorted_dict";
import {Power} from "./power";
import {Message} from "./message";
+import {Order} from "../../gui/utils/order";
export function comparablePhase(shortPhaseName) {
/** Return a unique integer corresponding to given short phase name, so that
@@ -420,6 +421,29 @@ export class Game {
return [this.role];
}
+ getServerOrders() {
+ /** Return a dictionary of server orders.
+ * Returned dictionary maps each power name to either:
+ * - a dictionary of orders, mapping a loc to an Order object with boolean flag `local` set to false.
+ * - an empty dictionary, to represent an empty orders set.
+ * - null value, if power.order_is_set is false.
+ * **/
+ const orders = {};
+ const controllablePowers = this.getControllablePowers();
+ for (let powerName of controllablePowers) {
+ const powerOrders = {};
+ let countOrders = 0;
+ const power = this.powers[powerName];
+ for (let orderString of power.orders) {
+ const serverOrder = new Order(orderString, false);
+ powerOrders[serverOrder.loc] = serverOrder;
+ ++countOrders;
+ }
+ orders[powerName] = (countOrders || power.order_is_set) ? powerOrders : null;
+ }
+ return orders;
+ }
+
getMessageChannels(role, all) {
const messageChannels = {};
role = role || this.role;
diff --git a/diplomacy/web/src/diplomacy/utils/diplog.js b/diplomacy/web/src/diplomacy/utils/diplog.js
index 1e4a753..261a8f4 100644
--- a/diplomacy/web/src/diplomacy/utils/diplog.js
+++ b/diplomacy/web/src/diplomacy/utils/diplog.js
@@ -25,7 +25,7 @@ export class Diplog {
}
static info(msg) {
- console.info(msg);
+ console.log(msg);
}
static success(msg) {
diff --git a/diplomacy/web/src/gui/core/action.jsx b/diplomacy/web/src/gui/components/action.jsx
index 73fe8cb..73fe8cb 100644
--- a/diplomacy/web/src/gui/core/action.jsx
+++ b/diplomacy/web/src/gui/components/action.jsx
diff --git a/diplomacy/web/src/gui/core/button.jsx b/diplomacy/web/src/gui/components/button.jsx
index 0d5dadd..0d5dadd 100644
--- a/diplomacy/web/src/gui/core/button.jsx
+++ b/diplomacy/web/src/gui/components/button.jsx
diff --git a/diplomacy/web/src/gui/core/delete_button.jsx b/diplomacy/web/src/gui/components/delete_button.jsx
index 59141fd..59141fd 100644
--- a/diplomacy/web/src/gui/core/delete_button.jsx
+++ b/diplomacy/web/src/gui/components/delete_button.jsx
diff --git a/diplomacy/web/src/gui/core/fancybox.jsx b/diplomacy/web/src/gui/components/fancybox.jsx
index 66a1efe..66a1efe 100644
--- a/diplomacy/web/src/gui/core/fancybox.jsx
+++ b/diplomacy/web/src/gui/components/fancybox.jsx
diff --git a/diplomacy/web/src/gui/core/forms.jsx b/diplomacy/web/src/gui/components/forms.jsx
index da7250d..da7250d 100644
--- a/diplomacy/web/src/gui/core/forms.jsx
+++ b/diplomacy/web/src/gui/components/forms.jsx
diff --git a/diplomacy/web/src/gui/diplomacy/widgets/help.jsx b/diplomacy/web/src/gui/components/help.jsx
index 1ec1a54..1ec1a54 100644
--- a/diplomacy/web/src/gui/diplomacy/widgets/help.jsx
+++ b/diplomacy/web/src/gui/components/help.jsx
diff --git a/diplomacy/web/src/gui/core/layouts.jsx b/diplomacy/web/src/gui/components/layouts.jsx
index 78189e4..78189e4 100644
--- a/diplomacy/web/src/gui/core/layouts.jsx
+++ b/diplomacy/web/src/gui/components/layouts.jsx
diff --git a/diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx b/diplomacy/web/src/gui/components/message_view.jsx
index 927ff52..927ff52 100644
--- a/diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx
+++ b/diplomacy/web/src/gui/components/message_view.jsx
diff --git a/diplomacy/web/src/gui/diplomacy/widgets/navigation.jsx b/diplomacy/web/src/gui/components/navigation.jsx
index 5d961bc..5d961bc 100644
--- a/diplomacy/web/src/gui/diplomacy/widgets/navigation.jsx
+++ b/diplomacy/web/src/gui/components/navigation.jsx
diff --git a/diplomacy/web/src/gui/diplomacy/widgets/page_context.jsx b/diplomacy/web/src/gui/components/page_context.jsx
index cfb8252..cfb8252 100644
--- a/diplomacy/web/src/gui/diplomacy/widgets/page_context.jsx
+++ b/diplomacy/web/src/gui/components/page_context.jsx
diff --git a/diplomacy/web/src/gui/diplomacy/widgets/power_order.jsx b/diplomacy/web/src/gui/components/power_orders.jsx
index 4ed4d8a..b702a9d 100644
--- a/diplomacy/web/src/gui/diplomacy/widgets/power_order.jsx
+++ b/diplomacy/web/src/gui/components/power_orders.jsx
@@ -16,9 +16,9 @@
// ==============================================================================
import React from "react";
import PropTypes from 'prop-types';
-import {Button} from "../../core/button";
+import {Button} from "./button";
-export class PowerOrder extends React.Component {
+export class PowerOrders extends React.Component {
render() {
const orderEntries = this.props.orders ? Object.entries(this.props.orders) : null;
let display = null;
@@ -50,10 +50,8 @@ export class PowerOrder extends React.Component {
} else {
if (this.props.serverCount < 0) {
display = <div className={'no-orders'}>No orders!</div>;
- } else if (this.props.serverCount === 0) {
- display = <div className={'empty-orders'}>Empty orders set</div>;
} else {
- display = (<div className={'empty-orders'}>Local empty orders set</div>);
+ display = <div className={'empty-orders'}>Asking to unset orders</div>;
}
}
return (
@@ -70,7 +68,7 @@ export class PowerOrder extends React.Component {
}
}
-PowerOrder.propTypes = {
+PowerOrders.propTypes = {
wait: PropTypes.bool,
name: PropTypes.string,
orders: PropTypes.object,
diff --git a/diplomacy/web/src/gui/components/power_orders_actions_bar.js b/diplomacy/web/src/gui/components/power_orders_actions_bar.js
new file mode 100644
index 0000000..2e33a6e
--- /dev/null
+++ b/diplomacy/web/src/gui/components/power_orders_actions_bar.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {Button} from "./button";
+import {Bar} from "./layouts";
+import PropTypes from 'prop-types';
+
+export class PowerOrdersActionBar extends React.Component {
+ render() {
+ return (
+ <Bar className={'p-2'}>
+ <strong className={'mr-4'}>Orders:</strong>
+ <Button title={'reset'} onClick={this.props.onReset}/>
+ <Button title={'delete all'} onClick={this.props.onDeleteAll}/>
+ <Button color={'primary'} title={'update'} onClick={this.props.onUpdate}/>
+ {(!this.props.onProcess &&
+ <Button color={'danger'} title={'process game'} onClick={this.props.onProcess}/>) || ''}
+ </Bar>
+ );
+ }
+}
+
+PowerOrdersActionBar.propTypes = {
+ onReset: PropTypes.func.isRequired,
+ onDeleteAll: PropTypes.func.isRequired,
+ onUpdate: PropTypes.func.isRequired,
+ onProcess: PropTypes.func
+};
diff --git a/diplomacy/web/src/gui/core/tab.jsx b/diplomacy/web/src/gui/components/tab.jsx
index f1ad4aa..f1ad4aa 100644
--- a/diplomacy/web/src/gui/core/tab.jsx
+++ b/diplomacy/web/src/gui/components/tab.jsx
diff --git a/diplomacy/web/src/gui/core/table.jsx b/diplomacy/web/src/gui/components/table.jsx
index cb729e7..cb729e7 100644
--- a/diplomacy/web/src/gui/core/table.jsx
+++ b/diplomacy/web/src/gui/components/table.jsx
diff --git a/diplomacy/web/src/gui/core/tabs.jsx b/diplomacy/web/src/gui/components/tabs.jsx
index a3f6b9b..a3f6b9b 100644
--- a/diplomacy/web/src/gui/core/tabs.jsx
+++ b/diplomacy/web/src/gui/components/tabs.jsx
diff --git a/diplomacy/web/src/gui/diplomacy/forms/connection_form.jsx b/diplomacy/web/src/gui/forms/connection_form.jsx
index 49ba381..eb7e049 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/connection_form.jsx
+++ b/diplomacy/web/src/gui/forms/connection_form.jsx
@@ -15,8 +15,8 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Forms} from "../../core/forms";
-import {UTILS} from "../../../diplomacy/utils/utils";
+import {Forms} from "../components/forms";
+import {UTILS} from "../../diplomacy/utils/utils";
import PropTypes from "prop-types";
import {DipStorage} from "../utils/dipStorage";
diff --git a/diplomacy/web/src/gui/diplomacy/forms/create_form.jsx b/diplomacy/web/src/gui/forms/create_form.jsx
index 48c733e..6753519 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/create_form.jsx
+++ b/diplomacy/web/src/gui/forms/create_form.jsx
@@ -15,8 +15,8 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Forms} from "../../core/forms";
-import {STRINGS} from "../../../diplomacy/utils/strings";
+import {Forms} from "../components/forms";
+import {STRINGS} from "../../diplomacy/utils/strings";
import PropTypes from "prop-types";
export class CreateForm extends React.Component {
diff --git a/diplomacy/web/src/gui/diplomacy/forms/find_form.jsx b/diplomacy/web/src/gui/forms/find_form.jsx
index c73d2b1..2d67aba 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/find_form.jsx
+++ b/diplomacy/web/src/gui/forms/find_form.jsx
@@ -15,8 +15,8 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Forms} from "../../core/forms";
-import {STRINGS} from "../../../diplomacy/utils/strings";
+import {Forms} from "../components/forms";
+import {STRINGS} from "../../diplomacy/utils/strings";
import PropTypes from "prop-types";
export class FindForm extends React.Component {
diff --git a/diplomacy/web/src/gui/diplomacy/forms/join_form.jsx b/diplomacy/web/src/gui/forms/join_form.jsx
index 5b3ec13..e7f8bb2 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/join_form.jsx
+++ b/diplomacy/web/src/gui/forms/join_form.jsx
@@ -15,8 +15,8 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Forms} from "../../core/forms";
-import {STRINGS} from "../../../diplomacy/utils/strings";
+import {Forms} from "../components/forms";
+import {STRINGS} from "../../diplomacy/utils/strings";
import PropTypes from "prop-types";
export class JoinForm extends React.Component {
diff --git a/diplomacy/web/src/gui/diplomacy/forms/message_form.jsx b/diplomacy/web/src/gui/forms/message_form.jsx
index a7c377a..d91a753 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/message_form.jsx
+++ b/diplomacy/web/src/gui/forms/message_form.jsx
@@ -15,8 +15,8 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Forms} from "../../core/forms";
-import {UTILS} from "../../../diplomacy/utils/utils";
+import {Forms} from "../components/forms";
+import {UTILS} from "../../diplomacy/utils/utils";
import PropTypes from "prop-types";
export class MessageForm extends React.Component {
diff --git a/diplomacy/web/src/gui/diplomacy/forms/power_actions_form.jsx b/diplomacy/web/src/gui/forms/power_order_creation_form.jsx
index 2f7c1f5..f15fff8 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/power_actions_form.jsx
+++ b/diplomacy/web/src/gui/forms/power_order_creation_form.jsx
@@ -15,15 +15,15 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Forms} from "../../core/forms";
+import {Forms} from "../components/forms";
import {ORDER_BUILDER} from "../utils/order_building";
-import {STRINGS} from "../../../diplomacy/utils/strings";
+import {STRINGS} from "../../diplomacy/utils/strings";
import PropTypes from "prop-types";
-import {Power} from "../../../diplomacy/engine/power";
+import {Power} from "../../diplomacy/engine/power";
const HotKey = require('react-shortcut');
-export class PowerActionsForm extends React.Component {
+export class PowerOrderCreationForm extends React.Component {
constructor(props) {
super(props);
this.state = this.initState();
@@ -61,7 +61,7 @@ export class PowerActionsForm extends React.Component {
title = 'No orders available for this power.';
}
if (!this.props.power.order_is_set) {
- header.push(Forms.createButton('pass', this.props.onNoOrders));
+ header.push(Forms.createButton('pass', this.props.onPass));
}
if (this.props.role !== STRINGS.OMNISCIENT_TYPE) {
@@ -108,14 +108,14 @@ export class PowerActionsForm extends React.Component {
}
}
-PowerActionsForm.propTypes = {
+PowerOrderCreationForm.propTypes = {
orderType: PropTypes.oneOf(Object.keys(ORDER_BUILDER)),
orderTypes: PropTypes.arrayOf(PropTypes.oneOf(Object.keys(ORDER_BUILDER))),
power: PropTypes.instanceOf(Power),
role: PropTypes.string,
onChange: PropTypes.func,
onSubmit: PropTypes.func,
- onNoOrders: PropTypes.func, // onNoOrders()
+ onPass: PropTypes.func, // onPass(), to submit empty orders set (powers want to do nothing at this phase)
onVote: PropTypes.func, // onVote(voteString)
onSetWaitFlag: PropTypes.func, // onSetWaitFlag(),
};
diff --git a/diplomacy/web/src/gui/diplomacy/forms/select_location_form.jsx b/diplomacy/web/src/gui/forms/select_location_form.jsx
index 6b966d0..ca7be09 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/select_location_form.jsx
+++ b/diplomacy/web/src/gui/forms/select_location_form.jsx
@@ -16,7 +16,7 @@
// ==============================================================================
import React from "react";
import PropTypes from "prop-types";
-import {Button} from "../../core/button";
+import {Button} from "../components/button";
export class SelectLocationForm extends React.Component {
render() {
diff --git a/diplomacy/web/src/gui/diplomacy/forms/select_via_form.jsx b/diplomacy/web/src/gui/forms/select_via_form.jsx
index 51f3306..b779b8d 100644
--- a/diplomacy/web/src/gui/diplomacy/forms/select_via_form.jsx
+++ b/diplomacy/web/src/gui/forms/select_via_form.jsx
@@ -16,7 +16,7 @@
// ==============================================================================
import React from "react";
import PropTypes from "prop-types";
-import {Button} from "../../core/button";
+import {Button} from "../components/button";
export class SelectViaForm extends React.Component {
render() {
diff --git a/diplomacy/web/src/gui/diplomacy/map/dom_order_builder.js b/diplomacy/web/src/gui/map/dom_order_builder.js
index 8b7072e..14ba743 100644
--- a/diplomacy/web/src/gui/diplomacy/map/dom_order_builder.js
+++ b/diplomacy/web/src/gui/map/dom_order_builder.js
@@ -14,10 +14,10 @@
// 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 {UTILS} from "../../../diplomacy/utils/utils";
+import {UTILS} from "../../diplomacy/utils/utils";
import $ from "jquery";
import {extendOrderBuilding} from "../utils/order_building";
-import {Diplog} from "../../../diplomacy/utils/diplog";
+import {Diplog} from "../../diplomacy/utils/diplog";
function parseLocation(txt) {
if (txt.length > 2 && txt[1] === ' ' && ['A', 'F'].includes(txt[0]))
diff --git a/diplomacy/web/src/gui/diplomacy/map/dom_past_map.js b/diplomacy/web/src/gui/map/dom_past_map.js
index eb44616..eb44616 100644
--- a/diplomacy/web/src/gui/diplomacy/map/dom_past_map.js
+++ b/diplomacy/web/src/gui/map/dom_past_map.js
diff --git a/diplomacy/web/src/gui/diplomacy/map/map.jsx b/diplomacy/web/src/gui/map/map.jsx
index 2a2949f..1130563 100644
--- a/diplomacy/web/src/gui/diplomacy/map/map.jsx
+++ b/diplomacy/web/src/gui/map/map.jsx
@@ -16,7 +16,7 @@
// ==============================================================================
import React from "react";
import SVG from 'react-inlinesvg';
-import mapSVG from '../../../standard.svg';
+import mapSVG from '../../standard.svg';
import {Renderer} from "./renderer";
import {MapData} from "../utils/map_data";
import {DOMOrderBuilder} from "./dom_order_builder";
diff --git a/diplomacy/web/src/gui/diplomacy/map/renderer.js b/diplomacy/web/src/gui/map/renderer.js
index e2586af..e2586af 100644
--- a/diplomacy/web/src/gui/diplomacy/map/renderer.js
+++ b/diplomacy/web/src/gui/map/renderer.js
diff --git a/diplomacy/web/src/gui/diplomacy/contents/content_connection.jsx b/diplomacy/web/src/gui/pages/content_connection.jsx
index 5947e01..8cd1d6e 100644
--- a/diplomacy/web/src/gui/diplomacy/contents/content_connection.jsx
+++ b/diplomacy/web/src/gui/pages/content_connection.jsx
@@ -15,12 +15,12 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from 'react';
-import {Connection} from "../../../diplomacy/client/connection";
+import {Connection} from "../../diplomacy/client/connection";
import {ConnectionForm} from "../forms/connection_form";
import {DipStorage} from "../utils/dipStorage";
import {Helmet} from "react-helmet";
-import {Navigation} from "../widgets/navigation";
-import {PageContext} from "../widgets/page_context";
+import {Navigation} from "../components/navigation";
+import {PageContext} from "../components/page_context";
export class ContentConnection extends React.Component {
constructor(props) {
diff --git a/diplomacy/web/src/gui/diplomacy/contents/content_game.jsx b/diplomacy/web/src/gui/pages/content_game.jsx
index b3d933b..f1bc76c 100644
--- a/diplomacy/web/src/gui/diplomacy/contents/content_game.jsx
+++ b/diplomacy/web/src/gui/pages/content_game.jsx
@@ -19,29 +19,32 @@ import Scrollchor from 'react-scrollchor';
import {SelectLocationForm} from "../forms/select_location_form";
import {SelectViaForm} from "../forms/select_via_form";
import {Order} from "../utils/order";
-import {Bar, Row} from "../../core/layouts";
-import {Tabs} from "../../core/tabs";
+import {Row} from "../components/layouts";
+import {Tabs} from "../components/tabs";
import {Map} from "../map/map";
import {extendOrderBuilding, ORDER_BUILDER, POSSIBLE_ORDERS} from "../utils/order_building";
-import {PowerActionsForm} from "../forms/power_actions_form";
+import {PowerOrderCreationForm} from "../forms/power_order_creation_form";
import {MessageForm} from "../forms/message_form";
-import {UTILS} from "../../../diplomacy/utils/utils";
-import {Message} from "../../../diplomacy/engine/message";
-import {PowerOrder} from "../widgets/power_order";
-import {MessageView} from "../widgets/message_view";
-import {STRINGS} from "../../../diplomacy/utils/strings";
-import {Diplog} from "../../../diplomacy/utils/diplog";
-import {Table} from "../../core/table";
+import {UTILS} from "../../diplomacy/utils/utils";
+import {Message} from "../../diplomacy/engine/message";
+import {PowerOrders} from "../components/power_orders";
+import {MessageView} from "../components/message_view";
+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 "../../core/fancybox";
+import {FancyBox} from "../components/fancybox";
import {DipStorage} from "../utils/dipStorage";
import Helmet from 'react-helmet';
-import {Navigation} from "../widgets/navigation";
-import {PageContext} from "../widgets/page_context";
+import {Navigation} from "../components/navigation";
+import {PageContext} from "../components/page_context";
import PropTypes from 'prop-types';
-import {Help} from "../widgets/help";
-import {Tab} from "../../core/tab";
-import {Button} from "../../core/button";
+import {Help} from "../components/help";
+import {Tab} from "../components/tab";
+import {Button} from "../components/button";
+import {saveGameToDisk} from "../utils/saveGameToDisk";
+import {Game} from '../../diplomacy/engine/game';
+import {PowerOrdersActionBar} from "../components/power_orders_actions_bar";
const HotKey = require('react-shortcut');
@@ -68,12 +71,6 @@ const TABLE_POWER_VIEW = {
wait: ['Waiting', 3]
};
-function gameReloaded(game, updates) {
- if (updates)
- return Object.assign({}, updates, game);
- return Object.assign({}, game);
-}
-
export class ContentGame extends React.Component {
constructor(props) {
@@ -145,11 +142,11 @@ export class ContentGame extends React.Component {
this.onOrderBuilding = this.onOrderBuilding.bind(this);
this.onOrderBuilt = this.onOrderBuilt.bind(this);
this.onProcessGame = this.onProcessGame.bind(this);
- this.onRemoveAllOrders = this.onRemoveAllOrders.bind(this);
+ this.onRemoveAllCurrentPowerOrders = this.onRemoveAllCurrentPowerOrders.bind(this);
this.onRemoveOrder = this.onRemoveOrder.bind(this);
this.onSelectLocation = this.onSelectLocation.bind(this);
this.onSelectVia = this.onSelectVia.bind(this);
- this.onSetNoOrders = this.onSetNoOrders.bind(this);
+ this.onSetEmptyOrdersSet = this.onSetEmptyOrdersSet.bind(this);
this.reloadServerOrders = this.reloadServerOrders.bind(this);
this.renderOrders = this.renderOrders.bind(this);
this.sendMessage = this.sendMessage.bind(this);
@@ -170,25 +167,6 @@ export class ContentGame extends React.Component {
return title;
}
- static saveGameToDisk(game, page) {
- if (game.client) {
- game.client.save()
- .then((savedData) => {
- const domLink = document.createElement('a');
- domLink.setAttribute(
- 'href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(savedData)));
- domLink.setAttribute('download', `${game.game_id}.json`);
- domLink.style.display = 'none';
- document.body.appendChild(domLink);
- domLink.click();
- document.body.removeChild(domLink);
- })
- .catch(exc => page.error(`Error while saving game: ${exc.toString()}`));
- } else {
- page.error(`Cannot save this game.`);
- }
- }
-
static getServerWaitFlags(engine) {
const wait = {};
const controllablePowers = engine.getControllablePowers();
@@ -198,23 +176,6 @@ export class ContentGame extends React.Component {
return wait;
}
- static getServerOrders(engine) {
- const orders = {};
- const controllablePowers = engine.getControllablePowers();
- for (let powerName of controllablePowers) {
- const powerOrders = {};
- let countOrders = 0;
- const power = engine.powers[powerName];
- for (let orderString of power.orders) {
- const serverOrder = new Order(orderString, false);
- powerOrders[serverOrder.loc] = serverOrder;
- ++countOrders;
- }
- orders[powerName] = (countOrders || power.order_is_set) ? powerOrders : null;
- }
- return orders;
- }
-
static getOrderBuilding(powerName, orderType, orderPath) {
return {
type: orderType,
@@ -224,6 +185,10 @@ export class ContentGame extends React.Component {
};
}
+ getPage() {
+ return this.context;
+ }
+
closeFancyBox() {
this.setState({
fancy_title: null,
@@ -233,6 +198,8 @@ export class ContentGame extends React.Component {
});
}
+ // [ Methods used to handle current map.
+
setSelectedLocation(location, powerName, orderType, orderPath) {
if (!location)
return;
@@ -283,30 +250,7 @@ export class ContentGame extends React.Component {
});
}
- __get_orders(engine) {
- const orders = ContentGame.getServerOrders(engine);
- if (this.state.orders) {
- for (let powerName of Object.keys(orders)) {
- const serverPowerOrders = orders[powerName];
- const localPowerOrders = this.state.orders[powerName];
- if (localPowerOrders) {
- for (let localOrder of Object.values(localPowerOrders)) {
- localOrder.local = (
- !serverPowerOrders
- || !serverPowerOrders.hasOwnProperty(localOrder.loc)
- || serverPowerOrders[localOrder.loc].order !== localOrder.order
- );
- }
- }
- orders[powerName] = localPowerOrders;
- }
- }
- return orders;
- }
-
- __get_wait(engine) {
- return this.state.wait ? this.state.wait : ContentGame.getServerWaitFlags(engine);
- }
+ // ]
getMapInfo() {
return this.getPage().availableMaps[this.props.data.map_name];
@@ -326,7 +270,7 @@ export class ContentGame extends React.Component {
engine.deadline_timer = 0;
this.clearScheduleTimeout();
}
- this.getPage().load(`game: ${engine.game_id}`, <ContentGame data={gameReloaded(engine)}/>);
+ this.getPage().load(`game: ${engine.game_id}`, <ContentGame data={engine}/>);
}
reloadDeadlineTimer(networkGame) {
@@ -348,6 +292,13 @@ export class ContentGame extends React.Component {
});
}
+ // [ Network game notifications.
+
+ /**
+ * Return True if given network game is the game currently displayed on the interface.
+ * @param {NetworkGame} networkGame - network game to check
+ * @returns {boolean}
+ */
networkGameIsDisplayed(networkGame) {
return this.getPage().getName() === `game: ${networkGame.local.game_id}`;
}
@@ -450,6 +401,8 @@ export class ContentGame extends React.Component {
}
}
+ // ]
+
onChangeCurrentPower(event) {
this.setState({power: event.target.value, tabPastMessages: null, tabCurrentMessages: null});
}
@@ -486,33 +439,145 @@ export class ContentGame extends React.Component {
.catch(error => page.error(error.toString()));
}
+ onProcessGame() {
+ const page = this.getPage();
+ this.props.data.client.process()
+ .then(() => page.success('Game processed.'))
+ .catch(err => {
+ page.error(err.toString());
+ });
+ }
+
+ /**
+ * Get name of current power selected on the game page.
+ * @returns {null|string}
+ */
+ getCurrentPowerName() {
+ const engine = this.props.data;
+ const controllablePowers = engine.getControllablePowers();
+ return this.state.power || (controllablePowers.length && controllablePowers[0]);
+ }
+
+ __get_wait(engine) {
+ return this.state.wait ? this.state.wait : ContentGame.getServerWaitFlags(engine);
+ }
+
+ // [ Methods involved in orders management.
+
+ /**
+ * Return a dictionary of local orders for given game engine.
+ * Returned dictionary maps each power name to either:
+ * - a dictionary of orders, mapping a location to an Order object with boolean flag `local` correctly set
+ * to determine if that order is a new local order or is a copy of an existing server order for this power.
+ * - null or empty dictionary, if there are no local orders defined for this power.
+ * @param {Game} engine - game engine from which we must get local orders
+ * @returns {{}}
+ * @private
+ */
+ __get_orders(engine) {
+ const orders = engine.getServerOrders();
+ if (this.state.orders) {
+ for (let powerName of Object.keys(orders)) {
+ const serverPowerOrders = orders[powerName];
+ const localPowerOrders = this.state.orders[powerName];
+ if (localPowerOrders) {
+ for (let localOrder of Object.values(localPowerOrders)) {
+ localOrder.local = (
+ !serverPowerOrders
+ || !serverPowerOrders.hasOwnProperty(localOrder.loc)
+ || serverPowerOrders[localOrder.loc].order !== localOrder.order
+ );
+ }
+ }
+ orders[powerName] = localPowerOrders;
+ }
+ }
+ return orders;
+ }
+
+ /**
+ * Save given orders into local storage.
+ * @param orders - orders to save
+ * @private
+ */
__store_orders(orders) {
- // Save local orders into local storage.
const username = this.props.data.client.channel.username;
const gameID = this.props.data.game_id;
const gamePhase = this.props.data.phase;
- if (!orders) {
+ if (!orders)
return DipStorage.clearUserGameOrders(username, gameID);
- }
for (let entry of Object.entries(orders)) {
const powerName = entry[0];
let powerOrdersList = null;
- if (entry[1]) {
+ if (entry[1])
powerOrdersList = Object.values(entry[1]).map(order => order.order);
- }
DipStorage.clearUserGameOrders(username, gameID, powerName);
DipStorage.addUserGameOrders(username, gameID, gamePhase, powerName, powerOrdersList);
}
}
+ /**
+ * Reset local orders and replace them with current server orders.
+ */
reloadServerOrders() {
- const serverOrders = ContentGame.getServerOrders(this.props.data);
+ // TODO: This method should reset orders to server version only for current powers, not for all powers as she currently does.
+ const serverOrders = this.props.data.getServerOrders();
this.__store_orders(serverOrders);
this.setState({orders: serverOrders});
}
+ /**
+ * Remove given order from local orders of given power name.
+ * @param {string} powerName - power name
+ * @param {Order} order - order to remove
+ */
+ onRemoveOrder(powerName, order) {
+ const orders = this.__get_orders(this.props.data);
+ if (orders.hasOwnProperty(powerName)
+ && orders[powerName].hasOwnProperty(order.loc)
+ && orders[powerName][order.loc].order === order.order) {
+ delete orders[powerName][order.loc];
+ if (!UTILS.javascript.count(orders[powerName]))
+ orders[powerName] = null;
+ this.__store_orders(orders);
+ this.setState({orders: orders});
+ }
+ }
+
+ /**
+ * Remove all local orders for current selected power
+ */
+ onRemoveAllCurrentPowerOrders() {
+ const currentPowerName = this.getCurrentPowerName();
+ if (currentPowerName) {
+ const engine = this.props.data;
+ const allOrders = this.__get_orders(engine);
+ if (!allOrders.hasOwnProperty(currentPowerName)) {
+ this.getPage().error(`Unknown power ${currentPowerName}.`);
+ return;
+ }
+ allOrders[currentPowerName] = null;
+ this.__store_orders(allOrders);
+ this.setState({orders: allOrders});
+ }
+ }
+
+ /**
+ * Set an empty local orders set for given power name.
+ * @param {string} powerName - power name
+ */
+ onSetEmptyOrdersSet(powerName) {
+ const orders = this.__get_orders(this.props.data);
+ orders[powerName] = {};
+ this.__store_orders(orders);
+ this.setState({orders: orders});
+ }
+
+ /**
+ * Send local orders to server.
+ */
setOrders() {
- const serverOrders = ContentGame.getServerOrders(this.props.data);
+ const serverOrders = this.props.data.getServerOrders();
const orders = this.__get_orders(this.props.data);
for (let entry of Object.entries(orders)) {
@@ -523,24 +588,20 @@ export class ContentGame extends React.Component {
if (serverPowerOrders === null) {
// No orders set on server.
- if (localPowerOrders === null)
- same = true;
+ same = localPowerOrders === null;
// Otherwise, we have local orders set (even empty local orders).
} else if (serverPowerOrders.length === 0) {
// Empty orders set on server.
- // If local orders are null or empty, then we assume
- // it's the same thing as empty order set on server.
- if (localPowerOrders === null || !localPowerOrders.length)
- same = true;
- // Otherwise, we have local non-empty orders set.
+ // If we have empty orders set locally, then it's same thing.
+ same = localPowerOrders && localPowerOrders.length === 0;
+ // Otherwise, we have either local non-empty orders set or local null order.
} else {
// Orders set on server. Identical to local orders only if we have exactly same orders on server and locally.
if (localPowerOrders && localPowerOrders.length === serverPowerOrders.length) {
localPowerOrders.sort();
serverPowerOrders.sort();
- const length = localPowerOrders.length;
same = true;
- for (let i = 0; i < length; ++i) {
+ for (let i = 0; i < localPowerOrders.length; ++i) {
if (localPowerOrders[i] !== serverPowerOrders[i]) {
same = false;
break;
@@ -553,8 +614,15 @@ export class ContentGame extends React.Component {
Diplog.warn(`Orders not changed for ${powerName}.`);
continue;
}
- Diplog.info('Sending orders for ' + powerName + ': ' + JSON.stringify(localPowerOrders));
- this.props.data.client.setOrders({power_name: powerName, orders: localPowerOrders || []})
+
+ Diplog.info(`Sending orders for ${powerName}: ${localPowerOrders ? JSON.stringify(localPowerOrders) : null}`);
+ let requestCall = null;
+ if (localPowerOrders) {
+ requestCall = this.props.data.client.setOrders({power_name: powerName, orders: localPowerOrders});
+ } else {
+ requestCall = this.props.data.client.clearOrders({power_name: powerName});
+ }
+ requestCall
.then(() => {
this.getPage().success('Orders sent.');
})
@@ -567,37 +635,7 @@ export class ContentGame extends React.Component {
}
}
- onProcessGame() {
- const page = this.getPage();
- this.props.data.client.process()
- .then(() => page.success('Game processed.'))
- .catch(err => {
- page.error(err.toString());
- });
- }
-
- onRemoveOrder(powerName, order) {
- const orders = this.__get_orders(this.props.data);
- if (orders.hasOwnProperty(powerName)
- && orders[powerName].hasOwnProperty(order.loc)
- && orders[powerName][order.loc].order === order.order) {
- delete orders[powerName][order.loc];
- if (!UTILS.javascript.count(orders[powerName]))
- orders[powerName] = null;
- this.__store_orders(orders);
- this.setState({orders: orders});
- }
- }
-
- onRemoveAllOrders() {
- const orders = {};
- const controllablePowers = this.props.data.getControllablePowers();
- for (let powerName of controllablePowers) {
- orders[powerName] = null;
- }
- this.__store_orders(orders);
- this.setState({orders: orders});
- }
+ // ]
onOrderBuilding(powerName, path) {
const pathToSave = path.slice(1);
@@ -634,13 +672,6 @@ export class ContentGame extends React.Component {
this.setState(state);
}
- onSetNoOrders(powerName) {
- const orders = this.__get_orders(this.props.data);
- orders[powerName] = {};
- this.__store_orders(orders);
- this.setState({orders: orders});
- }
-
onChangeOrderType(form) {
this.setState({
orderBuildingType: form.order_type,
@@ -730,19 +761,6 @@ export class ContentGame extends React.Component {
this.setState({historyShowOrders: event.target.checked});
}
- renderOrders(engine, currentPowerName) {
- const serverOrders = ContentGame.getServerOrders(this.props.data);
- const orders = this.__get_orders(engine);
- const wait = this.__get_wait(engine);
-
- const render = [];
- render.push(<PowerOrder key={currentPowerName} name={currentPowerName} wait={wait[currentPowerName]}
- orders={orders[currentPowerName]}
- serverCount={serverOrders[currentPowerName] ? UTILS.javascript.count(serverOrders[currentPowerName]) : -1}
- onRemove={this.onRemoveOrder}/>);
- return render;
- }
-
onClickMessage(message) {
if (!message.read) {
message.read = true;
@@ -765,6 +783,21 @@ export class ContentGame extends React.Component {
});
}
+ // [ Rendering methods.
+
+ renderOrders(engine, currentPowerName) {
+ const serverOrders = this.props.data.getServerOrders();
+ const orders = this.__get_orders(engine);
+ const wait = this.__get_wait(engine);
+
+ const render = [];
+ render.push(<PowerOrders key={currentPowerName} name={currentPowerName} wait={wait[currentPowerName]}
+ orders={orders[currentPowerName]}
+ serverCount={serverOrders[currentPowerName] ? UTILS.javascript.count(serverOrders[currentPowerName]) : -1}
+ onRemove={this.onRemoveOrder}/>);
+ return render;
+ }
+
renderPastMessages(engine, role) {
const messageChannels = engine.getMessageChannels(role, true);
const tabNames = [];
@@ -1061,15 +1094,13 @@ export class ContentGame extends React.Component {
{/* Orders. */}
<div className={'panel-orders mb-4'}>
{currentTabOrderCreation ? <div className="mb-4">{currentTabOrderCreation}</div> : ''}
- <Bar className={'p-2'}>
- <strong className={'mr-4'}>Orders:</strong>
- <Button title={'reset'} onClick={this.reloadServerOrders}/>
- <Button title={'delete all'} onClick={this.onRemoveAllOrders}/>
- <Button color={'primary'} title={'update'} onClick={this.setOrders}/>
- {(!this.props.data.isPlayerGame() && this.props.data.observer_level === STRINGS.MASTER_TYPE &&
- <Button color={'danger'} title={'process game'}
- onClick={this.onProcessGame}/>) || ''}
- </Bar>
+ <PowerOrdersActionBar
+ onReset={this.reloadServerOrders}
+ onDeleteAll={this.onRemoveAllCurrentPowerOrders}
+ onUpdate={this.setOrders}
+ onProcess={(!this.props.data.isPlayerGame()
+ && this.props.data.observer_level === STRINGS.MASTER_TYPE) ?
+ this.onProcessGame : null}/>
<div className={'orders'}>{this.renderOrders(this.props.data, powerName)}</div>
<div className={'table-responsive'}>
<Table className={'table table-striped table-sm'}
@@ -1085,9 +1116,9 @@ export class ContentGame extends React.Component {
);
}
- getPage() {
- return this.context;
- }
+ // ]
+
+ // [ React.Component overridden methods.
render() {
this.props.data.displayed = true;
@@ -1097,7 +1128,7 @@ export class ContentGame extends React.Component {
const navigation = [
['Help', () => page.loadFancyBox('Help', () => <Help/>)],
['Load a game from disk', page.loadGameFromDisk],
- ['Save game to disk', () => ContentGame.saveGameToDisk(engine)],
+ ['Save game to disk', () => saveGameToDisk(engine, page.error)],
[`${UTILS.html.UNICODE_SMALL_LEFT_ARROW} Games`, () => page.loadGames()],
[`${UTILS.html.UNICODE_SMALL_LEFT_ARROW} Leave game`, () => page.leaveGame(engine.game_id)],
[`${UTILS.html.UNICODE_SMALL_LEFT_ARROW} Logout`, page.logout]
@@ -1172,14 +1203,14 @@ export class ContentGame extends React.Component {
const currentTabOrderCreation = hasTabCurrentPhase && (
<div>
- <PowerActionsForm orderType={orderBuildingType}
- orderTypes={allowedPowerOrderTypes}
- onChange={this.onChangeOrderType}
- onNoOrders={() => this.onSetNoOrders(currentPowerName)}
- onSetWaitFlag={() => this.setWaitFlag(!currentPower.wait)}
- onVote={this.vote}
- role={engine.role}
- power={currentPower}/>
+ <PowerOrderCreationForm orderType={orderBuildingType}
+ orderTypes={allowedPowerOrderTypes}
+ onChange={this.onChangeOrderType}
+ onPass={() => this.onSetEmptyOrdersSet(currentPowerName)}
+ onSetWaitFlag={() => this.setWaitFlag(!currentPower.wait)}
+ onVote={this.vote}
+ role={engine.role}
+ power={currentPower}/>
{(allowedPowerOrderTypes.length && (
<span>
<strong>Orderable locations</strong>: {orderTypeToLocs[orderBuildingType].join(', ')}
@@ -1262,9 +1293,11 @@ export class ContentGame extends React.Component {
document.onkeydown = null;
}
+ // ]
+
}
ContentGame.contextType = PageContext;
ContentGame.propTypes = {
- data: PropTypes.object.isRequired
+ data: PropTypes.instanceOf(Game).isRequired
};
diff --git a/diplomacy/web/src/gui/diplomacy/contents/content_games.jsx b/diplomacy/web/src/gui/pages/content_games.jsx
index 4ae9f2f..31bd1af 100644
--- a/diplomacy/web/src/gui/diplomacy/contents/content_games.jsx
+++ b/diplomacy/web/src/gui/pages/content_games.jsx
@@ -15,18 +15,18 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import React from "react";
-import {Tabs} from "../../core/tabs";
-import {Table} from "../../core/table";
+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 {STRINGS} from "../../diplomacy/utils/strings";
import {Helmet} from "react-helmet";
-import {Navigation} from "../widgets/navigation";
-import {PageContext} from "../widgets/page_context";
+import {Navigation} from "../components/navigation";
+import {PageContext} from "../components/page_context";
import {ContentGame} from "./content_game";
import PropTypes from 'prop-types';
-import {Tab} from "../../core/tab";
+import {Tab} from "../components/tab";
const TABLE_LOCAL_GAMES = {
game_id: ['Game ID', 0],
diff --git a/diplomacy/web/src/gui/core/page.jsx b/diplomacy/web/src/gui/pages/page.jsx
index 5e7aee2..cd36f6c 100644
--- a/diplomacy/web/src/gui/core/page.jsx
+++ b/diplomacy/web/src/gui/pages/page.jsx
@@ -17,15 +17,15 @@
/** Main class to use to create app GUI. **/
import React from "react";
-import {ContentConnection} from "../diplomacy/contents/content_connection";
+import {ContentConnection} from "./content_connection";
import {UTILS} from "../../diplomacy/utils/utils";
import {Diplog} from "../../diplomacy/utils/diplog";
-import {FancyBox} from "./fancybox";
-import {DipStorage} from "../diplomacy/utils/dipStorage";
-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";
+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";
export class Page extends React.Component {
diff --git a/diplomacy/web/src/gui/diplomacy/utils/dipStorage.jsx b/diplomacy/web/src/gui/utils/dipStorage.jsx
index db5baad..db5baad 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/dipStorage.jsx
+++ b/diplomacy/web/src/gui/utils/dipStorage.jsx
diff --git a/diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx b/diplomacy/web/src/gui/utils/inline_game_view.jsx
index b6c9e67..ec2ca46 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx
+++ b/diplomacy/web/src/gui/utils/inline_game_view.jsx
@@ -16,10 +16,10 @@
// ==============================================================================
import React from "react";
import {JoinForm} from "../forms/join_form";
-import {STRINGS} from "../../../diplomacy/utils/strings";
-import {ContentGame} from "../contents/content_game";
-import {Button} from "../../core/button";
-import {DeleteButton} from "../../core/delete_button";
+import {STRINGS} from "../../diplomacy/utils/strings";
+import {ContentGame} from "../pages/content_game";
+import {Button} from "../components/button";
+import {DeleteButton} from "../components/delete_button";
export class InlineGameView {
constructor(page, gameData) {
diff --git a/diplomacy/web/src/gui/diplomacy/utils/load_game_from_disk.js b/diplomacy/web/src/gui/utils/load_game_from_disk.js
index 1e13f4f..ca49aa0 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/load_game_from_disk.js
+++ b/diplomacy/web/src/gui/utils/load_game_from_disk.js
@@ -1,6 +1,6 @@
import $ from "jquery";
-import {STRINGS} from "../../../diplomacy/utils/strings";
-import {Game} from "../../../diplomacy/engine/game";
+import {STRINGS} from "../../diplomacy/utils/strings";
+import {Game} from "../../diplomacy/engine/game";
export function loadGameFromDisk(onLoad, onError) {
const input = $(document.createElement('input'));
diff --git a/diplomacy/web/src/gui/diplomacy/utils/map_data.js b/diplomacy/web/src/gui/utils/map_data.js
index 73d5338..73d5338 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/map_data.js
+++ b/diplomacy/web/src/gui/utils/map_data.js
diff --git a/diplomacy/web/src/gui/diplomacy/utils/order.js b/diplomacy/web/src/gui/utils/order.js
index e314b9f..e314b9f 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/order.js
+++ b/diplomacy/web/src/gui/utils/order.js
diff --git a/diplomacy/web/src/gui/diplomacy/utils/order_building.js b/diplomacy/web/src/gui/utils/order_building.js
index 3758898..3758898 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/order_building.js
+++ b/diplomacy/web/src/gui/utils/order_building.js
diff --git a/diplomacy/web/src/gui/diplomacy/utils/power_view.jsx b/diplomacy/web/src/gui/utils/power_view.jsx
index 62b488e..df76da4 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/power_view.jsx
+++ b/diplomacy/web/src/gui/utils/power_view.jsx
@@ -14,7 +14,7 @@
// 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 {STRINGS} from "../../../diplomacy/utils/strings";
+import {STRINGS} from "../../diplomacy/utils/strings";
import React from "react";
function getName(power) {
diff --git a/diplomacy/web/src/gui/diplomacy/utils/province.js b/diplomacy/web/src/gui/utils/province.js
index fe54a82..fe54a82 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/province.js
+++ b/diplomacy/web/src/gui/utils/province.js
diff --git a/diplomacy/web/src/gui/utils/saveGameToDisk.js b/diplomacy/web/src/gui/utils/saveGameToDisk.js
new file mode 100644
index 0000000..aae69a4
--- /dev/null
+++ b/diplomacy/web/src/gui/utils/saveGameToDisk.js
@@ -0,0 +1,18 @@
+export function saveGameToDisk(game, onError) {
+ if (game.client) {
+ game.client.save()
+ .then((savedData) => {
+ const domLink = document.createElement('a');
+ domLink.setAttribute(
+ 'href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(savedData)));
+ domLink.setAttribute('download', `${game.game_id}.json`);
+ domLink.style.display = 'none';
+ document.body.appendChild(domLink);
+ domLink.click();
+ document.body.removeChild(domLink);
+ })
+ .catch(exc => onError(`Error while saving game: ${exc.toString()}`));
+ } else {
+ onError(`Cannot save this game.`);
+ }
+}
diff --git a/diplomacy/web/src/index.js b/diplomacy/web/src/index.js
index d074bce..66547af 100644
--- a/diplomacy/web/src/index.js
+++ b/diplomacy/web/src/index.js
@@ -16,7 +16,7 @@
// ==============================================================================
import React from 'react';
import ReactDOM from 'react-dom';
-import {Page} from "./gui/core/page";
+import {Page} from "./gui/pages/page";
import 'popper.js';
import 'bootstrap/dist/js/bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';