aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/web/src/gui
diff options
context:
space:
mode:
authornotoraptor <notoraptor@users.noreply.github.com>2019-07-16 10:39:16 -0400
committerPhilip Paquette <pcpaquette@gmail.com>2019-07-16 10:39:16 -0400
commit09be214dfa0741df2399c3e0c928929138de226b (patch)
tree3d76cb1d9e6f3cd5452563b476fb251103fd3ec7 /diplomacy/web/src/gui
parent5c71a0f73717bffefb5e23a9e100adb62fc54a61 (diff)
[web] Add a button to delete game. (#39)
- [server] Also delete game from RAM.
Diffstat (limited to 'diplomacy/web/src/gui')
-rw-r--r--diplomacy/web/src/gui/core/delete_button.jsx43
-rw-r--r--diplomacy/web/src/gui/core/page.jsx79
-rw-r--r--diplomacy/web/src/gui/diplomacy/contents/content_games.jsx2
-rw-r--r--diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx31
-rw-r--r--diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx2
5 files changed, 139 insertions, 18 deletions
diff --git a/diplomacy/web/src/gui/core/delete_button.jsx b/diplomacy/web/src/gui/core/delete_button.jsx
new file mode 100644
index 0000000..59141fd
--- /dev/null
+++ b/diplomacy/web/src/gui/core/delete_button.jsx
@@ -0,0 +1,43 @@
+import React from "react";
+import {Button} from "./button";
+import PropTypes from "prop-types";
+
+export class DeleteButton extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {step: 0};
+ this.onClick = this.onClick.bind(this);
+ }
+
+ onClick() {
+ this.setState({step: this.state.step + 1}, () => {
+ if (this.state.step === 2)
+ this.props.onClick();
+ });
+ }
+
+ render() {
+ let title = '';
+ let color = '';
+ if (this.state.step === 0) {
+ title = this.props.title;
+ color = 'secondary';
+ } else if (this.state.step === 1) {
+ title = this.props.confirmTitle;
+ color = 'danger';
+ } else if (this.state.step === 2) {
+ title = this.props.waitingTitle;
+ color = 'danger';
+ }
+ return (
+ <Button title={title} color={color} onClick={this.onClick} small={true} large={true}/>
+ );
+ }
+}
+
+DeleteButton.propTypes = {
+ title: PropTypes.string.isRequired,
+ confirmTitle: PropTypes.string.isRequired,
+ waitingTitle: PropTypes.string.isRequired,
+ onClick: PropTypes.func.isRequired
+};
diff --git a/diplomacy/web/src/gui/core/page.jsx b/diplomacy/web/src/gui/core/page.jsx
index ad830f1..560252c 100644
--- a/diplomacy/web/src/gui/core/page.jsx
+++ b/diplomacy/web/src/gui/core/page.jsx
@@ -55,6 +55,10 @@ export class Page extends React.Component {
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);
+ this._remove_from_games = this._remove_from_games.bind(this);
}
static wrapMessage(message) {
@@ -194,6 +198,12 @@ export class Page extends React.Component {
this.setState({myGames: myGames, games: gamesFound});
}
+ getGame(gameID) {
+ if (this.state.myGames.hasOwnProperty(gameID))
+ return this.state.myGames[gameID];
+ return this.state.games[gameID];
+ }
+
getMyGames() {
return Page.__sort_games(Object.values(this.state.myGames));
}
@@ -230,9 +240,40 @@ export class Page extends React.Component {
}
}
+ _post_remove(gameID) {
+ this.disconnectGame(gameID)
+ .then(() => {
+ const myGames = this._remove_from_my_games(gameID);
+ const games = this._remove_from_games(gameID);
+ this.setState(
+ {games, myGames},
+ () => this.loadGames({info: `Game ${gameID} deleted.`}));
+ });
+ }
+
+ removeGame(gameID) {
+ const game = this.getGame(gameID);
+ if (game) {
+ if (game.client) {
+ game.client.remove()
+ .then(() => this._post_remove(gameID))
+ .catch(error => this.error(`Error when deleting game ${gameID}: ${error.toString()}`));
+ } else {
+ this.channel.joinGame({game_id: gameID})
+ .then(networkGame => {
+ networkGame.remove()
+ .then(() => this._post_remove(gameID))
+ .catch(error => this.error(`Error when deleting game ${gameID}: ${error.toString()}`));
+ })
+ .catch(error => this.error(`Error when connecting to game to delete (${gameID}): ${error.toString()}`));
+ }
+ }
+ }
+
+
disconnectGame(gameID) {
- if (this.state.myGames.hasOwnProperty(gameID)) {
- const game = this.state.myGames[gameID];
+ const game = this.getGame(gameID);
+ if (game) {
if (game.client)
game.client.clearAllCallbacks();
return this.channel.getGamesInfo({games: [gameID]})
@@ -244,26 +285,48 @@ export class Page extends React.Component {
return null;
}
- addToMyGames(game) {
- // Update state myGames with given game **and** update local storage.
+ _add_to_my_games(game) {
const myGames = Object.assign({}, this.state.myGames);
const gamesFound = this.state.games.hasOwnProperty(game.game_id) ? Object.assign({}, this.state.games) : this.state.games;
myGames[game.game_id] = game;
if (gamesFound.hasOwnProperty(game.game_id))
gamesFound[game.game_id] = game;
- DipStorage.addUserGame(this.channel.username, game.game_id);
- this.setState({myGames: myGames, games: gamesFound}, () => this.loadGames());
+ return {myGames: myGames, games: gamesFound};
}
- removeFromMyGames(gameID) {
+ _remove_from_my_games(gameID) {
if (this.state.myGames.hasOwnProperty(gameID)) {
const games = Object.assign({}, this.state.myGames);
delete games[gameID];
DipStorage.removeUserGame(this.channel.username, gameID);
- this.setState({myGames: games}, () => this.loadGames());
+ return games;
+ } else {
+ return this.state.myGames;
+ }
+ }
+
+ _remove_from_games(gameID) {
+ if (this.state.games.hasOwnProperty(gameID)) {
+ const games = Object.assign({}, this.state.games);
+ delete games[gameID];
+ return games;
+ } else {
+ return this.state.games;
}
}
+ addToMyGames(game) {
+ // Update state myGames with given game **and** update local storage.
+ DipStorage.addUserGame(this.channel.username, game.game_id);
+ this.setState(this._add_to_my_games(game), () => this.loadGames());
+ }
+
+ removeFromMyGames(gameID) {
+ const myGames = this._remove_from_my_games(gameID);
+ if (myGames !== this.state.myGames)
+ this.setState({myGames}, () => this.loadGames());
+ }
+
hasMyGame(gameID) {
return this.state.myGames.hasOwnProperty(gameID);
}
diff --git a/diplomacy/web/src/gui/diplomacy/contents/content_games.jsx b/diplomacy/web/src/gui/diplomacy/contents/content_games.jsx
index 51ad998..4ae9f2f 100644
--- a/diplomacy/web/src/gui/diplomacy/contents/content_games.jsx
+++ b/diplomacy/web/src/gui/diplomacy/contents/content_games.jsx
@@ -37,7 +37,7 @@ const TABLE_LOCAL_GAMES = {
status: ['Status', 5],
phase: ['Phase', 6],
join: ['Join', 7],
- my_games: ['My Games', 8],
+ actions: ['Actions', 8],
};
export class ContentGames extends React.Component {
diff --git a/diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx b/diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx
index 3de649c..b6c9e67 100644
--- a/diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx
+++ b/diplomacy/web/src/gui/diplomacy/utils/inline_game_view.jsx
@@ -19,6 +19,7 @@ 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";
export class InlineGameView {
constructor(page, gameData) {
@@ -80,19 +81,33 @@ export class InlineGameView {
}
}
- getMyGamesButton() {
+ getActionButtons() {
+ const buttons = [];
+ // Button to add/remove game from "My games" list.
if (this.page.hasMyGame(this.game.game_id)) {
if (!this.game.client) {
// Game in My Games and not joined. We can remove it.
- return <Button key={`my-game-remove`} title={'Remove from My Games'}
- onClick={() => this.page.removeFromMyGames(this.game.game_id)}/>;
+ buttons.push(<Button key={`my-game-remove`} title={'Remove from My Games'}
+ small={true} large={true}
+ onClick={() => this.page.removeFromMyGames(this.game.game_id)}/>);
}
} else {
// Game not in My Games, we can add it.
- return <Button key={`my-game-add`} title={'Add to My Games'}
- onClick={() => this.page.addToMyGames(this.game)}/>;
+ buttons.push(<Button key={`my-game-add`} title={'Add to My Games'}
+ small={true} large={true}
+ onClick={() => this.page.addToMyGames(this.game)}/>);
}
- return '';
+ // Button to delete game.
+ if ([STRINGS.MASTER_TYPE, STRINGS.OMNISCIENT_TYPE].includes(this.game.observer_level)) {
+ buttons.push(
+ <DeleteButton key={`game-delete-${this.game.game_id}`}
+ title={'Delete this game'}
+ confirmTitle={'Click again to confirm deletion'}
+ waitingTitle={'Deleting ...'}
+ onClick={() => this.page.removeGame(this.game.game_id)}/>
+ );
+ }
+ return buttons;
}
get(name) {
@@ -128,8 +143,8 @@ export class InlineGameView {
}
if (name === 'join')
return this.getJoinUI();
- if (name === 'my_games')
- return this.getMyGamesButton();
+ if (name === 'actions')
+ return this.getActionButtons();
if (name === 'game_id') {
const date = new Date(this.game.timestamp_created / 1000);
const dateString = `${date.toLocaleDateString()} - ${date.toLocaleTimeString()}`;
diff --git a/diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx b/diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx
index 46153b8..927ff52 100644
--- a/diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx
+++ b/diplomacy/web/src/gui/diplomacy/widgets/message_view.jsx
@@ -48,7 +48,7 @@ export class MessageView extends React.Component {
</div>
<div className="message-content col-md">
{messagesLines.map((line, lineIndex) => <div key={lineIndex}>{
- line.replace(/(<([^>]+)>)/ig,"")
+ line.replace(/(<([^>]+)>)/ig, "")
}</div>)}
</div>
</div>