From 6187faf20384b0c5a4966343b2d4ca47f8b11e45 Mon Sep 17 00:00:00 2001 From: Philip Paquette Date: Wed, 26 Sep 2018 07:48:55 -0400 Subject: Release v1.0.0 - Diplomacy Game Engine - AGPL v3+ License --- diplomacy/web/src/gui/core/content.jsx | 51 ++++ diplomacy/web/src/gui/core/fancybox.jsx | 59 +++++ diplomacy/web/src/gui/core/forms.jsx | 116 +++++++++ diplomacy/web/src/gui/core/layouts.jsx | 55 ++++ diplomacy/web/src/gui/core/page.jsx | 434 ++++++++++++++++++++++++++++++++ diplomacy/web/src/gui/core/table.jsx | 112 +++++++++ diplomacy/web/src/gui/core/tabs.jsx | 96 +++++++ diplomacy/web/src/gui/core/widgets.jsx | 102 ++++++++ 8 files changed, 1025 insertions(+) create mode 100644 diplomacy/web/src/gui/core/content.jsx create mode 100644 diplomacy/web/src/gui/core/fancybox.jsx create mode 100644 diplomacy/web/src/gui/core/forms.jsx create mode 100644 diplomacy/web/src/gui/core/layouts.jsx create mode 100644 diplomacy/web/src/gui/core/page.jsx create mode 100644 diplomacy/web/src/gui/core/table.jsx create mode 100644 diplomacy/web/src/gui/core/tabs.jsx create mode 100644 diplomacy/web/src/gui/core/widgets.jsx (limited to 'diplomacy/web/src/gui/core') diff --git a/diplomacy/web/src/gui/core/content.jsx b/diplomacy/web/src/gui/core/content.jsx new file mode 100644 index 0000000..416ba9e --- /dev/null +++ b/diplomacy/web/src/gui/core/content.jsx @@ -0,0 +1,51 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from 'react'; +import PropTypes from 'prop-types'; + +export class Content extends React.Component { + // PROPERTIES: + // page: pointer to parent Page object + // data: data for current content + + // Each derived class must implement this static method. + static builder(page, data) { + return { + // page title (string) + title: `${data ? 'with data' : 'without data'}`, + // page navigation links: array of couples + // (navigation title, navigation callback ( onClick=() => callback() )) + navigation: [], + // page content: React component (e.g. , or
...
, etc). + component: null + }; + } + + getPage() { + return this.props.page; + } + + componentDidMount() { + window.scrollTo(0, 0); + } +} + + +Content.propTypes = { + page: PropTypes.object.isRequired, + data: PropTypes.object +}; diff --git a/diplomacy/web/src/gui/core/fancybox.jsx b/diplomacy/web/src/gui/core/fancybox.jsx new file mode 100644 index 0000000..4d1013d --- /dev/null +++ b/diplomacy/web/src/gui/core/fancybox.jsx @@ -0,0 +1,59 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from 'react'; +import {Button} from "./widgets"; +import PropTypes from 'prop-types'; + +const TIMES = '\u00D7'; + +export class FancyBox extends React.Component { + // open-tag () + // PROPERTIES + // title + // onClose + render() { + return ( +
+
{ + if (!event) + event = window.event; + if (event.hasOwnProperty('cancelBubble')) + event.cancelBubble = true; + if (event.stopPropagation) + event.stopPropagation(); + }}> +
+
{this.props.title}
+
+
+
+
+
{this.props.children}
+
+
+
+ ); + } +} + + +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 new file mode 100644 index 0000000..76d188c --- /dev/null +++ b/diplomacy/web/src/gui/core/forms.jsx @@ -0,0 +1,116 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Button} from "./widgets"; +import {UTILS} from "../../diplomacy/utils/utils"; + +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 +
+ {content.navigation.map((nav, index) => { + const navTitle = nav[0]; + const navAction = nav[1]; + return {navTitle}; + })} +
+ + )} + + + )) || ( +
{title}
+ )} + {content.component} + {this.state.onFancyBox && ( + + {this.state.onFancyBox()} + + )} + + ); + } +} diff --git a/diplomacy/web/src/gui/core/table.jsx b/diplomacy/web/src/gui/core/table.jsx new file mode 100644 index 0000000..cb729e7 --- /dev/null +++ b/diplomacy/web/src/gui/core/table.jsx @@ -0,0 +1,112 @@ +// ============================================================================== +// 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 . +// ============================================================================== +//// 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 ( + + {header.map((column, colIndex) => {column[2]})} + + ); + } + + static getBodyRow(header, row, rowIndex, wrapper) { + const wrapped = wrapper(row); + return ( + {header.map((headerColumn, colIndex) => {wrapped.get(headerColumn[1])})} + ); + } + + static getBodyLines(header, data, wrapper) { + return ({data.map((row, rowIndex) => Table.getBodyRow(header, row, rowIndex, wrapper))}); + } + + render() { + const header = Table.getHeader(this.props.columns); + return ( +
+ + + {Table.getHeaderLine(header)} + {Table.getBodyLines(header, this.props.data, this.props.wrapper)} +
{this.props.caption} ({this.props.data.length})
+
+ ); + } +} + +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 new file mode 100644 index 0000000..6123219 --- /dev/null +++ b/diplomacy/web/src/gui/core/tabs.jsx @@ -0,0 +1,96 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Action} from "./widgets"; +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 ( +
+ {this.props.children} +
+ ); + } +} + +Tab.propTypes = { + display: PropTypes.bool, + className: PropTypes.string, + id: PropTypes.string, + children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) +}; + +Tab.defaultProps = { + display: false, + className: '', + id: '' +}; + +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 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 ( +
+ + {this.props.children} +
+ ); + } +} + +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: {} +}; diff --git a/diplomacy/web/src/gui/core/widgets.jsx b/diplomacy/web/src/gui/core/widgets.jsx new file mode 100644 index 0000000..62a5eb4 --- /dev/null +++ b/diplomacy/web/src/gui/core/widgets.jsx @@ -0,0 +1,102 @@ +// ============================================================================== +// 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 . +// ============================================================================== +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.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 +}; + + +export class Action extends React.Component { + // title + // isActive + // onClick + // See Button parameters. + + render() { + return ( +
+
+ {this.props.title} + {this.props.highlight !== null + && this.props.highlight !== undefined + && {this.props.highlight}} +
+
+ ); + } +} + +Action.propTypes = { + title: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + highlight: PropTypes.any, + isActive: PropTypes.bool +}; + +Action.defaultProps = { + highlight: null, + isActive: false +}; -- cgit v1.2.3