mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-26 17:13:19 +00:00
INIT
This commit is contained in:
parent
e8530a146d
commit
93c073e2df
295 changed files with 86794 additions and 0 deletions
52
diplomacy/web/src/gui/components/action.jsx
Normal file
52
diplomacy/web/src/gui/components/action.jsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// ==============================================================================
|
||||
// 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
|
||||
};
|
||||
68
diplomacy/web/src/gui/components/button.jsx
Normal file
68
diplomacy/web/src/gui/components/button.jsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// ==============================================================================
|
||||
// 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 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
|
||||
};
|
||||
59
diplomacy/web/src/gui/components/delete_button.jsx
Normal file
59
diplomacy/web/src/gui/components/delete_button.jsx
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
// ==============================================================================
|
||||
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
|
||||
};
|
||||
47
diplomacy/web/src/gui/components/fancyBox.js
Normal file
47
diplomacy/web/src/gui/components/fancyBox.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// ==============================================================================
|
||||
// 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 {
|
||||
render() {
|
||||
return (
|
||||
<div className="fancy-box">
|
||||
<div className="fancy-bar p-1 d-flex flex-row">
|
||||
<div
|
||||
className="flex-grow-1 fancy-title d-flex flex-column justify-content-center pr-0 pr-sm-1">{this.props.title}</div>
|
||||
<div className="fancy-button">
|
||||
<Button title={TIMES} color={'danger'} onClick={this.props.onClose}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fancy-content p-2">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FancyBox.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
children: PropTypes.any.isRequired
|
||||
};
|
||||
116
diplomacy/web/src/gui/components/forms.jsx
Normal file
116
diplomacy/web/src/gui/components/forms.jsx
Normal file
|
|
@ -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 <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;
|
||||
}
|
||||
}
|
||||
|
||||
39
diplomacy/web/src/gui/components/help.jsx
Normal file
39
diplomacy/web/src/gui/components/help.jsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// ==============================================================================
|
||||
// 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 {FancyBox} from "./fancyBox";
|
||||
|
||||
export class Help extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FancyBox title={'Help'} onClose={this.props.onClose}>
|
||||
<div>
|
||||
<p>When building an order, press <strong>ESC</strong> to reset build.</p>
|
||||
<p>Press letter associated to an order type to start building an order of this type.
|
||||
<br/> Order type letter is indicated in order type name after order type radio button.
|
||||
</p>
|
||||
<p>In Phase History tab, use keyboard left and right arrows to navigate in past phases.</p>
|
||||
</div>
|
||||
</FancyBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Help.propTypes = {
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
55
diplomacy/web/src/gui/components/layouts.jsx
Normal file
55
diplomacy/web/src/gui/components/layouts.jsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// ==============================================================================
|
||||
// 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';
|
||||
}
|
||||
}
|
||||
67
diplomacy/web/src/gui/components/message_view.jsx
Normal file
67
diplomacy/web/src/gui/components/message_view.jsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// ==============================================================================
|
||||
// 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 MessageView extends React.Component {
|
||||
// message
|
||||
render() {
|
||||
const message = this.props.message;
|
||||
const owner = this.props.owner;
|
||||
const id = this.props.id ? {id: this.props.id} : {};
|
||||
const messagesLines = message.message.replace('\r\n', '\n')
|
||||
.replace('\r', '\n')
|
||||
.replace('<br>', '\n')
|
||||
.replace('<br/>', '\n')
|
||||
.split('\n');
|
||||
let onClick = null;
|
||||
const classNames = ['game-message', 'row'];
|
||||
if (owner === message.sender)
|
||||
classNames.push('message-sender');
|
||||
else {
|
||||
classNames.push('message-recipient');
|
||||
if (message.read || this.props.read)
|
||||
classNames.push('message-read');
|
||||
onClick = this.props.onClick ? {onClick: () => this.props.onClick(message)} : {};
|
||||
}
|
||||
return (
|
||||
<div className={'game-message-wrapper' + (
|
||||
this.props.phase && this.props.phase !== message.phase ? ' other-phase' : ' new-phase')}
|
||||
{...id}>
|
||||
<div className={classNames.join(' ')} {...onClick}>
|
||||
<div className="message-header col-md-auto text-md-right text-center">
|
||||
{message.phase}
|
||||
</div>
|
||||
<div className="message-content col-md">
|
||||
{messagesLines.map((line, lineIndex) => <div key={lineIndex}>{
|
||||
line.replace(/(<([^>]+)>)/ig, "")
|
||||
}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MessageView.propTypes = {
|
||||
message: PropTypes.object,
|
||||
phase: PropTypes.string,
|
||||
owner: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
id: PropTypes.string,
|
||||
read: PropTypes.bool
|
||||
};
|
||||
77
diplomacy/web/src/gui/components/navigation.jsx
Normal file
77
diplomacy/web/src/gui/components/navigation.jsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// ==============================================================================
|
||||
// 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 Octicon, {Person} from "@primer/octicons-react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class Navigation extends React.Component {
|
||||
render() {
|
||||
const hasNavigation = this.props.navigation && this.props.navigation.length;
|
||||
if (hasNavigation) {
|
||||
return (
|
||||
<div className={'title row'}>
|
||||
<div className={'col align-self-center'}>
|
||||
<strong>{this.props.title}</strong>
|
||||
{this.props.afterTitle ? this.props.afterTitle : ''}
|
||||
</div>
|
||||
<div className={'col-sm-1'}>
|
||||
{(!hasNavigation && (
|
||||
<div className={'float-right'}>
|
||||
<strong>
|
||||
<u className={'mr-2'}>{this.props.username}</u>
|
||||
<Octicon icon={Person}/>
|
||||
</strong>
|
||||
</div>
|
||||
)) || (
|
||||
<div className="dropdown float-right">
|
||||
<button className="btn btn-secondary dropdown-toggle" type="button"
|
||||
id="dropdownMenuButton" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
{(this.props.username && (
|
||||
<span>
|
||||
<u className={'mr-2'}>{this.props.username}</u>
|
||||
<Octicon icon={Person}/>
|
||||
</span>
|
||||
)) || 'Menu'}
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-right"
|
||||
aria-labelledby="dropdownMenuButton">
|
||||
{this.props.navigation.map((nav, index) => {
|
||||
const navTitle = nav[0];
|
||||
const navAction = nav[1];
|
||||
return <span key={index} className="dropdown-item"
|
||||
onClick={navAction}>{navTitle}</span>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={'title'}><strong>{this.props.title}</strong></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Navigation.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
afterTitle: PropTypes.object,
|
||||
navigation: PropTypes.array,
|
||||
username: PropTypes.string,
|
||||
};
|
||||
19
diplomacy/web/src/gui/components/page_context.jsx
Normal file
19
diplomacy/web/src/gui/components/page_context.jsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// ==============================================================================
|
||||
// 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";
|
||||
|
||||
export const PageContext = React.createContext(null);
|
||||
77
diplomacy/web/src/gui/components/power_orders.jsx
Normal file
77
diplomacy/web/src/gui/components/power_orders.jsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// ==============================================================================
|
||||
// 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";
|
||||
|
||||
export class PowerOrders extends React.Component {
|
||||
render() {
|
||||
const orderEntries = this.props.orders ? Object.entries(this.props.orders) : null;
|
||||
let display = null;
|
||||
if (orderEntries) {
|
||||
if (orderEntries.length) {
|
||||
orderEntries.sort((a, b) => a[1].order.localeCompare(b[1].order));
|
||||
display = (
|
||||
<div className={'container order-list'}>
|
||||
{orderEntries.map((entry, index) => (
|
||||
<div
|
||||
className={`row order-entry entry-${1 + index % 2} ` + (entry[1].local ? 'local' : 'server')}
|
||||
key={index}>
|
||||
<div className={'col align-self-center order'}>
|
||||
<span className={'order-string'}>{entry[1].order}</span>
|
||||
{entry[1].local ? '' : <span className={'order-mark'}> [S]</span>}
|
||||
</div>
|
||||
<div className={'col remove-button'}>
|
||||
<Button title={'-'} onClick={() => this.props.onRemove(this.props.name, entry[1])}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</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>);
|
||||
}
|
||||
} else {
|
||||
if (this.props.serverCount < 0) {
|
||||
display = <div className={'no-orders'}>No orders!</div>;
|
||||
} else {
|
||||
display = <div className={'empty-orders'}>Asking to unset orders</div>;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={'power-orders'}>
|
||||
<div className={'title'}>
|
||||
<span className={'name'}>{this.props.name}</span>
|
||||
<span className={this.props.wait ? 'wait' : 'no-wait'}>
|
||||
{(this.props.wait ? ' ' : ' not') + ' waiting'}
|
||||
</span>
|
||||
</div>
|
||||
{display}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PowerOrders.propTypes = {
|
||||
wait: PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
orders: PropTypes.object,
|
||||
serverCount: PropTypes.number,
|
||||
onRemove: PropTypes.func,
|
||||
};
|
||||
42
diplomacy/web/src/gui/components/power_orders_actions_bar.js
Normal file
42
diplomacy/web/src/gui/components/power_orders_actions_bar.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// ==============================================================================
|
||||
// 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 {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
|
||||
};
|
||||
45
diplomacy/web/src/gui/components/tab.jsx
Normal file
45
diplomacy/web/src/gui/components/tab.jsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// ==============================================================================
|
||||
// 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 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: ''
|
||||
};
|
||||
112
diplomacy/web/src/gui/components/table.jsx
Normal file
112
diplomacy/web/src/gui/components/table.jsx
Normal file
|
|
@ -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 <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
|
||||
};
|
||||
69
diplomacy/web/src/gui/components/tabs.jsx
Normal file
69
diplomacy/web/src/gui/components/tabs.jsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// ==============================================================================
|
||||
// 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: {}
|
||||
};
|
||||
123
diplomacy/web/src/gui/forms/connection_form.jsx
Normal file
123
diplomacy/web/src/gui/forms/connection_form.jsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
// ==============================================================================
|
||||
// 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 {Forms} from "../components/forms";
|
||||
import {UTILS} from "../../diplomacy/utils/utils";
|
||||
import PropTypes from "prop-types";
|
||||
import {DipStorage} from "../utils/dipStorage";
|
||||
|
||||
export class ConnectionForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Load fields values from local storage.
|
||||
const initialState = this.initState();
|
||||
const savedState = DipStorage.getConnectionForm();
|
||||
if (savedState) {
|
||||
if (savedState.hostname)
|
||||
initialState.hostname = savedState.hostname;
|
||||
if (savedState.port)
|
||||
initialState.port = savedState.port;
|
||||
if (savedState.username)
|
||||
initialState.username = savedState.username;
|
||||
if (savedState.showServerFields)
|
||||
initialState.showServerFields = savedState.showServerFields;
|
||||
}
|
||||
this.state = initialState;
|
||||
this.updateServerFieldsView = this.updateServerFieldsView.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
initState() {
|
||||
return {
|
||||
hostname: window.location.hostname,
|
||||
port: (window.location.protocol.toLowerCase() === 'https:') ? 8433 : 8432,
|
||||
username: '',
|
||||
password: '',
|
||||
showServerFields: false
|
||||
};
|
||||
}
|
||||
|
||||
updateServerFieldsView() {
|
||||
DipStorage.setConnectionshowServerFields(!this.state.showServerFields);
|
||||
this.setState({showServerFields: !this.state.showServerFields});
|
||||
}
|
||||
|
||||
onChange(newState) {
|
||||
const initialState = this.initState();
|
||||
if (newState.hostname !== initialState.hostname)
|
||||
DipStorage.setConnectionHostname(newState.hostname);
|
||||
else
|
||||
DipStorage.setConnectionHostname(null);
|
||||
if (newState.port !== initialState.port)
|
||||
DipStorage.setConnectionPort(newState.port);
|
||||
else
|
||||
DipStorage.setConnectionPort(null);
|
||||
if (newState.username !== initialState.username)
|
||||
DipStorage.setConnectionUsername(newState.username);
|
||||
else
|
||||
DipStorage.setConnectionUsername(null);
|
||||
if (this.props.onChange)
|
||||
this.props.onChange(newState);
|
||||
}
|
||||
|
||||
render() {
|
||||
const onChange = Forms.createOnChangeCallback(this, this.onChange);
|
||||
const onSubmit = Forms.createOnSubmitCallback(this, this.props.onSubmit);
|
||||
return (
|
||||
<form>
|
||||
{Forms.createRow(
|
||||
Forms.createColLabel('username', 'username:'),
|
||||
<input className={'form-control'} type={'text'} id={'username'}
|
||||
value={Forms.getValue(this.state, 'username')} onChange={onChange}/>
|
||||
)}
|
||||
{Forms.createRow(
|
||||
Forms.createColLabel('password', 'password:'),
|
||||
<input className={'form-control'} type={'password'} id={'password'}
|
||||
value={Forms.getValue(this.state, 'password')} onChange={onChange}/>
|
||||
)}
|
||||
<div>
|
||||
<div className={this.state.showServerFields ? 'mb-2' : 'mb-4'}>
|
||||
<span className={'button-server'} onClick={this.updateServerFieldsView}>
|
||||
server settings {this.state.showServerFields ? UTILS.html.UNICODE_BOTTOM_ARROW : UTILS.html.UNICODE_TOP_ARROW}
|
||||
</span>
|
||||
</div>
|
||||
{this.state.showServerFields && (
|
||||
<div className={'mb-4'}>
|
||||
{Forms.createRow(
|
||||
<label className={'col'} htmlFor={'hostname'}>hostname:</label>,
|
||||
<input className={'form-control'} type={'text'} id={'hostname'}
|
||||
value={Forms.getValue(this.state, 'hostname')} onChange={onChange}/>
|
||||
)}
|
||||
{Forms.createRow(
|
||||
<label className={'col'} htmlFor={'port'}>port:</label>,
|
||||
<input className={'form-control'} type={'number'} id={'port'}
|
||||
value={Forms.getValue(this.state, 'port')}
|
||||
onChange={onChange}/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{Forms.createRow('', Forms.createSubmit('connect', true, onSubmit))}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionForm.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
};
|
||||
70
diplomacy/web/src/gui/forms/find_form.jsx
Normal file
70
diplomacy/web/src/gui/forms/find_form.jsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// ==============================================================================
|
||||
// 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 {Forms} from "../components/forms";
|
||||
import {STRINGS} from "../../diplomacy/utils/strings";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class FindForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.initState();
|
||||
}
|
||||
|
||||
initState() {
|
||||
return {
|
||||
game_id: '',
|
||||
status: '',
|
||||
include_protected: false,
|
||||
for_omniscience: false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const onChange = Forms.createOnChangeCallback(this, this.props.onChange);
|
||||
const onSubmit = Forms.createOnSubmitCallback(this, this.props.onSubmit);
|
||||
return (
|
||||
<form>
|
||||
{Forms.createRow(
|
||||
Forms.createColLabel('game_id', 'game id (should contain):'),
|
||||
<input className={'form-control'} id={'game_id'} type={'text'}
|
||||
value={Forms.getValue(this.state, 'game_id')}
|
||||
onChange={onChange}/>
|
||||
)}
|
||||
{Forms.createRow(
|
||||
Forms.createColLabel('status', 'status:'),
|
||||
(<select className={'form-control custom-select'}
|
||||
id={'status'} value={Forms.getValue(this.state, 'status')} onChange={onChange}>
|
||||
{Forms.createSelectOptions(STRINGS.ALL_GAME_STATUSES, true)}
|
||||
</select>)
|
||||
)}
|
||||
<div className={'form-check'}>
|
||||
{Forms.createCheckbox('include_protected', 'include protected games.', Forms.getValue(this.state, 'include_protected'), onChange)}
|
||||
</div>
|
||||
<div className={'form-check mb-4'}>
|
||||
{Forms.createCheckbox('for_omniscience', 'for omniscience.', Forms.getValue(this.state, 'for_omniscience'), onChange)}
|
||||
</div>
|
||||
{Forms.createRow('', Forms.createSubmit('find games', true, onSubmit))}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FindForm.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
};
|
||||
80
diplomacy/web/src/gui/forms/join_form.jsx
Normal file
80
diplomacy/web/src/gui/forms/join_form.jsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// ==============================================================================
|
||||
// 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 {Forms} from "../components/forms";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class JoinForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.initState();
|
||||
}
|
||||
|
||||
initState() {
|
||||
return {
|
||||
[this.getPowerNameID()]: this.getDefaultPowerName(),
|
||||
[this.getPasswordID()]: ''
|
||||
};
|
||||
}
|
||||
|
||||
getPowerNameID() {
|
||||
return `power_name_${this.props.game_id}`;
|
||||
}
|
||||
|
||||
getPasswordID() {
|
||||
return `registration_password_${this.props.game_id}`;
|
||||
}
|
||||
|
||||
getDefaultPowerName() {
|
||||
return (this.props.powers && this.props.powers.length && this.props.powers[0]) || '';
|
||||
}
|
||||
|
||||
render() {
|
||||
const onChange = Forms.createOnChangeCallback(this, this.props.onChange);
|
||||
const onSubmit = Forms.createOnSubmitCallback(this, this.props.onSubmit);
|
||||
return (
|
||||
<form className={'form-inline'}>
|
||||
<div className={'form-group mr-2'}>
|
||||
{Forms.createLabel(this.getPowerNameID(), 'Power:')}
|
||||
<select id={this.getPowerNameID()} className={'from-control custom-select ml-2'}
|
||||
value={Forms.getValue(this.state, this.getPowerNameID())} onChange={onChange}>
|
||||
{Forms.createSelectOptions(this.props.availablePowers, true)}
|
||||
</select>
|
||||
</div>
|
||||
{this.props.password_required ? (
|
||||
<div className={'form-group mr-2'}>
|
||||
{Forms.createLabel(this.getPasswordID(), '', 'sr-only')}
|
||||
<input id={this.getPasswordID()} type={'password'} className={'form-control'}
|
||||
placeholder={'registration password'}
|
||||
value={Forms.getValue(this.state, this.getPasswordID())}
|
||||
onChange={onChange}/>
|
||||
</div>
|
||||
) : ''}
|
||||
{Forms.createSubmit('join', false, onSubmit)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
JoinForm.propTypes = {
|
||||
game_id: PropTypes.string.isRequired,
|
||||
password_required: PropTypes.bool.isRequired,
|
||||
powers: PropTypes.arrayOf(PropTypes.string),
|
||||
availablePowers: PropTypes.arrayOf(PropTypes.string),
|
||||
onChange: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
};
|
||||
53
diplomacy/web/src/gui/forms/message_form.jsx
Normal file
53
diplomacy/web/src/gui/forms/message_form.jsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// ==============================================================================
|
||||
// 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 {Forms} from "../components/forms";
|
||||
import {UTILS} from "../../diplomacy/utils/utils";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class MessageForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.initState();
|
||||
}
|
||||
|
||||
initState() {
|
||||
return {message: ''};
|
||||
}
|
||||
|
||||
render() {
|
||||
const onChange = Forms.createOnChangeCallback(this, this.props.onChange);
|
||||
const onSubmit = Forms.createOnSubmitCallback(this, this.props.onSubmit, this.initState());
|
||||
return (
|
||||
<form>
|
||||
<div className={'form-group'}>
|
||||
{Forms.createLabel('message', '', 'sr-only')}
|
||||
<textarea id={'message'} className={'form-control'}
|
||||
value={Forms.getValue(this.state, 'message')} onChange={onChange}/>
|
||||
</div>
|
||||
{Forms.createSubmit(`send (${this.props.sender} ${UTILS.html.UNICODE_SMALL_RIGHT_ARROW} ${this.props.recipient})`, true, onSubmit)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MessageForm.propTypes = {
|
||||
sender: PropTypes.string,
|
||||
recipient: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
};
|
||||
121
diplomacy/web/src/gui/forms/power_order_creation_form.jsx
Normal file
121
diplomacy/web/src/gui/forms/power_order_creation_form.jsx
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// ==============================================================================
|
||||
// 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 {Forms} from "../components/forms";
|
||||
import {ORDER_BUILDER} from "../utils/order_building";
|
||||
import {STRINGS} from "../../diplomacy/utils/strings";
|
||||
import PropTypes from "prop-types";
|
||||
import {Power} from "../../diplomacy/engine/power";
|
||||
|
||||
const HotKey = require('react-shortcut');
|
||||
|
||||
export class PowerOrderCreationForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.initState();
|
||||
}
|
||||
|
||||
initState() {
|
||||
return {order_type: this.props.orderType};
|
||||
}
|
||||
|
||||
render() {
|
||||
const onChange = Forms.createOnChangeCallback(this, this.props.onChange);
|
||||
const onReset = Forms.createOnResetCallback(this, this.props.onChange, this.initState());
|
||||
const onSetOrderType = (letter) => {
|
||||
this.setState({order_type: letter}, () => {
|
||||
if (this.props.onChange)
|
||||
this.props.onChange(this.state);
|
||||
});
|
||||
};
|
||||
let title = '';
|
||||
let titleClass = 'mr-4';
|
||||
const header = [];
|
||||
const votes = [];
|
||||
if (this.props.orderTypes.length) {
|
||||
title = 'Create order:';
|
||||
header.push(...this.props.orderTypes.map((orderLetter, index) => (
|
||||
<div key={index} className={'form-check-inline'}>
|
||||
{Forms.createRadio('order_type', orderLetter, ORDER_BUILDER[orderLetter].name, this.props.orderType, onChange)}
|
||||
</div>
|
||||
)));
|
||||
header.push(Forms.createReset('reset', false, onReset));
|
||||
} else if (this.props.power.order_is_set) {
|
||||
title = 'Unorderable power.';
|
||||
titleClass += ' neutral';
|
||||
} else {
|
||||
title = 'No orders available for this power.';
|
||||
}
|
||||
if (!this.props.power.order_is_set) {
|
||||
header.push(Forms.createButton('pass', this.props.onPass));
|
||||
}
|
||||
|
||||
if (this.props.role !== STRINGS.OMNISCIENT_TYPE) {
|
||||
votes.push(<strong key={0} className={'ml-4 mr-2'}>Vote for draw:</strong>);
|
||||
switch (this.props.power.vote) {
|
||||
case 'yes':
|
||||
votes.push(Forms.createButton('no', () => this.props.onVote('no'), 'danger'));
|
||||
votes.push(Forms.createButton('neutral', () => this.props.onVote('neutral'), 'info'));
|
||||
break;
|
||||
case 'no':
|
||||
votes.push(Forms.createButton('yes', () => this.props.onVote('yes'), 'success'));
|
||||
votes.push(Forms.createButton('neutral', () => this.props.onVote('neutral'), 'info'));
|
||||
break;
|
||||
case 'neutral':
|
||||
votes.push(Forms.createButton('yes', () => this.props.onVote('yes'), 'success'));
|
||||
votes.push(Forms.createButton('no', () => this.props.onVote('no'), 'danger'));
|
||||
break;
|
||||
default:
|
||||
votes.push(Forms.createButton('yes', () => this.props.onVote('yes'), 'success'));
|
||||
votes.push(Forms.createButton('no', () => this.props.onVote('no'), 'danger'));
|
||||
votes.push(Forms.createButton('neutral', () => this.props.onVote('neutral'), 'info'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div><strong key={'title'} className={titleClass}>{title}</strong></div>
|
||||
<form className={'form-inline power-actions-form'}>
|
||||
{header}
|
||||
{Forms.createButton(
|
||||
(this.props.power.wait ? 'no wait' : 'wait'),
|
||||
this.props.onSetWaitFlag,
|
||||
(this.props.power.wait ? 'success' : 'danger')
|
||||
)}
|
||||
{votes}
|
||||
<HotKey keys={['escape']} onKeysCoincide={onReset}/>
|
||||
{this.props.orderTypes.map((letter, index) => (
|
||||
<HotKey key={index} keys={[letter.toLowerCase()]}
|
||||
onKeysCoincide={() => onSetOrderType(letter)}/>
|
||||
))}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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(),
|
||||
};
|
||||
43
diplomacy/web/src/gui/forms/select_location_form.jsx
Normal file
43
diplomacy/web/src/gui/forms/select_location_form.jsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// ==============================================================================
|
||||
// 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 "../components/button";
|
||||
import {FancyBox} from "../components/fancyBox";
|
||||
|
||||
export class SelectLocationForm extends React.Component {
|
||||
render() {
|
||||
const title = `Select location to continue building order: ${this.props.path.join(' ')}`;
|
||||
return (
|
||||
<FancyBox title={title} onClose={this.props.onClose}>
|
||||
<div>
|
||||
{this.props.locations.map((location, index) => (
|
||||
<Button key={index} title={location} large={true}
|
||||
onClick={() => this.props.onSelect(location)}/>
|
||||
))}
|
||||
</div>
|
||||
</FancyBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectLocationForm.propTypes = {
|
||||
locations: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onSelect: PropTypes.func.isRequired, // onSelect(location)
|
||||
onClose: PropTypes.func.isRequired,
|
||||
path: PropTypes.array.isRequired
|
||||
};
|
||||
45
diplomacy/web/src/gui/forms/select_via_form.jsx
Normal file
45
diplomacy/web/src/gui/forms/select_via_form.jsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// ==============================================================================
|
||||
// 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 "../components/button";
|
||||
import {FancyBox} from "../components/fancyBox";
|
||||
|
||||
const HotKey = require('react-shortcut');
|
||||
|
||||
export class SelectViaForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FancyBox title={`Select move type for move order: ${this.props.path.join(' ')}`}
|
||||
onClose={this.props.onClose}>
|
||||
<div>
|
||||
<Button title={'regular move (M)'} large={true} onClick={() => this.props.onSelect('M')}/>
|
||||
<Button title={'move via (V)'} large={true} onClick={() => this.props.onSelect('V')}/>
|
||||
<HotKey keys={['m']} onKeysCoincide={() => this.props.onSelect('M')}/>
|
||||
<HotKey keys={['v']} onKeysCoincide={() => this.props.onSelect('V')}/>
|
||||
</div>
|
||||
</FancyBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectViaForm.propTypes = {
|
||||
path: PropTypes.array.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
19
diplomacy/web/src/gui/maps/ancmed/SvgAncMed.css
Normal file
19
diplomacy/web/src/gui/maps/ancmed/SvgAncMed.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
==============================================================================
|
||||
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/>.
|
||||
==============================================================================
|
||||
**/
|
||||
/* text */ .SvgAncMed .smalllabeltext {text-anchor:middle;stroke-width:0.1;stroke:black;fill:black;font-family:serif;font-style:italic;font-size:14px;} .SvgAncMed .currentnoterect {fill:#c5dfea; stroke-width:0; stroke:black;} .SvgAncMed .currentnotetext {font-family:serif,sans-serif; font-size:12px; fill:black; stroke:black;} .SvgAncMed .currentphasetext {font-family:serif,sans-serif; font-size:18px; fill:black; stroke:black;} /* style class for invisible regions */ .SvgAncMed .invisibleContent {stroke:white;fill:white;fill-opacity:0.0;opacity:0.0} /* default region coloring, by power (nopower if region not owned) */ .SvgAncMed .provinceRed {fill:url(#patternRed); stroke: black; stroke-width:2} .SvgAncMed .provinceBrown {fill:url(#patternBrown); stroke: black; stroke-width:2} .SvgAncMed .provinceGreen {fill:url(#patternGreen); stroke: black; stroke-width:2} .SvgAncMed .provinceBlack {fill:url(#patternBlack); stroke: black; stroke-width:2} .SvgAncMed .provinceBlue {fill:url(#patternBlue); stroke: black; stroke-width:2} .SvgAncMed .nopower {fill:none;stroke:black;stroke-width:2} .SvgAncMed .water {fill:lightblue;stroke:black;stroke-width:2} .SvgAncMed .rome {fill:#c48f85;stroke:black;stroke-width:2} .SvgAncMed .carthage {fill:royalblue;stroke:black;stroke-width:2} .SvgAncMed .persia {fill:gray;stroke:black;stroke-width:2} .SvgAncMed .greece {fill:forestgreen;stroke:black;stroke-width:2} .SvgAncMed .egypt {fill:#b9a61c;stroke:black;stroke-width:2} /* unit colors fills, by power (units always have a power) */ .SvgAncMed .unitrome {fill:red;} .SvgAncMed .unitcarthage {fill:deepskyblue;} .SvgAncMed .unitpersia {fill:white;} .SvgAncMed .unitgreece {fill:#8db600;} .SvgAncMed .unitegypt {fill:yellow;} /* map accents */ .SvgAncMed .thickdash {fill:none;stroke:black;stroke-width:6;stroke-dasharray:2,1.5;} /* order drawing styles, stroke and fill colors should not be specified */ .SvgAncMed .supportorder {stroke-width:3; fill:none; stroke-dasharray:5,5;} .SvgAncMed .convoyorder {stroke-dasharray:15,5; stroke-width:3; fill:none;} .SvgAncMed .shadowdash {stroke-width:5; fill:none; stroke:black; opacity:0.45;} .SvgAncMed .varwidthorder {fill:none;} .SvgAncMed .varwidthshadow {fill:none; stroke:black;} .SvgAncMed .style1 {fill:none;stroke:#17A1F8;stroke-width:5;} .SvgAncMed .style2 {fill:none;stroke:black;stroke-width:2}
|
||||
20
diplomacy/web/src/gui/maps/ancmed/SvgAncMed.js
Normal file
20
diplomacy/web/src/gui/maps/ancmed/SvgAncMed.js
Normal file
File diff suppressed because one or more lines are too long
21
diplomacy/web/src/gui/maps/ancmed/SvgAncMedMetadata.js
Normal file
21
diplomacy/web/src/gui/maps/ancmed/SvgAncMedMetadata.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
==============================================================================
|
||||
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/>.
|
||||
==============================================================================
|
||||
**/
|
||||
export const Coordinates = {"ADR":{"unit":["472.5","201.0"],"disl":["479.5","194.0"]},"AEG":{"unit":["657.5","335.0"],"disl":["664.5","328.0"]},"ALE":{"unit":["705.5","504.0"],"disl":["712.5","497.0"]},"ANT":{"unit":["911.5","304.0"],"disl":["918.5","297.0"]},"APU":{"unit":["443.5","222.0"],"disl":["450.5","215.0"]},"ARA":{"unit":["971.5","416.0"],"disl":["978.5","409.0"]},"ARM":{"unit":["977.5","214.0"],"disl":["984.5","207.0"]},"ATH":{"unit":["581.5","300.0"],"disl":["588.5","293.0"]},"AUS":{"unit":["485.5","382.0"],"disl":["492.5","375.0"]},"BAL":{"unit":["153.5","240.0"],"disl":["160.5","233.0"]},"BAY":{"unit":["682.5","617.0"],"disl":["689.5","610.0"]},"BER":{"unit":["205.5","315.0"],"disl":["212.5","308.0"]},"BIT":{"unit":["759.5","209.0"],"disl":["766.5","202.0"]},"BLA":{"unit":["770.5","143.0"],"disl":["777.5","136.0"]},"BYZ":{"unit":["682.5","242.0"],"disl":["689.5","235.0"]},"CAP":{"unit":["865.5","269.0"],"disl":["872.5","262.0"]},"CAR":{"unit":["303.5","372.0"],"disl":["310.5","365.0"]},"CHE":{"unit":["881.5","59.0"],"disl":["888.5","52.0"]},"CIL":{"unit":["785.5","353.0"],"disl":["792.5","346.0"]},"CIR":{"unit":["236.5","442.0"],"disl":["243.5","435.0"]},"COR":{"unit":["307.5","215.0"],"disl":["314.5","208.0"]},"CRE":{"unit":["650.5","382.0"],"disl":["657.5","375.0"]},"CYP":{"unit":["816.5","374.0"],"disl":["823.5","367.0"]},"CYR":{"unit":["625.5","508.0"],"disl":["632.5","501.0"]},"DAC":{"unit":["621.5","138.0"],"disl":["628.5","131.0"]},"DAL":{"unit":["502.5","178.0"],"disl":["509.5","171.0"]},"DAM":{"unit":["973.5","308.0"],"disl":["980.5","301.0"]},"EGY":{"unit":["728.5","441.0"],"disl":["735.5","434.0"]},"EPI":{"unit":["544.5","267.0"],"disl":["551.5","260.0"]},"ETR":{"unit":["313.5","129.0"],"disl":["320.5","122.0"]},"GAL":{"unit":["774.5","264.0"],"disl":["781.5","257.0"]},"GAU":{"unit":["197.5","83.0"],"disl":["204.5","76.0"]},"GOP":{"unit":["843.5","446.0"],"disl":["850.5","439.0"]},"GOS":{"unit":["509.5","498.0"],"disl":["516.5","491.0"]},"GOT":{"unit":["475.5","428.0"],"disl":["482.5","421.0"]},"IBE":{"unit":["82.5","356.0"],"disl":["89.5","349.0"]},"ILL":{"unit":["536.5","157.0"],"disl":["543.5","150.0"]},"ION":{"unit":["508.5","306.0"],"disl":["515.5","299.0"]},"ISA":{"unit":["806.5","313.0"],"disl":["813.5","306.0"]},"JER":{"unit":["899.5","451.0"],"disl":["906.5","444.0"]},"LEP":{"unit":["468.5","533.0"],"disl":["475.5","526.0"]},"LIB":{"unit":["544.5","428.0"],"disl":["551.5","421.0"]},"LIG":{"unit":["256.5","215.0"],"disl":["263.5","208.0"]},"LUS":{"unit":["18.5","227.0"],"disl":["25.5","220.0"]},"MAC":{"unit":["611.5","231.0"],"disl":["618.5","224.0"]},"MAR":{"unit":["601.5","561.0"],"disl":["608.5","554.0"]},"MAS":{"unit":["253.5","127.0"],"disl":["260.5","120.0"]},"MAU":{"unit":["112.5","410.0"],"disl":["119.5","403.0"]},"MEM":{"unit":["783.5","554.0"],"disl":["790.5","547.0"]},"MES":{"unit":["535.5","378.0"],"disl":["542.5","371.0"]},"MIL":{"unit":["730.5","313.0"],"disl":["737.5","306.0"]},"MIN":{"unit":["709.5","370.0"],"disl":["716.5","363.0"]},"NAB":{"unit":["976.5","528.0"],"disl":["983.5","521.0"]},"NEA":{"unit":["457.5","274.0"],"disl":["464.5","267.0"]},"NUM":{"unit":["343.5","471.0"],"disl":["350.5","464.0"]},"PET":{"unit":["917.5","508.0"],"disl":["924.5","501.0"]},"PHA":{"unit":["400.5","553.0"],"disl":["407.5","546.0"]},"PUN":{"unit":["358.5","338.0"],"disl":["365.5","331.0"]},"RAV":{"unit":["391.5","169.0"],"disl":["398.5","162.0"]},"REE":{"unit":["950.5","594.0"],"disl":["957.5","587.0"]},"RHA":{"unit":["339.5","59.0"],"disl":["346.5","52.0"]},"ROM":{"unit":["368.5","188.0"],"disl":["375.5","181.0"]},"SAD":{"unit":["304.5","264.0"],"disl":["311.5","257.0"]},"SAG":{"unit":["58.5","302.0"],"disl":["65.5","295.0"]},"SAH":{"unit":["269.5","588.0"],"disl":["276.5","581.0"]},"SAM":{"unit":["517.5","56.0"],"disl":["524.5","49.0"]},"SIC":{"unit":["421.5","333.0"],"disl":["428.5","326.0"]},"SID":{"unit":["919.5","355.0"],"disl":["926.5","348.0"]},"SIN":{"unit":["871.5","490.0"],"disl":["878.5","483.0"]},"SIP":{"unit":["865.5","196.0"],"disl":["872.5","189.0"]},"SPA":{"unit":["583.5","322.0"],"disl":["590.5","315.0"]},"SYR":{"unit":["864.5","402.0"],"disl":["871.5","395.0"]},"TAR":{"unit":["128.5","221.0"],"disl":["135.5","214.0"]},"THA":{"unit":["307.5","419.0"],"disl":["314.5","412.0"]},"THB":{"unit":["879.5","591.0"],"disl":["886.5","584.0"]},"TYE":{"unit":["914.5","383.0"],"disl":["921.5","376.0"]},"TYN":{"unit":["395.5","268.0"],"disl":["402.5","261.0"]},"VEN":{"unit":["346.5","100.0"],"disl":["353.5","93.0"]},"VIN":{"unit":["410.5","71.0"],"disl":["417.5","64.0"]}};
|
||||
export const SymbolSizes = {"Fleet":{"width":20.0,"height":20.0},"Army":{"width":20.0,"height":20.0},"Wing":{"width":20.0,"height":20.0},"DislodgedFleet":{"width":20.0,"height":20.0},"DislodgedArmy":{"width":20.0,"height":20.0},"DislodgedWing":{"width":20.0,"height":20.0},"FailedOrder":{"width":20.0,"height":20.0},"SupplyCenter":{"width":10.0,"height":10.0},"BuildUnit":{"width":30.0,"height":30.0},"RemoveUnit":{"width":30.0,"height":30.0},"WaivedBuild":{"width":25.0,"height":25.0},"HoldUnit":{"width":40.0,"height":40.0},"SupportHoldUnit":{"width":46.0,"height":46.0},"ConvoyTriangle":{"width":50.0,"height":43.2},"Stroke":{"width":3.0,"height":5.0}};
|
||||
export const Colors = {"ROME":"#c48f85","CARTHAGE":"royalblue","PERSIA":"gray","GREECE":"forestgreen","EGYPT":"#b9a61c"};
|
||||
57
diplomacy/web/src/gui/maps/common/build.js
Normal file
57
diplomacy/web/src/gui/maps/common/build.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// ==============================================================================
|
||||
// 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 {ARMY, centerSymbolAroundUnit, FLEET} from "./common";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class Build extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const loc = this.props.loc;
|
||||
const unit_type = this.props.unitType;
|
||||
const build_symbol = 'BuildUnit';
|
||||
const loc_x = Coordinates[loc].unit[0];
|
||||
const loc_y = Coordinates[loc].unit[1];
|
||||
const [build_loc_x, build_loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, loc, false, build_symbol);
|
||||
|
||||
const symbol = unit_type === 'A' ? ARMY : FLEET;
|
||||
return (
|
||||
<g>
|
||||
<use x={build_loc_x}
|
||||
y={build_loc_y}
|
||||
height={SymbolSizes[build_symbol].height}
|
||||
width={SymbolSizes[build_symbol].width}
|
||||
href={`#${build_symbol}`}/>
|
||||
<use x={loc_x}
|
||||
y={loc_y}
|
||||
height={SymbolSizes[symbol].height}
|
||||
width={SymbolSizes[symbol].width}
|
||||
href={`#${symbol}`}
|
||||
className={`unit${this.props.powerName.toLowerCase()}`}/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Build.propTypes = {
|
||||
unitType: PropTypes.string.isRequired,
|
||||
loc: PropTypes.string.isRequired,
|
||||
powerName: PropTypes.string.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired
|
||||
};
|
||||
83
diplomacy/web/src/gui/maps/common/common.js
Normal file
83
diplomacy/web/src/gui/maps/common/common.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
|
||||
export const ARMY = 'Army';
|
||||
export const FLEET = 'Fleet';
|
||||
|
||||
export function offset(floatString, offset) {
|
||||
return "" + (parseFloat(floatString) + offset);
|
||||
}
|
||||
|
||||
export function setInfluence(classes, mapData, loc, power_name) {
|
||||
const province = mapData.getProvince(loc);
|
||||
if (!province)
|
||||
throw new Error(`Unable to find province ${loc}`);
|
||||
if (!['LAND', 'COAST'].includes(province.type))
|
||||
return;
|
||||
const id = province.getID(classes);
|
||||
if (!id)
|
||||
throw new Error(`Unable to find SVG path for loc ${id}`);
|
||||
classes[id] = power_name ? power_name.toLowerCase() : 'nopower';
|
||||
}
|
||||
|
||||
export function getClickedID(event) {
|
||||
let node = event.target;
|
||||
if (!node.id && node.parentNode.id && node.parentNode.tagName === 'g')
|
||||
node = node.parentNode;
|
||||
let id = node.id;
|
||||
return id ? id.substr(0, 3) : null;
|
||||
}
|
||||
|
||||
export function parseLocation(txt) {
|
||||
if (txt.length > 2 && txt[1] === ' ' && ['A', 'F'].includes(txt[0]))
|
||||
return txt.substr(2);
|
||||
return txt;
|
||||
}
|
||||
|
||||
export function centerSymbolAroundUnit(coordinates, symbolSizes, loc, isDislodged, symbol) {
|
||||
const key = isDislodged ? 'disl' : 'unit';
|
||||
const unitKey = ARMY;
|
||||
const [unit_x, unit_y] = coordinates[loc][key];
|
||||
const unit_height = symbolSizes[unitKey].height;
|
||||
const unit_width = symbolSizes[unitKey].width;
|
||||
const symbol_height = symbolSizes[symbol].height;
|
||||
const symbol_width = symbolSizes[symbol].width;
|
||||
return [
|
||||
`${(parseFloat(unit_x) + parseFloat(unit_width) / 2 - parseFloat(symbol_width) / 2)}`,
|
||||
`${(parseFloat(unit_y) + parseFloat(unit_height) / 2 - parseFloat(symbol_height) / 2)}`
|
||||
];
|
||||
}
|
||||
|
||||
export function getUnitCenter(coordinates, symbolSizes, loc, isDislodged) {
|
||||
const key = isDislodged ? 'disl' : 'unit';
|
||||
const unitKey = ARMY;
|
||||
const [unit_x, unit_y] = coordinates[loc][key];
|
||||
const unit_height = symbolSizes[unitKey].height;
|
||||
const unit_width = symbolSizes[unitKey].width;
|
||||
return [
|
||||
`${parseFloat(unit_x) + parseFloat(unit_width) / 2}`,
|
||||
`${parseFloat(unit_y) + parseFloat(unit_height) / 2}`
|
||||
];
|
||||
}
|
||||
|
||||
export function plainStrokeWidth(symbolSizes) {
|
||||
return parseFloat(symbolSizes.Stroke.height);
|
||||
}
|
||||
|
||||
export function coloredStrokeWidth(symbolSizes) {
|
||||
return parseFloat(symbolSizes.Stroke.width);
|
||||
}
|
||||
103
diplomacy/web/src/gui/maps/common/convoy.js
Normal file
103
diplomacy/web/src/gui/maps/common/convoy.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// ==============================================================================
|
||||
// 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 {ARMY, centerSymbolAroundUnit, coloredStrokeWidth, getUnitCenter} from "./common";
|
||||
import {EquilateralTriangle} from "./equilateralTriangle";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class Convoy extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const Colors = this.props.colors;
|
||||
const loc = this.props.loc;
|
||||
const src_loc = this.props.srcLoc;
|
||||
const dest_loc = this.props.dstLoc;
|
||||
|
||||
const symbol = 'ConvoyTriangle';
|
||||
let [symbol_loc_x, symbol_loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, src_loc, false, symbol);
|
||||
const symbol_height = parseFloat(SymbolSizes[symbol].height);
|
||||
const symbol_width = parseFloat(SymbolSizes[symbol].width);
|
||||
const triangle = new EquilateralTriangle(
|
||||
parseFloat(symbol_loc_x) + symbol_width / 2,
|
||||
parseFloat(symbol_loc_y),
|
||||
parseFloat(symbol_loc_x) + symbol_width,
|
||||
parseFloat(symbol_loc_y) + symbol_height,
|
||||
parseFloat(symbol_loc_x),
|
||||
parseFloat(symbol_loc_y) + symbol_height
|
||||
);
|
||||
symbol_loc_y = '' + (parseFloat(symbol_loc_y) - symbol_height / 6);
|
||||
const [loc_x, loc_y] = getUnitCenter(Coordinates, SymbolSizes, loc, false);
|
||||
const [src_loc_x, src_loc_y] = getUnitCenter(Coordinates, SymbolSizes, src_loc, false);
|
||||
let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, false);
|
||||
|
||||
const [src_loc_x_1, src_loc_y_1] = triangle.intersection(loc_x, loc_y);
|
||||
const [src_loc_x_2, src_loc_y_2] = triangle.intersection(dest_loc_x, dest_loc_y);
|
||||
|
||||
const dest_delta_x = dest_loc_x - src_loc_x;
|
||||
const dest_delta_y = dest_loc_y - src_loc_y;
|
||||
const dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y);
|
||||
const delta_dec = parseFloat(SymbolSizes[ARMY].width) / 2 + 2 * coloredStrokeWidth(SymbolSizes);
|
||||
dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (dest_vector_length - delta_dec) / dest_vector_length * dest_delta_x) * 100.) / 100.;
|
||||
dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (dest_vector_length - delta_dec) / dest_vector_length * dest_delta_y) * 100.) / 100.;
|
||||
|
||||
return (
|
||||
<g stroke={Colors[this.props.powerName]}>
|
||||
<line x1={loc_x}
|
||||
y1={loc_y}
|
||||
x2={src_loc_x_1}
|
||||
y2={src_loc_y_1}
|
||||
className={'shadowdash'}/>
|
||||
<line x1={src_loc_x_2}
|
||||
y1={src_loc_y_2}
|
||||
x2={dest_loc_x}
|
||||
y2={dest_loc_y}
|
||||
className={'shadowdash'}/>
|
||||
<line x1={loc_x}
|
||||
y1={loc_y}
|
||||
x2={src_loc_x_1}
|
||||
y2={src_loc_y_1}
|
||||
className={'convoyorder'}
|
||||
stroke={Colors[this.props.powerName]}/>
|
||||
<line x1={src_loc_x_2}
|
||||
y1={src_loc_y_2}
|
||||
x2={dest_loc_x}
|
||||
y2={dest_loc_y}
|
||||
className={'convoyorder'}
|
||||
markerEnd={'url(#arrow)'}
|
||||
stroke={Colors[this.props.powerName]}/>
|
||||
<use
|
||||
x={symbol_loc_x}
|
||||
y={symbol_loc_y}
|
||||
width={'' + symbol_width}
|
||||
height={'' + symbol_height}
|
||||
href={`#${symbol}`}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Convoy.propTypes = {
|
||||
loc: PropTypes.string.isRequired,
|
||||
srcLoc: PropTypes.string.isRequired,
|
||||
dstLoc: PropTypes.string.isRequired,
|
||||
powerName: PropTypes.string.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired,
|
||||
colors: PropTypes.object.isRequired
|
||||
};
|
||||
47
diplomacy/web/src/gui/maps/common/disband.js
Normal file
47
diplomacy/web/src/gui/maps/common/disband.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// ==============================================================================
|
||||
// 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 {centerSymbolAroundUnit} from "./common";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class Disband extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const loc = this.props.loc;
|
||||
const phaseType = this.props.phaseType;
|
||||
const symbol = 'RemoveUnit';
|
||||
const [loc_x, loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, loc, phaseType === 'R', symbol);
|
||||
return (
|
||||
<g>
|
||||
<use x={loc_x}
|
||||
y={loc_y}
|
||||
height={SymbolSizes[symbol].height}
|
||||
width={SymbolSizes[symbol].width}
|
||||
href={`#${symbol}`}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Disband.propTypes = {
|
||||
loc: PropTypes.string.isRequired,
|
||||
phaseType: PropTypes.string.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired
|
||||
};
|
||||
122
diplomacy/web/src/gui/maps/common/equilateralTriangle.js
Normal file
122
diplomacy/web/src/gui/maps/common/equilateralTriangle.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
export class EquilateralTriangle {
|
||||
/** Helper class that represent an equilateral triangle.;
|
||||
Used to compute intersection of a line with a side of convoy symbol, which is an equilateral triangle. **/
|
||||
constructor(x_top, y_top, x_right, y_right, x_left, y_left) {
|
||||
this.x_A = x_top;
|
||||
this.y_A = y_top;
|
||||
this.x_B = x_right;
|
||||
this.y_B = y_right;
|
||||
this.x_C = x_left;
|
||||
this.y_C = y_left;
|
||||
this.h = this.y_B - this.y_A;
|
||||
this.x_O = this.x_A;
|
||||
this.y_O = this.y_A + 2 * this.h / 3;
|
||||
this.line_AB_a = (this.y_B - this.y_A) / (this.x_B - this.x_A);
|
||||
this.line_AB_b = this.y_B - this.x_B * this.line_AB_a;
|
||||
this.line_AC_a = (this.y_C - this.y_A) / (this.x_C - this.x_A);
|
||||
this.line_AC_b = this.y_C - this.x_C * this.line_AC_a;
|
||||
}
|
||||
|
||||
__line_OM(x_M, y_M) {
|
||||
const a = (y_M - this.y_O) / (x_M - this.x_O);
|
||||
const b = y_M - a * x_M;
|
||||
return [a, b];
|
||||
}
|
||||
|
||||
__intersection_with_AB(x_M, y_M) {
|
||||
const [a, b] = [this.line_AB_a, this.line_AB_b];
|
||||
let x = null;
|
||||
if (x_M === this.x_O) {
|
||||
x = x_M;
|
||||
} else {
|
||||
const [u, v] = this.__line_OM(x_M, y_M);
|
||||
if (a === u)
|
||||
return [null, null];
|
||||
x = (v - b) / (a - u);
|
||||
}
|
||||
const y = a * x + b;
|
||||
if (this.x_A <= x && x <= this.x_B && this.y_A <= y && y <= this.y_B)
|
||||
return [x, y];
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
__intersection_with_AC(x_M, y_M) {
|
||||
const [a, b] = [this.line_AC_a, this.line_AC_b];
|
||||
let x = null;
|
||||
if (x_M === this.x_O) {
|
||||
x = x_M;
|
||||
} else {
|
||||
const [u, v] = this.__line_OM(x_M, y_M);
|
||||
if (a === u)
|
||||
return [null, null];
|
||||
x = (v - b) / (a - u);
|
||||
}
|
||||
const y = a * x + b;
|
||||
if (this.x_C <= x && x <= this.x_A && this.y_A <= y && y <= this.y_C)
|
||||
return [x, y];
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
__intersection_with_BC(x_M, y_M) {
|
||||
const y = this.y_C;
|
||||
let x = null;
|
||||
if (x_M === this.x_O) {
|
||||
x = x_M;
|
||||
} else {
|
||||
const [a, b] = this.__line_OM(x_M, y_M);
|
||||
if (a === 0)
|
||||
return [null, null];
|
||||
x = (y - b) / a;
|
||||
}
|
||||
if (this.x_C <= x && x <= this.x_A)
|
||||
return [x, y];
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
intersection(x_M, y_M) {
|
||||
if (this.x_O === x_M && this.y_O === y_M)
|
||||
return [x_M, y_M];
|
||||
if (this.x_O === x_M) {
|
||||
if (y_M < this.y_O)
|
||||
return [x_M, this.y_A];
|
||||
else {
|
||||
// vertical line intersects BC;
|
||||
return [x_M, this.y_C];
|
||||
}
|
||||
} else if (this.y_O === y_M) {
|
||||
let a = null;
|
||||
let b = null;
|
||||
if (x_M < this.x_O) {
|
||||
// horizontal line intersects AC;
|
||||
[a, b] = [this.line_AC_a, this.line_AC_b];
|
||||
} else {
|
||||
// horizontal line intersects AB;
|
||||
[a, b] = [this.line_AB_a, this.line_AB_b];
|
||||
}
|
||||
const x = (y_M - b) / a;
|
||||
return [x, y_M];
|
||||
} else {
|
||||
// get nearest point in intersections with AB, AC, BC;
|
||||
const [p1_x, p1_y] = this.__intersection_with_AB(x_M, y_M);
|
||||
const [p2_x, p2_y] = this.__intersection_with_AC(x_M, y_M);
|
||||
const [p3_x, p3_y] = this.__intersection_with_BC(x_M, y_M);
|
||||
const distances = [];
|
||||
if (p1_x !== null) {
|
||||
const d1 = Math.sqrt((p1_x - x_M) * (p1_x - x_M) + (p1_y - y_M) * (p1_y - y_M));
|
||||
distances.push([d1, p1_x, p1_y]);
|
||||
}
|
||||
if (p2_x !== null) {
|
||||
const d2 = Math.sqrt((p2_x - x_M) * (p2_x - x_M) + (p2_y - y_M) * (p2_y - y_M));
|
||||
distances.push([d2, p2_x, p2_y]);
|
||||
}
|
||||
if (p3_x !== null) {
|
||||
const d3 = Math.sqrt((p3_x - x_M) * (p3_x - x_M) + (p3_y - y_M) * (p3_y - y_M));
|
||||
distances.push([d3, p3_x, p3_y]);
|
||||
}
|
||||
distances.sort();
|
||||
const output = distances[0];
|
||||
output.shift();
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
diplomacy/web/src/gui/maps/common/hold.js
Normal file
47
diplomacy/web/src/gui/maps/common/hold.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// ==============================================================================
|
||||
// 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 {centerSymbolAroundUnit} from "./common";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class Hold extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const Colors = this.props.colors;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const symbol = 'HoldUnit';
|
||||
const [loc_x, loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, this.props.loc, false, symbol);
|
||||
return (
|
||||
<g stroke={Colors[this.props.powerName]}>
|
||||
<use
|
||||
x={loc_x}
|
||||
y={loc_y}
|
||||
width={SymbolSizes[symbol].width}
|
||||
height={SymbolSizes[symbol].height}
|
||||
href={`#${symbol}`}/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Hold.propTypes = {
|
||||
loc: PropTypes.string.isRequired,
|
||||
powerName: PropTypes.string.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired,
|
||||
colors: PropTypes.object.isRequired
|
||||
};
|
||||
67
diplomacy/web/src/gui/maps/common/move.js
Normal file
67
diplomacy/web/src/gui/maps/common/move.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// ==============================================================================
|
||||
// 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 {ARMY, coloredStrokeWidth, getUnitCenter, plainStrokeWidth} from "./common";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class Move extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const Colors = this.props.colors;
|
||||
const src_loc = this.props.srcLoc;
|
||||
const dest_loc = this.props.dstLoc;
|
||||
const is_dislodged = this.props.phaseType === 'R';
|
||||
const [src_loc_x, src_loc_y] = getUnitCenter(Coordinates, SymbolSizes, src_loc, is_dislodged);
|
||||
let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, is_dislodged);
|
||||
// Adjusting destination
|
||||
const delta_x = dest_loc_x - src_loc_x;
|
||||
const delta_y = dest_loc_y - src_loc_y;
|
||||
const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
|
||||
const delta_dec = parseFloat(SymbolSizes[ARMY].width) / 2 + 2 * coloredStrokeWidth(SymbolSizes);
|
||||
dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - delta_dec) / vector_length * delta_x) * 100.) / 100.;
|
||||
dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - delta_dec) / vector_length * delta_y) * 100.) / 100.;
|
||||
return (
|
||||
<g>
|
||||
<line x1={src_loc_x}
|
||||
y1={src_loc_y}
|
||||
x2={dest_loc_x}
|
||||
y2={dest_loc_y}
|
||||
className={'varwidthshadow'}
|
||||
strokeWidth={'' + plainStrokeWidth(SymbolSizes)}/>
|
||||
<line x1={src_loc_x}
|
||||
y1={src_loc_y}
|
||||
x2={dest_loc_x}
|
||||
y2={dest_loc_y}
|
||||
className={'varwidthorder'}
|
||||
markerEnd={'url(#arrow)'}
|
||||
stroke={Colors[this.props.powerName]}
|
||||
strokeWidth={'' + coloredStrokeWidth(SymbolSizes)}/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Move.propTypes = {
|
||||
srcLoc: PropTypes.string.isRequired,
|
||||
dstLoc: PropTypes.string.isRequired,
|
||||
powerName: PropTypes.string.isRequired,
|
||||
phaseType: PropTypes.string.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired,
|
||||
colors: PropTypes.object.isRequired
|
||||
};
|
||||
72
diplomacy/web/src/gui/maps/common/supportHold.js
Normal file
72
diplomacy/web/src/gui/maps/common/supportHold.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// ==============================================================================
|
||||
// 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 {centerSymbolAroundUnit, getUnitCenter} from "./common";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class SupportHold extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const Colors = this.props.colors;
|
||||
const loc = this.props.loc;
|
||||
const dest_loc = this.props.dstLoc;
|
||||
const symbol = 'SupportHoldUnit';
|
||||
const [symbol_loc_x, symbol_loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, dest_loc, false, symbol);
|
||||
const [loc_x, loc_y] = getUnitCenter(Coordinates, SymbolSizes, loc, false);
|
||||
let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, false);
|
||||
|
||||
const delta_x = dest_loc_x - loc_x;
|
||||
const delta_y = dest_loc_y - loc_y;
|
||||
const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
|
||||
const delta_dec = parseFloat(SymbolSizes[symbol].height) / 2;
|
||||
dest_loc_x = '' + Math.round((parseFloat(loc_x) + (vector_length - delta_dec) / vector_length * delta_x) * 100.) / 100.;
|
||||
dest_loc_y = '' + Math.round((parseFloat(loc_y) + (vector_length - delta_dec) / vector_length * delta_y) * 100.) / 100.;
|
||||
|
||||
return (
|
||||
<g stroke={Colors[this.props.powerName]}>
|
||||
<line x1={loc_x}
|
||||
y1={loc_y}
|
||||
x2={dest_loc_x}
|
||||
y2={dest_loc_y}
|
||||
className={'shadowdash'}/>
|
||||
<line x1={loc_x}
|
||||
y1={loc_y}
|
||||
x2={dest_loc_x}
|
||||
y2={dest_loc_y}
|
||||
className={'supportorder'}
|
||||
stroke={Colors[this.props.powerName]}/>
|
||||
<use
|
||||
x={symbol_loc_x}
|
||||
y={symbol_loc_y}
|
||||
width={SymbolSizes[symbol].width}
|
||||
height={SymbolSizes[symbol].height}
|
||||
href={`#${symbol}`}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SupportHold.propTypes = {
|
||||
loc: PropTypes.string.isRequired,
|
||||
dstLoc: PropTypes.string.isRequired,
|
||||
powerName: PropTypes.string.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired,
|
||||
colors: PropTypes.object.isRequired
|
||||
};
|
||||
61
diplomacy/web/src/gui/maps/common/supportMove.js
Normal file
61
diplomacy/web/src/gui/maps/common/supportMove.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// ==============================================================================
|
||||
// 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 {ARMY, coloredStrokeWidth, getUnitCenter} from "./common";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class SupportMove extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const Colors = this.props.colors;
|
||||
const loc = this.props.loc;
|
||||
const src_loc = this.props.srcLoc;
|
||||
const dest_loc = this.props.dstLoc;
|
||||
const [loc_x, loc_y] = getUnitCenter(Coordinates, SymbolSizes, loc, false);
|
||||
const [src_loc_x, src_loc_y] = getUnitCenter(Coordinates, SymbolSizes, src_loc, false);
|
||||
let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, false);
|
||||
|
||||
// Adjusting destination
|
||||
const delta_x = dest_loc_x - src_loc_x;
|
||||
const delta_y = dest_loc_y - src_loc_y;
|
||||
const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
|
||||
const delta_dec = parseFloat(SymbolSizes[ARMY].width) / 2 + 2 * coloredStrokeWidth(SymbolSizes);
|
||||
dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - delta_dec) / vector_length * delta_x) * 100.) / 100.;
|
||||
dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - delta_dec) / vector_length * delta_y) * 100.) / 100.;
|
||||
return (
|
||||
<g>
|
||||
<path className={'shadowdash'}
|
||||
d={`M ${loc_x},${loc_y} C ${src_loc_x},${src_loc_y} ${src_loc_x},${src_loc_y} ${dest_loc_x},${dest_loc_y}`}/>
|
||||
<path className={'supportorder'}
|
||||
markerEnd={'url(#arrow)'}
|
||||
stroke={Colors[this.props.powerName]}
|
||||
d={`M ${loc_x},${loc_y} C ${src_loc_x},${src_loc_y} ${src_loc_x},${src_loc_y} ${dest_loc_x},${dest_loc_y}`}/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SupportMove.propTypes = {
|
||||
loc: PropTypes.string.isRequired,
|
||||
srcLoc: PropTypes.string.isRequired,
|
||||
dstLoc: PropTypes.string.isRequired,
|
||||
powerName: PropTypes.string.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired,
|
||||
colors: PropTypes.object.isRequired
|
||||
};
|
||||
50
diplomacy/web/src/gui/maps/common/unit.js
Normal file
50
diplomacy/web/src/gui/maps/common/unit.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// ==============================================================================
|
||||
// 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 {ARMY, FLEET} from "./common";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class Unit extends React.Component {
|
||||
render() {
|
||||
const Coordinates = this.props.coordinates;
|
||||
const SymbolSizes = this.props.symbolSizes;
|
||||
const split_unit = this.props.unit.split(/ +/);
|
||||
const unit_type = split_unit[0];
|
||||
const loc = split_unit[1];
|
||||
const dislogged_type = this.props.isDislodged ? 'disl' : 'unit';
|
||||
const symbol = unit_type === 'F' ? FLEET : ARMY;
|
||||
const loc_x = Coordinates[loc][dislogged_type][0];
|
||||
const loc_y = Coordinates[loc][dislogged_type][1];
|
||||
return (
|
||||
<use href={`#${this.props.isDislodged ? 'Dislodged' : ''}${symbol}`}
|
||||
x={loc_x}
|
||||
y={loc_y}
|
||||
id={`${this.props.isDislodged ? 'dislodged_' : ''}unit_${loc}`}
|
||||
width={SymbolSizes[symbol].width}
|
||||
height={SymbolSizes[symbol].height}
|
||||
className={`unit${this.props.powerName.toLowerCase()}`}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Unit.propTypes = {
|
||||
unit: PropTypes.string.isRequired,
|
||||
powerName: PropTypes.string.isRequired,
|
||||
isDislodged: PropTypes.bool.isRequired,
|
||||
coordinates: PropTypes.object.isRequired,
|
||||
symbolSizes: PropTypes.object.isRequired
|
||||
};
|
||||
19
diplomacy/web/src/gui/maps/modern/SvgModern.css
Normal file
19
diplomacy/web/src/gui/maps/modern/SvgModern.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
==============================================================================
|
||||
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/>.
|
||||
==============================================================================
|
||||
**/
|
||||
/* Default Text */ .SvgModern text {font-size:10pt;fill:black;font-family:Arial;} /* Text */ .SvgModern /* .credits {font-size:8pt;font-family:Arial;} */ .SvgModern /* .titletext {font-size:14px;font-weight:bolder;font-family:Arial;} */ .SvgModern .labeltext {font-size:12px;fill:black;font-family:Arial;} .SvgModern .currentnoterect {fill:#c5dfea; stroke-width:0; stroke:black;} .SvgModern .currentnotetext {font-family:serif,sans-serif; font-size:12px; fill:black; stroke:black;} .SvgModern .currentphasetext {font-family:serif,sans-serif; font-size:14px; fill:black; stroke:black;} /* Mouse Layer */ .SvgModern .invisibleContent {stroke:#000000;fill:#000000;fill-opacity:0.0;opacity:0.0} .SvgModern .provinceRed {fill:url(#patternRed); stroke:black; stroke-width: 2} .SvgModern .provinceBrown {fill:url(#patternBrown); stroke:black; stroke-width: 2} .SvgModern .provinceGreen {fill:url(#patternGreen); stroke:black; stroke-width: 2} .SvgModern .provinceBlack {fill:url(#patternBlack); stroke:black; stroke-width: 2} .SvgModern .provinceBlue {fill:url(#patternBlue); stroke:black; stroke-width: 2} /* Nations */ .SvgModern .nopower {fill:#FFEEEE; stroke:black; stroke-linejoin:round; stroke-width: 2} .SvgModern .water {fill:#DDDDFF; stroke:black; stroke-linejoin:round; stroke-width: 2} .SvgModern .britain {fill:royalblue;stroke:black;; stroke-width: 2} .SvgModern .egypt {fill:#808000;stroke:black;; stroke-width: 2} .SvgModern .france {fill:#00FFFF;stroke:black;; stroke-width: 2} .SvgModern .germany {fill:darkgrey;stroke:black;; stroke-width: 2} .SvgModern .italy {fill:#80FF80;stroke:black;; stroke-width: 2} .SvgModern .poland {fill:#FF0000;stroke:black;; stroke-width: 2} .SvgModern .russia {fill:#008000;stroke:black;; stroke-width: 2} .SvgModern .spain {fill:#FF8080;stroke:black;; stroke-width: 2} .SvgModern .turkey {fill:#FFFF00;stroke:black;; stroke-width: 2} .SvgModern .ukraine {fill:#FF00FF;stroke:black;; stroke-width: 2} /* Unit Colors */ .SvgModern .unitbritain {fill:deepskyblue;stroke:black;fill-opacity:0.90;} .SvgModern .unitegypt {fill:#808000;stroke:black;fill-opacity:0.90;} .SvgModern .unitfrance {fill:#00FFFF;stroke:black;fill-opacity:0.90;} .SvgModern .unitgermany {fill:darkgrey;stroke:black;fill-opacity:0.90;} .SvgModern .unititaly {fill:#80FF80;stroke:black;fill-opacity:0.90;} .SvgModern .unitpoland {fill:#FF0000;stroke:black;fill-opacity:0.90;} .SvgModern .unitrussia {fill:#008000;stroke:black;fill-opacity:0.90;} .SvgModern .unitspain {fill:#FF8080;stroke:black;fill-opacity:0.90;} .SvgModern .unitturkey {fill:#FFFF00;stroke:black;fill-opacity:0.90;} .SvgModern .unitukraine {fill:#FF00FF;stroke:black;fill-opacity:0.90;} /* order drawing styles, stroke and fill colors should not be specified */ .SvgModern .supportorder {stroke-width:2; fill:none; stroke-dasharray:5,5;} .SvgModern .convoyorder {stroke-dasharray:15,5; stroke-width:2; fill:none;} .SvgModern .shadowdash {stroke-width:4; fill:none; stroke:black; opacity:0.45;} .SvgModern .varwidthorder {fill:none;} .SvgModern .varwidthshadow {fill:none; stroke:black;} .SvgModern .style1 {fill:darkGray}
|
||||
20
diplomacy/web/src/gui/maps/modern/SvgModern.js
Normal file
20
diplomacy/web/src/gui/maps/modern/SvgModern.js
Normal file
File diff suppressed because one or more lines are too long
21
diplomacy/web/src/gui/maps/modern/SvgModernMetadata.js
Normal file
21
diplomacy/web/src/gui/maps/modern/SvgModernMetadata.js
Normal file
File diff suppressed because one or more lines are too long
19
diplomacy/web/src/gui/maps/pure/SvgPure.css
Normal file
19
diplomacy/web/src/gui/maps/pure/SvgPure.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
==============================================================================
|
||||
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/>.
|
||||
==============================================================================
|
||||
**/
|
||||
/* text */ .SvgPure svg { font-size: 100% } .SvgPure .labeltext {stroke-width:0.1; stroke:black; fill:black;} .SvgPure .currentnotetext {font-family:serif,sans-serif; font-size:1.5em; fill:black; stroke:black;} .SvgPure .currentphasetext {font-family:serif,sans-serif; fill:black; stroke:black;} /* invisible click rects fill:none does not work */ .SvgPure .invisibleContent {stroke:#000000; fill:#000000; fill-opacity:0.0; opacity:0.0} /* default region coloring, by power */ .SvgPure .provinceRed {fill:url(#patternRed); stroke: black; stroke-width: 2} .SvgPure .provinceBrown {fill:url(#patternBrown); stroke: black; stroke-width: 2} .SvgPure .provinceGreen {fill:url(#patternGreen); stroke: black; stroke-width: 2} .SvgPure .provinceBlack {fill:url(#patternBlack); stroke: black; stroke-width: 2} .SvgPure .provinceBlue {fill:url(#patternBlue); stroke: black; stroke-width: 2} .SvgPure .nopower {fill:antiquewhite; stroke:#000000; stroke-width:2} .SvgPure .water {fill:#c5dfea; stroke:#000000; stroke-width:2} .SvgPure .austria {fill:#c48f85; stroke:#000000; stroke-width:2} .SvgPure .england {fill:darkviolet; stroke:#000000; stroke-width:2} .SvgPure .france {fill:royalblue; stroke:#000000; stroke-width:2} .SvgPure .germany {fill:#a08a75; stroke:#000000; stroke-width:2} .SvgPure .italy {fill:forestgreen; stroke:#000000; stroke-width:2} .SvgPure .russia {fill:#757d91; stroke:#000000; stroke-width:2} .SvgPure .turkey {fill:#b9a61c; stroke:#000000; stroke-width:2} /* unit colors, by power note that underscores are not supported */ .SvgPure .unitaustria {fill:red; fill-opacity:0.85} .SvgPure .unitengland {fill:mediumpurple; fill-opacity:0.85} .SvgPure .unitfrance {fill:deepskyblue; fill-opacity:0.85} .SvgPure .unitgermany {fill:dimgray; fill-opacity:0.85} .SvgPure .unititaly {fill:olive; fill-opacity:0.85} .SvgPure .unitrussia {fill:white; fill-opacity:1.0} .SvgPure .unitturkey {fill:yellow; fill-opacity:0.85} /* order drawing styles, stroke and fill colors should not be specified */ .SvgPure .supportorder {stroke-width:6; fill:none; stroke-dasharray:5,5;} .SvgPure .convoyorder {stroke-dasharray:15,5; stroke-width:6; fill:none;} .SvgPure .shadowdash {stroke-width:10; fill:none; stroke:black; opacity:0.45;} .SvgPure .varwidthorder {fill:none;} .SvgPure .varwidthshadow {fill:none; stroke:black;}
|
||||
20
diplomacy/web/src/gui/maps/pure/SvgPure.js
Normal file
20
diplomacy/web/src/gui/maps/pure/SvgPure.js
Normal file
File diff suppressed because one or more lines are too long
21
diplomacy/web/src/gui/maps/pure/SvgPureMetadata.js
Normal file
21
diplomacy/web/src/gui/maps/pure/SvgPureMetadata.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
==============================================================================
|
||||
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/>.
|
||||
==============================================================================
|
||||
**/
|
||||
export const Coordinates = {"BER":{"unit":["362.5","864.0"],"disl":["374.5","852.0"]},"CON":{"unit":["412.5","98.0"],"disl":["424.5","86.0"]},"LON":{"unit":["878.5","514.0"],"disl":["890.5","502.0"]},"MOS":{"unit":["140.5","289.0"],"disl":["152.5","277.0"]},"PAR":{"unit":["707.5","815.0"],"disl":["719.5","803.0"]},"ROM":{"unit":["116.5","628.0"],"disl":["128.5","616.0"]},"VIE":{"unit":["750.5","195.0"],"disl":["762.5","183.0"]}};
|
||||
export const SymbolSizes = {"Fleet":{"width":40.0,"height":40.0},"Army":{"width":40.0,"height":40.0},"Wing":{"width":40.0,"height":40.0},"DislodgedFleet":{"width":40.0,"height":40.0},"DislodgedArmy":{"width":40.0,"height":40.0},"DislodgedWing":{"width":40.0,"height":40.0},"FailedOrder":{"width":30.0,"height":30.0},"SupplyCenter":{"width":20.0,"height":20.0},"BuildUnit":{"width":60.0,"height":60.0},"RemoveUnit":{"width":50.0,"height":50.0},"WaivedBuild":{"width":40.0,"height":40.0},"HoldUnit":{"width":66.6,"height":66.6},"SupportHoldUnit":{"width":76.6,"height":76.6},"ConvoyTriangle":{"width":66.4,"height":57.4},"Stroke":{"width":6.0,"height":10.0}};
|
||||
export const Colors = {"AUSTRIA":"#DA251D","ENGLAND":"#2D77B2","FRANCE":"#8E85B7","GERMANY":"#666666","ITALY":"#40A340","RUSSIA":"#EEF0E9","TURKEY":"#E9F507"};
|
||||
19
diplomacy/web/src/gui/maps/standard/SvgStandard.css
Normal file
19
diplomacy/web/src/gui/maps/standard/SvgStandard.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
==============================================================================
|
||||
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/>.
|
||||
==============================================================================
|
||||
**/
|
||||
/* text */ .SvgStandard svg { font-size: 100% } .SvgStandard .labeltext24 {text-anchor:middle; stroke-width:0.1; stroke:black; fill:black; font-family:serif,sans-serif; font-style:italic; font-size:1.4em;} .SvgStandard .labeltext18 {text-anchor:middle; stroke-width:0.1; stroke:black; fill:black; font-family:serif,sans-serif; font-style:italic; font-size:1.1em;} .SvgStandard .currentnotetext {font-family:serif,sans-serif; font-size:1.5em; fill:black; stroke:black;} .SvgStandard .currentnoterect {fill:#c5dfea;} .SvgStandard .currentphasetext {font-family:serif,sans-serif; font-size:2.5em; fill:black; stroke:black;} .SvgStandard .labeltext24 text {cursor:default;} /* map and object features */ .SvgStandard .impassable {fill:#353433; stroke:#000000; stroke-width:1} /* invisible click rects fill:none does not work */ .SvgStandard .invisibleContent {stroke:#000000; fill:#000000; fill-opacity:0.0; opacity:0.0} /* default region coloring, by power */ .SvgStandard .provinceRed {fill:url(#patternRed)} .SvgStandard .provinceBrown {fill:url(#patternBrown)} .SvgStandard .provinceGreen {fill:url(#patternGreen)} .SvgStandard .provinceBlack {fill:url(#patternBlack)} .SvgStandard .provinceBlue {fill:url(#patternBlue)} .SvgStandard .nopower {fill:antiquewhite; stroke:#000000; stroke-width:1} .SvgStandard .water {fill:#c5dfea; stroke:#000000; stroke-width:1} .SvgStandard .neutral {fill:lightgray; stroke:#000000; stroke-width:1} .SvgStandard .austria {fill:#c48f85; stroke:#000000; stroke-width:1} .SvgStandard .england {fill:darkviolet; stroke:#000000; stroke-width:1} .SvgStandard .france {fill:royalblue; stroke:#000000; stroke-width:1} .SvgStandard .germany {fill:#a08a75; stroke:#000000; stroke-width:1} .SvgStandard .italy {fill:forestgreen; stroke:#000000; stroke-width:1} .SvgStandard .russia {fill:#757d91; stroke:#000000; stroke-width:1} .SvgStandard .turkey {fill:#b9a61c; stroke:#000000; stroke-width:1} /* unit colors, by power note that underscores are not supported */ .SvgStandard .unitaustria {fill:red; fill-opacity:0.85} .SvgStandard .unitengland {fill:mediumpurple; fill-opacity:0.85} .SvgStandard .unitfrance {fill:deepskyblue; fill-opacity:0.85} .SvgStandard .unitgermany {fill:dimgray; fill-opacity:0.85} .SvgStandard .unititaly {fill:olive; fill-opacity:0.85} .SvgStandard .unitrussia {fill:white; fill-opacity:1.0} .SvgStandard .unitturkey {fill:yellow; fill-opacity:0.85} /* order drawing styles, stroke and fill colors should not be specified */ .SvgStandard .supportorder {stroke-width:6; fill:none; stroke-dasharray:5,5;} .SvgStandard .convoyorder {stroke-dasharray:15,5; stroke-width:6; fill:none;} .SvgStandard .shadowdash {stroke-width:10; fill:none; stroke:black; opacity:0.45;} .SvgStandard .varwidthorder {fill:none;} .SvgStandard .varwidthshadow {fill:none; stroke:black;}
|
||||
20
diplomacy/web/src/gui/maps/standard/SvgStandard.js
Normal file
20
diplomacy/web/src/gui/maps/standard/SvgStandard.js
Normal file
File diff suppressed because one or more lines are too long
21
diplomacy/web/src/gui/maps/standard/SvgStandardMetadata.js
Normal file
21
diplomacy/web/src/gui/maps/standard/SvgStandardMetadata.js
Normal file
File diff suppressed because one or more lines are too long
99
diplomacy/web/src/gui/pages/content_connection.jsx
Normal file
99
diplomacy/web/src/gui/pages/content_connection.jsx
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// ==============================================================================
|
||||
// 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 {Connection} from "../../diplomacy/client/connection";
|
||||
import {ConnectionForm} from "../forms/connection_form";
|
||||
import {DipStorage} from "../utils/dipStorage";
|
||||
import {Helmet} from "react-helmet";
|
||||
import {Navigation} from "../components/navigation";
|
||||
import {PageContext} from "../components/page_context";
|
||||
|
||||
export class ContentConnection extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.connection = null;
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
}
|
||||
|
||||
onSubmit(data) {
|
||||
const page = this.context;
|
||||
for (let fieldName of ['hostname', 'port', 'username', 'password', 'showServerFields'])
|
||||
if (!data.hasOwnProperty(fieldName))
|
||||
return page.error(`Missing ${fieldName}, got ${JSON.stringify(data)}`);
|
||||
page.info('Connecting ...');
|
||||
if (this.connection) {
|
||||
this.connection.currentConnectionProcessing.stop();
|
||||
}
|
||||
this.connection = new Connection(data.hostname, data.port, window.location.protocol.toLowerCase() === 'https:');
|
||||
this.connection.onReconnectionError = page.onReconnectionError;
|
||||
// Page is passed as logger object (with methods info(), error(), success()) when connecting.
|
||||
this.connection.connect(page)
|
||||
.then(() => {
|
||||
page.connection = this.connection;
|
||||
this.connection = null;
|
||||
page.success(`Successfully connected to server ${data.username}:${data.port}`);
|
||||
page.connection.authenticate(data.username, data.password)
|
||||
.then((channel) => {
|
||||
page.channel = channel;
|
||||
return channel.getAvailableMaps();
|
||||
})
|
||||
.then(availableMaps => {
|
||||
for (let mapName of Object.keys(availableMaps))
|
||||
availableMaps[mapName].powers.sort();
|
||||
page.availableMaps = availableMaps;
|
||||
const userGameIndices = DipStorage.getUserGames(page.channel.username);
|
||||
if (userGameIndices && userGameIndices.length) {
|
||||
return page.channel.getGamesInfo({games: userGameIndices});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then((gamesInfo) => {
|
||||
if (gamesInfo) {
|
||||
page.success('Found ' + gamesInfo.length + ' user games.');
|
||||
page.updateMyGames(gamesInfo);
|
||||
}
|
||||
page.loadGames({success: `Account ${data.username} connected.`});
|
||||
})
|
||||
.catch((error) => {
|
||||
page.error('Error while authenticating: ' + error + ' Please re-try.');
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
page.error('Error while connecting: ' + error + ' Please re-try.');
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const title = 'Connection';
|
||||
return (
|
||||
<main>
|
||||
<Helmet>
|
||||
<title>{title} | Diplomacy</title>
|
||||
</Helmet>
|
||||
<Navigation title={title}/>
|
||||
<ConnectionForm onSubmit={this.onSubmit}/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ContentConnection.contextType = PageContext;
|
||||
1403
diplomacy/web/src/gui/pages/content_game.jsx
Normal file
1403
diplomacy/web/src/gui/pages/content_game.jsx
Normal file
File diff suppressed because it is too large
Load diff
167
diplomacy/web/src/gui/pages/content_games.jsx
Normal file
167
diplomacy/web/src/gui/pages/content_games.jsx
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// ==============================================================================
|
||||
// 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 {Tabs} from "../components/tabs";
|
||||
import {Table} from "../components/table";
|
||||
import {FindForm} from "../forms/find_form";
|
||||
import {InlineGameView} from "../utils/inline_game_view";
|
||||
import {Helmet} from "react-helmet";
|
||||
import {Navigation} from "../components/navigation";
|
||||
import {PageContext} from "../components/page_context";
|
||||
import {ContentGame} from "./content_game";
|
||||
import PropTypes from 'prop-types';
|
||||
import {Tab} from "../components/tab";
|
||||
import {GameCreationWizard} from "../wizards/gameCreation/gameCreationWizard";
|
||||
|
||||
const TABLE_LOCAL_GAMES = {
|
||||
game_id: ['Game ID', 0],
|
||||
deadline: ['Deadline', 1],
|
||||
rights: ['Rights', 2],
|
||||
rules: ['Rules', 3],
|
||||
players: ['Players/Expected', 4],
|
||||
status: ['Status', 5],
|
||||
phase: ['Phase', 6],
|
||||
join: ['Join', 7],
|
||||
actions: ['Actions', 8],
|
||||
};
|
||||
|
||||
export class ContentGames extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {tab: null};
|
||||
this.changeTab = this.changeTab.bind(this);
|
||||
this.onFind = this.onFind.bind(this);
|
||||
this.onCreate = this.onCreate.bind(this);
|
||||
this.wrapGameData = this.wrapGameData.bind(this);
|
||||
}
|
||||
|
||||
getPage() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
onFind(form) {
|
||||
for (let field of ['game_id', 'status', 'include_protected', 'for_omniscience'])
|
||||
if (!form[field])
|
||||
form[field] = null;
|
||||
this.getPage().channel.listGames(form)
|
||||
.then((data) => {
|
||||
this.getPage().success('Found ' + data.length + ' data.');
|
||||
this.getPage().addGamesFound(data);
|
||||
this.getPage().loadGames();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.getPage().error('Error when looking for distant games: ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
onCreate(form) {
|
||||
let networkGame = null;
|
||||
this.getPage().channel.createGame(form)
|
||||
.then((game) => {
|
||||
this.getPage().addToMyGames(game.local);
|
||||
networkGame = game;
|
||||
return networkGame.getAllPossibleOrders();
|
||||
})
|
||||
.then(allPossibleOrders => {
|
||||
networkGame.local.setPossibleOrders(allPossibleOrders);
|
||||
this.getPage().load(
|
||||
`game: ${networkGame.local.game_id}`,
|
||||
<ContentGame data={networkGame.local}/>,
|
||||
{success: 'Game created.'}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.getPage().error('Error when creating a game: ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
changeTab(tabIndex) {
|
||||
this.setState({tab: tabIndex});
|
||||
}
|
||||
|
||||
wrapGameData(gameData) {
|
||||
return new InlineGameView(this.getPage(), gameData, this.getPage().availableMaps);
|
||||
}
|
||||
|
||||
gameCreationButton() {
|
||||
return (
|
||||
<button type="button"
|
||||
className="btn btn-danger btn-sm mx-0 mx-sm-4"
|
||||
onClick={() => this.getPage().dialog(onClose => (
|
||||
<GameCreationWizard availableMaps={this.getPage().availableMaps}
|
||||
onCancel={onClose}
|
||||
username={this.getPage().channel.username}
|
||||
onSubmit={(form) => {
|
||||
onClose();
|
||||
this.onCreate(form);
|
||||
}}/>
|
||||
))}>
|
||||
<strong>create a game</strong>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const title = 'Games';
|
||||
const page = this.getPage();
|
||||
const navigation = [
|
||||
['load a game from disk', page.loadGameFromDisk],
|
||||
['logout', page.logout]
|
||||
];
|
||||
const myGames = this.props.myGames;
|
||||
const gamesFound = this.props.gamesFound;
|
||||
myGames.sort((a, b) => b.timestamp_created - a.timestamp_created);
|
||||
gamesFound.sort((a, b) => b.timestamp_created - a.timestamp_created);
|
||||
const tab = this.state.tab ? this.state.tab : (myGames.length ? 'my-games' : 'find');
|
||||
return (
|
||||
<main>
|
||||
<Helmet>
|
||||
<title>{title} | Diplomacy</title>
|
||||
</Helmet>
|
||||
<Navigation title={title} afterTitle={this.gameCreationButton()}
|
||||
username={page.channel.username} navigation={navigation}/>
|
||||
<Tabs menu={['find', 'my-games']} titles={['Find', 'My Games']}
|
||||
onChange={this.changeTab} active={tab}>
|
||||
{tab === 'find' ? (
|
||||
<Tab id="tab-games-find" display={true}>
|
||||
<FindForm onSubmit={this.onFind}/>
|
||||
<Table className={"table table-striped"} caption={"Games"} columns={TABLE_LOCAL_GAMES}
|
||||
data={gamesFound} wrapper={this.wrapGameData}/>
|
||||
</Tab>
|
||||
) : ''}
|
||||
{tab === 'my-games' ? (
|
||||
<Tab id={'tab-my-games'} display={true}>
|
||||
<Table className={"table table-striped"} caption={"My games"} columns={TABLE_LOCAL_GAMES}
|
||||
data={myGames} wrapper={this.wrapGameData}/>
|
||||
</Tab>
|
||||
) : ''}
|
||||
</Tabs>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ContentGames.contextType = PageContext;
|
||||
ContentGames.propTypes = {
|
||||
gamesFound: PropTypes.array.isRequired,
|
||||
myGames: PropTypes.array.isRequired
|
||||
};
|
||||
372
diplomacy/web/src/gui/pages/page.jsx
Normal file
372
diplomacy/web/src/gui/pages/page.jsx
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
// ==============================================================================
|
||||
// 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 "./content_connection";
|
||||
import {UTILS} from "../../diplomacy/utils/utils";
|
||||
import {Diplog} from "../../diplomacy/utils/diplog";
|
||||
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";
|
||||
import {confirmAlert} from 'react-confirm-alert';
|
||||
import 'react-confirm-alert/src/react-confirm-alert.css';
|
||||
|
||||
export class Page extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.connection = null;
|
||||
this.channel = null;
|
||||
this.availableMaps = null;
|
||||
this.state = {
|
||||
// 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._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/>;
|
||||
}
|
||||
|
||||
setState(state) {
|
||||
return new Promise(resolve => super.setState(state, resolve));
|
||||
}
|
||||
|
||||
onReconnectionError(error) {
|
||||
this.__disconnect(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback OnClose
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback DialogBuilder
|
||||
* @param {OnClose} onClose
|
||||
*/
|
||||
|
||||
/**
|
||||
* open a dialog box
|
||||
* @param {DialogBuilder} builder - a callback to generate dialog GUI. Will be executed with a `onClose` callback
|
||||
* parameter to call when dialog must be closed: `builder(onClose)`.
|
||||
*/
|
||||
dialog(builder) {
|
||||
confirmAlert({customUI: ({onClose}) => builder(onClose)});
|
||||
}
|
||||
|
||||
//// 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;
|
||||
return this.setState(newState);
|
||||
}
|
||||
|
||||
loadGames(messages) {
|
||||
return this.load(
|
||||
'games',
|
||||
<ContentGames myGames={this.getMyGames()} gamesFound={this.getGamesFound()}/>,
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
loadGameFromDisk() {
|
||||
return loadGameFromDisk()
|
||||
.then((game) => this.load(
|
||||
`game: ${game.game_id}`,
|
||||
<ContentGame data={game}/>,
|
||||
{success: `Game loaded from disk: ${game.game_id}`}
|
||||
))
|
||||
.catch(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);
|
||||
return 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) {
|
||||
return this.channel.logout()
|
||||
.then(() => this.__disconnect())
|
||||
.catch(error => this.error(`Error while disconnecting: ${error.toString()}.`));
|
||||
} else {
|
||||
return this.__disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
//// Methods to be used to set page title and messages.
|
||||
|
||||
error(message) {
|
||||
message = Page.wrapMessage(message);
|
||||
Diplog.error(message);
|
||||
return this.setState({error: message});
|
||||
}
|
||||
|
||||
info(message) {
|
||||
message = Page.wrapMessage(message);
|
||||
Diplog.info(message);
|
||||
return this.setState({info: message});
|
||||
}
|
||||
|
||||
success(message) {
|
||||
message = Page.wrapMessage(message);
|
||||
Diplog.success(message);
|
||||
return this.setState({success: message});
|
||||
}
|
||||
|
||||
warn(message) {
|
||||
return 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;
|
||||
return 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
|
||||
);
|
||||
}
|
||||
return this.setState({games: gamesFound});
|
||||
}
|
||||
|
||||
leaveGame(gameID) {
|
||||
if (this.state.myGames.hasOwnProperty(gameID)) {
|
||||
const game = this.state.myGames[gameID];
|
||||
if (game.client) {
|
||||
return 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 {
|
||||
return this.loadGames({info: `No game to left.`});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_post_remove(gameID) {
|
||||
return this.disconnectGame(gameID)
|
||||
.then(() => {
|
||||
const myGames = this._remove_from_my_games(gameID);
|
||||
const games = this._remove_from_games(gameID);
|
||||
return this.setState({games, myGames});
|
||||
})
|
||||
.then(() => this.loadGames({info: `Game ${gameID} deleted.`}));
|
||||
}
|
||||
|
||||
removeGame(gameID) {
|
||||
const game = this.getGame(gameID);
|
||||
if (game) {
|
||||
if (game.client) {
|
||||
return game.client.remove()
|
||||
.then(() => this._post_remove(gameID))
|
||||
.catch(error => this.error(`Error when deleting game ${gameID}: ${error.toString()}`));
|
||||
} else {
|
||||
return this.channel.joinGame({game_id: gameID})
|
||||
.then(networkGame => networkGame.remove())
|
||||
.then(() => this._post_remove(gameID))
|
||||
.catch(error => this.error(`Error when deleting game after joining it (${gameID}): ${error.toString()}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnectGame(gameID) {
|
||||
const game = this.getGame(gameID);
|
||||
if (game) {
|
||||
if (game.client) {
|
||||
game.client.clearAllCallbacks();
|
||||
game.client.callbacksBound = false;
|
||||
if (game.client.queue)
|
||||
game.client.queue.append(null);
|
||||
}
|
||||
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);
|
||||
return this.setState(this._add_to_my_games(game)).then(() => this.loadGames());
|
||||
}
|
||||
|
||||
removeFromMyGames(gameID) {
|
||||
const myGames = this._remove_from_my_games(gameID);
|
||||
return this.setState({myGames}).then(() => 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()}
|
||||
</div>
|
||||
</PageContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
140
diplomacy/web/src/gui/utils/dipStorage.jsx
Normal file
140
diplomacy/web/src/gui/utils/dipStorage.jsx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
/* DipStorage scheme:
|
||||
global
|
||||
- connection
|
||||
- username
|
||||
- hostname
|
||||
- port
|
||||
- showServerFields
|
||||
users
|
||||
- (username)
|
||||
- games
|
||||
- (game_id)
|
||||
- phase: string
|
||||
- local_orders: {power_name => [orders]}
|
||||
*/
|
||||
|
||||
let STORAGE = null;
|
||||
|
||||
export class DipStorage {
|
||||
static load() {
|
||||
if (!STORAGE) {
|
||||
const global = window.localStorage.global;
|
||||
const users = window.localStorage.users;
|
||||
STORAGE = {
|
||||
global: (global && JSON.parse(global)) || {
|
||||
connection: {
|
||||
username: null,
|
||||
hostname: null,
|
||||
port: null,
|
||||
showServerFields: null
|
||||
}
|
||||
},
|
||||
users: (users && JSON.parse(users)) || {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static save() {
|
||||
if (STORAGE) {
|
||||
window.localStorage.global = JSON.stringify(STORAGE.global);
|
||||
window.localStorage.users = JSON.stringify(STORAGE.users);
|
||||
}
|
||||
}
|
||||
|
||||
static getConnectionForm() {
|
||||
DipStorage.load();
|
||||
return Object.assign({}, STORAGE.global.connection);
|
||||
}
|
||||
|
||||
static getUserGames(username) {
|
||||
DipStorage.load();
|
||||
if (STORAGE.users[username])
|
||||
return Object.keys(STORAGE.users[username].games);
|
||||
return null;
|
||||
}
|
||||
|
||||
static getUserGameOrders(username, gameID, gamePhase) {
|
||||
DipStorage.load();
|
||||
if (STORAGE.users[username] && STORAGE.users[username].games[gameID]
|
||||
&& STORAGE.users[username].games[gameID].phase === gamePhase)
|
||||
return Object.assign({}, STORAGE.users[username].games[gameID].local_orders);
|
||||
return null;
|
||||
}
|
||||
|
||||
static setConnectionUsername(username) {
|
||||
DipStorage.load();
|
||||
STORAGE.global.connection.username = username;
|
||||
DipStorage.save();
|
||||
}
|
||||
|
||||
static setConnectionHostname(hostname) {
|
||||
DipStorage.load();
|
||||
STORAGE.global.connection.hostname = hostname;
|
||||
DipStorage.save();
|
||||
}
|
||||
|
||||
static setConnectionPort(port) {
|
||||
DipStorage.load();
|
||||
STORAGE.global.connection.port = port;
|
||||
DipStorage.save();
|
||||
}
|
||||
|
||||
static setConnectionshowServerFields(showServerFields) {
|
||||
DipStorage.load();
|
||||
STORAGE.global.connection.showServerFields = showServerFields;
|
||||
DipStorage.save();
|
||||
}
|
||||
|
||||
static addUserGame(username, gameID) {
|
||||
DipStorage.load();
|
||||
if (!STORAGE.users[username])
|
||||
STORAGE.users[username] = {games: {}};
|
||||
if (!STORAGE.users[username].games[gameID])
|
||||
STORAGE.users[username].games[gameID] = {phase: null, local_orders: {}};
|
||||
DipStorage.save();
|
||||
}
|
||||
|
||||
static addUserGameOrders(username, gameID, gamePhase, powerName, orders) {
|
||||
DipStorage.addUserGame(username, gameID);
|
||||
if (STORAGE.users[username].games[gameID].phase !== gamePhase)
|
||||
STORAGE.users[username].games[gameID] = {phase: null, local_orders: {}};
|
||||
STORAGE.users[username].games[gameID].phase = gamePhase;
|
||||
STORAGE.users[username].games[gameID].local_orders[powerName] = orders;
|
||||
DipStorage.save();
|
||||
}
|
||||
|
||||
static removeUserGame(username, gameID) {
|
||||
DipStorage.load();
|
||||
if (STORAGE.users[username] && STORAGE.users[username].games[gameID]) {
|
||||
delete STORAGE.users[username].games[gameID];
|
||||
DipStorage.save();
|
||||
}
|
||||
}
|
||||
|
||||
static clearUserGameOrders(username, gameID, powerName) {
|
||||
DipStorage.addUserGame(username, gameID);
|
||||
if (powerName) {
|
||||
if (STORAGE.users[username].games[gameID].local_orders[powerName])
|
||||
delete STORAGE.users[username].games[gameID].local_orders[powerName];
|
||||
} else {
|
||||
STORAGE.users[username].games[gameID] = {phase: null, local_orders: {}};
|
||||
}
|
||||
DipStorage.save();
|
||||
}
|
||||
}
|
||||
163
diplomacy/web/src/gui/utils/inline_game_view.jsx
Normal file
163
diplomacy/web/src/gui/utils/inline_game_view.jsx
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// ==============================================================================
|
||||
// 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 {JoinForm} from "../forms/join_form";
|
||||
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, maps) {
|
||||
this.page = page;
|
||||
this.game = gameData;
|
||||
this.maps = maps;
|
||||
this.get = this.get.bind(this);
|
||||
this.joinGame = this.joinGame.bind(this);
|
||||
this.showGame = this.showGame.bind(this);
|
||||
}
|
||||
|
||||
joinGame(formData) {
|
||||
const form = {
|
||||
power_name: formData[`power_name_${this.game.game_id}`],
|
||||
registration_password: formData[`registration_password_${this.game.game_id}`]
|
||||
};
|
||||
if (!form.power_name)
|
||||
form.power_name = null;
|
||||
if (!form.registration_password)
|
||||
form.registration_password = null;
|
||||
form.game_id = this.game.game_id;
|
||||
this.page.channel.joinGame(form)
|
||||
.then((networkGame) => {
|
||||
this.game = networkGame.local;
|
||||
this.page.addToMyGames(this.game);
|
||||
return networkGame.getAllPossibleOrders();
|
||||
})
|
||||
.then(allPossibleOrders => {
|
||||
this.game.setPossibleOrders(allPossibleOrders);
|
||||
this.page.load(
|
||||
`game: ${this.game.game_id}`,
|
||||
<ContentGame data={this.game}/>,
|
||||
{success: 'Game joined.'}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.page.error('Error when joining game ' + this.game.game_id + ': ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
showGame() {
|
||||
this.page.load(`game: ${this.game.game_id}`, <ContentGame data={this.game}/>);
|
||||
}
|
||||
|
||||
getJoinUI() {
|
||||
if (this.game.role) {
|
||||
// Game already joined.
|
||||
return (
|
||||
<div className={'games-form'}>
|
||||
<Button key={'button-show-' + this.game.game_id} title={'show'} onClick={this.showGame}/>
|
||||
<Button key={'button-leave-' + this.game.game_id} title={'leave'}
|
||||
onClick={() => this.page.leaveGame(this.game.game_id)}/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// Game not yet joined.
|
||||
return <JoinForm key={this.game.game_id}
|
||||
game_id={this.game.game_id}
|
||||
powers={this.game.controlled_powers}
|
||||
availablePowers={this.maps[this.game.map_name].powers}
|
||||
password_required={this.game.registration_password}
|
||||
onSubmit={this.joinGame}/>;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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.
|
||||
buttons.push(<Button key={`my-game-add`} title={'Add to My Games'}
|
||||
small={true} large={true}
|
||||
onClick={() => this.page.addToMyGames(this.game)}/>);
|
||||
}
|
||||
// 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) {
|
||||
if (name === 'players') {
|
||||
return `${this.game.n_players} / ${this.game.n_controls}`;
|
||||
}
|
||||
if (name === 'rights') {
|
||||
const elements = [];
|
||||
if (this.game.observer_level) {
|
||||
let levelName = '';
|
||||
if (this.game.observer_level === STRINGS.MASTER_TYPE)
|
||||
levelName = 'master';
|
||||
else if (this.game.observer_level === STRINGS.OMNISCIENT_TYPE)
|
||||
levelName = 'omniscient';
|
||||
else
|
||||
levelName = 'observer';
|
||||
elements.push((<p key={0}><strong>Observer right:</strong><br/>{levelName}</p>));
|
||||
}
|
||||
if (this.game.controlled_powers && this.game.controlled_powers.length) {
|
||||
const powers = this.game.controlled_powers.slice();
|
||||
powers.sort();
|
||||
elements.push((
|
||||
<div key={1}><strong>Currently handled power{powers.length === 1 ? '' : 's'}</strong></div>));
|
||||
for (let power of powers)
|
||||
elements.push((<div key={power}>{power}</div>));
|
||||
}
|
||||
return elements.length ? (<div>{elements}</div>) : '';
|
||||
}
|
||||
if (name === 'rules') {
|
||||
if (this.game.rules)
|
||||
return <div>{this.game.rules.map(rule => <div key={rule}>{rule}</div>)}</div>;
|
||||
return '';
|
||||
}
|
||||
if (name === 'join')
|
||||
return this.getJoinUI();
|
||||
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()}`;
|
||||
return <div>
|
||||
<div><strong>{this.game.game_id}</strong></div>
|
||||
<div>({dateString})</div>
|
||||
<div><em>{this.game.map_name}</em></div>
|
||||
</div>;
|
||||
}
|
||||
return this.game[name];
|
||||
}
|
||||
}
|
||||
101
diplomacy/web/src/gui/utils/load_game_from_disk.js
Normal file
101
diplomacy/web/src/gui/utils/load_game_from_disk.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// ==============================================================================
|
||||
// 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 $ from "jquery";
|
||||
import {STRINGS} from "../../diplomacy/utils/strings";
|
||||
import {Game} from "../../diplomacy/engine/game";
|
||||
|
||||
export function loadGameFromDisk() {
|
||||
return new Promise((onLoad, onError) => {
|
||||
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)) {
|
||||
onError(`Invalid JSON filename ${file.name}`);
|
||||
return;
|
||||
}
|
||||
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;
|
||||
gameObject.state_history = {};
|
||||
gameObject.message_history = {};
|
||||
gameObject.order_history = {};
|
||||
gameObject.result_history = {};
|
||||
|
||||
// Load all saved phases (expect the latest one) to history fields.
|
||||
for (let i = 0; i < savedData.phases.length - 1; ++i) {
|
||||
const savedPhase = savedData.phases[i];
|
||||
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;
|
||||
gameObject.state_history[gameState.name] = gameState;
|
||||
gameObject.message_history[gameState.name] = phaseMessages;
|
||||
gameObject.order_history[gameState.name] = phaseOrders;
|
||||
gameObject.result_history[gameState.name] = phaseResults;
|
||||
}
|
||||
|
||||
// Load latest phase separately and use it later to define the current game phase.
|
||||
const latestPhase = savedData.phases[savedData.phases.length - 1];
|
||||
const latestGameState = latestPhase.state;
|
||||
const latestPhaseOrders = latestPhase.orders || {};
|
||||
const latestPhaseResults = latestPhase.results || {};
|
||||
const latestPhaseMessages = {};
|
||||
if (latestPhase.messages) {
|
||||
for (let message of latestPhase.messages) {
|
||||
latestPhaseMessages[message.time_sent] = message;
|
||||
}
|
||||
}
|
||||
if (!latestGameState.name)
|
||||
latestGameState.name = latestPhase.name;
|
||||
// TODO: NB: What if latest phase in loaded JSON contains order results? Not sure if it is well handled.
|
||||
gameObject.result_history[latestGameState.name] = latestPhaseResults;
|
||||
|
||||
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);
|
||||
|
||||
// Set game current phase and state using latest phase found in JSON file.
|
||||
game.setPhaseData({
|
||||
name: latestGameState.name,
|
||||
state: latestGameState,
|
||||
orders: latestPhaseOrders,
|
||||
messages: latestPhaseMessages
|
||||
});
|
||||
onLoad(game);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
100
diplomacy/web/src/gui/utils/map_data.js
Normal file
100
diplomacy/web/src/gui/utils/map_data.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// ==============================================================================
|
||||
// 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 {Province} from "./province";
|
||||
|
||||
export class MapData {
|
||||
constructor(mapInfo, game) {
|
||||
// mapInfo: {powers: [], supply_centers: [], aliases: {alias: name}, loc_type: {loc => type}, loc_abut: {loc => [abuts]}}
|
||||
// game: a NetworkGame object.
|
||||
this.game = game;
|
||||
this.powers = new Set(mapInfo.powers);
|
||||
this.supplyCenters = new Set(mapInfo.supply_centers);
|
||||
this.aliases = Object.assign({}, mapInfo.aliases);
|
||||
this.provinces = {};
|
||||
for (let entry of Object.entries(mapInfo.loc_type)) {
|
||||
const provinceName = entry[0];
|
||||
const provinceType = entry[1];
|
||||
this.provinces[provinceName] = new Province(provinceName, provinceType, this.supplyCenters.has(provinceName));
|
||||
}
|
||||
for (let entry of Object.entries(mapInfo.loc_abut)) {
|
||||
this.getProvince(entry[0]).setNeighbors(entry[1].map(name => this.getProvince(name)));
|
||||
}
|
||||
for (let province of Object.values(this.provinces)) {
|
||||
province.setCoasts(this.provinces);
|
||||
}
|
||||
for (let power of Object.values(this.game.powers)) {
|
||||
for (let center of power.centers) {
|
||||
this.getProvince(center).setController(power.name, 'C');
|
||||
}
|
||||
for (let loc of power.influence) {
|
||||
this.getProvince(loc).setController(power.name, 'I');
|
||||
}
|
||||
for (let unit of power.units) {
|
||||
this.__add_unit(unit, power.name);
|
||||
}
|
||||
for (let unit of Object.keys(power.retreats)) {
|
||||
this.__add_retreat(unit, power.name);
|
||||
}
|
||||
}
|
||||
for (let entry of Object.entries(this.aliases)) {
|
||||
const alias = entry[0];
|
||||
const provinceName = entry[1];
|
||||
const province = this.getProvince(provinceName);
|
||||
if (province)
|
||||
province.aliases.push(alias);
|
||||
}
|
||||
}
|
||||
|
||||
__add_unit(unit, power_name) {
|
||||
const splitUnit = unit.split(/ +/);
|
||||
const unitType = splitUnit[0];
|
||||
const location = splitUnit[1];
|
||||
const province = this.getProvince(location);
|
||||
province.setController(power_name, 'U');
|
||||
province.unit = unitType;
|
||||
}
|
||||
|
||||
__add_retreat(unit, power_name) {
|
||||
const splitUnit = unit.split(/ +/);
|
||||
const location = splitUnit[1];
|
||||
const province = this.getProvince(location);
|
||||
province.retreatController = power_name;
|
||||
province.retreatUnit = unit;
|
||||
}
|
||||
|
||||
getProvince(abbr) {
|
||||
if (abbr === '')
|
||||
return null;
|
||||
if (abbr[0] === '_')
|
||||
abbr = abbr.substr(1, 3);
|
||||
if (!abbr)
|
||||
return null;
|
||||
if (this.provinces.hasOwnProperty(abbr))
|
||||
return this.provinces[abbr];
|
||||
if (this.provinces.hasOwnProperty(abbr.toUpperCase()))
|
||||
return this.provinces[abbr.toUpperCase()];
|
||||
if (this.provinces.hasOwnProperty(abbr.toLowerCase()))
|
||||
return this.provinces[abbr.toLowerCase()];
|
||||
if (this.aliases.hasOwnProperty(abbr))
|
||||
return this.provinces[this.aliases[abbr]];
|
||||
if (this.aliases.hasOwnProperty(abbr.toUpperCase()))
|
||||
return this.provinces[this.aliases[abbr.toUpperCase()]];
|
||||
if (this.aliases.hasOwnProperty(abbr.toLowerCase()))
|
||||
return this.provinces[this.aliases[abbr.toLowerCase()]];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
24
diplomacy/web/src/gui/utils/order.js
Normal file
24
diplomacy/web/src/gui/utils/order.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
export class Order {
|
||||
constructor(orderString, isLocal) {
|
||||
const pieces = orderString.split(/ +/);
|
||||
this.loc = pieces[1];
|
||||
this.order = orderString;
|
||||
this.local = Boolean(isLocal);
|
||||
}
|
||||
}
|
||||
211
diplomacy/web/src/gui/utils/order_building.js
Normal file
211
diplomacy/web/src/gui/utils/order_building.js
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
/*eslint no-unused-vars: ["error", { "args": "none" }]*/
|
||||
|
||||
function assertLength(expected, given) {
|
||||
if (expected !== given)
|
||||
throw new Error(`Length error: expected ${expected}, given ${given}.`);
|
||||
}
|
||||
|
||||
export class ProvinceCheck {
|
||||
|
||||
static retreated(province, powerName) {
|
||||
const retreatProvince = province.getRetreated(powerName);
|
||||
if (!retreatProvince)
|
||||
throw new Error(`No retreated location at province ${province.name}.`);
|
||||
// No confusion possible, we select the only occupied location at this province.
|
||||
return [retreatProvince.retreatUnit];
|
||||
}
|
||||
|
||||
static present(province, powerName) {
|
||||
let unit = null;
|
||||
let presenceProvince = province.getOccupied(powerName);
|
||||
if (presenceProvince) {
|
||||
unit = `${presenceProvince.unit} ${presenceProvince.name}`;
|
||||
} else {
|
||||
presenceProvince = province.getRetreated(powerName);
|
||||
if (!presenceProvince)
|
||||
throw new Error(`No unit or retreat at province ${province.name}.`);
|
||||
unit = presenceProvince.retreatUnit;
|
||||
}
|
||||
return [unit];
|
||||
}
|
||||
|
||||
static occupied(province, powerName) {
|
||||
const occupiedProvince = province.getOccupied(powerName);
|
||||
if (!occupiedProvince)
|
||||
throw new Error(`No occupied location at province ${province.name}.`);
|
||||
// No confusion possible, we select the only occupied location at this province.
|
||||
const unit = occupiedProvince.unit;
|
||||
const name = occupiedProvince.name.toUpperCase();
|
||||
return [`${unit} ${name}`];
|
||||
}
|
||||
|
||||
static occupiedByAny(province, unusedPowerName) {
|
||||
return ProvinceCheck.occupied(province, null);
|
||||
}
|
||||
|
||||
static any(province, unusedPowerName) {
|
||||
// There may be many locations available for a province (e.g. many coasts).
|
||||
return province.getLocationNames();
|
||||
}
|
||||
|
||||
static buildOrder(path) {
|
||||
switch (path[0]) {
|
||||
case 'H':
|
||||
return ProvinceCheck.holdToString(path);
|
||||
case 'M':
|
||||
return ProvinceCheck.moveToString(path);
|
||||
case 'V':
|
||||
return ProvinceCheck.moveViaToString(path);
|
||||
case 'S':
|
||||
return ProvinceCheck.supportToString(path);
|
||||
case 'C':
|
||||
return ProvinceCheck.convoyToString(path);
|
||||
case 'R':
|
||||
return ProvinceCheck.retreatToString(path);
|
||||
case 'D':
|
||||
return ProvinceCheck.disbandToString(path);
|
||||
case 'A':
|
||||
return ProvinceCheck.buildArmyToString(path);
|
||||
case 'F':
|
||||
return ProvinceCheck.buildFleetToString(path);
|
||||
default:
|
||||
throw new Error('Unable to build order from path ' + JSON.stringify(path));
|
||||
}
|
||||
}
|
||||
|
||||
static holdToString(path) {
|
||||
assertLength(2, path.length);
|
||||
return `${path[1]} ${path[0]}`;
|
||||
}
|
||||
|
||||
static moveToString(path) {
|
||||
assertLength(3, path.length);
|
||||
return `${path[1]} - ${path[2]}`;
|
||||
}
|
||||
|
||||
static moveViaToString(path) {
|
||||
return ProvinceCheck.moveToString(path) + ' VIA';
|
||||
}
|
||||
|
||||
static supportToString(path) {
|
||||
assertLength(4, path.length);
|
||||
let order = `${path[1]} ${path[0]} ${path[2]}`;
|
||||
if (path[2].substr(2) !== path[3])
|
||||
order += ` - ${path[3]}`;
|
||||
return order;
|
||||
}
|
||||
|
||||
static convoyToString(path) {
|
||||
assertLength(4, path.length);
|
||||
return `${path[1]} ${path[0]} ${path[2]} - ${path[3]}`;
|
||||
}
|
||||
|
||||
static retreatToString(path) {
|
||||
assertLength(3, path.length);
|
||||
return `${path[1]} ${path[0]} ${path[2]}`;
|
||||
}
|
||||
|
||||
static disbandToString(path) {
|
||||
assertLength(2, path.length);
|
||||
return `${path[1]} ${path[0]}`;
|
||||
}
|
||||
|
||||
static buildArmyToString(path) {
|
||||
assertLength(2, path.length);
|
||||
return `${path[0]} ${path[1]} B`;
|
||||
}
|
||||
|
||||
static buildFleetToString(path) {
|
||||
assertLength(2, path.length);
|
||||
return `${path[0]} ${path[1]} B`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const ORDER_BUILDER = {
|
||||
H: {
|
||||
name: 'hold (H)',
|
||||
steps: [ProvinceCheck.occupied]
|
||||
},
|
||||
M: {
|
||||
name: 'move (M)',
|
||||
steps: [ProvinceCheck.occupied, ProvinceCheck.any]
|
||||
},
|
||||
V: {
|
||||
name: 'move VIA (V)',
|
||||
steps: [ProvinceCheck.occupied, ProvinceCheck.any]
|
||||
},
|
||||
S: {
|
||||
name: 'support (S)',
|
||||
steps: [ProvinceCheck.occupied, ProvinceCheck.occupiedByAny, ProvinceCheck.any]
|
||||
},
|
||||
C: {
|
||||
name: 'convoy (C)',
|
||||
steps: [ProvinceCheck.occupied, ProvinceCheck.occupiedByAny, ProvinceCheck.any]
|
||||
},
|
||||
R: {
|
||||
name: 'retreat (R)',
|
||||
steps: [ProvinceCheck.retreated, ProvinceCheck.any]
|
||||
},
|
||||
D: {
|
||||
name: 'disband (D)',
|
||||
steps: [ProvinceCheck.present]
|
||||
},
|
||||
A: {
|
||||
name: 'build army (A)',
|
||||
steps: [ProvinceCheck.any]
|
||||
},
|
||||
F: {
|
||||
name: 'build fleet (F)',
|
||||
steps: [ProvinceCheck.any]
|
||||
},
|
||||
};
|
||||
|
||||
export const POSSIBLE_ORDERS = {
|
||||
// Allowed orders for movement phase step.
|
||||
M: ['H', 'M', 'V', 'S', 'C'],
|
||||
// Allowed orders for retreat phase step.
|
||||
R: ['R', 'D'],
|
||||
// Allowed orders for adjustment phase step.
|
||||
A: ['D', 'A', 'F'],
|
||||
sorting: {
|
||||
M: {M: 0, V: 1, S: 2, C: 3, H: 4},
|
||||
R: {R: 0, D: 1},
|
||||
A: {A: 0, F: 1, D: 2}
|
||||
},
|
||||
sortOrderTypes: function (arr, phaseType) {
|
||||
arr.sort((a, b) => POSSIBLE_ORDERS.sorting[phaseType][a] - POSSIBLE_ORDERS.sorting[phaseType][b]);
|
||||
}
|
||||
};
|
||||
|
||||
export function extendOrderBuilding(powerName, orderType, currentOrderPath, location, onBuilding, onBuilt, onError) {
|
||||
const selectedPath = [orderType].concat(currentOrderPath, location);
|
||||
if (selectedPath.length - 1 < ORDER_BUILDER[orderType].steps.length) {
|
||||
// Checker OK, update.
|
||||
onBuilding(powerName, selectedPath);
|
||||
} else {
|
||||
try {
|
||||
// Order created.
|
||||
const orderString = ProvinceCheck.buildOrder(selectedPath);
|
||||
onBuilt(powerName, orderString);
|
||||
} catch (error) {
|
||||
onError(error.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
65
diplomacy/web/src/gui/utils/power_view.jsx
Normal file
65
diplomacy/web/src/gui/utils/power_view.jsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// ==============================================================================
|
||||
// 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 {STRINGS} from "../../diplomacy/utils/strings";
|
||||
import React from "react";
|
||||
|
||||
function getName(power) {
|
||||
if (power.isEliminated())
|
||||
return <span className="dummy"><em><s>{power.name.toLowerCase()}</s> (eliminated)</em></span>;
|
||||
return power.name;
|
||||
}
|
||||
|
||||
function getController(power) {
|
||||
if (power.isEliminated())
|
||||
return <span className="dummy"><em>N/A</em></span>;
|
||||
const controller = power.getController();
|
||||
return <span className={controller === STRINGS.DUMMY ? 'dummy' : 'controller'}>{controller}</span>;
|
||||
}
|
||||
|
||||
function getOrderFlag(power) {
|
||||
if (power.isEliminated() || power.game.orderableLocations[power.name].length === 0)
|
||||
return <span className="dummy"><em>N/A</em></span>;
|
||||
const value = ['no', 'empty', 'yes'][power.order_is_set];
|
||||
return <span className={value}>{value}</span>;
|
||||
}
|
||||
|
||||
function getWaitFlag(power) {
|
||||
if (power.isEliminated())
|
||||
return <span className="dummy"><em>N/A</em></span>;
|
||||
return <span className={power.wait ? 'wait' : 'no-wait'}>{power.wait ? 'yes' : 'no'}</span>;
|
||||
}
|
||||
|
||||
const GETTERS = {
|
||||
name: getName,
|
||||
controller: getController,
|
||||
order_is_set: getOrderFlag,
|
||||
wait: getWaitFlag
|
||||
};
|
||||
|
||||
export class PowerView {
|
||||
constructor(power) {
|
||||
this.power = power;
|
||||
}
|
||||
|
||||
static wrap(power) {
|
||||
return new PowerView(power);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return GETTERS[key](this.power);
|
||||
}
|
||||
}
|
||||
135
diplomacy/web/src/gui/utils/province.js
Normal file
135
diplomacy/web/src/gui/utils/province.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
const ProvinceType = {
|
||||
WATER: 'WATER',
|
||||
COAST: 'COAST',
|
||||
PORT: 'PORT',
|
||||
LAND: 'LAND'
|
||||
};
|
||||
|
||||
export class Province {
|
||||
constructor(name, type, isSupplyCenter) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.coasts = {};
|
||||
this.parent = null;
|
||||
this.neighbors = {};
|
||||
this.isSupplyCenter = isSupplyCenter;
|
||||
this.controller = null; // null or power name.
|
||||
this.controlType = null; // null, C (center), I (influence) or U (unit).
|
||||
this.unit = null; // null, A or F
|
||||
this.retreatController = null;
|
||||
this.retreatUnit = null; // null or `{unit type} {loc}`
|
||||
this.aliases = [];
|
||||
}
|
||||
|
||||
compareControlType(a, b) {
|
||||
const controlTypeLevels = {C: 0, I: 1, U: 2};
|
||||
return controlTypeLevels[a] - controlTypeLevels[b];
|
||||
}
|
||||
|
||||
__set_controller(controller, controlType) {
|
||||
this.controller = controller;
|
||||
this.controlType = controlType;
|
||||
for (let coast of Object.values(this.coasts))
|
||||
coast.setController(controller, controlType);
|
||||
}
|
||||
|
||||
setController(controller, controlType) {
|
||||
if (!['C', 'I', 'U'].includes(controlType))
|
||||
throw new Error(`Invalid province control type (${controlType}), expected 'C', 'I' or 'U'.`);
|
||||
if (this.controller && this.controller !== controller) {
|
||||
const controlTypeComparison = this.compareControlType(controlType, this.controlType);
|
||||
if (controlTypeComparison === 0)
|
||||
throw new Error(`Found 2 powers (${this.controller}, ${controller}) trying to control same province ` +
|
||||
`(${this.name}) with same control type (${controlType} VS ${this.controlType}).`);
|
||||
if (controlTypeComparison > 0)
|
||||
this.__set_controller(controller, controlType);
|
||||
} else
|
||||
this.__set_controller(controller, controlType);
|
||||
}
|
||||
|
||||
setCoasts(provinces) {
|
||||
const name = this.name.toUpperCase();
|
||||
for (let entry of Object.entries(provinces)) {
|
||||
const pieces = entry[0].split(/[^A-Za-z0-9]+/);
|
||||
if (pieces.length > 1 && pieces[0].toUpperCase() === name) {
|
||||
this.coasts[entry[0]] = entry[1];
|
||||
entry[1].parent = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNeighbors(neighborProvinces) {
|
||||
for (let province of neighborProvinces)
|
||||
this.neighbors[province.name] = province;
|
||||
}
|
||||
|
||||
getLocationNames() {
|
||||
const arr = Object.keys(this.coasts);
|
||||
arr.splice(0, 0, this.name);
|
||||
return arr;
|
||||
}
|
||||
|
||||
getOccupied(powerName) {
|
||||
if (!this.controller)
|
||||
return null;
|
||||
if (powerName && this.controller !== powerName)
|
||||
return null;
|
||||
if (this.unit)
|
||||
return this;
|
||||
for (let coast of Object.values(this.coasts))
|
||||
if (coast.unit)
|
||||
return coast;
|
||||
return null;
|
||||
}
|
||||
|
||||
getRetreated(powerName) {
|
||||
if (this.retreatController === powerName)
|
||||
return this;
|
||||
for (let coast of Object.values(this.coasts))
|
||||
if (coast.retreatController === powerName)
|
||||
return coast;
|
||||
return null;
|
||||
}
|
||||
|
||||
isCoast() {
|
||||
return this.type === ProvinceType.COAST;
|
||||
}
|
||||
|
||||
isWater() {
|
||||
return this.type === ProvinceType.WATER;
|
||||
}
|
||||
|
||||
_id(id) {
|
||||
return `_${id.toLowerCase()}`;
|
||||
}
|
||||
|
||||
getID(identifiers) {
|
||||
let id = this._id(this.name);
|
||||
if (!identifiers[id]) {
|
||||
for (let alias of this.aliases) {
|
||||
id = this._id(alias);
|
||||
if (identifiers[id])
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!identifiers[id] && this.isCoast())
|
||||
id = this.parent.getID(identifiers);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
34
diplomacy/web/src/gui/utils/saveGameToDisk.js
Normal file
34
diplomacy/web/src/gui/utils/saveGameToDisk.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
.game-creation-wizard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.game-creation-wizard .row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.game-creation-wizard .fancy-box {
|
||||
width: 75vw;
|
||||
height: 75vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.game-creation-wizard .fancy-content {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.panel-choose-map {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-choose-map .map-list {
|
||||
border: 1px solid gray;
|
||||
height: 100%;
|
||||
}
|
||||
126
diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js
Normal file
126
diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// ==============================================================================
|
||||
// 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 {Panels} from "./panelList";
|
||||
import {PanelChooseMap} from "./panelChooseMap";
|
||||
import {PanelChoosePlayers} from "./panelChoosePlayers";
|
||||
import {PanelChoosePower} from "./panelChoosePower";
|
||||
import {PanelChooseSettings} from "./panelChooseSettings";
|
||||
import {Maps} from "./mapList";
|
||||
import {UTILS} from "../../../diplomacy/utils/utils";
|
||||
import './gameCreationWizard.css';
|
||||
|
||||
export class GameCreationWizard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
panel: Panels.CHOOSE_MAP,
|
||||
game_id: UTILS.createGameID(this.props.username),
|
||||
power_name: null,
|
||||
n_controls: -1,
|
||||
deadline: 0,
|
||||
registration_password: '',
|
||||
|
||||
map: Maps[0],
|
||||
no_press: false
|
||||
};
|
||||
this.backward = this.backward.bind(this);
|
||||
this.forward = this.forward.bind(this);
|
||||
this.updateParams = this.updateParams.bind(this);
|
||||
}
|
||||
|
||||
updateParams(params) {
|
||||
this.setState(params);
|
||||
}
|
||||
|
||||
goToPanel(panelID) {
|
||||
if (panelID < Panels.CHOOSE_MAP)
|
||||
this.props.onCancel();
|
||||
else if (panelID > Panels.CHOOSE_SETTINGS) {
|
||||
const rules = ['POWER_CHOICE'];
|
||||
if (this.state.no_press)
|
||||
rules.push('NO_PRESS');
|
||||
if (!this.state.deadline) {
|
||||
rules.push('NO_DEADLINE');
|
||||
rules.push('REAL_TIME');
|
||||
}
|
||||
this.props.onSubmit({
|
||||
game_id: this.state.game_id,
|
||||
map_name: this.state.map.name,
|
||||
power_name: this.state.power_name,
|
||||
n_controls: this.state.n_controls,
|
||||
deadline: this.state.deadline,
|
||||
registration_password: this.state.registration_password || null,
|
||||
rules: rules
|
||||
});
|
||||
} else
|
||||
this.setState({panel: panelID, registration_password: ''});
|
||||
}
|
||||
|
||||
backward(step) {
|
||||
this.goToPanel(this.state.panel - (step ? step : 1));
|
||||
}
|
||||
|
||||
forward(step) {
|
||||
this.goToPanel(this.state.panel + (step ? step : 1));
|
||||
}
|
||||
|
||||
renderPanel() {
|
||||
switch (this.state.panel) {
|
||||
case Panels.CHOOSE_MAP:
|
||||
return <PanelChooseMap forward={this.forward}
|
||||
params={this.state}
|
||||
onUpdateParams={this.updateParams}
|
||||
cancel={this.props.onCancel}/>;
|
||||
case Panels.CHOOSE_PLAYERS:
|
||||
return <PanelChoosePlayers backward={this.backward}
|
||||
forward={this.forward}
|
||||
onUpdateParams={this.updateParams}
|
||||
nbPowers={this.props.availableMaps[this.state.map.name].powers.length}
|
||||
cancel={this.props.onCancel}/>;
|
||||
case Panels.CHOOSE_POWER:
|
||||
return <PanelChoosePower backward={this.backward}
|
||||
forward={this.forward}
|
||||
onUpdateParams={this.updateParams}
|
||||
powers={this.props.availableMaps[this.state.map.name].powers}
|
||||
cancel={this.props.onCancel}/>;
|
||||
case Panels.CHOOSE_SETTINGS:
|
||||
return <PanelChooseSettings backward={this.backward}
|
||||
forward={this.forward}
|
||||
onUpdateParams={this.updateParams}
|
||||
username={this.props.username}
|
||||
params={this.state}
|
||||
cancel={this.props.onCancel}/>;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="game-creation-wizard">{this.renderPanel()}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GameCreationWizard.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
availableMaps: PropTypes.object.isRequired,
|
||||
username: PropTypes.string.isRequired
|
||||
};
|
||||
63
diplomacy/web/src/gui/wizards/gameCreation/mapList.js
Normal file
63
diplomacy/web/src/gui/wizards/gameCreation/mapList.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
class VariantInfo {
|
||||
constructor(variantName, variantTitle) {
|
||||
this.name = variantName;
|
||||
this.title = variantTitle;
|
||||
this.map = null;
|
||||
}
|
||||
|
||||
svgName() {
|
||||
return this.map.name;
|
||||
}
|
||||
}
|
||||
|
||||
class MapInfo {
|
||||
constructor(mapName, mapTitle, variants) {
|
||||
this.name = mapName;
|
||||
this.title = mapTitle;
|
||||
this.variants = null;
|
||||
if (variants) {
|
||||
this.variants = [];
|
||||
for (let variant of variants) {
|
||||
variant.map = this;
|
||||
this.variants.push(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svgName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
export const Maps = [
|
||||
new MapInfo('standard', 'Standard', [
|
||||
new VariantInfo('standard', 'Default'),
|
||||
new VariantInfo('standard_age_of_empires', 'Age of empires'),
|
||||
new VariantInfo('standard_age_of_empires_2', 'Age of empires II'),
|
||||
new VariantInfo('standard_fleet_rome', 'Fleet at Rome'),
|
||||
new VariantInfo('standard_france_austria', 'France VS Austria'),
|
||||
new VariantInfo('standard_germany_italy', 'Germany VS Italy')
|
||||
]),
|
||||
new MapInfo('ancmed', 'Ancient Mediterranean', [
|
||||
new VariantInfo('ancmed', 'Default'),
|
||||
new VariantInfo('ancmed_age_of_empires', 'Age of empires')
|
||||
]),
|
||||
new MapInfo('modern', 'Modern'),
|
||||
new MapInfo('pure', 'Pure'),
|
||||
];
|
||||
113
diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js
Normal file
113
diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// ==============================================================================
|
||||
// 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 {Maps} from "./mapList";
|
||||
import {FancyBox} from "../../components/fancyBox";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class PanelChooseMap extends React.Component {
|
||||
render() {
|
||||
const mapImg = require(`../../../diplomacy/maps/svg/${this.props.params.map.svgName()}.svg`);
|
||||
const mapEntries = [];
|
||||
let count = 0;
|
||||
for (let mapInfo of Maps) {
|
||||
++count;
|
||||
if (!mapInfo.variants) {
|
||||
mapEntries.push(
|
||||
<div key={count} className="mb-1 d-flex flex-row">
|
||||
<button type="button"
|
||||
className="btn btn-secondary btn-sm flex-grow-1 mr-1"
|
||||
onMouseOver={() => this.props.onUpdateParams({map: mapInfo})}
|
||||
onClick={() => this.props.forward()}>
|
||||
{mapInfo.title}
|
||||
</button>
|
||||
<button type="button" className="btn btn-outline-secondary btn-sm" disabled={true}>
|
||||
<strong>+</strong>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const dropDownID = `collapse-${count}-${mapInfo.name}`;
|
||||
const variants = mapInfo.variants.slice();
|
||||
const defaultVariant = variants[0];
|
||||
mapEntries.push(
|
||||
<div key={count}>
|
||||
<div className="mb-1 d-flex flex-row">
|
||||
<button type="button"
|
||||
className="btn btn-secondary btn-sm flex-grow-1 mr-1"
|
||||
onMouseOver={() => this.props.onUpdateParams({map: defaultVariant})}
|
||||
onClick={() => this.props.forward()}>
|
||||
{mapInfo.title} ({defaultVariant.title})
|
||||
</button>
|
||||
<button type="button"
|
||||
className="btn btn-outline-secondary btn-sm collapsed"
|
||||
data-toggle="collapse"
|
||||
data-target={`#${dropDownID}`}
|
||||
aria-expanded={false}
|
||||
aria-controls={dropDownID}>
|
||||
<span className="unroll"><strong>+</strong></span>
|
||||
<span className="roll"><strong>-</strong></span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="collapse" id={dropDownID}>
|
||||
<div>
|
||||
{(() => {
|
||||
const views = [];
|
||||
for (let i = 1; i < variants.length; ++i) {
|
||||
const variantInfo = variants[i];
|
||||
views.push(
|
||||
<div key={variantInfo.name} className="mb-1">
|
||||
<button type="button"
|
||||
className="btn btn-outline-secondary btn-sm btn-block"
|
||||
onMouseOver={() => this.props.onUpdateParams({map: variantInfo})}
|
||||
onClick={() => this.props.forward()}>
|
||||
{variantInfo.title}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return views;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<FancyBox title={'Choose a map'} onClose={this.props.cancel}>
|
||||
<div className="row panel-choose-map">
|
||||
<div className="col-md">
|
||||
<div className="map-list p-1 ml-0 ml-sm-1">
|
||||
{mapEntries}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md">
|
||||
<img className="img-fluid" src={mapImg} alt={this.props.params.map.title}/>
|
||||
</div>
|
||||
</div>
|
||||
</FancyBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PanelChooseMap.propTypes = {
|
||||
forward: PropTypes.func.isRequired,
|
||||
cancel: PropTypes.func.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
onUpdateParams: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// ==============================================================================
|
||||
// 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 {FancyBox} from "../../components/fancyBox";
|
||||
import PropTypes from "prop-types";
|
||||
import Octicon, {ArrowLeft} from "@primer/octicons-react";
|
||||
|
||||
export class PanelChoosePlayers extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FancyBox title={'Number of human players'} onClose={this.props.cancel}>
|
||||
<div className="row">
|
||||
<div className="col-sm">
|
||||
<button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
|
||||
this.props.onUpdateParams({n_controls: 0});
|
||||
this.props.forward(2);
|
||||
}}>None - just bots
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-sm">
|
||||
<button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
|
||||
this.props.onUpdateParams({n_controls: this.props.nbPowers});
|
||||
this.props.forward();
|
||||
}}>All humans - no bots
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="d-flex flex-row justify-content-center my-2">
|
||||
{(() => {
|
||||
const choice = [];
|
||||
for (let i = 0; i < this.props.nbPowers; ++i) {
|
||||
choice.push(
|
||||
<button key={i} type="button"
|
||||
className={`btn btn-secondary btn-sm flex-grow-1 ${i === 0 ? '' : 'ml-sm-1'}`}
|
||||
onClick={() => {
|
||||
this.props.onUpdateParams({n_controls: i + 1});
|
||||
this.props.forward();
|
||||
}}>
|
||||
{i + 1}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return choice;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<button type="button" className="btn btn-secondary btn-sm px-3"
|
||||
onClick={() => this.props.backward()}>
|
||||
<Octicon icon={ArrowLeft}/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</FancyBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PanelChoosePlayers.propTypes = {
|
||||
backward: PropTypes.func.isRequired,
|
||||
forward: PropTypes.func.isRequired,
|
||||
cancel: PropTypes.func.isRequired,
|
||||
onUpdateParams: PropTypes.func.isRequired,
|
||||
nbPowers: PropTypes.number.isRequired
|
||||
};
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// ==============================================================================
|
||||
// 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 {FancyBox} from "../../components/fancyBox";
|
||||
import PropTypes from "prop-types";
|
||||
import Octicon, {ArrowLeft} from "@primer/octicons-react";
|
||||
|
||||
export class PanelChoosePower extends React.Component {
|
||||
render() {
|
||||
this.props.powers.sort();
|
||||
return (
|
||||
<FancyBox title={'Choose your power'} onClose={this.props.cancel}>
|
||||
<div className="row">
|
||||
<div className="col-sm">
|
||||
<button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
|
||||
this.props.onUpdateParams({power_name: null});
|
||||
this.props.forward();
|
||||
}}>I just want to observe
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-sm">
|
||||
<button type="button" className="btn btn-secondary btn-sm btn-block inline" onClick={() => {
|
||||
const powerName = this.props.powers[Math.floor(Math.random() * this.props.powers.length)];
|
||||
this.props.onUpdateParams({power_name: powerName});
|
||||
this.props.forward();
|
||||
}}>Choose randomly for me
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="d-flex flex-row justify-content-center my-2">
|
||||
{(() => {
|
||||
const choice = [];
|
||||
for (let i = 0; i < this.props.powers.length; ++i) {
|
||||
choice.push(
|
||||
<button key={i} type="button"
|
||||
className={`btn btn-secondary btn-sm flex-grow-1 ${i === 0 ? '' : 'ml-sm-1'}`}
|
||||
onClick={() => {
|
||||
this.props.onUpdateParams({power_name: this.props.powers[i]});
|
||||
this.props.forward();
|
||||
}}>
|
||||
{this.props.powers[i]}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return choice;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<button type="button" className="btn btn-secondary btn-sm px-3"
|
||||
onClick={() => this.props.backward()}>
|
||||
<Octicon icon={ArrowLeft}/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</FancyBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PanelChoosePower.propTypes = {
|
||||
backward: PropTypes.func.isRequired,
|
||||
forward: PropTypes.func.isRequired,
|
||||
cancel: PropTypes.func.isRequired,
|
||||
onUpdateParams: PropTypes.func.isRequired,
|
||||
powers: PropTypes.arrayOf(PropTypes.string).isRequired
|
||||
};
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// ==============================================================================
|
||||
// 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 {FancyBox} from "../../components/fancyBox";
|
||||
import PropTypes from "prop-types";
|
||||
import {UTILS} from "../../../diplomacy/utils/utils";
|
||||
import Octicon, {ArrowLeft} from "@primer/octicons-react";
|
||||
|
||||
const DEADLINES = [
|
||||
[0, '(no deadline)'],
|
||||
[60, '1 min'],
|
||||
[60 * 5, '5 min'],
|
||||
[60 * 30, '30 min'],
|
||||
[60 * 60 * 2, '2 hrs'],
|
||||
[60 * 60 * 24, '24 hrs'],
|
||||
];
|
||||
|
||||
export class PanelChooseSettings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onCheckNoPress = this.onCheckNoPress.bind(this);
|
||||
this.onSelectDeadline = this.onSelectDeadline.bind(this);
|
||||
this.onSetRegistrationPassword = this.onSetRegistrationPassword.bind(this);
|
||||
this.onSetGameID = this.onSetGameID.bind(this);
|
||||
}
|
||||
|
||||
onCheckNoPress(event) {
|
||||
this.props.onUpdateParams({no_press: event.target.checked});
|
||||
}
|
||||
|
||||
onSelectDeadline(event) {
|
||||
this.props.onUpdateParams({deadline: parseInt(event.target.value)});
|
||||
}
|
||||
|
||||
onSetRegistrationPassword(event) {
|
||||
this.props.onUpdateParams({registration_password: event.target.value});
|
||||
}
|
||||
|
||||
onSetGameID(event) {
|
||||
let gameID = event.target.value;
|
||||
if (!gameID)
|
||||
gameID = UTILS.createGameID(this.props.username);
|
||||
this.props.onUpdateParams({game_id: gameID});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FancyBox title={'Other settings'} onClose={this.props.cancel}>
|
||||
<div>
|
||||
<form>
|
||||
<div className="form-group row align-items-center mb-2">
|
||||
<label className="col-md col-form-label" htmlFor="deadline">Deadline</label>
|
||||
<div className="col-md">
|
||||
<select id="deadline" className="custom-select custom-select-sm"
|
||||
value={this.props.params.deadline}
|
||||
onChange={this.onSelectDeadline}>
|
||||
{DEADLINES.map((deadline, index) => (
|
||||
<option key={index} value={deadline[0]}>{deadline[1]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group row mb-2">
|
||||
<label className="col-md col-form-label" htmlFor="registration-password">Login
|
||||
password</label>
|
||||
<div className="col-md">
|
||||
<input type="password" className="form-control form-control-sm"
|
||||
id="registration-password"
|
||||
value={this.props.params.registration_password}
|
||||
onChange={this.onSetRegistrationPassword} placeholder="(no password)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group row mb-2">
|
||||
<label className="col-md col-form-label" htmlFor="game-id">Game ID</label>
|
||||
<div className="col-md">
|
||||
<input type="text" className="form-control form-control-sm"
|
||||
id="game-id"
|
||||
value={this.props.params.game_id}
|
||||
onChange={this.onSetGameID}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="custom-control custom-checkbox mb-5">
|
||||
<input type="checkbox" className="custom-control-input" id="no-press"
|
||||
checked={this.props.params.no_press} onChange={this.onCheckNoPress}/>
|
||||
<label className="custom-control-label" htmlFor="no-press">No messages allowed</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm">
|
||||
<button type="button" className="btn btn-secondary btn-sm btn-block"
|
||||
onClick={() => this.props.backward()}>
|
||||
<Octicon icon={ArrowLeft}/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-sm">
|
||||
<button type="button" className="btn btn-success btn-sm btn-block inline"
|
||||
onClick={() => this.props.forward()}>
|
||||
<strong>create the game</strong>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</FancyBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PanelChooseSettings.propTypes = {
|
||||
backward: PropTypes.func.isRequired,
|
||||
forward: PropTypes.func.isRequired,
|
||||
cancel: PropTypes.func.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
onUpdateParams: PropTypes.func.isRequired,
|
||||
username: PropTypes.string.isRequired
|
||||
};
|
||||
22
diplomacy/web/src/gui/wizards/gameCreation/panelList.js
Normal file
22
diplomacy/web/src/gui/wizards/gameCreation/panelList.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// ==============================================================================
|
||||
// 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/>.
|
||||
// ==============================================================================
|
||||
export const Panels = {
|
||||
CHOOSE_MAP: 0,
|
||||
CHOOSE_PLAYERS: 1,
|
||||
CHOOSE_POWER: 2,
|
||||
CHOOSE_SETTINGS: 3
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue