From 5c71a0f73717bffefb5e23a9e100adb62fc54a61 Mon Sep 17 00:00:00 2001 From: Philip Paquette Date: Sun, 7 Jul 2019 09:23:59 -0400 Subject: Updated the web interface to have 3 tabs - Results / Messages / Current - Updated dependencies in package-lock.json - Set default homepage to "." so that built pages are relative to "index.html". - Add module "helmet" to handle page title. - Simplified page loading (replace static function builder with direct call to a method with component as argument). - Move function loadGameFromDisk in a separate file. - Use React context to access page object. - Add a new React component "Navigation" and simplify page rendering. - Add ability to choose power for any kind of loaded game. In phase history: - Show messages from all past and currently displayed phase. - Display messages from past phase with gray background. - Show messages per protagonist in tabs. - Show message phase in message header - Display message wide (header left, body right). - Display short names for powers in message tabs header. - Add warn function to page component. - Messages from previous phase are displayed with gray color text. - Game registration password input is displayed only if required - On games page: - sorted by descending timestamp created. - In table, game ID is displayed with human readable created date. - Prevent messages from displaying twice. - Re-add checkbox "show orders" to display arrow orders on past maps. - Handle HTML break-lines
and remove all other HTML tags when displaying messages. - Use latest phase as current game phase when loading a game from disk. --- diplomacy/web/src/gui/core/page.jsx | 277 ++++++++++-------------------------- 1 file changed, 75 insertions(+), 202 deletions(-) (limited to 'diplomacy/web/src/gui/core/page.jsx') diff --git a/diplomacy/web/src/gui/core/page.jsx b/diplomacy/web/src/gui/core/page.jsx index 5ca09fd..ad830f1 100644 --- a/diplomacy/web/src/gui/core/page.jsx +++ b/diplomacy/web/src/gui/core/page.jsx @@ -18,22 +18,14 @@ import React from "react"; import {ContentConnection} from "../diplomacy/contents/content_connection"; -import {ContentGames} from "../diplomacy/contents/content_games"; -import {ContentGame} from "../diplomacy/contents/content_game"; import {UTILS} from "../../diplomacy/utils/utils"; import {Diplog} from "../../diplomacy/utils/diplog"; -import {STRINGS} from "../../diplomacy/utils/strings"; -import {Game} from "../../diplomacy/engine/game"; -import Octicon, {Person} from '@githubprimer/octicons-react'; -import $ from "jquery"; import {FancyBox} from "./fancybox"; import {DipStorage} from "../diplomacy/utils/dipStorage"; - -const CONTENTS = { - connection: ContentConnection, - games: ContentGames, - game: ContentGame -}; +import {PageContext} from "../diplomacy/widgets/page_context"; +import {ContentGames} from "../diplomacy/contents/content_games"; +import {loadGameFromDisk} from "../diplomacy/utils/load_game_from_disk"; +import {ContentGame} from "../diplomacy/contents/content_game"; export class Page extends React.Component { @@ -50,23 +42,18 @@ export class Page extends React.Component { error: null, info: null, success: null, - title: null, // Page content parameters - contentName: 'connection', - contentData: null, + name: null, + body: null, // Games. games: {}, // Games found. myGames: {} // Games locally stored. }; - this.loadPage = this.loadPage.bind(this); - this.loadConnection = this.loadConnection.bind(this); - this.loadGames = this.loadGames.bind(this); - this.loadGame = this.loadGame.bind(this); - this.loadGameFromDisk = this.loadGameFromDisk.bind(this); - this.logout = this.logout.bind(this); this.error = this.error.bind(this); this.info = this.info.bind(this); this.success = this.success.bind(this); + this.logout = this.logout.bind(this); + this.loadGameFromDisk = this.loadGameFromDisk.bind(this); this.unloadFancyBox = this.unloadFancyBox.bind(this); } @@ -80,26 +67,8 @@ export class Page extends React.Component { return games; } - copyState(updatedFields) { - return Object.assign({}, this.state, updatedFields || {}); - } - - //// Methods to check page type. - - __page_is(contentName, contentData) { - return this.state.contentName === contentName && (!contentData || this.state.contentData === contentData); - } - - pageIsConnection(contentData) { - return this.__page_is('connection', contentData); - } - - pageIsGames(contentData) { - return this.__page_is('games', contentData); - } - - pageIsGame(contentData) { - return this.__page_is('game', contentData); + static defaultPage() { + return ; } //// Methods to load a global fancybox. @@ -114,91 +83,39 @@ export class Page extends React.Component { //// Methods to load a page. - loadPage(contentName, contentData, messages) { - messages = messages || {}; - messages.error = Page.wrapMessage(messages.error); - messages.info = Page.wrapMessage(messages.info); - messages.success = Page.wrapMessage(messages.success); - Diplog.printMessages(messages); - this.setState(this.copyState({ - error: messages.error, - info: messages.info, - success: messages.success, - contentName: contentName, - contentData: contentData, - title: null, - fancyTitle: null, - onFancyBox: null - })); - } - - loadConnection(contentData, messages) { - this.loadPage('connection', contentData, messages); + load(name, body, messages) { + const newState = {}; + if (messages) { + for (let key of ['error', 'info', 'success']) + newState[key] = Page.wrapMessage(messages[key]); + } + Diplog.printMessages(newState); + newState.name = name; + newState.body = body; + this.setState(newState); } - loadGames(contentData, messages) { - this.loadPage('games', contentData, messages); + loadGames(messages) { + this.load( + 'games', + , + messages + ); } - loadGame(gameInfo, messages) { - this.loadPage('game', gameInfo, messages); + loadGameFromDisk() { + loadGameFromDisk( + (game) => this.load( + `game: ${game.game_id}`, + , + {success: `Game loaded from disk: ${game.game_id}`} + ), + this.error + ); } - loadGameFromDisk() { - const input = $(document.createElement('input')); - input.attr("type", "file"); - input.trigger('click'); - input.change(event => { - const file = event.target.files[0]; - if (!file.name.match(/\.json$/i)) { - this.error(`Invalid JSON filename ${file.name}`); - } else { - const reader = new FileReader(); - reader.onload = () => { - const savedData = JSON.parse(reader.result); - const gameObject = {}; - gameObject.game_id = `(local) ${savedData.id}`; - gameObject.map_name = savedData.map; - gameObject.rules = savedData.rules; - const state_history = {}; - const message_history = {}; - const order_history = {}; - const result_history = {}; - for (let savedPhase of savedData.phases) { - const gameState = savedPhase.state; - const phaseOrders = savedPhase.orders || {}; - const phaseResults = savedPhase.results || {}; - const phaseMessages = {}; - if (savedPhase.messages) { - for (let message of savedPhase.messages) { - phaseMessages[message.time_sent] = message; - } - } - if (!gameState.name) - gameState.name = savedPhase.name; - state_history[gameState.name] = gameState; - order_history[gameState.name] = phaseOrders; - message_history[gameState.name] = phaseMessages; - result_history[gameState.name] = phaseResults; - } - gameObject.state_history = state_history; - gameObject.message_history = message_history; - gameObject.order_history = order_history; - gameObject.state_history = state_history; - gameObject.result_history = result_history; - gameObject.messages = []; - gameObject.role = STRINGS.OBSERVER_TYPE; - gameObject.status = STRINGS.COMPLETED; - gameObject.timestamp_created = 0; - gameObject.deadline = 0; - gameObject.n_controls = 0; - gameObject.registration_password = ''; - const game = new Game(gameObject); - this.loadGame(game); - }; - reader.readAsText(file); - } - }); + getName() { + return this.state.name; } //// Methods to sign out channel and go back to connection page. @@ -211,16 +128,16 @@ export class Page extends React.Component { this.availableMaps = null; const message = Page.wrapMessage(`Disconnected from channel and server.`); Diplog.success(message); - this.setState(this.copyState({ + this.setState({ error: null, info: null, success: message, - contentName: 'connection', - contentData: null, + name: null, + body: null, // When disconnected, remove all games previously loaded. games: {}, myGames: {} - })); + }); } logout() { @@ -236,10 +153,6 @@ export class Page extends React.Component { //// Methods to be used to set page title and messages. - setTitle(title) { - this.setState({title: title}); - } - error(message) { message = Page.wrapMessage(message); Diplog.error(message); @@ -306,11 +219,14 @@ export class Page extends React.Component { if (game.client) { game.client.leave() .then(() => { - this.disconnectGame(gameID); - this.loadGames(null, {info: `Game ${gameID} left.`}); + this.disconnectGame(gameID).then(() => { + this.loadGames({info: `Game ${gameID} left.`}); + }); }) .catch(error => this.error(`Error when leaving game ${gameID}: ${error.toString()}`)); } + } else { + this.loadGames({info: `No game to left.`}); } } @@ -319,12 +235,13 @@ export class Page extends React.Component { const game = this.state.myGames[gameID]; if (game.client) game.client.clearAllCallbacks(); - this.channel.getGamesInfo({games: [gameID]}) + return this.channel.getGamesInfo({games: [gameID]}) .then(gamesInfo => { this.updateMyGames(gamesInfo); }) .catch(error => this.error(`Error while leaving game ${gameID}: ${error.toString()}`)); } + return null; } addToMyGames(game) { @@ -335,7 +252,7 @@ export class Page extends React.Component { if (gamesFound.hasOwnProperty(game.game_id)) gamesFound[game.game_id] = game; DipStorage.addUserGame(this.channel.username, game.game_id); - this.setState({myGames: myGames, games: gamesFound}); + this.setState({myGames: myGames, games: gamesFound}, () => this.loadGames()); } removeFromMyGames(gameID) { @@ -343,7 +260,7 @@ export class Page extends React.Component { const games = Object.assign({}, this.state.myGames); delete games[gameID]; DipStorage.removeUserGame(this.channel.username, gameID); - this.setState({myGames: games}); + this.setState({myGames: games}, () => this.loadGames()); } } @@ -354,81 +271,37 @@ export class Page extends React.Component { //// Render method. render() { - const content = CONTENTS[this.state.contentName].builder(this, this.state.contentData); - const hasNavigation = UTILS.javascript.hasArray(content.navigation); - - // NB: I currently don't find a better way to update document title from content details. const successMessage = this.state.success || '-'; const infoMessage = this.state.info || '-'; const errorMessage = this.state.error || '-'; - const title = this.state.title || content.title; - document.title = title + ' | Diplomacy'; - return ( -
-
-
this.success()}> - {successMessage} -
-
this.info()}> - {infoMessage} -
-
this.error()}> - {errorMessage} -
-
- {((hasNavigation || this.channel) && ( -
-
{title}
-
- {(!hasNavigation && ( -
- - {this.channel.username} - - -
- )) || ( -
- -
- {content.navigation.map((nav, index) => { - const navTitle = nav[0]; - const navAction = nav[1]; - return {navTitle}; - })} -
-
- )} + +
+
+
this.success()}> + {successMessage} +
+
this.info()}> + {infoMessage} +
+
this.error()}> + {errorMessage}
- )) || ( -
{title}
- )} - {content.component} - {this.state.onFancyBox && ( - - {this.state.onFancyBox()} - - )} -
+ {this.state.body || Page.defaultPage()} + {this.state.onFancyBox && ( + + {this.state.onFancyBox()} + + )} +
+ ); } } -- cgit v1.2.3