aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/web/svg_to_react.py
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy/web/svg_to_react.py')
-rw-r--r--diplomacy/web/svg_to_react.py640
1 files changed, 556 insertions, 84 deletions
diff --git a/diplomacy/web/svg_to_react.py b/diplomacy/web/svg_to_react.py
index 12a68b6..dafe4d3 100644
--- a/diplomacy/web/svg_to_react.py
+++ b/diplomacy/web/svg_to_react.py
@@ -19,11 +19,122 @@
"""
import argparse
import os
-import sys
+import re
from xml.dom import minidom, Node
import ujson as json
+LICENSE_TEXT = """/**
+==============================================================================
+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/>.
+==============================================================================
+**/"""
+
+TAG_ORDERDRAWING = 'jdipNS:ORDERDRAWING'
+TAG_POWERCOLORS = 'jdipNS:POWERCOLORS'
+TAG_POWERCOLOR = 'jdipNS:POWERCOLOR'
+TAG_SYMBOLSIZE = 'jdipNS:SYMBOLSIZE'
+TAG_PROVINCE_DATA = 'jdipNS:PROVINCE_DATA'
+TAG_PROVINCE = 'jdipNS:PROVINCE'
+TAG_UNIT = 'jdipNS:UNIT'
+TAG_DISLODGED_UNIT = 'jdipNS:DISLODGED_UNIT'
+TAG_SUPPLY_CENTER = 'jdipNS:SUPPLY_CENTER'
+
+SELECTOR_REGEX = re.compile(r'([\r\n][ \t]*)([^{\r\n]+){')
+LINES_REGEX = re.compile(r'[\r\n]+')
+SPACES_REGEX = re.compile(r'[\t ]+')
+STRING_REGEX = re.compile(r'[`\'"] {0,1}\+ {0,1}[`\'"]')
+
+
+def prepend_css_selectors(prefix, css_text):
+ """ Prepend all CSS selector with given prefix (e.g. ID selector) followed by a space.
+ :param prefix: prefix to prepend
+ :param css_text: CSS text to parse
+ :rtype: str
+ """
+ def repl(match):
+ return '%s%s %s{' % (match.group(1), prefix, match.group(2))
+
+ return SELECTOR_REGEX.sub(repl, css_text)
+
+
+class ExtractedData:
+ """ Helper class to store extra data collected while parsing SVG file. Properties:
+ - name: class name of parsed SVG component
+ - extra: data parsed from invalid tags found in SVG content
+ - style_lines: string lines parsed from <style> tag if found in SVG content
+ - id_to_class: dictionary mapping and ID to corresponding class name
+ for each tag found with both ID and class name in SVG content.
+ """
+ __slots__ = ('name', 'extra', 'style_lines', 'id_to_class')
+
+ def __init__(self, name):
+ """ Initialize extracted data object.
+ :param name: class name of parsed SVG content
+ """
+ self.name = name
+ self.extra = {}
+ self.style_lines = []
+ self.id_to_class = {}
+
+ def get_coordinates(self):
+ """ Parse and return unit coordinates from extra field.
+ :return: a dictionary mapping a province name to coordinates [x, y] (as string values)
+ for unit ('unit'), dislodged unit ('disl'), and supply center ('sc', if available).
+ :rtype: dict
+ """
+ coordinates = {}
+ for province_definition in self.extra[TAG_PROVINCE_DATA][TAG_PROVINCE]:
+ name = province_definition['name'].upper().replace('-', '/')
+ coordinates[name] = {}
+ if TAG_UNIT in province_definition:
+ coordinates[name]['unit'] = [
+ province_definition[TAG_UNIT]['x'], province_definition[TAG_UNIT]['y']]
+ if TAG_DISLODGED_UNIT in province_definition:
+ coordinates[name]['disl'] = [province_definition[TAG_DISLODGED_UNIT]['x'],
+ province_definition[TAG_DISLODGED_UNIT]['y']]
+ if TAG_SUPPLY_CENTER in province_definition:
+ coordinates[name]['sc'] = [province_definition[TAG_SUPPLY_CENTER]['x'],
+ province_definition[TAG_SUPPLY_CENTER]['y']]
+ return coordinates
+
+ def get_symbol_sizes(self):
+ """ Parse and return symbol sizes from extra field.
+ :return: a dictionary mapping a symbol name to sizes
+ ('width' and 'height' as floating values).
+ :rtype: dict
+ """
+ sizes = {}
+ for definition in self.extra[TAG_ORDERDRAWING][TAG_SYMBOLSIZE]:
+ sizes[definition['name']] = {
+ 'width': float(definition['width']),
+ 'height': float(definition['height'])
+ }
+ return sizes
+
+ def get_colors(self):
+ """ Parse and return power colors from extra field.
+ :return: a dictionary mapping a power name to a HTML color.
+ :rtype: dict
+ """
+ colors = {}
+ for definition in self.extra[TAG_ORDERDRAWING][TAG_POWERCOLORS][TAG_POWERCOLOR]:
+ colors[definition['power'].upper()] = definition['color']
+ return colors
+
def safe_react_attribute_name(name):
""" Convert given raw attribute name into a valid React HTML tag attribute name.
@@ -42,6 +153,8 @@ def safe_react_attribute_name(name):
for piece in input_pieces[1:]:
output_pieces.append('%s%s' % (piece[0].upper(), piece[1:]))
return ''.join(output_pieces)
+ if name == 'xlink:href':
+ return 'href'
# Otherwise, return name as-is.
return name
@@ -51,6 +164,7 @@ def compact_extra(extra):
:param extra: dictionary of extra data
:type extra: dict
"""
+ # pylint:disable=too-many-branches
if 'children' in extra:
names = set()
text_found = False
@@ -60,7 +174,8 @@ def compact_extra(extra):
else:
names.add(child['name'])
if len(names) == len(extra['children']):
- # Each child has a different name, so they cannot be confused, and extra dictionary can be merged with them.
+ # Each child has a different name, so they cannot be confused,
+ # and extra dictionary can be merged with them.
children_dict = {}
for child in extra['children']:
child_name = child.pop('name')
@@ -83,7 +198,8 @@ def compact_extra(extra):
compact_extra(child)
extra[name] = child
else:
- # We found many children with same name. Merge them as a list into extra dictionary.
+ # We found many children with same name.
+ # Merge them as a list into extra dictionary.
values = []
for child in children:
child.pop('name')
@@ -117,7 +233,7 @@ def extract_extra(node, extra):
text = child.data.strip()
if text:
extra_dictionary['children'].append(text)
- else:
+ elif child.nodeType != Node.COMMENT_NODE:
# Child is a normal node. We still consider it as an extra node.
extract_extra(child, extra_dictionary)
# Save extra node data into list field extra['children'].
@@ -141,28 +257,25 @@ def attributes_to_string(attributes):
return ' '.join(pieces)
-def extract_dom(node, nb_indentation, lines, extra, style_lines, id_to_class, identifiers_to_remove, action_parents):
+def extract_dom(node, nb_indentation, lines, data):
""" Parse given node.
:param node: (input) node to parse
- :param nb_indentation: (input) number of indentation to use for current node content into output lines
- 1 indentation is converted to 4 spaces.
- :param lines: (output) lines to collect output lines of text corresponding to parsed content
- :param extra: (output) dictionary to collect extra data (corresponding to invalid/unhandled tags(
- :param style_lines: (output) lines to collect output lines of CSS file corresponding to `style` tag (if found)
+ :param nb_indentation: (input) number of indentation to use for current node content
+ into output lines. 1 indentation is converted to 4 spaces.
+ :param lines: (output) collector for output lines of text corresponding to parsed content
+ :param data: ExtractedData object to collect extracted data
+ (extra, style lines, ID-to-class mapping).
:type nb_indentation: int
:type lines: List[str]
- :type extra: dict
- :type style_lines: List[str]
- :type id_to_class: dict
- :type identifiers_to_remove: Iterable[str]
- :type action_parents: Iterable[str]
+ :type data: ExtractedData
"""
+ # pylint: disable=too-many-branches, too-many-statements
if node.nodeType != Node.ELEMENT_NODE:
return
- tag_name = node.tagName
- if ':' in tag_name:
- # Found unhandled tag (example: `<jdipNS:DISPLAY>`). Collect it (and all its descendants) into extra.
- extract_extra(node, extra)
+ if ':' in node.tagName:
+ # Found unhandled tag (example: `<jdipNS:DISPLAY>`).
+ # Collect it (and all its descendants) into extra.
+ extract_extra(node, data.extra)
else:
# Found valid tag.
attributes = {}
@@ -181,14 +294,11 @@ def extract_dom(node, nb_indentation, lines, extra, style_lines, id_to_class, id
elif attribute_name == 'className':
node_class = attribute.value
if node_id:
- if identifiers_to_remove and node_id in identifiers_to_remove:
- # This node must be skipped.
- return
if node_class:
# We parameterize class name for this node.
attributes['className'] = "{classes['%s']}" % node_id
- id_to_class[node_id] = node_class
- if node.parentNode.getAttribute('id') in action_parents:
+ data.id_to_class[node_id] = node_class
+ if node.parentNode.getAttribute('id') == 'MouseLayer':
# This node must react to onClick and onMouseOver.
attributes['onClick'] = '{this.onClick}'
attributes['onMouseOver'] = '{this.onHover}'
@@ -201,34 +311,74 @@ def extract_dom(node, nb_indentation, lines, extra, style_lines, id_to_class, id
child_lines.append(text)
else:
# Found an element node.
- extract_dom(child, nb_indentation + 1, child_lines, extra, style_lines,
- id_to_class, identifiers_to_remove, action_parents)
- if tag_name == 'style':
+ extract_dom(child, nb_indentation + 1, child_lines, data)
+ if node.tagName == 'style':
# Found 'style' tag. Save its children lines into style lines and return immediately,
- style_lines.extend(child_lines)
+ data.style_lines.extend(child_lines)
return
+ if node.tagName == 'svg':
+ if node_class:
+ attributes['className'] += ' %s' % data.name
+ else:
+ attributes['className'] = data.name
+ if node_id:
+ if not child_lines:
+ if node_id == 'Layer2':
+ child_lines.append('{renderedOrders2}')
+ elif node_id == 'Layer1':
+ child_lines.append('{renderedOrders}')
+ elif node_id == 'UnitLayer':
+ child_lines.append('{renderedUnits}')
+ elif node_id == 'DislodgedUnitLayer':
+ child_lines.append('{renderedDislodgedUnits}')
+ elif node_id == 'HighestOrderLayer':
+ child_lines.append('{renderedHighestOrders}')
+ elif node_id == 'CurrentNote':
+ child_lines.append("{nb_centers_per_power ? nb_centers_per_power : ''}")
+ elif node_id == 'CurrentNote2':
+ child_lines.append("{note ? note : ''}")
+ if (node_id == 'CurrentPhase'
+ and len(child_lines) == 1
+ and isinstance(child_lines[0], str)):
+ child_lines = ['{current_phase}']
# We have a normal element node (not style node). Convert it to output lines.
indentation = ' ' * (4 * nb_indentation)
- attributes_string = attributes_to_string(attributes)
+ attr_string = attributes_to_string(attributes)
if child_lines:
# Node must be written as an open tag.
if len(child_lines) == 1:
# If we just have 1 child line, write a compact line.
lines.append(
'%s<%s%s>%s</%s>' % (
- indentation, tag_name, (' %s' % attributes_string) if attributes_string else '',
+ indentation, node.tagName,
+ (' %s' % attr_string) if attr_string else '',
child_lines[0].lstrip(),
- tag_name))
+ node.tagName))
else:
# Otherwise, write node normally.
lines.append(
- '%s<%s%s>' % (indentation, tag_name, (' %s' % attributes_string) if attributes_string else ''))
+ '%s<%s%s>' % (indentation, node.tagName,
+ (' %s' % attr_string) if attr_string else ''))
lines.extend(child_lines)
- lines.append('%s</%s>' % (indentation, tag_name))
+ lines.append('%s</%s>' % (indentation, node.tagName))
else:
# Node can be written as a close tag.
lines.append(
- '%s<%s%s/>' % (indentation, tag_name, (' %s' % attributes_string) if attributes_string else ''))
+ '%s<%s%s/>' % (
+ indentation, node.tagName, (' %s' % attr_string) if attr_string else ''))
+
+
+def to_json_string(dictionary):
+ """ Converts to a JSON string, without escaping the '/' characters """
+ return json.dumps(dictionary).replace(r'\/', r'/')
+
+
+def minify(code):
+ """ Minifyies a Javascript / CSS file """
+ code = LINES_REGEX.sub(' ', code)
+ code = SPACES_REGEX.sub(' ', code)
+ code = STRING_REGEX.sub(' ', code)
+ return code
def main():
@@ -240,55 +390,63 @@ def main():
parser.add_argument('--name', '-n', type=str, required=True, help="Component name.")
parser.add_argument('--output', '-o', type=str, default=os.getcwd(),
help='Output folder (default to working folder).')
- parser.add_argument('--remove', '-r', action='append', help='(optional) Identifiers of nodes to remove')
- parser.add_argument('--actionable', '-a', action='append',
- help='(optional) Identifiers for which '
- 'all immediate children must have onClick and onMouseOver.')
args = parser.parse_args()
root = minidom.parse(args.input).documentElement
class_name = args.name
output_folder = args.output
- identifiers_to_remove = set(args.remove) if args.remove else set()
- action_parents = set(args.actionable) if args.actionable else set()
+ if not os.path.exists(output_folder):
+ os.makedirs(output_folder)
assert os.path.isdir(output_folder), 'Not a directory: %s' % output_folder
- extra_class_name = '%sExtra' % class_name
+ extra_class_name = '%sMetadata' % class_name
lines = []
- extra = {}
- style_lines = []
- id_to_class = {}
- extract_dom(root, 3, lines, extra, style_lines, id_to_class, identifiers_to_remove, action_parents)
- compact_extra(extra)
+ data = ExtractedData(class_name)
+ extract_dom(root, 3, lines, data)
+ compact_extra(data.extra)
output_file_name = os.path.join(output_folder, '%s.js' % class_name)
style_file_name = os.path.join(output_folder, '%s.css' % class_name)
- extra_file_name = os.path.join(output_folder, '%s.js' % extra_class_name)
+ extra_parsed_file_name = os.path.join(output_folder, '%s.js' % extra_class_name)
- if style_lines:
+ # CSS
+ if data.style_lines:
with open(style_file_name, 'w') as style_file:
- style_file.writelines(style_lines)
+ style_file.write(LICENSE_TEXT)
+ style_file.write('\n')
+ style_file.writelines(
+ minify(prepend_css_selectors('.%s' % class_name, '\n'.join(data.style_lines))))
- if extra:
- with open(extra_file_name, 'w') as extra_file:
- extra_file.write("""export const %(extra_class_name)s = %(extra_content)s;""" % {
- 'extra_class_name': extra_class_name,
- 'extra_content': json.dumps(extra, indent=4)
- })
+ # Metadata
+ if data.extra:
+ with open(extra_parsed_file_name, 'w') as extra_parsed_file:
+ extra_parsed_file.write("""%(license_text)s
+export const Coordinates = %(coordinates)s;
+export const SymbolSizes = %(symbol_sizes)s;
+export const Colors = %(colors)s;
+""" % {'license_text': LICENSE_TEXT,
+ 'coordinates': to_json_string(data.get_coordinates()),
+ 'symbol_sizes': to_json_string(data.get_symbol_sizes()),
+ 'colors': to_json_string(data.get_colors())})
- with open(output_file_name, 'w') as file:
- file.write("""/** Generated using %(program_name)s with parameters:
-%(args)s
-**/
+ # Map javacript
+ map_js_code = ("""
import React from 'react';
import PropTypes from 'prop-types';
%(style_content)s
%(extra_content)s
-
-function getClickedID(event) {
- let node = event.target;
- if (!node.id && node.parentNode.id && node.parentNode.tagName === 'g')
- node = node.parentNode;
- return node.id;
-}
+import {getClickedID, parseLocation, setInfluence} from "../common/common";
+import {Game} from "../../../diplomacy/engine/game";
+import {MapData} from "../../utils/map_data";
+import {UTILS} from "../../../diplomacy/utils/utils";
+import {Diplog} from "../../../diplomacy/utils/diplog";
+import {extendOrderBuilding} from "../../utils/order_building";
+import {Unit} from "../common/unit";
+import {Hold} from "../common/hold";
+import {Move} from "../common/move";
+import {SupportMove} from "../common/supportMove";
+import {SupportHold} from "../common/supportHold";
+import {Convoy} from "../common/convoy";
+import {Build} from "../common/build";
+import {Disband} from "../common/disband";
export class %(classname)s extends React.Component {
constructor(props) {
@@ -297,40 +455,354 @@ export class %(classname)s extends React.Component {
this.onHover = this.onHover.bind(this);
}
onClick(event) {
- if (this.props.onClick) {
- const id = getClickedID(event);
- if (id) {
- this.props.onClick(id);
+ 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)
+ throw new Error(`Cannot find a province named ${id}`);
+
+ 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
+ );
}
- onHover(event) {
+ handleHoverID(id) {
if (this.props.onHover) {
- const id = getClickedID(event);
- if (id) {
- this.props.onHover(id);
+ 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 = %(classes)s;
+ 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, influence and orders. */
+ const renderedUnits = [];
+ const renderedDislodgedUnits = [];
+ const renderedOrders = [];
+ const renderedOrders2 = [];
+ const renderedHighestOrders = [];
+ for (let power of Object.values(game.powers)) if (!power.isEliminated()) {
+ for (let unit of power.units) {
+ renderedUnits.push(
+ <Unit key={unit}
+ unit={unit}
+ powerName={power.name}
+ isDislodged={false}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}/>
+ );
+ }
+ for (let unit of Object.keys(power.retreats)) {
+ renderedDislodgedUnits.push(
+ <Unit key={unit}
+ unit={unit}
+ powerName={power.name}
+ isDislodged={true}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}/>
+ );
+ }
+ for (let center of power.centers) {
+ setInfluence(classes, mapData, center, power.name);
+ }
+ 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(
+ <Hold key={order}
+ loc={unit_loc}
+ powerName={power.name}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}
+ colors={Colors}/>
+ );
+ } else if (tokens[2] === '-') {
+ const destLoc = tokens[tokens.length - (tokens[tokens.length - 1] === 'VIA' ? 2 : 1)];
+ renderedOrders.push(
+ <Move key={order}
+ srcLoc={unit_loc}
+ dstLoc={destLoc}
+ powerName={power.name}
+ phaseType={game.getPhaseType()}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}
+ colors={Colors}/>
+ );
+ } else if (tokens[2] === 'S') {
+ const destLoc = tokens[tokens.length - 1];
+ if (tokens.includes('-')) {
+ const srcLoc = tokens[4];
+ renderedOrders2.push(
+ <SupportMove key={order}
+ loc={unit_loc}
+ srcLoc={srcLoc}
+ dstLoc={destLoc}
+ powerName={power.name}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}
+ colors={Colors}/>
+ );
+ } else {
+ renderedOrders2.push(
+ <SupportHold key={order}
+ loc={unit_loc}
+ dstLoc={destLoc}
+ powerName={power.name}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}
+ colors={Colors}/>
+ );
+ }
+ } else if (tokens[2] === 'C') {
+ const srcLoc = tokens[4];
+ const destLoc = tokens[tokens.length - 1];
+ if ((srcLoc !== destLoc) && (tokens.includes('-'))) {
+ renderedOrders2.push(
+ <Convoy key={order}
+ loc={unit_loc}
+ srcLoc={srcLoc}
+ dstLoc={destLoc}
+ powerName={power.name}
+ coordinates={Coordinates} colors={Colors}
+ symbolSizes={SymbolSizes}/>
+ );
+ }
+ } else if (tokens[2] === 'B') {
+ renderedHighestOrders.push(
+ <Build key={order}
+ unitType={tokens[0]}
+ loc={unit_loc}
+ powerName={power.name}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}/>
+ );
+ } else if (tokens[2] === 'D') {
+ renderedHighestOrders.push(
+ <Disband key={order}
+ loc={unit_loc}
+ phaseType={game.getPhaseType()}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}/>
+ );
+ } else if (tokens[2] === 'R') {
+ const destLoc = tokens[3];
+ renderedOrders.push(
+ <Move key={order}
+ srcLoc={unit_loc}
+ dstLoc={destLoc}
+ powerName={power.name}
+ phaseType={game.getPhaseType()}
+ coordinates={Coordinates}
+ symbolSizes={SymbolSizes}
+ colors={Colors}/>
+ );
+ } else {
+ throw new Error(`Unknown error to render (${order}).`);
+ }
+ }
+ }
+ }
+
+ 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';
+ }
+ }
+ }
+
+ if (this.props.showAbbreviations === false) {
+ classes['BriefLabelLayer'] = 'visibilityHidden';
+ }
+
return (
%(svg)s
);
}
}
%(classname)s.propTypes = {
- onHover: PropTypes.func
+ 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,
+ showAbbreviations: PropTypes.bool
};
-""" % {
- 'style_content': "import './%s.css';" % class_name if style_lines else '',
- 'extra_content': "import {%s} from './%s';" % (extra_class_name, extra_class_name) if extra else '',
- 'classname': class_name,
- 'classes': json.dumps(id_to_class),
- 'svg': '\n'.join(lines),
- 'program_name': sys.argv[0],
- 'args': args
-})
+""" % {'style_content': "import './%s.css';" % class_name if data.style_lines else '',
+ 'extra_content': 'import {Coordinates, SymbolSizes, Colors} from "./%s";' %
+ (extra_class_name) if data.extra else '',
+ 'classname': class_name,
+ 'classes': to_json_string(data.id_to_class),
+ 'svg': '\n'.join(lines)})
+
+ # Adding license and minifying
+ map_js_code = LICENSE_TEXT \
+ + '\n/** Generated with parameters: %s **/\n' % args \
+ + minify(map_js_code) \
+ + '// eslint-disable-line semi'
+
+ # Writing to disk
+ with open(output_file_name, 'w') as file:
+ file.write(map_js_code)
if __name__ == '__main__':