From 891fb62a77b9a86f2bc71cc02a82089412982b2f Mon Sep 17 00:00:00 2001 From: notoraptor Date: Thu, 1 Aug 2019 15:53:23 -0400 Subject: Refactored SVG map into a React component - Create link to diplomacy map folder into web/src/diplomacy/maps - Remove old web/src/gui/map folder. - [web] Handle click only on current map. - [web/game] Remove useless `wait` state. - Remove unused nodejs modules. - [web] Use queue to handle game notifications in sequential order. - Make all calls to setState() asynchronous in Page and ContentGame components. - Make sure notifications are handled in the order in which they come. --- .../web/src/gui/maps/standard/SvgStandard.css | 106 ++ diplomacy/web/src/gui/maps/standard/SvgStandard.js | 743 ++++++++++++ .../web/src/gui/maps/standard/SvgStandardExtra.js | 1279 ++++++++++++++++++++ diplomacy/web/src/gui/maps/standard/build.js | 53 + diplomacy/web/src/gui/maps/standard/common.js | 75 ++ diplomacy/web/src/gui/maps/standard/convoy.js | 98 ++ diplomacy/web/src/gui/maps/standard/disband.js | 51 + diplomacy/web/src/gui/maps/standard/hold.js | 45 + diplomacy/web/src/gui/maps/standard/move.js | 69 ++ .../web/src/gui/maps/standard/supplyCenter.js | 40 + diplomacy/web/src/gui/maps/standard/supportHold.js | 73 ++ diplomacy/web/src/gui/maps/standard/supportMove.js | 57 + diplomacy/web/src/gui/maps/standard/unit.js | 45 + 13 files changed, 2734 insertions(+) create mode 100644 diplomacy/web/src/gui/maps/standard/SvgStandard.css create mode 100644 diplomacy/web/src/gui/maps/standard/SvgStandard.js create mode 100644 diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js create mode 100644 diplomacy/web/src/gui/maps/standard/build.js create mode 100644 diplomacy/web/src/gui/maps/standard/common.js create mode 100644 diplomacy/web/src/gui/maps/standard/convoy.js create mode 100644 diplomacy/web/src/gui/maps/standard/disband.js create mode 100644 diplomacy/web/src/gui/maps/standard/hold.js create mode 100644 diplomacy/web/src/gui/maps/standard/move.js create mode 100644 diplomacy/web/src/gui/maps/standard/supplyCenter.js create mode 100644 diplomacy/web/src/gui/maps/standard/supportHold.js create mode 100644 diplomacy/web/src/gui/maps/standard/supportMove.js create mode 100644 diplomacy/web/src/gui/maps/standard/unit.js (limited to 'diplomacy/web/src/gui/maps') diff --git a/diplomacy/web/src/gui/maps/standard/SvgStandard.css b/diplomacy/web/src/gui/maps/standard/SvgStandard.css new file mode 100644 index 0000000..3aeebaa --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/SvgStandard.css @@ -0,0 +1,106 @@ +/* text */ + svg { font-size: 100% } + .titletext {text-anchor:middle; stroke-width:0.3; font-family:sans-serif; font-size:0.7em; stroke:black; fill:black;} + .provtext {text-anchor:middle; stroke-width:0.3; font-family:sans-serif; font-size:0.7em; stroke:black; fill:black;} + .labeltext {stroke-width:0.1; stroke:black; fill:black;} + .unordered {fill:red; stroke:black; stroke-width:1; fill-opacity:0.90;} + .labeltext24 {text-anchor:middle; stroke-width:0.1; stroke:black; fill:black; font-family:serif,sans-serif; font-style:italic; font-size:1.4em;} + .labeltext18 {text-anchor:middle; stroke-width:0.1; stroke:black; fill:black; font-family:serif,sans-serif; font-style:italic; font-size:1.1em;} + .fulllabeltext {font-family:serif,sans-serif; font-style:italic; font-size:1.2em; fill:black; stroke:black;} + .currentnotetext {font-family:serif,sans-serif; font-size:1.5em; fill:black; stroke:black;} + .currentnoterect {fill:#c5dfea;} + .currentphasetext {font-family:serif,sans-serif; font-size:2.5em; fill:black; stroke:black;} + + .labeltext24 text {cursor:default;} + + /* NB: this style is not yet used. */ + .labeltext24 text.allowed { + font-weight:bold; + fill:blue; + font-size: 1.5em; + stroke: red; + stroke-width:2; + text-decoration: underline; + background-color: green; + } + + /* map and object features */ + + .seapoly {stroke:#000000; stroke-width:1; fill:#B5DEF8} + .dashline {stroke:darkslateblue; stroke-width:3; stroke-linecap:round; stroke-dasharray:5,6;} + .impassable {fill:#353433; stroke:#000000; stroke-width:1} + .sealine {stroke:#B5DEFF; stroke-width:3;} + + /* invisible click rects fill:none does not work */ + + .invisibleContent {stroke:#000000; fill:#000000; fill-opacity:0.0; opacity:0.0} + + /* default region coloring, by power */ + + .provinceRed {fill:url(#patternRed)} + .provinceBrown {fill:url(#patternBrown)} + .provinceGreen {fill:url(#patternGreen)} + .provinceBlack {fill:url(#patternBlack)} + .provinceBlue {fill:url(#patternBlue)} + + .nopower {fill:antiquewhite; stroke:#000000; stroke-width:1} + .water {fill:#c5dfea; stroke:#000000; stroke-width:1} + + .neutral {fill:lightgray; stroke:#000000; stroke-width:1} + + .austria {fill:#c48f85; stroke:#000000; stroke-width:1} + .england {fill:darkviolet; stroke:#000000; stroke-width:1} + .france {fill:royalblue; stroke:#000000; stroke-width:1} + .germany {fill:#a08a75; stroke:#000000; stroke-width:1} + .italy {fill:forestgreen; stroke:#000000; stroke-width:1} + .russia {fill:#757d91; stroke:#000000; stroke-width:1} + .turkey {fill:#b9a61c; stroke:#000000; stroke-width:1} + + /* unit colors, by power note that underscores are not supported */ + + .unitaustria {fill:red; fill-opacity:0.85} + .unitengland {fill:mediumpurple; fill-opacity:0.85} + .unitfrance {fill:deepskyblue; fill-opacity:0.85} + .unitgermany {fill:dimgray; fill-opacity:0.85} + .unititaly {fill:olive; fill-opacity:0.85} + .unitrussia {fill:white; fill-opacity:1.0} + .unitturkey {fill:yellow; fill-opacity:0.85} + + /* supply center styles */ + + .scnopower {fill:black; stroke:black;} + .scaustria {fill:black; stroke:black;} + .scengland {fill:black; stroke:black;} + .scfrance {fill:black; stroke:black;} + .scgermany {fill:black; stroke:black;} + .scitaly {fill:black; stroke:black;} + .scrussia {fill:black; stroke:black;} + .scturkey {fill:black; stroke:black;} + + /* order drawing styles, stroke and fill colors should not be specified */ + + .defaultorder {stroke-width:6; fill:none;} + .supportorder {stroke-width:6; fill:none; stroke-dasharray:5,5;} + .convoyorder {stroke-dasharray:15,5; stroke-width:6; fill:none;} + + .shadoworder {stroke-width:10; fill:none; stroke:black;} + .shadowdash {stroke-width:10; fill:none; stroke:black; opacity:0.45;} + + .varwidthorder {fill:none;} + .varwidthshadow {fill:none; stroke:black;} + + /* Symbol private styles. Always start with "sym" to avoid name collisions! */ + + .symBuildShadow {fill:none;stroke:black;opacity:0.5;stroke-width:7;} + .symBuild {stroke:yellow;stroke-width:7;fill:none;} + .symRemove {stroke:red;stroke-width:1;fill:none;} + + .symShadow {stroke:black;fill:black;stroke-width:1;opacity:0.40;} + .symDislodgedShadow {stroke:red;fill:red;stroke-width:1;opacity:0.50;} + .symDislodgedBorder {stroke:red;stroke-width:3%;} + + .symDarkener {fill:black;opacity:0.45;fill-opacity:0.45;} + .symCenterHub {fill:black; stroke:black; opacity:0.60; stroke-width:0.5px;} + .symBorder {stroke:black;stroke-width:3%;} + .symThinBorder {stroke:black;stroke-width:0.4;} + .symSilhouette {stroke:black;fill:black;stroke-width:1;} diff --git a/diplomacy/web/src/gui/maps/standard/SvgStandard.js b/diplomacy/web/src/gui/maps/standard/SvgStandard.js new file mode 100644 index 0000000..87c6217 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/SvgStandard.js @@ -0,0 +1,743 @@ +// ============================================================================== +// 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 . +// ============================================================================== +/** Generated using svg_to_react.py with parameters: +Namespace(actionable=['MouseLayer'], input='src/diplomacy/maps/svg/standard.svg', name='SvgStandard', output='src/gui/maps/', remove=None) +**/ +import React from 'react'; +import PropTypes from 'prop-types'; +import "./SvgStandard.css"; +import {Game} from "../../../diplomacy/engine/game"; +import {MapData} from "../../utils/map_data"; +import {Unit} from "./unit"; +import {SupplyCenter} from "./supplyCenter"; +import {Hold} from "./hold"; +import {Move} from "./move"; +import {SupportMove} from "./supportMove"; +import {SupportHold} from "./supportHold"; +import {Convoy} from "./convoy"; +import {Build} from "./build"; +import {Disband} from "./disband"; +import {UTILS} from "../../../diplomacy/utils/utils"; +import {Diplog} from "../../../diplomacy/utils/diplog"; +import {extendOrderBuilding} from "../../utils/order_building"; + +function setInfluence(classes, mapData, loc, power_name) { + loc = loc.toUpperCase().substr(0, 3); + if (!['LAND', 'COAST'].includes(mapData.getProvince(loc).type)) + return; + const id = '_' + loc.toLowerCase(); + if (!classes.hasOwnProperty(id)) + throw new Error(`Unable to find SVG path for loc ${id}`); + classes[id] = power_name ? power_name.toLowerCase() : 'nopower'; +} + +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; +} + +function parseLocation(txt) { + if (txt.length > 2 && txt[1] === ' ' && ['A', 'F'].includes(txt[0])) + return txt.substr(2); + return txt; +} + +export class SvgStandard extends React.Component { + constructor(props) { + super(props); + this.onClick = this.onClick.bind(this); + this.onHover = this.onHover.bind(this); + } + onClick(event) { + if (this.props.orderBuilding) + return this.handleClickedID(getClickedID(event)); + } + onHover(event) { + return this.handleHoverID(getClickedID(event)); + } + handleClickedID(id) { + const orderBuilding = this.props.orderBuilding; + if (!orderBuilding.builder) + return this.props.onError('No orderable locations.'); + const province = this.props.mapData.getProvince(id); + if (!province) + return; + + const stepLength = orderBuilding.builder.steps.length; + if (orderBuilding.path.length >= stepLength) + throw new Error(`Order building: current steps count (${orderBuilding.path.length}) should be less than` + + ` expected steps count (${stepLength}) (${orderBuilding.path.join(', ')}).`); + + const lengthAfterClick = orderBuilding.path.length + 1; + let validLocations = []; + const testedPath = [orderBuilding.type].concat(orderBuilding.path); + const value = UTILS.javascript.getTreeValue(this.props.game.ordersTree, testedPath); + if (value !== null) { + const checker = orderBuilding.builder.steps[lengthAfterClick - 1]; + try { + const possibleLocations = checker(province, orderBuilding.power); + for (let possibleLocation of possibleLocations) { + possibleLocation = possibleLocation.toUpperCase(); + if (value.includes(possibleLocation)) + validLocations.push(possibleLocation); + } + } catch (error) { + return this.props.onError(error); + } + } + if (!validLocations.length) + return this.props.onError('Disallowed.'); + + if (validLocations.length > 1 && orderBuilding.type === 'S' && orderBuilding.path.length >= 2) { + // We are building a support order and we have a multiple choice for a location. + // Let's check if next location to choose is a coast. To have a coast: + // - all possible locations must start with same 3 characters. + // - we expect at least province name in possible locations (e.g. 'SPA' for 'SPA/NC'). + // If we have a coast, we will remove province name from possible locations. + let isACoast = true; + let validLocationsNoProvinceName = []; + for (let i = 0; i < validLocations.length; ++i) { + let location = validLocations[i]; + if (i > 0) { + // Compare 3 first letters with previous location. + if (validLocations[i - 1].substring(0, 3).toUpperCase() !== validLocations[i].substring(0, 3).toUpperCase()) { + // No same prefix with previous location. We does not have a coast. + isACoast = false; + break; + } + } + if (location.length !== 3) + validLocationsNoProvinceName.push(location); + } + if (validLocations.length === validLocationsNoProvinceName.length) { + // We have not found province name. + isACoast = false; + } + if (isACoast) { + // We want to choose location in a coastal province. Let's remove province name. + validLocations = validLocationsNoProvinceName; + } + } + + if (validLocations.length > 1) { + if (this.props.onSelectLocation) { + return this.props.onSelectLocation(validLocations, orderBuilding.power, orderBuilding.type, orderBuilding.path); + } else { + Diplog.warn(`Forced to select first valid location.`); + validLocations = [validLocations[0]]; + } + } + let orderBuildingType = orderBuilding.type; + if (lengthAfterClick === stepLength && orderBuildingType === 'M') { + const moveOrderPath = ['M'].concat(orderBuilding.path, validLocations[0]); + const moveTypes = UTILS.javascript.getTreeValue(this.props.game.ordersTree, moveOrderPath); + if (moveTypes !== null) { + if (moveTypes.length === 2 && this.props.onSelectVia) { + // This move can be done either regularly or VIA a fleet. Let user choose. + return this.props.onSelectVia(validLocations[0], orderBuilding.power, orderBuilding.path); + } else { + orderBuildingType = moveTypes[0]; + } + } + } + extendOrderBuilding( + orderBuilding.power, orderBuildingType, orderBuilding.path, validLocations[0], + this.props.onOrderBuilding, this.props.onOrderBuilt, this.props.onError + ); + } + handleHoverID(id) { + if (this.props.onHover) { + const province = this.props.mapData.getProvince(id); + if (province) { + this.props.onHover(province.name, this.getRelatedOrders(province.name)); + } + } + } + getRelatedOrders(name) { + const orders = []; + if (this.props.orders) { + for (let powerOrders of Object.values(this.props.orders)) { + if (powerOrders) { + for (let order of powerOrders) { + const pieces = order.split(/ +/); + if (pieces[1].slice(0, 3) === name.toUpperCase().slice(0, 3)) + orders.push(order); + } + } + } + } + return orders; + } + getNeighbors(extraLocation) { + const selectedPath = [this.props.orderBuilding.type].concat(this.props.orderBuilding.path); + if (extraLocation) + selectedPath.push(extraLocation); + const possibleNeighbors = UTILS.javascript.getTreeValue(this.props.game.ordersTree, selectedPath); + const neighbors = possibleNeighbors ? possibleNeighbors.map(neighbor => parseLocation(neighbor)) : []; + return neighbors.length ? neighbors: null; + } + render() { + const classes = {"_ank":"nopower","_arm":"nopower","_con":"nopower","_mos":"nopower","_sev":"nopower","_stp":"nopower","_syr":"nopower","_ukr":"nopower","_lvn":"nopower","_war":"nopower","_pru":"nopower","_sil":"nopower","_ber":"nopower","_kie":"nopower","_ruh":"nopower","_mun":"nopower","_rum":"nopower","_bul":"nopower","_gre":"nopower","_smy":"nopower","_alb":"nopower","_ser":"nopower","_bud":"nopower","_gal":"nopower","_vie":"nopower","_boh":"nopower","_tyr":"nopower","_tri":"nopower","_fin":"nopower","_swe":"nopower","_nwy":"nopower","_den":"nopower","_hol":"nopower","_bel":"nopower","_swi":"impassable","_ven":"nopower","_pie":"nopower","_tus":"nopower","_rom":"nopower","_apu":"nopower","_nap":"nopower","_bur":"nopower","_mar":"nopower","_gas":"nopower","_pic":"nopower","_par":"nopower","_bre":"nopower","_spa":"nopower","_por":"nopower","_naf":"nopower","_tun":"nopower","_lon":"nopower","_wal":"nopower","_lvp":"nopower","_yor":"nopower","_edi":"nopower","_cly":"nopower","unplayable":"neutral","unplayable_water":"water","_nat":"water","_nrg":"water","_bar":"water","_bot":"water","_bal":"water","denmark_water":"water","_ska":"water","_hel":"water","_nth":"water","_eng":"water","_iri":"water","_mid":"water","_wes":"water","_gol":"water","_tyn":"water","_adr":"water","_ion":"water","_aeg":"water","_eas":"water","constantinople_water":"water","_bla":"water","BriefLabelLayer":"labeltext24","CurrentNote":"currentnotetext","CurrentNote2":"currentnotetext","CurrentPhase":"currentphasetext","MouseLayer":"invisibleContent"}; + const game = this.props.game; + const mapData = this.props.mapData; + const orders = this.props.orders; + + //// Current phase. + const current_phase = (game.phase[0] === '?' || game.phase === 'COMPLETED') ? 'FINAL' : game.phase; + + //// Notes. + const nb_centers = []; + for (let power of Object.values(game.powers)) { + if (!power.isEliminated()) + nb_centers.push([power.name.substr(0, 3), power.centers.length]); + } + // Sort nb_centers by descending number of centers. + nb_centers.sort((a, b) => { + return -(a[1] - b[1]) || a[0].localeCompare(b[0]); + }); + const nb_centers_per_power = nb_centers.map((couple) => (couple[0] + ': ' + couple[1])).join(' '); + const note = game.note; + + //// Adding units, supply centers, influence and orders. + const scs = new Set(mapData.supplyCenters); + const renderedUnits = []; + const renderedDislodgedUnits = []; + const renderedSupplyCenters = []; + const renderedOrders = []; + const renderedOrders2 = []; + const renderedHighestOrders = []; + for (let power of Object.values(game.powers)) { + for (let unit of power.units) { + renderedUnits.push(); + } + for (let unit of Object.keys(power.retreats)) { + renderedDislodgedUnits.push(); + } + for (let center of power.centers) { + renderedSupplyCenters.push(); + setInfluence(classes, mapData, center, power.name); + scs.delete(center); + } + if (!power.isEliminated()) { + for (let loc of power.influence) { + if (!mapData.supplyCenters.has(loc)) + setInfluence(classes, mapData, loc, power.name); + } + } + + if (orders) { + const powerOrders = (orders && orders.hasOwnProperty(power.name) && orders[power.name]) || []; + for (let order of powerOrders) { + const tokens = order.split(/ +/); + if (!tokens || tokens.length < 3) + continue; + const unit_loc = tokens[1]; + if (tokens[2] === 'H') { + renderedOrders.push(); + } else if (tokens[2] === '-') { + const destLoc = tokens[tokens.length - (tokens[tokens.length - 1] === 'VIA' ? 2 : 1)]; + renderedOrders.push(); + } else if (tokens[2] === 'S') { + const destLoc = tokens[tokens.length - 1]; + if (tokens.includes('-')) { + const srcLoc = tokens[4]; + renderedOrders2.push(); + } else { + renderedOrders2.push(); + } + } else if (tokens[2] === 'C') { + const srcLoc = tokens[4]; + const destLoc = tokens[tokens.length - 1]; + if ((srcLoc !== destLoc) && (tokens.includes('-'))) { + renderedOrders2.push(); + } + } else if (tokens[2] === 'B') { + renderedHighestOrders.push(); + } else if (tokens[2] === 'D') { + renderedHighestOrders.push(); + } else if (tokens[2] === 'R') { + const srcLoc = tokens[1]; + const destLoc = tokens[3]; + renderedOrders.push(); + } else { + throw new Error(`Unknown error to render (${order}).`); + } + } + } + } + // Adding remaining supply centers. + for (let remainingCenter of scs) { + renderedSupplyCenters.push(); + } + + if (this.props.orderBuilding && this.props.orderBuilding.path.length) { + const clicked = parseLocation(this.props.orderBuilding.path[0]); + const province = this.props.mapData.getProvince(clicked); + if (!province) + throw new Error(('Unknown clicked province ' + clicked)); + const clickedID = province.getID(classes); + if (!clicked) + throw new Error(`Unknown path (${clickedID}) for province (${clicked}).`); + classes[clickedID] = 'provinceRed'; + const neighbors = this.getNeighbors(); + if (neighbors) { + for (let neighbor of neighbors) { + const neighborProvince = this.props.mapData.getProvince(neighbor); + if (!neighborProvince) + throw new Error('Unknown neighbor province ' + neighbor); + const neighborID = neighborProvince.getID(classes); + if (!neighborID) + throw new Error(`Unknown neoghbor path (${neighborID}) for province (${neighbor}).`); + classes[neighborID] = neighborProvince.isWater() ? 'provinceBlue' : 'provinceGreen'; + } + } + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {renderedSupplyCenters} + + + + {renderedOrders2} + + + {renderedOrders} + + + + {renderedUnits} + + + {renderedDislodgedUnits} + + + {renderedHighestOrders} + + + SWI + ADR + AEG + ALB + ANK + APU + ARM + BAL + BAR + BEL + BER + BLA + BOH + BRE + BUD + BUL + BUR + CLY + CON + DEN + EAS + EDI + ENG + FIN + GAL + GAS + GRE + BOT + LYO + HEL + HOL + ION + IRI + KIE + LON + LVN + LVP + MAR + MAO + MOS + MUN + NAF + NAP + NAO + NTH + NWY + NWG + PAR + PIC + PIE + POR + PRU + ROM + RUH + RUM + SER + SEV + SIL + SKA + SMY + SPA + STP + SWE + SYR + TRI + TUN + TUS + TYR + TYS + UKR + VEN + VIE + WAL + WAR + WES + YOR + + + + {nb_centers_per_power ? nb_centers_per_power : ''} + + + {note ? note : ''} + + + {current_phase} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} +SvgStandard.propTypes = { + game: PropTypes.instanceOf(Game).isRequired, + mapData: PropTypes.instanceOf(MapData).isRequired, + orders: PropTypes.object, + onHover: PropTypes.func, + onError: PropTypes.func.isRequired, + onSelectLocation: PropTypes.func, + onSelectVia: PropTypes.func, + onOrderBuilding: PropTypes.func, + onOrderBuilt: PropTypes.func, + orderBuilding: PropTypes.object +}; diff --git a/diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js b/diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js new file mode 100644 index 0000000..be4e02e --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js @@ -0,0 +1,1279 @@ +// ============================================================================== +// 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 . +// ============================================================================== +export const SvgStandardExtra = { + "jdipNS:DISPLAY": { + "jdipNS:ZOOM": { + "min": "5", + "max": "2200", + "factor": "1.2" + }, + "jdipNS:LABELS": { + "brief": "true", + "full": "true" + } + }, + "jdipNS:ORDERDRAWING": { + "jdipNS:POWERCOLORS": { + "jdipNS:POWERCOLOR": [ + { + "power": "austria", + "color": "#c48f85" + }, + { + "power": "england", + "color": "darkviolet" + }, + { + "power": "france", + "color": "royalblue" + }, + { + "power": "germany", + "color": "#a08a75" + }, + { + "power": "italy", + "color": "forestgreen" + }, + { + "power": "russia", + "color": "#757d91" + }, + { + "power": "turkey", + "color": "#b9a61c" + } + ] + }, + "jdipNS:SYMBOLSIZE": [ + { + "name": "Fleet", + "width": "40", + "height": "40" + }, + { + "name": "Army", + "width": "40", + "height": "40" + }, + { + "name": "Wing", + "width": "40", + "height": "40" + }, + { + "name": "DislodgedFleet", + "width": "40", + "height": "40" + }, + { + "name": "DislodgedArmy", + "width": "40", + "height": "40" + }, + { + "name": "DislodgedWing", + "width": "40", + "height": "40" + }, + { + "name": "FailedOrder", + "width": "30", + "height": "30" + }, + { + "name": "SupplyCenter", + "width": "20", + "height": "20" + }, + { + "name": "BuildUnit", + "width": "60", + "height": "60" + }, + { + "name": "RemoveUnit", + "width": "50", + "height": "50" + }, + { + "name": "WaivedBuild", + "width": "40", + "height": "40" + } + ], + "jdipNS:BUILD": { + "deltaRadius": "0" + }, + "jdipNS:REMOVE": { + "deltaRadius": "5" + }, + "jdipNS:DISBAND": { + "deltaRadius": "5" + }, + "jdipNS:WAIVE": { + "deltaRadius": "0" + }, + "jdipNS:HOLD": { + "deltaRadius": "5", + "strokeCSSStyle": "varwidthorder", + "highlightOffset": "0", + "highlightCSSClass": "varwidthshadow", + "widths": "6,9,12,18", + "shadowWidths": "10,15,20,25" + }, + "jdipNS:MOVE": { + "deltaRadius": "5", + "strokeCSSStyle": "varwidthorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "varwidthshadow", + "widths": "6,9,12,18", + "shadowWidths": "10,15,20,25" + }, + "jdipNS:RETREAT": { + "deltaRadius": "5", + "strokeCSSStyle": "defaultorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "shadoworder" + }, + "jdipNS:SUPPORT": { + "deltaRadius": "10", + "strokeCSSStyle": "supportorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "shadowdash" + }, + "jdipNS:CONVOY": { + "deltaRadius": "10", + "strokeCSSStyle": "convoyorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "shadowdash" + } + }, + "jdipNS:PROVINCE_DATA": { + "jdipNS:PROVINCE": [ + { + "jdipNS:UNIT": { + "x": "805", + "y": "1058" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "792.5", + "y": "1045.5" + }, + "name": "adr" + }, + { + "jdipNS:UNIT": { + "x": "1055", + "y": "1240" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1042.5", + "y": "1227.5" + }, + "name": "aeg" + }, + { + "jdipNS:UNIT": { + "x": "918", + "y": "1123" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "905.5", + "y": "1110.5" + }, + "name": "alb" + }, + { + "jdipNS:UNIT": { + "x": "1313", + "y": "1120" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1300.5", + "y": "1107.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1271", + "y": "1144" + }, + "name": "ank" + }, + { + "jdipNS:UNIT": { + "x": "803", + "y": "1116" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "790.5", + "y": "1103.5" + }, + "name": "apu" + }, + { + "jdipNS:UNIT": { + "x": "1496", + "y": "1100" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1483.5", + "y": "1087.5" + }, + "name": "arm" + }, + { + "jdipNS:UNIT": { + "x": "890", + "y": "620" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "877.5", + "y": "607.5" + }, + "name": "bal" + }, + { + "jdipNS:UNIT": { + "x": "1174", + "y": "83" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1161.5", + "y": "70.5" + }, + "name": "bar" + }, + { + "jdipNS:UNIT": { + "x": "573", + "y": "763" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "560.5", + "y": "750.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "575", + "y": "755" + }, + "name": "bel" + }, + { + "jdipNS:UNIT": { + "x": "783", + "y": "700" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "770.5", + "y": "687.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "783", + "y": "736" + }, + "name": "ber" + }, + { + "jdipNS:UNIT": { + "x": "1245", + "y": "1010" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1232.5", + "y": "997.5" + }, + "name": "bla" + }, + { + "jdipNS:UNIT": { + "x": "818", + "y": "824" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "805.5", + "y": "811.5" + }, + "name": "boh" + }, + { + "jdipNS:UNIT": { + "x": "953", + "y": "495" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "940.5", + "y": "482.5" + }, + "name": "bot" + }, + { + "jdipNS:UNIT": { + "x": "416", + "y": "829" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "403.5", + "y": "816.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "369", + "y": "799" + }, + "name": "bre" + }, + { + "jdipNS:UNIT": { + "x": "962", + "y": "914" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "949.5", + "y": "901.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "900", + "y": "916" + }, + "name": "bud" + }, + { + "jdipNS:UNIT": { + "x": "1060", + "y": "1078" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1047.5", + "y": "1065.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1013", + "y": "1078" + }, + "name": "bul" + }, + { + "jdipNS:UNIT": { + "x": "571", + "y": "881" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "558.5", + "y": "868.5" + }, + "name": "bur" + }, + { + "jdipNS:UNIT": { + "x": "448", + "y": "502" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "435.5", + "y": "489.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "446", + "y": "480" + }, + "name": "cly" + }, + { + "jdipNS:UNIT": { + "x": "1157", + "y": "1147" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1144.5", + "y": "1134.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1141", + "y": "1119" + }, + "name": "con" + }, + { + "jdipNS:UNIT": { + "x": "715", + "y": "597" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "702.5", + "y": "584.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "767", + "y": "626" + }, + "name": "den" + }, + { + "jdipNS:UNIT": { + "x": "1230", + "y": "1321" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1217.5", + "y": "1308.5" + }, + "name": "eas" + }, + { + "jdipNS:UNIT": { + "x": "485", + "y": "524" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "472.5", + "y": "511.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "489", + "y": "565" + }, + "name": "edi" + }, + { + "jdipNS:UNIT": { + "x": "406", + "y": "761" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "393.5", + "y": "748.5" + }, + "name": "eng" + }, + { + "jdipNS:UNIT": { + "x": "1000", + "y": "390" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "987.5", + "y": "377.5" + }, + "name": "fin" + }, + { + "jdipNS:UNIT": { + "x": "1011", + "y": "841" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "998.5", + "y": "828.5" + }, + "name": "gal" + }, + { + "jdipNS:UNIT": { + "x": "434", + "y": "922" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "421.5", + "y": "909.5" + }, + "name": "gas" + }, + { + "jdipNS:UNIT": { + "x": "556", + "y": "1060" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "543.5", + "y": "1047.5" + }, + "name": "gol" + }, + { + "jdipNS:UNIT": { + "x": "978", + "y": "1200" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "965.5", + "y": "1187.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1023", + "y": "1237" + }, + "name": "gre" + }, + { + "jdipNS:UNIT": { + "x": "663", + "y": "641" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "650.5", + "y": "628.5" + }, + "name": "hel" + }, + { + "jdipNS:UNIT": { + "x": "608", + "y": "721" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "595.5", + "y": "708.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "630", + "y": "692" + }, + "name": "hol" + }, + { + "jdipNS:UNIT": { + "x": "858", + "y": "1296" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "845.5", + "y": "1283.5" + }, + "name": "ion" + }, + { + "jdipNS:UNIT": { + "x": "347", + "y": "671" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "334.5", + "y": "658.5" + }, + "name": "iri" + }, + { + "jdipNS:UNIT": { + "x": "695", + "y": "711" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "682.5", + "y": "698.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "724", + "y": "685" + }, + "name": "kie" + }, + { + "jdipNS:UNIT": { + "x": "500", + "y": "685" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "487.5", + "y": "672.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "489", + "y": "717" + }, + "name": "lon" + }, + { + "jdipNS:UNIT": { + "x": "1037", + "y": "577" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1024.5", + "y": "564.5" + }, + "name": "lvn" + }, + { + "jdipNS:UNIT": { + "x": "462", + "y": "586" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "449.5", + "y": "573.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "458", + "y": "638" + }, + "name": "lvp" + }, + { + "jdipNS:UNIT": { + "x": "525.8", + "y": "1065" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "513.3", + "y": "1052.5" + }, + "name": "lyo" + }, + { + "jdipNS:UNIT": { + "x": "536", + "y": "985" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "523.5", + "y": "972.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "562", + "y": "1018" + }, + "name": "mar" + }, + { + "jdipNS:UNIT": { + "x": "153.3", + "y": "845.3" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "140.8", + "y": "832.8" + }, + "name": "mao" + }, + { + "jdipNS:UNIT": { + "x": "126", + "y": "902" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "113.5", + "y": "889.5" + }, + "name": "mid" + }, + { + "jdipNS:UNIT": { + "x": "1212", + "y": "600" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1199.5", + "y": "587.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1267", + "y": "584" + }, + "name": "mos" + }, + { + "jdipNS:UNIT": { + "x": "705", + "y": "838" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "692.5", + "y": "825.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "730", + "y": "878" + }, + "name": "mun" + }, + { + "jdipNS:UNIT": { + "x": "337", + "y": "1291" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "324.5", + "y": "1278.5" + }, + "name": "naf" + }, + { + "jdipNS:UNIT": { + "x": "191.6", + "y": "298.2" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "179.1", + "y": "285.7" + }, + "name": "nao" + }, + { + "jdipNS:UNIT": { + "x": "818", + "y": "1180" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "815.5", + "y": "1167.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "778", + "y": "1143" + }, + "name": "nap" + }, + { + "jdipNS:UNIT": { + "x": "238", + "y": "427" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "225.5", + "y": "414.5" + }, + "name": "nat" + }, + { + "jdipNS:UNIT": { + "x": "605", + "y": "250" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "592.5", + "y": "237.5" + }, + "name": "nrg" + }, + { + "jdipNS:UNIT": { + "x": "565", + "y": "570" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "552.5", + "y": "557.5" + }, + "name": "nth" + }, + { + "jdipNS:UNIT": { + "x": "664.2", + "y": "191.8" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "651.7", + "y": "179.3" + }, + "name": "nwg" + }, + { + "jdipNS:UNIT": { + "x": "715", + "y": "420" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "702.5", + "y": "407.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "761", + "y": "463" + }, + "name": "nwy" + }, + { + "jdipNS:UNIT": { + "x": "500", + "y": "855" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "487.5", + "y": "842.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "518", + "y": "819" + }, + "name": "par" + }, + { + "jdipNS:UNIT": { + "x": "535", + "y": "791" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "522.5", + "y": "778.5" + }, + "name": "pic" + }, + { + "jdipNS:UNIT": { + "x": "642", + "y": "978" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "629.5", + "y": "965.5" + }, + "name": "pie" + }, + { + "jdipNS:UNIT": { + "x": "193", + "y": "1023" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "180.5", + "y": "1010.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "151", + "y": "1060" + }, + "name": "por" + }, + { + "jdipNS:UNIT": { + "x": "877", + "y": "700" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "864.5", + "y": "687.5" + }, + "name": "pru" + }, + { + "jdipNS:UNIT": { + "x": "743", + "y": "1112" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "730.5", + "y": "1099.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "719", + "y": "1087" + }, + "name": "rom" + }, + { + "jdipNS:UNIT": { + "x": "648", + "y": "789" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "635.5", + "y": "776.5" + }, + "name": "ruh" + }, + { + "jdipNS:UNIT": { + "x": "1108", + "y": "977" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1095.5", + "y": "964.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1083", + "y": "1014" + }, + "name": "rum" + }, + { + "jdipNS:UNIT": { + "x": "945", + "y": "1060" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "932.5", + "y": "1047.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "936", + "y": "1021" + }, + "name": "ser" + }, + { + "jdipNS:UNIT": { + "x": "1296", + "y": "855" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1283.5", + "y": "842.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1267", + "y": "973" + }, + "name": "sev" + }, + { + "jdipNS:UNIT": { + "x": "844", + "y": "779" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "831.5", + "y": "766.5" + }, + "name": "sil" + }, + { + "jdipNS:UNIT": { + "x": "747", + "y": "528" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "734.5", + "y": "515.5" + }, + "name": "ska" + }, + { + "jdipNS:UNIT": { + "x": "1265", + "y": "1220" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1252.5", + "y": "1207.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1132", + "y": "1222" + }, + "name": "smy" + }, + { + "jdipNS:UNIT": { + "x": "347", + "y": "1049" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "334.5", + "y": "1036.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "302", + "y": "1056" + }, + "name": "spa" + }, + { + "jdipNS:UNIT": { + "x": "1178", + "y": "415" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1165.5", + "y": "402.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1113", + "y": "469" + }, + "name": "stp" + }, + { + "jdipNS:UNIT": { + "x": "841", + "y": "469" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "828.5", + "y": "456.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "890", + "y": "489" + }, + "name": "swe" + }, + { + "jdipNS:UNIT": { + "x": "642", + "y": "928" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "629", + "y": "915.5" + }, + "name": "swi" + }, + { + "jdipNS:UNIT": { + "x": "1464", + "y": "1216" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1451.5", + "y": "1203.5" + }, + "name": "syr" + }, + { + "jdipNS:UNIT": { + "x": "837", + "y": "1006" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "824.5", + "y": "993.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "794", + "y": "975" + }, + "name": "tri" + }, + { + "jdipNS:UNIT": { + "x": "634", + "y": "1310" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "621.5", + "y": "1297.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "647", + "y": "1282" + }, + "name": "tun" + }, + { + "jdipNS:UNIT": { + "x": "698", + "y": "1044" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "685.5", + "y": "1031.5" + }, + "name": "tus" + }, + { + "jdipNS:UNIT": { + "x": "720", + "y": "1160" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "707.5", + "y": "1147.5" + }, + "name": "tyn" + }, + { + "jdipNS:UNIT": { + "x": "754", + "y": "914" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "741.5", + "y": "901.5" + }, + "name": "tyr" + }, + { + "jdipNS:UNIT": { + "x": "710.0", + "y": "1159.1" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "697.5", + "y": "1146.6" + }, + "name": "tys" + }, + { + "jdipNS:UNIT": { + "x": "1136", + "y": "810" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1123.5", + "y": "797.5" + }, + "name": "ukr" + }, + { + "jdipNS:UNIT": { + "x": "719", + "y": "1004" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "706.5", + "y": "991.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "733", + "y": "971" + }, + "name": "ven" + }, + { + "jdipNS:UNIT": { + "x": "867", + "y": "874" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "854.5", + "y": "861.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "835", + "y": "887" + }, + "name": "vie" + }, + { + "jdipNS:UNIT": { + "x": "440", + "y": "668" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "427.5", + "y": "655.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "440", + "y": "712" + }, + "name": "wal" + }, + { + "jdipNS:UNIT": { + "x": "995", + "y": "750" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "982.5", + "y": "737.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "937", + "y": "748" + }, + "name": "war" + }, + { + "jdipNS:UNIT": { + "x": "474", + "y": "1173" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "461.5", + "y": "1160.5" + }, + "name": "wes" + }, + { + "jdipNS:UNIT": { + "x": "504", + "y": "626" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "491.5", + "y": "613.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "506", + "y": "647" + }, + "name": "yor" + }, + { + "jdipNS:UNIT": { + "x": "1218", + "y": "222" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1205.5", + "y": "209.5" + }, + "name": "stp-nc" + }, + { + "jdipNS:UNIT": { + "x": "1066", + "y": "487" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1053.5", + "y": "474.5" + }, + "name": "stp-sc" + }, + { + "jdipNS:UNIT": { + "x": "1127", + "y": "1067" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1114.5", + "y": "1054.5" + }, + "name": "bul-ec" + }, + { + "jdipNS:UNIT": { + "x": "1070", + "y": "1140" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1057.5", + "y": "1127.5" + }, + "name": "bul-sc" + }, + { + "jdipNS:UNIT": { + "x": "289", + "y": "965" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "276.5", + "y": "952.5" + }, + "name": "spa-nc" + }, + { + "jdipNS:UNIT": { + "x": "291", + "y": "1166" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "278.5", + "y": "1153.5" + }, + "name": "spa-sc" + } + ] + } +}; diff --git a/diplomacy/web/src/gui/maps/standard/build.js b/diplomacy/web/src/gui/maps/standard/build.js new file mode 100644 index 0000000..cf28f19 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/build.js @@ -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 . +// ============================================================================== +import React from "react"; +import {ARMY, Coordinates, FLEET, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class Build extends React.Component { + render() { + const loc = this.props.loc; + const unit_type = this.props.unitType; + const loc_x = offset(Coordinates[loc].unit[0], -11.5); + const loc_y = offset(Coordinates[loc].unit[1], -10.); + const build_loc_x = offset(Coordinates[loc].unit[0], -20.5); + const build_loc_y = offset(Coordinates[loc].unit[1], -20.5); + const symbol = unit_type === 'A' ? ARMY : FLEET; + const build_symbol = 'BuildUnit'; + return ( + + + + + ); + } +} + +Build.propTypes = { + unitType: PropTypes.string.isRequired, + loc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/common.js b/diplomacy/web/src/gui/maps/standard/common.js new file mode 100644 index 0000000..cb768c2 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/common.js @@ -0,0 +1,75 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see . +// ============================================================================== +import {SvgStandardExtra} from "./SvgStandardExtra"; + +const TAG_ORDERDRAWING = 'jdipNS:ORDERDRAWING'; +const TAG_POWERCOLORS = 'jdipNS:POWERCOLORS'; +const TAG_POWERCOLOR = 'jdipNS:POWERCOLOR'; +const TAG_SYMBOLSIZE = 'jdipNS:SYMBOLSIZE'; +const TAG_PROVINCE_DATA = 'jdipNS:PROVINCE_DATA'; +const TAG_PROVINCE = 'jdipNS:PROVINCE'; +const TAG_UNIT = 'jdipNS:UNIT'; +const TAG_DISLODGED_UNIT = 'jdipNS:DISLODGED_UNIT'; +const TAG_SUPPLY_CENTER = 'jdipNS:SUPPLY_CENTER'; + +export const ARMY = 'Army'; +export const FLEET = 'Fleet'; + +function getCoordinates() { + const coordinates = {}; + for (let provinceDefiniton of SvgStandardExtra[TAG_PROVINCE_DATA][TAG_PROVINCE]) { + const name = provinceDefiniton.name.toUpperCase().replace('-', '/'); + coordinates[name] = {}; + if (provinceDefiniton.hasOwnProperty(TAG_UNIT)) { + coordinates[name].unit = [provinceDefiniton[TAG_UNIT]['x'], provinceDefiniton[TAG_UNIT]['y']]; + } + if (provinceDefiniton.hasOwnProperty(TAG_DISLODGED_UNIT)) { + coordinates[name].disl = [provinceDefiniton[TAG_DISLODGED_UNIT]['x'], provinceDefiniton[TAG_DISLODGED_UNIT]['y']]; + } + if (provinceDefiniton.hasOwnProperty(TAG_SUPPLY_CENTER)) { + coordinates[name].sc = [provinceDefiniton[TAG_SUPPLY_CENTER]['x'], provinceDefiniton[TAG_SUPPLY_CENTER]['y']]; + } + } + return coordinates; +} + +function getSymbolSizes() { + const sizes = {}; + for (let definition of SvgStandardExtra[TAG_ORDERDRAWING][TAG_SYMBOLSIZE]) { + sizes[definition.name] = { + width: parseInt(definition.width), + height: parseInt(definition.height) + }; + } + return sizes; +} + +function getColors() { + const colors = {}; + for (let definition of SvgStandardExtra[TAG_ORDERDRAWING][TAG_POWERCOLORS][TAG_POWERCOLOR]) { + colors[definition.power.toUpperCase()] = definition.color; + } + return colors; +} + +export const Coordinates = getCoordinates(); +export const SymbolSizes = getSymbolSizes(); +export const Colors = getColors(); + +export function offset(floatString, offset) { + return "" + (parseFloat(floatString) + offset); +} diff --git a/diplomacy/web/src/gui/maps/standard/convoy.js b/diplomacy/web/src/gui/maps/standard/convoy.js new file mode 100644 index 0000000..03b4749 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/convoy.js @@ -0,0 +1,98 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class Convoy extends React.Component { + render() { + const loc = this.props.loc; + const src_loc = this.props.srcLoc; + const dest_loc = this.props.dstLoc; + const loc_x = offset(Coordinates[loc].unit[0], 10); + const loc_y = offset(Coordinates[loc].unit[1], 10); + const src_loc_x = offset(Coordinates[src_loc].unit[0], 10); + const src_loc_y = offset(Coordinates[src_loc].unit[1], 10); + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[dest_loc].unit[1], 10); + + const src_delta_x = parseFloat(src_loc_x) - parseFloat(loc_x); + const src_delta_y = parseFloat(src_loc_y) - parseFloat(loc_y); + const src_vector_length = Math.sqrt(src_delta_x * src_delta_x + src_delta_y * src_delta_y); + const src_loc_x_1 = '' + Math.round((parseFloat(loc_x) + (src_vector_length - 30.) / src_vector_length * src_delta_x) * 100.) / 100.; + const src_loc_y_1 = '' + Math.round((parseFloat(loc_y) + (src_vector_length - 30.) / src_vector_length * src_delta_y) * 100.) / 100.; + + let dest_delta_x = parseFloat(src_loc_x) - parseFloat(dest_loc_x); + let dest_delta_y = parseFloat(src_loc_y) - parseFloat(dest_loc_y); + let dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y); + const src_loc_x_2 = '' + Math.round((parseFloat(dest_loc_x) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_x) * 100.) / 100.; + const src_loc_y_2 = '' + Math.round((parseFloat(dest_loc_y) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_y) * 100.) / 100.; + + dest_delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x); + dest_delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y); + dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y); + dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_y) * 100.) / 100.; + + const triangle_coord = []; + const triangle_loc_x = offset(Coordinates[src_loc].unit[0], 10); + const triangle_loc_y = offset(Coordinates[src_loc].unit[1], 10); + for (let ofs of [[0, -38.3], [33.2, 19.1], [-33.2, 19.1]]) { + triangle_coord.push(offset(triangle_loc_x, ofs[0]) + ',' + offset(triangle_loc_y, ofs[1])); + } + + return ( + + + + + + + + + ); + } +} + +Convoy.propTypes = { + loc: PropTypes.string.isRequired, + srcLoc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/disband.js b/diplomacy/web/src/gui/maps/standard/disband.js new file mode 100644 index 0000000..857ba70 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/disband.js @@ -0,0 +1,51 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see . +// ============================================================================== +import React from "react"; +import {Coordinates, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class Disband extends React.Component { + render() { + const loc = this.props.loc; + const phaseType = this.props.phaseType; + let loc_x = 0; + let loc_y = 0; + if (phaseType === 'R') { + loc_x = offset(Coordinates[loc].unit[0], -29.); + loc_y = offset(Coordinates[loc].unit[1], -27.5); + } else { + loc_x = offset(Coordinates[loc].unit[0], -16.5); + loc_y = offset(Coordinates[loc].unit[1], -15.); + } + const symbol = 'RemoveUnit'; + return ( + + + + ); + } +} + +Disband.propTypes = { + loc: PropTypes.string.isRequired, + phaseType: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/hold.js b/diplomacy/web/src/gui/maps/standard/hold.js new file mode 100644 index 0000000..4a79deb --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/hold.js @@ -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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class Hold extends React.Component { + render() { + const polygon_coord = []; + const loc_x = offset(Coordinates[this.props.loc].unit[0], 8.5); + const loc_y = offset(Coordinates[this.props.loc].unit[1], 9.5); + for (let ofs of [ + [13.8, -33.3], [33.3, -13.8], [33.3, 13.8], [13.8, 33.3], [-13.8, 33.3], + [-33.3, 13.8], [-33.3, -13.8], [-13.8, -33.3]] + ) { + polygon_coord.push(offset(loc_x, ofs[0]) + ',' + offset(loc_y, ofs[1])); + } + return ( + + + + + ); + } +} + +Hold.propTypes = { + loc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/move.js b/diplomacy/web/src/gui/maps/standard/move.js new file mode 100644 index 0000000..8351a96 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/move.js @@ -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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class Move extends React.Component { + render() { + const src_loc = this.props.srcLoc; + const dest_loc = this.props.dstLoc; + let src_loc_x = 0; + let src_loc_y = 0; + if (this.props.phaseType === 'R') { + src_loc_x = offset(Coordinates[src_loc].unit[0], -2.5); + src_loc_y = offset(Coordinates[src_loc].unit[1], -2.5); + } else { + src_loc_x = offset(Coordinates[src_loc].unit[0], 10); + src_loc_y = offset(Coordinates[src_loc].unit[1], 10); + } + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[dest_loc].unit[1], 10); + + // Adjusting destination + const delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x); + const delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y); + const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y); + dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - 30.) / vector_length * delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - 30.) / vector_length * delta_y) * 100.) / 100.; + return ( + + + + + ); + } +} + +Move.propTypes = { + srcLoc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, + phaseType: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/supplyCenter.js b/diplomacy/web/src/gui/maps/standard/supplyCenter.js new file mode 100644 index 0000000..0663ede --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/supplyCenter.js @@ -0,0 +1,40 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see . +// ============================================================================== +import React from "react"; +import {Coordinates, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class SupplyCenter extends React.Component { + render() { + const symbol = 'SupplyCenter'; + const loc_x = offset(Coordinates[this.props.loc].sc[0], -8.5); + const loc_y = offset(Coordinates[this.props.loc].sc[1], -11.0); + return ( + + ); + } +} + +SupplyCenter.propTypes = { + loc: PropTypes.string.isRequired, + powerName: PropTypes.string +}; diff --git a/diplomacy/web/src/gui/maps/standard/supportHold.js b/diplomacy/web/src/gui/maps/standard/supportHold.js new file mode 100644 index 0000000..bfa2656 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/supportHold.js @@ -0,0 +1,73 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class SupportHold extends React.Component { + render() { + const loc = this.props.loc; + const dest_loc = this.props.dstLoc; + const loc_x = offset(Coordinates[loc].unit[0], 10); + const loc_y = offset(Coordinates[loc].unit[1], 10); + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[dest_loc].unit[1], 10); + + const delta_x = parseFloat(dest_loc_x) - parseFloat(loc_x); + const delta_y = parseFloat(dest_loc_y) - parseFloat(loc_y); + const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y); + dest_loc_x = '' + Math.round((parseFloat(loc_x) + (vector_length - 35.) / vector_length * delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(loc_y) + (vector_length - 35.) / vector_length * delta_y) * 100.) / 100.; + + const polygon_coord = []; + const poly_loc_x = offset(Coordinates[dest_loc].unit[0], 8.5); + const poly_loc_y = offset(Coordinates[dest_loc].unit[1], 9.5); + for (let ofs of [ + [15.9, -38.3], [38.3, -15.9], [38.3, 15.9], [15.9, 38.3], [-15.9, 38.3], [-38.3, 15.9], + [-38.3, -15.9], [-15.9, -38.3] + ]) { + polygon_coord.push(offset(poly_loc_x, ofs[0]) + ',' + offset(poly_loc_y, ofs[1])); + } + return ( + + + + + + + ); + } +} + +SupportHold.propTypes = { + loc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, +}; diff --git a/diplomacy/web/src/gui/maps/standard/supportMove.js b/diplomacy/web/src/gui/maps/standard/supportMove.js new file mode 100644 index 0000000..e46abc3 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/supportMove.js @@ -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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class SupportMove extends React.Component { + render() { + const loc = this.props.loc; + const src_loc = this.props.srcLoc; + const dest_loc = this.props.dstLoc; + const loc_x = offset(Coordinates[loc].unit[0], 10); + const loc_y = offset(Coordinates[loc].unit[1], 10); + const src_loc_x = offset(Coordinates[src_loc].unit[0], 10); + const src_loc_y = offset(Coordinates[src_loc].unit[1], 10); + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[dest_loc].unit[1], 10); + + // Adjusting destination + const delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x); + const delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y); + const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y); + dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - 30.) / vector_length * delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - 30.) / vector_length * delta_y) * 100.) / 100.; + return ( + + + + + ); + } +} + +SupportMove.propTypes = { + loc: PropTypes.string.isRequired, + srcLoc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, +}; diff --git a/diplomacy/web/src/gui/maps/standard/unit.js b/diplomacy/web/src/gui/maps/standard/unit.js new file mode 100644 index 0000000..f32a678 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/unit.js @@ -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 . +// ============================================================================== +import React from "react"; +import {ARMY, Coordinates, FLEET, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class Unit extends React.Component { + render() { + 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 = offset(Coordinates[loc][dislogged_type][0], -11.5); + const loc_y = offset(Coordinates[loc][dislogged_type][1], -10.0); + return ( + + ); + } +} + +Unit.propTypes = { + unit: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, + isDislodged: PropTypes.bool.isRequired, +}; -- cgit v1.2.3