diff options
Diffstat (limited to 'diplomacy/web/src/gui/core')
-rw-r--r-- | diplomacy/web/src/gui/core/action.jsx | 52 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/button.jsx | 52 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/delete_button.jsx | 43 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/fancybox.jsx | 59 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/forms.jsx | 116 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/layouts.jsx | 55 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/page.jsx | 375 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/tab.jsx | 29 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/table.jsx | 112 | ||||
-rw-r--r-- | diplomacy/web/src/gui/core/tabs.jsx | 69 |
10 files changed, 0 insertions, 962 deletions
diff --git a/diplomacy/web/src/gui/core/action.jsx b/diplomacy/web/src/gui/core/action.jsx deleted file mode 100644 index 73fe8cb..0000000 --- a/diplomacy/web/src/gui/core/action.jsx +++ /dev/null @@ -1,52 +0,0 @@ -// ============================================================================== -// Copyright (C) 2019 - Philip Paquette, Steven Bocco -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License along -// with this program. If not, see <https://www.gnu.org/licenses/>. -// ============================================================================== -import React from "react"; -import PropTypes from 'prop-types'; - - -export class Action extends React.Component { - // title - // isActive - // onClick - // See Button parameters. - - render() { - return ( - <div className="action nav-item" onClick={this.props.onClick}> - <div - className={'nav-link' + (this.props.isActive ? ' active' : '') + (this.props.highlight !== null ? ' updated' : '')}> - {this.props.title} - {this.props.highlight !== null - && this.props.highlight !== undefined - && <span className={'update'}>{this.props.highlight}</span>} - </div> - </div> - ); - } -} - -Action.propTypes = { - title: PropTypes.string.isRequired, - onClick: PropTypes.func.isRequired, - highlight: PropTypes.any, - isActive: PropTypes.bool -}; - -Action.defaultProps = { - highlight: null, - isActive: false -}; diff --git a/diplomacy/web/src/gui/core/button.jsx b/diplomacy/web/src/gui/core/button.jsx deleted file mode 100644 index 0d5dadd..0000000 --- a/diplomacy/web/src/gui/core/button.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; - -export class Button extends React.Component { - /** Bootstrap button. - * Bootstrap classes: - * - btn - * - btn-primary - * - mx-1 (margin-left 1px, margin-right 1px) - * Props: title (str), onClick (function). - * **/ - // title - // onClick - // pickEvent = false - // large = false - // small = false - - constructor(props) { - super(props); - this.onClick = this.onClick.bind(this); - } - - onClick(event) { - if (this.props.onClick) - this.props.onClick(this.props.pickEvent ? event : null); - } - - render() { - return ( - <button - className={`btn btn-${this.props.color || 'secondary'}` + (this.props.large ? ' btn-block' : '') + (this.props.small ? ' btn-sm' : '')} - disabled={this.props.disabled} - onClick={this.onClick}> - <strong>{this.props.title}</strong> - </button> - ); - } -} - -Button.propTypes = { - title: PropTypes.string.isRequired, - onClick: PropTypes.func.isRequired, - color: PropTypes.string, - large: PropTypes.bool, - small: PropTypes.bool, - pickEvent: PropTypes.bool, - disabled: PropTypes.bool -}; - -Button.defaultPropTypes = { - disabled: false -}; diff --git a/diplomacy/web/src/gui/core/delete_button.jsx b/diplomacy/web/src/gui/core/delete_button.jsx deleted file mode 100644 index 59141fd..0000000 --- a/diplomacy/web/src/gui/core/delete_button.jsx +++ /dev/null @@ -1,43 +0,0 @@ -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/fancybox.jsx b/diplomacy/web/src/gui/core/fancybox.jsx deleted file mode 100644 index 66a1efe..0000000 --- a/diplomacy/web/src/gui/core/fancybox.jsx +++ /dev/null @@ -1,59 +0,0 @@ -// ============================================================================== -// Copyright (C) 2019 - Philip Paquette, Steven Bocco -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License along -// with this program. If not, see <https://www.gnu.org/licenses/>. -// ============================================================================== -import React from 'react'; -import PropTypes from 'prop-types'; -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> - </div> - </div> - ); - } -} - - -FancyBox.propTypes = { - title: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) -}; diff --git a/diplomacy/web/src/gui/core/forms.jsx b/diplomacy/web/src/gui/core/forms.jsx deleted file mode 100644 index da7250d..0000000 --- a/diplomacy/web/src/gui/core/forms.jsx +++ /dev/null @@ -1,116 +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 {UTILS} from "../../diplomacy/utils/utils"; -import {Button} from "./button"; - -export class Forms { - static createOnChangeCallback(component, callback) { - return (event) => { - const value = UTILS.html.isCheckBox(event.target) ? event.target.checked : event.target.value; - const fieldName = UTILS.html.isRadioButton(event.target) ? event.target.name : event.target.id; - const update = {[fieldName]: value}; - const state = Object.assign({}, component.state, update); - if (callback) - callback(state); - component.setState(state); - }; - } - - static createOnSubmitCallback(component, callback, resetState) { - return (event) => { - if (callback) - callback(Object.assign({}, component.state)); - if (resetState) - component.setState(resetState); - event.preventDefault(); - }; - } - - static createOnResetCallback(component, onChangeCallback, resetState) { - return (event) => { - if (onChangeCallback) - onChangeCallback(resetState); - component.setState(resetState); - if (event && event.preventDefault) - event.preventDefault(); - }; - } - - static getValue(fieldValues, fieldName, defaultValue) { - return fieldValues.hasOwnProperty(fieldName) ? fieldValues[fieldName] : defaultValue; - } - - static createReset(title, large, onReset) { - return <Button key={'reset'} title={title || 'reset'} onClick={onReset} pickEvent={true} large={large}/>; - } - - static createSubmit(title, large, onSubmit) { - return <Button key={'submit'} title={title || 'submit'} onClick={onSubmit} pickEvent={true} large={large}/>; - } - - static createButton(title, fn, color, large) { - const wrapFn = (event) => { - fn(); - event.preventDefault(); - }; - return <Button large={large} key={title} color={color} title={title} onClick={wrapFn} pickEvent={true}/>; - } - - static createCheckbox(id, title, value, onChange) { - const input = <input className={'form-check-input'} key={id} type={'checkbox'} id={id} checked={value} - onChange={onChange}/>; - const label = <label className={'form-check-label'} key={`label-${id}`} htmlFor={id}>{title}</label>; - return [input, label]; - } - - static createRadio(name, value, title, currentValue, onChange) { - const id = `[${name}][${value}]`; - const input = <input className={'form-check-input'} key={id} type={'radio'} - name={name} value={value} checked={currentValue === value} - id={id} onChange={onChange}/>; - const label = <label className={'form-check-label'} key={`label-${id}`} htmlFor={id}>{title || value}</label>; - return [input, label]; - } - - static createRow(label, input) { - return ( - <div className={'form-group row'}> - {label} - <div className={'col'}>{input}</div> - </div> - ); - } - - static createLabel(htmFor, title, className) { - return <label className={className} htmlFor={htmFor}>{title}</label>; - } - - static createColLabel(htmlFor, title) { - return Forms.createLabel(htmlFor, title, 'col'); - } - - static createSelectOptions(values, none) { - const options = values.slice(); - const components = options.map((option, index) => <option key={index} value={option}>{option}</option>); - if (none) { - components.splice(0, 0, [<option key={-1} value={''}>{none === true ? '(none)' : `${none}`}</option>]); - } - return components; - } -} - diff --git a/diplomacy/web/src/gui/core/layouts.jsx b/diplomacy/web/src/gui/core/layouts.jsx deleted file mode 100644 index 78189e4..0000000 --- a/diplomacy/web/src/gui/core/layouts.jsx +++ /dev/null @@ -1,55 +0,0 @@ -// ============================================================================== -// Copyright (C) 2019 - Philip Paquette, Steven Bocco -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License along -// with this program. If not, see <https://www.gnu.org/licenses/>. -// ============================================================================== -import React from 'react'; -import PropTypes from 'prop-types'; - -class Div extends React.Component { - getClassName() { - return ''; - } - - render() { - return ( - <div className={this.getClassName() + (this.props.className ? ' ' + this.props.className : '')}> - {this.props.children} - </div> - ); - } -} - -Div.propTypes = { - className: PropTypes.string, - children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) -}; - -export class Bar extends Div { - getClassName() { - return 'bar'; - } -} - -export class Row extends Div { - getClassName() { - return 'row'; - } -} - -export class Col extends Div { - getClassName() { - return 'col'; - } -} diff --git a/diplomacy/web/src/gui/core/page.jsx b/diplomacy/web/src/gui/core/page.jsx deleted file mode 100644 index 5e7aee2..0000000 --- a/diplomacy/web/src/gui/core/page.jsx +++ /dev/null @@ -1,375 +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/>. -// ============================================================================== -/** Main class to use to create app GUI. **/ - -import React from "react"; -import {ContentConnection} from "../diplomacy/contents/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"; - -export class Page extends React.Component { - - constructor(props) { - super(props); - this.connection = null; - this.channel = null; - this.availableMaps = null; - this.state = { - // fancybox, - fancyTitle: null, - onFancyBox: null, - // Page messages - error: null, - info: null, - success: null, - // Page content parameters - name: null, - body: null, - // Games. - games: {}, // Games found. - myGames: {} // Games locally stored. - }; - 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); - 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); - this.onReconnectionError = this.onReconnectionError.bind(this); - } - - static wrapMessage(message) { - return message ? `(${UTILS.date()}) ${message}` : ''; - } - - static __sort_games(games) { - // Sort games with not-joined games first, else compare game ID. - games.sort((a, b) => (((a.role ? 1 : 0) - (b.role ? 1 : 0)) || a.game_id.localeCompare(b.game_id))); - return games; - } - - static defaultPage() { - return <ContentConnection/>; - } - - onReconnectionError(error) { - this.__disconnect(error); - } - - //// Methods to load a global fancybox. - - loadFancyBox(title, callback) { - this.setState({fancyTitle: title, onFancyBox: callback}); - } - - unloadFancyBox() { - this.setState({fancyTitle: null, onFancyBox: null}); - } - - //// Methods to load a page. - - 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(messages) { - this.load( - 'games', - <ContentGames myGames={this.getMyGames()} gamesFound={this.getGamesFound()}/>, - messages - ); - } - - loadGameFromDisk() { - loadGameFromDisk( - (game) => this.load( - `game: ${game.game_id}`, - <ContentGame data={game}/>, - {success: `Game loaded from disk: ${game.game_id}`} - ), - this.error - ); - } - - getName() { - return this.state.name; - } - - //// Methods to sign out channel and go back to connection page. - - __disconnect(error) { - // Clear local data and go back to connection page. - this.connection.close(); - this.connection = null; - this.channel = null; - this.availableMaps = null; - const message = Page.wrapMessage(error ? `${error.toString()}` : `Disconnected from channel and server.`); - Diplog.success(message); - this.setState({ - error: error ? message : null, - info: null, - success: error ? null : message, - name: null, - body: null, - // When disconnected, remove all games previously loaded. - games: {}, - myGames: {} - }); - } - - logout() { - // Disconnect channel and go back to connection page. - if (this.channel) { - this.channel.logout() - .then(() => this.__disconnect()) - .catch(error => this.error(`Error while disconnecting: ${error.toString()}.`)); - } else { - this.__disconnect(); - } - } - - //// Methods to be used to set page title and messages. - - error(message) { - message = Page.wrapMessage(message); - Diplog.error(message); - this.setState({error: message}); - } - - info(message) { - message = Page.wrapMessage(message); - Diplog.info(message); - this.setState({info: message}); - } - - success(message) { - message = Page.wrapMessage(message); - Diplog.success(message); - this.setState({success: message}); - } - - warn(message) { - this.info(message); - } - - //// Methods to manage games. - - updateMyGames(gamesToAdd) { - // Update state myGames with given games. This method does not update local storage. - const myGames = Object.assign({}, this.state.myGames); - let gamesFound = null; - for (let gameToAdd of gamesToAdd) { - myGames[gameToAdd.game_id] = gameToAdd; - if (this.state.games.hasOwnProperty(gameToAdd.game_id)) { - if (!gamesFound) - gamesFound = Object.assign({}, this.state.games); - gamesFound[gameToAdd.game_id] = gameToAdd; - } - } - if (!gamesFound) - gamesFound = this.state.games; - 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)); - } - - getGamesFound() { - return Page.__sort_games(Object.values(this.state.games)); - } - - addGamesFound(gamesToAdd) { - const gamesFound = {}; - for (let game of gamesToAdd) { - gamesFound[game.game_id] = ( - this.state.myGames.hasOwnProperty(game.game_id) ? - this.state.myGames[game.game_id] : game - ); - } - this.setState({games: gamesFound}); - } - - leaveGame(gameID) { - if (this.state.myGames.hasOwnProperty(gameID)) { - const game = this.state.myGames[gameID]; - if (game.client) { - game.client.leave() - .then(() => { - 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.`}); - } - } - - _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) { - const game = this.getGame(gameID); - if (game) { - if (game.client) - game.client.clearAllCallbacks(); - return this.channel.getGamesInfo({games: [gameID]}) - .then(gamesInfo => { - this.updateMyGames(gamesInfo); - }) - .catch(error => this.error(`Error while leaving game ${gameID}: ${error.toString()}`)); - } - return null; - } - - _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; - return {myGames: myGames, games: gamesFound}; - } - - _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); - 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); - } - - //// Render method. - - render() { - const successMessage = this.state.success || '-'; - const infoMessage = this.state.info || '-'; - const errorMessage = this.state.error || '-'; - return ( - <PageContext.Provider value={this}> - <div className="page container-fluid" id={this.state.contentName}> - <div className={'top-msg row'}> - <div title={successMessage !== '-' ? successMessage : ''} - className={'col-sm-4 msg success ' + (this.state.success ? 'with-msg' : 'no-msg')} - onClick={() => this.success()}> - {successMessage} - </div> - <div title={infoMessage !== '-' ? infoMessage : ''} - className={'col-sm-4 msg info ' + (this.state.info ? 'with-msg' : 'no-msg')} - onClick={() => this.info()}> - {infoMessage} - </div> - <div title={errorMessage !== '-' ? errorMessage : ''} - className={'col-sm-4 msg error ' + (this.state.error ? 'with-msg' : 'no-msg')} - onClick={() => this.error()}> - {errorMessage} - </div> - </div> - {this.state.body || Page.defaultPage()} - {this.state.onFancyBox && ( - <FancyBox title={this.state.fancyTitle} onClose={this.unloadFancyBox}> - {this.state.onFancyBox()} - </FancyBox> - )} - </div> - </PageContext.Provider> - ); - } -} diff --git a/diplomacy/web/src/gui/core/tab.jsx b/diplomacy/web/src/gui/core/tab.jsx deleted file mode 100644 index f1ad4aa..0000000 --- a/diplomacy/web/src/gui/core/tab.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; - -export class Tab extends React.Component { - render() { - const style = { - display: this.props.display ? 'block' : 'none' - }; - const id = this.props.id ? {id: this.props.id} : {}; - return ( - <div className={'tab mb-4 ' + this.props.className} style={style} {...id}> - {this.props.children} - </div> - ); - } -} - -Tab.propTypes = { - display: PropTypes.bool, - className: PropTypes.string, - id: PropTypes.string, - children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) -}; - -Tab.defaultProps = { - display: false, - className: '', - id: '' -}; diff --git a/diplomacy/web/src/gui/core/table.jsx b/diplomacy/web/src/gui/core/table.jsx deleted file mode 100644 index cb729e7..0000000 --- a/diplomacy/web/src/gui/core/table.jsx +++ /dev/null @@ -1,112 +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/>. -// ============================================================================== -//// Tables. - -import React from "react"; -import PropTypes from 'prop-types'; - -class DefaultWrapper { - constructor(data) { - this.data = data; - this.get = this.get.bind(this); - } - - get(fieldName) { - return this.data[fieldName]; - } -} - -function defaultWrapper(data) { - return new DefaultWrapper(data); -} - -export class Table extends React.Component { - // className - // caption - // columns : {name: [title, order]} - // data: [objects with expected column names] - // wrapper: (optional) function to use to wrap one data entry into an object before accessing fields. - // Must return an instance with a method get(name). - // If provided: wrapper(data_entry).get(field_name) - // else: data_entry[field_name] - - constructor(props) { - super(props); - if (!this.props.wrapper) - this.props.wrapper = defaultWrapper; - } - - static getHeader(columns) { - const header = []; - for (let entry of Object.entries(columns)) { - const name = entry[0]; - const title = entry[1][0]; - const order = entry[1][1]; - header.push([order, name, title]); - } - header.sort((a, b) => { - let t = a[0] - b[0]; - if (t === 0) - t = a[1].localeCompare(b[1]); - if (t === 0) - t = a[2].localeCompare(b[2]); - return t; - }); - return header; - } - - static getHeaderLine(header) { - return ( - <thead className={'thead-light'}> - <tr>{header.map((column, colIndex) => <th key={colIndex}>{column[2]}</th>)}</tr> - </thead> - ); - } - - static getBodyRow(header, row, rowIndex, wrapper) { - const wrapped = wrapper(row); - return (<tr key={rowIndex}> - {header.map((headerColumn, colIndex) => <td className={'align-middle'} - key={colIndex}>{wrapped.get(headerColumn[1])}</td>)} - </tr>); - } - - static getBodyLines(header, data, wrapper) { - return (<tbody>{data.map((row, rowIndex) => Table.getBodyRow(header, row, rowIndex, wrapper))}</tbody>); - } - - render() { - const header = Table.getHeader(this.props.columns); - return ( - <div className={'table-responsive'}> - <table className={this.props.className}> - <caption>{this.props.caption} ({this.props.data.length})</caption> - {Table.getHeaderLine(header)} - {Table.getBodyLines(header, this.props.data, this.props.wrapper)} - </table> - </div> - ); - } -} - -Table.propTypes = { - wrapper: PropTypes.func, - columns: PropTypes.object, - className: PropTypes.string, - caption: PropTypes.string, - data: PropTypes.array -}; diff --git a/diplomacy/web/src/gui/core/tabs.jsx b/diplomacy/web/src/gui/core/tabs.jsx deleted file mode 100644 index a3f6b9b..0000000 --- a/diplomacy/web/src/gui/core/tabs.jsx +++ /dev/null @@ -1,69 +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 {Action} from "./action"; -import PropTypes from 'prop-types'; - -export class Tabs extends React.Component { - /** PROPERTIES - * active: index of active menu (must be > menu.length). - * highlights: dictionary mapping a menu indice to a highlight message - * onChange: callback(index): receive index of menu to display. - * **/ - - generateTabAction(tabTitle, tabId, isActive, onChange, highlight) { - return <Action isActive={isActive} - title={tabTitle} - onClick={() => onChange(tabId)} - highlight={highlight} - key={tabId}/>; - } - - render() { - if (!this.props.menu.length) - throw new Error(`No tab menu given.`); - if (this.props.menu.length !== this.props.titles.length) - throw new Error(`Menu length (${this.props.menu.length}) != titles length (${this.props.titles.length})`); - if (this.props.active && !this.props.menu.includes(this.props.active)) - throw new Error(`Invalid active tab name, got ${this.props.active}, expected one of: ${this.props.menu.join(', ')}`); - const active = this.props.active || this.props.menu[0]; - return ( - <div className={'tabs mb-3'}> - <nav className={'tabs-bar nav nav-tabs justify-content-center mb-3'}> - {this.props.menu.map((tabName, index) => this.generateTabAction( - this.props.titles[index], tabName, active === tabName, this.props.onChange, - (this.props.highlights.hasOwnProperty(tabName) && this.props.highlights[tabName]) || null - ))} - </nav> - {this.props.children} - </div> - ); - } -} - -Tabs.propTypes = { - menu: PropTypes.arrayOf(PropTypes.string).isRequired, // tab names - titles: PropTypes.arrayOf(PropTypes.string).isRequired, // tab titles - onChange: PropTypes.func.isRequired, // callback(tab name) - children: PropTypes.array.isRequired, - active: PropTypes.string, // current active tab name - highlights: PropTypes.object, // {tab name => highligh message (optional)} -}; - -Tabs.defaultProps = { - highlights: {} -}; |