Compare commits

...

13 Commits

  1. 2
      .pylintrc
  2. 14
      Dockerfile
  3. 23
      conftest.py
  4. 1
      docker/config/site.cfg
  5. 14
      requirements-dev.txt
  6. 13
      requirements.txt
  7. 2
      setup.py
  8. 5
      vasl_templates/about.py
  9. 3
      vasl_templates/main.py
  10. 26
      vasl_templates/ui/about.ui
  11. 3
      vasl_templates/webapp/config/constants.py
  12. 2
      vasl_templates/webapp/config/site.cfg.example
  13. 3
      vasl_templates/webapp/data/default-template-pack/national-capabilities.json
  14. 6
      vasl_templates/webapp/data/ordnance/french.json
  15. 4
      vasl_templates/webapp/data/vasl-6.6.6/piece-info.json
  16. 72
      vasl_templates/webapp/data/vasl-6.6.7/expected-multiple-images.json
  17. 2
      vasl_templates/webapp/data/vasl-6.6.7/online-counter-images.json
  18. 71
      vasl_templates/webapp/data/vasl-6.6.7/piece-info.json
  19. 147
      vasl_templates/webapp/data/vasl-6.6.7/vasl-overrides.json
  20. 6
      vasl_templates/webapp/main.py
  21. 2
      vasl_templates/webapp/scenarios.py
  22. BIN
      vasl_templates/webapp/static/help/images/asl-rulebook2.png
  23. BIN
      vasl_templates/webapp/static/help/images/asl-rulebook2.small.png
  24. 31
      vasl_templates/webapp/static/help/index.html
  25. 2
      vasl_templates/webapp/static/main.js
  26. 83
      vasl_templates/webapp/templates/index.html
  27. 2
      vasl_templates/webapp/tests/control_tests.py
  28. 2
      vasl_templates/webapp/tests/control_tests_servicer.py
  29. 0
      vasl_templates/webapp/tests/fixtures/counters/6.5.0.txt
  30. 0
      vasl_templates/webapp/tests/fixtures/counters/6.5.1.txt
  31. 5
      vasl_templates/webapp/tests/fixtures/counters/6.6.0.txt
  32. 5
      vasl_templates/webapp/tests/fixtures/counters/6.6.1.txt
  33. 5
      vasl_templates/webapp/tests/fixtures/counters/6.6.2.txt
  34. 5
      vasl_templates/webapp/tests/fixtures/counters/6.6.3.txt
  35. 5
      vasl_templates/webapp/tests/fixtures/counters/6.6.4.txt
  36. 5
      vasl_templates/webapp/tests/fixtures/counters/6.6.5.txt
  37. 5
      vasl_templates/webapp/tests/fixtures/counters/6.6.6.txt
  38. 1363
      vasl_templates/webapp/tests/fixtures/counters/6.6.7.txt
  39. 1610
      vasl_templates/webapp/tests/proto/generated/control_tests_pb2.py
  40. 2
      vasl_templates/webapp/tests/proto/set_data_dir_request.py
  41. 2
      vasl_templates/webapp/tests/proto/set_default_template_pack_request.py
  42. 2
      vasl_templates/webapp/tests/proto/set_vasl_version_request.py
  43. 2
      vasl_templates/webapp/tests/proto/set_veh_ord_notes_dir_request.py
  44. 4
      vasl_templates/webapp/tests/test_counters.py
  45. 4
      vasl_templates/webapp/tests/test_national_capabilities.py
  46. 5
      vasl_templates/webapp/tests/test_scenario_search.py
  47. 2
      vasl_templates/webapp/tests/test_vasl_extensions.py
  48. 38
      vasl_templates/webapp/tests/test_vassal.py
  49. 9
      vasl_templates/webapp/tests/utils.py
  50. 4
      vasl_templates/webapp/vasl_mod.py
  51. 10
      vasl_templates/webapp/vassal.py
  52. 14
      vasl_templates/webapp/webdriver.py
  53. BIN
      vassal-shim/release/vassal-shim.jar
  54. 8
      vassal-shim/src/vassal_shim/VassalShim.java

@ -480,4 +480,4 @@ known-third-party=enchant
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
overgeneral-exceptions=builtins.Exception

@ -1,28 +1,28 @@
# NOTE: Use the run-container.sh script to build and launch this container.
# NOTE: Multi-stage builds require Docker >= 17.05.
FROM rockylinux:8.5 AS base
FROM rockylinux:9.1 AS base
# update packages and install requirements
RUN dnf -y upgrade-minimal && \
dnf install -y python39
dnf install -y python3.11
# NOTE: We don't need the following stuff for the build step, but it's nice to not have to re-install
# it all every time we change the requirements :-/
# install Java
ARG JAVA_URL=https://download.oracle.com/java/17/archive/jdk-17.0.2_linux-x64_bin.tar.gz
RUN curl -s "$JAVA_URL" | tar -xz -C /usr/bin/
RUN dnf install -y java-17-openjdk
# install Firefox
ARG FIREFOX_URL=https://ftp.mozilla.org/pub/firefox/releases/94.0.2/linux-x86_64/en-US/firefox-94.0.2.tar.bz2
# NOTE: We could install this using dnf, but the version of geckodriver needs to match it.
ARG FIREFOX_URL=https://ftp.mozilla.org/pub/firefox/releases/117.0.1/linux-x86_64/en-US/firefox-117.0.1.tar.bz2
RUN dnf install -y bzip2 xorg-x11-server-Xvfb gtk3 dbus-glib && \
curl -s "$FIREFOX_URL" | tar -jx -C /usr/local/ && \
ln -s /usr/local/firefox/firefox /usr/bin/firefox && \
echo "exclude=firefox" >>/etc/dnf/dnf.conf
# install geckodriver
ARG GECKODRIVER_URL=https://github.com/mozilla/geckodriver/releases/download/v0.31.0/geckodriver-v0.31.0-linux64.tar.gz
ARG GECKODRIVER_URL=https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-linux64.tar.gz
RUN curl -sL "$GECKODRIVER_URL" | tar -xz -C /usr/bin/
# clean up
@ -33,7 +33,7 @@ RUN dnf clean all
FROM base AS build
# set up a virtualenv
RUN python3 -m venv /opt/venv
RUN python3.11 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --upgrade pip

@ -1,6 +1,7 @@
""" pytest support functions. """
import os
import shutil
import threading
import json
import re
@ -10,8 +11,6 @@ import urllib.request
from urllib.error import URLError
import pytest
from flask import url_for
from vasl_templates.webapp import app
from vasl_templates.webapp.tests import utils
from vasl_templates.webapp.tests.control_tests import ControlTests
@ -19,6 +18,7 @@ from vasl_templates.webapp.tests.control_tests import ControlTests
FLASK_WEBAPP_PORT = 5011
_pytest_options = None
_orig_url_for = app.url_for
# ---------------------------------------------------------------------
@ -129,7 +129,9 @@ def _make_webapp():
# NOTE: It's not a bad idea to bypass the clipboard, even when running in a browser,
# to avoid problems if something else uses the clipboard while the tests are running.
kwargs["store_clipboard"] = 1
url = url_for( endpoint, _external=True, **kwargs )
if kwargs.get( "_external" ) is None:
kwargs["_external"] = True
url = _orig_url_for( endpoint, **kwargs )
url = url.replace( "http://localhost", app.base_url )
return url
app.url_for = make_webapp_url
@ -187,9 +189,9 @@ def _make_webapp():
# we won't have even got this far, since it needs to be there to drive the browser.
# NOTE: This will have no effect if we're talking to a remote server, but we can live with that.
if _pytest_options.webdriver == "firefox":
app.config[ "WEBDRIVER_PATH" ] = "geckodriver"
app.config[ "WEBDRIVER_PATH" ] = shutil.which( "geckodriver" )
elif _pytest_options.webdriver == "chrome":
app.config[ "WEBDRIVER_PATH" ] = "chromedriver"
app.config[ "WEBDRIVER_PATH" ] = shutil.which( "chromedriver" )
return app
@ -216,15 +218,18 @@ def webdriver( request ):
# initialize
driver = request.config.getoption( "--webdriver" )
from selenium import webdriver as wb
log_fname = os.path.join( tempfile.gettempdir(), "webdriver-pytest.log" )
if driver == "firefox":
service = wb.firefox.service.Service(
log_output = os.path.join( tempfile.gettempdir(), "webdriver-pytest.log" )
)
options = wb.FirefoxOptions()
options.headless = _pytest_options.headless
service = wb.firefox.service.Service( log_path=log_fname )
if _pytest_options.headless:
options.add_argument( "--headless" )
driver = wb.Firefox( options=options, service=service )
elif driver == "chrome":
options = wb.ChromeOptions()
options.headless = _pytest_options.headless
if _pytest_options.headless:
options.add_argument( "--headless" )
options.add_argument( "--disable-gpu" )
driver = wb.Chrome( options=options )
else:

@ -2,5 +2,4 @@
IS_CONTAINER = 1
JAVA_PATH = /usr/bin/jdk-17.0.2/bin/java
WEBDRIVER_PATH = /usr/bin/geckodriver

@ -1,7 +1,7 @@
pytest==7.1.2
grpcio-tools==1.46.3
tabulate==0.8.9
lxml==4.9.0
pylint==2.14.1
pytest-pylint==0.18.0
pyinstaller==5.1
pytest==7.4.2
grpcio-tools==1.58.0
tabulate==0.9.0
lxml==4.9.3
pylint==2.17.5
pytest-pylint==0.19.0
pyinstaller==5.13.2

@ -1,9 +1,10 @@
# python 3.10.7
# python 3.11.4
flask==2.1.2
pyyaml==6.0
pillow==9.1.1
selenium==4.2.0
flask==2.3.3
pyyaml==6.0.1
# NOTE: Pillow 9.5.0 is the last version that provides 32-bit wheels.
pillow==9.5.0
selenium==4.12.0
waitress==2.1.2
appdirs==1.4.4
click==8.1.3
click==8.1.7

@ -28,7 +28,7 @@ def parse_requirements( fname ):
setup(
name = "vasl_templates",
version = "1.11", # nb: also update constants.py
version = "1.12", # nb: also update constants.py
description = "Create HTML snippets for use in VASL.",
license = "AGPLv3",
url = "https://code.pacman-ghost.com/public/vasl-templates",

@ -11,7 +11,7 @@ from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices, QIcon, QCursor
from PyQt5.QtWidgets import QDialog
from vasl_templates.webapp.config.constants import APP_NAME, APP_VERSION, APP_HOME_URL, IS_FROZEN
from vasl_templates.webapp.config.constants import APP_NAME, APP_VERSION, APP_HOME_URL, APP_ISSUES_URL, IS_FROZEN
from vasl_templates.utils import get_build_info
# ---------------------------------------------------------------------
@ -60,6 +60,9 @@ class AboutDialog( QDialog ):
self.home_url.setText( "Visit us at <a href='{}'>{}</a>.".format(
APP_HOME_URL, mo.group(1) if mo else APP_HOME_URL
) )
self.issues_url.setText( "Report a bug, request a feature, ask a question <a href='{}'>here</a>.".format(
APP_ISSUES_URL
) )
def on_app_icon_clicked( self, event ): #pylint: disable=unused-argument
"""Click handler."""

@ -243,6 +243,9 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin
if is_windows():
QApplication.setStyle( "windowsvista" )
# disable the context help button in the title bar (Windows only)
QApplication.setAttribute( PyQt5.QtCore.Qt.AA_DisableWindowContextHelpButton )
# check if we should disable the embedded browser
disable_browser = webapp.config.get( "DISABLE_WEBENGINEVIEW" )

@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>460</width>
<height>195</height>
<height>215</height>
</rect>
</property>
<property name="windowTitle">
@ -88,7 +88,7 @@
<property name="geometry">
<rect>
<x>19</x>
<y>152</y>
<y>172</y>
<width>425</width>
<height>31</height>
</rect>
@ -147,6 +147,28 @@
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="issues_url">
<property name="geometry">
<rect>
<x>80</x>
<y>142</y>
<width>361</width>
<height>21</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>*** ISSUES URL ***</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="license">
<property name="geometry">
<rect>

@ -4,9 +4,10 @@ import sys
import os
APP_NAME = "VASL Templates"
APP_VERSION = "v1.11" # nb: also update setup.py
APP_VERSION = "v1.12" # nb: also update setup.py
APP_DESCRIPTION = "Generate HTML for use in VASL scenarios."
APP_HOME_URL = "https://vasl-templates.org"
APP_ISSUES_URL = "https://code.pacman-ghost.com/public/vasl-templates/issues"
if getattr( sys, "frozen", False ):
IS_FROZEN = True

@ -2,7 +2,7 @@
; configure VASSAL and VASL
VASSAL_DIR = ...configure the VASSAL installation directory...
VASL_MOD = ...configure the VASL module (e.g. vasl-6.6.6.vmod)...
VASL_MOD = ...configure the VASL module (e.g. vasl-6.6.7.vmod)...
VASL_EXTNS_DIR = ...configure the VASL extensions directory...
BOARDS_DIR = ...configure the VASL boards directory...

@ -306,7 +306,8 @@
},
"kfw-rok": {
"th_color": "{! -08/1950 = Red | 09/1950-04/1951 = Red (ROK) ; Black (KMC) | 05/1951- = Black | ??? !}",
"_comment_": "Errata (ASLJ 13 p48): KMC: '9/50+ black' s.b. '8/50+ black",
"th_color": "{! -07/1950 = Red | 08/1950-04/1951 = Red (ROK) ; Black (KMC) | 05/1951- = Black | ??? !}",
"oba": [ "{! 06/1950- = 10B | ??? !}", "3R",
"{? 09/1950- | Plentiful Ammo included | Plentiful Ammo included (KMC) | Plentiful Ammo included (ROK: 9/50+) ?}",
"{! 06/1950-08/1950 = ROK: 6B/3R !}"

@ -5,7 +5,7 @@
"note_number": "1",
"notes": [ "A", "B", "C\u2020", "E" ],
"id": "fr/o:000",
"gpid": 1636
"gpid": 12830
},
{ "name": "Mortier de 60 mle 35",
"type": "MTR",
@ -13,7 +13,7 @@
"note_number": "2\u2020",
"notes": [ "A", "B", "V" ],
"id": "fr/o:001",
"gpid": 1641
"gpid": 1633
},
{ "name": "Mortier de 81 mle 27/31",
"type": "MTR",
@ -22,7 +22,7 @@
"note_number": "3\u2020",
"notes": [ "A", "B", "D", "V" ],
"id": "fr/o:002",
"gpid": 1667
"gpid": [ 1665, 1667 ]
},
{ "name": "Fusil Antichar Boys",
"type": "ATR",

@ -16,9 +16,9 @@
"11359": {"is_small": true},
"3b5:11259": {"is_small": true},
"1632": {"is_small": true},
"1636": {"is_small": true},
"1641": {"is_small": true},
"1633": {"is_small": true},
"1648": {"is_small": true},
"12830": {"is_small": true},
"1982": {"is_small": true},
"1983": {"is_small": true},
"1984": {"is_small": true},

@ -0,0 +1,72 @@
{
"1555": {
"name": "2pdr Portee",
"front_images": [ "br/vehicles/portee.gif", "br/vehicles/portee0.gif" ],
"back_images": null
},
"2212": {
"name": "76* INF FRC",
"front_images": [ "al/gun/alINF76.gif", "al/gun/alINF76u.gif" ],
"back_images": "al/gun/alINF76b.gif"
},
"2698": {
"name": "SPW 251/10",
"front_images": "ge/veh/spw25110.gif",
"back_images": [ "No_ATR.gif", "No_PSK.gif" ]
},
"6765": {
"name": "81* MTR Krh/32",
"front_images": "fi/gun/fi81mmMTR.png",
"back_images": [ "fi/gun/fi81mmMTR.png", "fi/gun/fi81mmMTRB.png" ]
},
"6782": {
"name": "81* MTR Savu M42",
"front_images": [ "fi/gun/fi81mmMTR SavuB.png", "fi/gun/fi81mmMTR Savu.png" ],
"back_images": "fi/gun/fi81mmMTR SavuB.png"
},
"6797": {
"name": "20L (4) AA (g)",
"front_images": [ "fi/gun/fi20L4.png", "fi/gun/fi20L4L.png" ]
},
"6801": {
"name": "20L (6) AA (g)",
"front_images": "fi/gun/fi20L6.png",
"back_images": [ "fi/gun/fi20L6.png", "fi/gun/fi20L6L.png" ]
},
"7409": {
"name": "76 ItK/28 B(s)",
"front_images": "fi/gun/fiAA76L.png",
"back_images": [ "fi/gun/fiAA76L.png", "fi/gun/fiAA76LB.png" ]
},
"adf:1828": {
"name": "105 ART wz.29",
"front_images": "po/gun/poARTwz29-BFP.png",
"back_images": [ "po/gun/poARTwz29-BFP.png", "po/gun/poARTwz29-BFPb.png" ]
},
"adf:1829": {
"name": "120* ART wz09.31",
"front_images": "po/gun/poARTwz0931-BFP.png",
"back_images": [ "po/gun/poARTwz0931-BFP.png", "po/gun/poARTwz0931-BFPb.png" ]
},
"adf:1830": {
"name": "155 ART wz.17",
"front_images": "po/gun/poARTwz17-BFP.png",
"back_images": [ "po/gun/poARTwz17-BFP.png", "po/gun/poARTwz17-BFPb.png" ]
},
"3b5:3676": {
"name": "M19A1 MGMC",
"front_images": [ "us/veh/usM19A1MGMC(trailer)KFW.png", "us/veh/usM19A1MGMC(KFW).png" ],
"back_images": null
}
}

@ -0,0 +1,71 @@
{
"6996": {"is_small": true},
"485": {"is_small": true},
"850": {"is_small": true},
"849": {"is_small": true},
"12689": {"is_small": true},
"856": {"is_small": true},
"857": {"is_small": true},
"11336": {"is_small": true},
"858": {"is_small": true},
"11337": {"is_small": true},
"1149": {"is_small": true},
"1153": {"is_small": true},
"12687": {"is_small": true},
"3b5:7613": {"is_small": true},
"11359": {"is_small": true},
"3b5:11259": {"is_small": true},
"1632": {"is_small": true},
"1633": {"is_small": true},
"1648": {"is_small": true},
"12830": {"is_small": true},
"1982": {"is_small": true},
"1983": {"is_small": true},
"1984": {"is_small": true},
"1985": {"is_small": true},
"1986": {"is_small": true},
"1987": {"is_small": true},
"1988": {"is_small": true},
"2172": {"is_small": true},
"2173": {"is_small": true},
"2176": {"is_small": true},
"2179": {"is_small": true},
"11391": {"is_small": true},
"11392": {"is_small": true},
"11395": {"is_small": true},
"11396": {"is_small": true},
"11440": {"is_small": true},
"3b5:8401": {"is_small": true},
"3b5:8402": {"is_small": true},
"2465": {"is_small": true},
"2474": {"is_small": true},
"3252": {"is_small": true},
"3253": {"is_small": true},
"3263": {"is_small": true},
"3422": {"is_small": true},
"3428": {"is_small": true},
"6730": {"is_small": true},
"3605": {"is_small": true},
"3608": {"is_small": true},
"6763": {"is_small": true},
"3679": {"is_small": true},
"3680": {"is_small": true},
"3681": {"is_small": true},
"3682": {"is_small": true},
"3691": {"is_small": true},
"3692": {"is_small": true},
"3959": {"is_small": true},
"11558": {"is_small": true},
"11559": {"is_small": true},
"3b5:10150": {"is_small": true},
"3b5:10151": {"is_small": true},
"11600": {"is_small": true},
"11604": {"is_small": true},
"3b5:7871": {"is_small": true},
"adf:1948": {"is_small": true},
"adf:75": {"is_small": true},
"adf:77": {"is_small": true},
"adf:76": {"is_small": true},
"adf:1407": {"is_small": true},
"08d:75": {"is_small": true}
}

@ -0,0 +1,147 @@
{
"2474": {
"expected": {
"name": "Goliath",
"front_images": [ "ge/gegol.gif", "ge/gegolb.gif" ],
"back_images": null
},
"updated": {
"front_images": "ge/gegol.gif"
}
},
"1555": {
"expected": {
"name": "2pdr Portee",
"front_images": "br/vehicles/portee.gif",
"back_images": [ "br/vehicles/portee.gif", "br/vehicles/portee0.gif" ]
},
"updated": {
"front_images": [ "br/vehicles/portee.gif", "br/vehicles/portee0.gif" ],
"back_images": null
}
},
"3463": {
"expected": {
"name": "75L AA 75/46",
"front_images": [ "it/gun/itAA7546.gif", "it/gun/itAA7546b.gif" ],
"back_images": [ "it/gun/itAA7546b.gif", "it/gun/itAA7546lb.gif" ]
},
"updated": {
"front_images": "it/gun/itAA7546.gif",
"back_images": "it/gun/itAA7546b.gif"
}
},
"3776": {
"expected": {
"name": "37* INF Skoda IG",
"front_images": [ "ax/gun/buIN37s.gif", "ax/gun/buIN37s2.gif" ],
"back_images": "ax/gun/buIN37sb.gif"
},
"updated": {
"front_images": "ax/gun/buIN37s.gif"
}
},
"3777": {
"expected": {
"name": "70* INF Skoda IG",
"front_images": [ "ax/gun/buIN37s.gif", "ax/gun/buIN37s2.gif" ],
"back_images": "ax/gun/buIN37sb.gif"
},
"updated": {
"front_images": "ax/gun/buIN37s2.gif"
}
},
"6802": {
"expected": {
"name": "20L (4) AA",
"front_images": [ "fi/gun/fi20L4 _2.png", "fi/gun/fi20L4 _2 LIM.png" ],
"back_images": null
},
"updated": {
"front_images": "fi/gun/fi20L4 _2.png"
}
},
"6803": {
"expected": {
"name": "20L VKT (12) AA",
"front_images": [ "fi/gun/fi20L12.png", "fi/gun/fi20L12L.png" ],
"back_images": null
},
"updated": {
"front_images": "fi/gun/fi20L12.png"
}
},
"6804": {
"expected": {
"name": "40L Bofors AA (s)",
"front_images": [ "fi/gun/fi40L.png", "fi/gun/fi40LL.png" ],
"back_images": null
},
"updated": {
"front_images": "fi/gun/fi40L.png"
}
},
"adf:1824": {
"expected": {
"name": "37L AT PTP obr. 30",
"front_images": "ru/gun/ruAT37L.gif",
"back_images": "ru/gun/ruAT37Lb.gif"
},
"updated": {
"front_images": "ru/gun/ru37LPTPobr30.png"
}
},
"adf:1822": {
"expected": {
"name": "37* INF PP obr. 15R",
"front_images": "ru/gun/ruINF37s.gif",
"back_images": "ru/gun/ruINF37sb.gif"
},
"updated": {
"front_images": "ru/gun/ru37PPobr15R.png"
}
},
"adf:1823": {
"expected": {
"name": "76* INF PP obr. 27",
"front_images": "ru/gun/ruINF76s.gif",
"back_images": "ru/gun/ruINF76sb.gif"
},
"updated": {
"front_images": "ru/gun/ru76PPobr27.png"
}
},
"3b5:10093": {
"expected": {
"name": "SL truck",
"front_images": [ "sh/SL3b(KFW).png", "sh/SL4b(KFW).png", "sh/SL5b(KFW).png", "sh/SL6b(KFW).png", "sh/SL1b(KFW).png", "sh/SL2b(KFW).png" ],
"back_images": [ "sh/SL3(KFW).png", "sh/SL4(KFW).png", "sh/SL5(KFW).png", "sh/SL6(KFW).png", "sh/SL1(KFW).png", "sh/SL2(KFW).png" ]
},
"updated": {
"front_images": "us/veh/usSearchlight(KFW).png",
"back_images": null
}
},
"08d:75": {
"expected": {
"name": "RCL 75*",
"front_images": "amrcl75-malf.png",
"back_images": "dm-75rcl.gif"
},
"updated": {
"front_images": "amrcl75.png"
}
}
}

@ -235,9 +235,13 @@ def get_program_info():
return tstamp + timedelta( minutes=tz_offset )
# set the basic details
try:
vassal_version = VassalShim.get_version()
except Exception: #pylint: disable=broad-except
vassal_version = "???"
params = {
"APP_VERSION": vasl_templates.webapp.config.constants.APP_VERSION,
"VASSAL_VERSION": VassalShim.get_version()
"VASSAL_VERSION": vassal_version
}
build_git_info = get_build_git_info()
if build_git_info:

@ -539,7 +539,7 @@ def prepare_asa_upload(): #pylint: disable=too-many-locals
max_size = parse_int( app.config.get( "ASA_MAX_SCREENSHOT_SIZE" ), 200 ) * 1024
if len(screenshot_data) > max_size:
ratio = math.sqrt( float(max_size) / len(screenshot_data) )
img = img.resize( ( int(img.width * ratio), int(img.height * ratio) ), Image.ANTIALIAS )
img = img.resize( ( int(img.width * ratio), int(img.height * ratio) ), Image.LANCZOS )
# add a border
border_size = parse_int( app.config.get( "ASA_SCREENSHOT_BORDER_SIZE" ), 5 )
img = ImageOps.expand( img, border_size, (255,255,255,255) )

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -54,8 +54,8 @@ If you have Docker installed, the webapp can be run in a container e.g.
<div class="code">
./run-container.sh \
--port 5010 \
--vassal ~/vassal-3.6.14/ \
--vasl ~/vasl/vasl-6.6.6.vmod \
--vassal ~/vassal-3.7.5/ \
--vasl ~/vasl/vasl-6.6.7.vmod \
--vasl-extensions ~/vasl/extensions/ \
--boards ~/vasl/boards/ \
--chapter-h ~/vasl/chapter-h/ \
@ -73,7 +73,7 @@ If you have Docker installed, the webapp can be run in a container e.g.
<p> You can also run the program directly from the source code. Get a copy from <a href="https://code.pacman-ghost.com/public/vasl-templates">here</a> in the usual way, by <tt>git clone</tt>'ing it, or downloading a ZIP and unpacking it somewhere.
<p> The web server was written and tested using Python 3.10.7, but it doesn't do anything particularly funky, so any recent version of Python <em>should</em> work.
<p> The web server was written and tested using Python 3.11.4, but it doesn't do anything particularly funky, so any recent version of Python <em>should</em> work.
<p> While not essential, it is <em>strongly</em> recommended that you set up a <a href="https://virtualenv.pypa.io/en/stable/">virtual environment</a> first. Then <tt>cd</tt> into the project's root directory, and install the requirements (note the trailing period):
<div class="code">
@ -101,6 +101,18 @@ pip install --editable .[gui]
<p> Then, run the <tt>vasl-templates</tt> command.
<a name="asl-rulebook2"></a>
<h2> Integrating with <tt>asl-rulebook2</tt></h2>
<p> <img src="images/asl-rulebook2.png" class="preview" style="float:right;">
If you are using <a href="https://code.pacman-ghost.com/public/asl-rulebook2"><tt>asl-rulebook2</tt></a>, you can connect it to this program, and vehicle/ordnance entries will show an icon that you can click to open their Chapter H entries.
<p> In your <tt>site.cfg</tt>, configure the base URL of the <tt>asl-rulebook2</tt> application e.g.
<div class="code">
[Site Config]
ASL_RULEBOOK2_BASE_URL = http://localhost:5020
</div>
<a name="install-webdriver"></a>
<h2>Installing a webdriver</h2>
@ -154,11 +166,11 @@ Configuring the program
<p> Choose <em>Settings</em> from the <em>File</em> menu and configure the highlighted settings. As a guide, here are some example settings:
<table class="settings">
<tr> <td class="key"> VASSAL&nbsp;installation: </td> <td class="val"> <nobr>C:\Program Files\VASSAL-3.6.14\</nobr>
<tr> <td class="key"> VASL&nbsp;module: </td> <td class="val"> <nobr>C:\bin\vasl\vasl-6.6.6.vmod</nobr>
<tr> <td class="key"> VASSAL&nbsp;installation: </td> <td class="val"> <nobr>C:\Program Files\VASSAL-3.7.5\</nobr>
<tr> <td class="key"> VASL&nbsp;module: </td> <td class="val"> <nobr>C:\bin\vasl\vasl-6.6.7.vmod</nobr>
<tr> <td class="key"> VASL&nbsp;extensions: </td> <td class="val"> <nobr>C:\bin\vasl\extensions\</nobr>
<tr> <td class="key"> VASL&nbsp;boards: </td> <td class="val"> <nobr>C:\bin\vasl\boards\</nobr>
<tr> <td class="key" valign="top"> Java: </td> <td class="val"> <nobr>C:\Program Files\VASSAL-3.6.14\jre\bin\java.exe</nobr>
<tr> <td class="key" valign="top"> Java: </td> <td class="val"> <nobr>C:\Program Files\VASSAL-3.7.5\jre\bin\java.exe</nobr>
<div class="hint" style="margin:0.5em 1em;"> Leave this field blank to use the Java that comes with VASSAL (Windows only), or on your PATH. </div>
<tr> <td class="key"> Web&nbsp;driver: </td> <td class="val"> <nobr>C:\bin\geckodriver.exe</nobr>
</table>
@ -548,6 +560,13 @@ When you're writing a new template file, it would be painful to have to ZIP up a
<a name="dev-setup"></a>
<h2 style="margin-top:0;"> Setting up </h2>
<div class="info" style="float:right;max-width:40%;margin-left:1em;">
If you want to run the GUI, you will also need to install the GUI requirements:
<div class="code">
pip install --editable .[gui]
</div>
</div>
<p> After cloning the repo, install the <em>developer</em> requirements:
<div class="code">
pip install --editable .[dev]

@ -77,8 +77,6 @@ $(document).ready( function () {
program_info: { label: "Program info", icon: imagesDir+"/info.png", action: show_program_info },
show_help: { label: "Help", icon: imagesDir+"/help.png", action: show_help },
} ;
if ( getUrlParam( "pyqt" ) )
delete menuItems.program_info ;
$menu.popmenu( menuItems ) ;
// nb: we only show the popmenu on left click (not the normal right-click)
$menu.off( "contextmenu" ) ;

@ -141,34 +141,66 @@
<script src="{{url_for('static',filename='trumbowyg/plugins/table/trumbowyg.table.min.js')}}"></script>
<script src="{{url_for('static',filename='DOMPurify/purify.min.js')}}"></script>
<script src="{{url_for('static',filename='utils.js')}}"></script>
<script>
function removeUrlTestParams( url )
{
// remove any URL parameters
// NOTE: We can't use URL.searchParams since we might be passed a relative URL.
// FUDGE! The test suite is giving us absolute, encoded URL's :-/
url = strReplaceAll( url, "&amp;", "&" ) ;
var match = url.match( new RegExp( "^http://.+?:\\d+?/" ) ) ;
if ( match )
url = url.substring( match[0].length - 1 ) ;
var pos = url.indexOf( "?" ) ;
if ( pos >= 0 ) {
params = url.substring( pos+1 ).split( "&" ) ;
url = url.substring( 0, pos ) ;
var nKeeps = 0 ;
params.forEach( function( param ) {
var keep = true ;
[ "store_msgs", "disable_close_window_check", "store_clipboard" ].forEach( function( key ) {
if ( param.substring( 0, key.length+1 ) === key + "=" )
keep = false ;
} ) ;
if ( keep ) {
url += ( nKeeps === 0 ) ? "?" : "&" ;
url += param ;
nKeeps += 1 ;
}
} ) ;
}
return url ;
}
gAppName = "{{APP_NAME}}" ;
gAppVersion = "{{APP_VERSION}}" ;
gImagesBaseUrl = "{{url_for('static',filename='images')}}" ;
gAppConfigUrl = "{{url_for('get_app_config')}}" ;
gGetStartupMsgsUrl = "{{url_for('get_startup_msgs')}}" ;
gGetTemplatePackUrl = "{{url_for('get_template_pack')}}" ;
gGetAslRulebook2VoNoteTargetsUrl = "{{url_for('get_asl_rulebook2_vo_note_targets')}}" ;
gShowAslRulebook2VoNoteUrl = "{{url_for('show_asl_rulebook2_target',target='TARGET')}}" ;
gGetDefaultScenarioUrl = "{{url_for('get_default_scenario')}}" ;
gVehicleListingsUrl = "{{url_for('get_vehicle_listings',merge_common=1)}}" ;
gOrdnanceListingsUrl = "{{url_for('get_ordnance_listings',merge_common=1)}}" ;
gVehicleNotesUrl = "{{url_for('get_vehicle_notes')}}" ;
gOrdnanceNotesUrl = "{{url_for('get_ordnance_notes')}}" ;
gGetVaslPieceInfoUrl = "{{url_for('get_vasl_piece_info')}}" ;
gGetOnlineCounterImagesUrl = "{{url_for('get_online_counter_images')}}" ;
gGetScenarioIndexUrl = "{{url_for('get_scenario_index')}}" ;
gGetScenarioUrl = "{{url_for('get_scenario',scenario_id='ID')}}" ;
gGetScenarioCardUrl = "{{url_for('get_scenario_card',scenario_id='ID')}}" ;
gGetRoarScenarioIndexUrl = "{{url_for('get_roar_scenario_index')}}" ;
gAnalyzeVsavUrl = "{{url_for('analyze_vsav')}}" ;
gAnalyzeVlogsUrl = "{{url_for('analyze_vlogs')}}" ;
gUpdateVsavUrl = "{{url_for('update_vsav')}}" ;
gPrepareAsaUploadUrl = "{{url_for('prepare_asa_upload')}}" ;
gOnSuccessfulAsaUploadUrl = "{{url_for('on_successful_asa_upload',scenario_id='ID')}}" ;
gMakeSnippetImageUrl = "{{url_for('make_snippet_image')}}" ;
gGetProgramInfoUrl = "{{url_for('get_program_info')}}" ;
gHelpUrl = "{{url_for('show_help')}}" ;
gImagesBaseUrl = removeUrlTestParams( "{{url_for('static',filename='images')}}" ) ;
gAppConfigUrl = removeUrlTestParams( "{{url_for('get_app_config')}}" ) ;
gGetStartupMsgsUrl = removeUrlTestParams( "{{url_for('get_startup_msgs')}}" ) ;
gGetTemplatePackUrl = removeUrlTestParams( "{{url_for('get_template_pack')}}" ) ;
gGetAslRulebook2VoNoteTargetsUrl = removeUrlTestParams( "{{url_for('get_asl_rulebook2_vo_note_targets')}}" ) ;
gShowAslRulebook2VoNoteUrl = removeUrlTestParams( "{{url_for('show_asl_rulebook2_target',target='TARGET')}}" ) ;
gGetDefaultScenarioUrl = removeUrlTestParams( "{{url_for('get_default_scenario')}}" ) ;
gVehicleListingsUrl = removeUrlTestParams( "{{url_for('get_vehicle_listings',merge_common=1)}}" ) ;
gOrdnanceListingsUrl = removeUrlTestParams( "{{url_for('get_ordnance_listings',merge_common=1)}}" ) ;
gVehicleNotesUrl = removeUrlTestParams( "{{url_for('get_vehicle_notes')}}" ) ;
gOrdnanceNotesUrl = removeUrlTestParams( "{{url_for('get_ordnance_notes')}}" ) ;
gGetVaslPieceInfoUrl = removeUrlTestParams( "{{url_for('get_vasl_piece_info')}}" ) ;
gGetOnlineCounterImagesUrl = removeUrlTestParams( "{{url_for('get_online_counter_images')}}" ) ;
gGetScenarioIndexUrl = removeUrlTestParams( "{{url_for('get_scenario_index')}}" ) ;
gGetScenarioUrl = removeUrlTestParams( "{{url_for('get_scenario',scenario_id='ID')}}" ) ;
gGetScenarioCardUrl = removeUrlTestParams( "{{url_for('get_scenario_card',scenario_id='ID')}}" ) ;
gGetRoarScenarioIndexUrl = removeUrlTestParams( "{{url_for('get_roar_scenario_index')}}" ) ;
gAnalyzeVsavUrl = removeUrlTestParams( "{{url_for('analyze_vsav')}}" ) ;
gAnalyzeVlogsUrl = removeUrlTestParams( "{{url_for('analyze_vlogs')}}" ) ;
gUpdateVsavUrl = removeUrlTestParams( "{{url_for('update_vsav')}}" ) ;
gPrepareAsaUploadUrl = removeUrlTestParams( "{{url_for('prepare_asa_upload')}}" ) ;
gOnSuccessfulAsaUploadUrl = removeUrlTestParams( "{{url_for('on_successful_asa_upload',scenario_id='ID')}}" ) ;
gMakeSnippetImageUrl = removeUrlTestParams( "{{url_for('make_snippet_image')}}" ) ;
gGetProgramInfoUrl = removeUrlTestParams( "{{url_for('get_program_info')}}" ) ;
gHelpUrl = removeUrlTestParams( "{{url_for('show_help')}}" ) ;
</script>
<script src="{{url_for('static',filename='main.js')}}"></script>
@ -190,7 +222,6 @@ gHelpUrl = "{{url_for('show_help')}}" ;
<script src="{{url_for('static',filename='user_settings.js')}}"></script>
<script src="{{url_for('static',filename='html-editor.js')}}"></script>
<script src="{{url_for('static',filename='jQueryHandlers.js')}}"></script>
<script src="{{url_for('static',filename='utils.js')}}"></script>
<script src="{{url_for('static',filename='timer.js')}}"></script>
{%include "testing.html"%}

@ -9,6 +9,7 @@ from google.protobuf.empty_pb2 import Empty #pylint: disable=no-name-in-module
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2_grpc import ControlTestsStub
from vasl_templates.webapp.tests.proto.utils import enum_from_string
#pylint: disable=no-name-in-module
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import \
SetVassalVersionRequest, SetVaslVersionRequest, SetVaslExtnInfoDirRequest, SetGpidRemappingsRequest, \
SetDataDirRequest, SetDefaultScenarioRequest, SetDefaultTemplatePackRequest, \
@ -17,6 +18,7 @@ from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import \
DumpVsavRequest, GetVaslPiecesRequest, \
SetAppConfigValRequest, DeleteAppConfigValRequest, \
SaveTempFileRequest
#pylint: enable=no-name-in-module
# ---------------------------------------------------------------------

@ -28,6 +28,7 @@ from vasl_templates.webapp import \
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2_grpc \
import ControlTestsServicer as BaseControlTestsServicer
#pylint: disable=no-name-in-module
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import \
SetVassalVersionRequest, SetVaslVersionRequest, SetVaslExtnInfoDirRequest, SetGpidRemappingsRequest, \
SetDataDirRequest, SetDefaultScenarioRequest, SetDefaultTemplatePackRequest, \
@ -39,6 +40,7 @@ from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import \
GetVassalVersionsResponse, GetVaslVersionsResponse, GetVaslExtnsResponse, GetVaslModWarningsResponse, \
GetLastSnippetImageResponse, GetLastAsaUploadResponse, \
DumpVsavResponse, GetVaslPiecesResponse, GetAppConfigResponse
#pylint: enable=no-name-in-module
# nb: these are defined as a convenience
_VaslExtnsTypes_NONE = SetVaslVersionRequest.VaslExtnsType.NONE #pylint: disable=no-member

@ -412,9 +412,9 @@ GPID Name Front images
1575 2-1/2 ton Truck(a) br/vehicles/ton212.gif
1577 7-1/2 ton Truck(a) br/vehicles/ton712.gif
1632 37* INF fr/frINF.gif fr/frINFb.gif
1636 50 MTR(f) fr/frMTR.gif fr/frMTRb.gif
1641 60* MTR(a) br/brMTRa.gif br/brMTRab.gif
1633 60* MTR fr/frMTR60.gif fr/frMTR60b.gif
1648 ATR fr/frATR.gif fr/frATRb.gif
1665 81* MTR mle 27/31 fr/gun/frMTR81s.gif fr/gun/frMTR81sb.gif
1667 81* MTR(f) mle 27/31 fr/gun/frMTR81sf.gif fr/gun/frMTR81sfb.gif
1669 25LL AT SA-L mle 34 fr/gun/frAT25LL.gif fr/gun/frAT25LLb.gif
1670 47L AT SA mle 37 APX <41 fr/gun/frAT47L-40.gif fr/gun/frAT47Lb.gif
@ -1281,6 +1281,7 @@ GPID Name Front images
12687 OML 2in MTR (KW) br/brMTR.gif br/brMTRb.gif
12689 60* MTR M2 (KW) am/amMTR.gif am/amMTRb.gif
12730 IP Carrier AOV br/vehicles/ipcaov.gif
12830 50 MTR ff/ffMTR50.png ff/ffMTR50b.png
3b5:10093 SL truck us/veh/usSearchlight(KFW).png
3b5:10114 57LL AT PTP obr. 43 cc/gun/ccAT57LL(KFW).png cc/gun/ccAT57LLm(KFW).png
3b5:10115 70* INF Type 92 cc/gun/ccINF70(KFW).png cc/gun/ccINF70m(KFW).png

@ -412,9 +412,9 @@ GPID Name Front images
1575 2-1/2 ton Truck(a) br/vehicles/ton212.gif
1577 7-1/2 ton Truck(a) br/vehicles/ton712.gif
1632 37* INF fr/frINF.gif fr/frINFb.gif
1636 50 MTR(f) fr/frMTR.gif fr/frMTRb.gif
1641 60* MTR(a) br/brMTRa.gif br/brMTRab.gif
1633 60* MTR fr/frMTR60.gif fr/frMTR60b.gif
1648 ATR fr/frATR.gif fr/frATRb.gif
1665 81* MTR mle 27/31 fr/gun/frMTR81s.gif fr/gun/frMTR81sb.gif
1667 81* MTR(f) mle 27/31 fr/gun/frMTR81sf.gif fr/gun/frMTR81sfb.gif
1669 25LL AT SA-L mle 34 fr/gun/frAT25LL.gif fr/gun/frAT25LLb.gif
1670 47L AT SA mle 37 APX <41 fr/gun/frAT47L-40.gif fr/gun/frAT47Lb.gif
@ -1281,6 +1281,7 @@ GPID Name Front images
12687 OML 2in MTR (KW) br/brMTR.gif br/brMTRb.gif
12689 60* MTR M2 (KW) am/amMTR.gif am/amMTRb.gif
12730 IP Carrier AOV br/vehicles/ipcaov.gif
12830 50 MTR ff/ffMTR50.png ff/ffMTR50b.png
3b5:10093 SL truck us/veh/usSearchlight(KFW).png
3b5:10114 57LL AT PTP obr. 43 cc/gun/ccAT57LL(KFW).png cc/gun/ccAT57LLm(KFW).png
3b5:10115 70* INF Type 92 cc/gun/ccINF70(KFW).png cc/gun/ccINF70m(KFW).png

@ -412,9 +412,9 @@ GPID Name Front images
1575 2-1/2 ton Truck(a) br/vehicles/ton212.gif
1577 7-1/2 ton Truck(a) br/vehicles/ton712.gif
1632 37* INF fr/frINF.gif fr/frINFb.gif
1636 50 MTR(f) fr/frMTR.gif fr/frMTRb.gif
1641 60* MTR(a) br/brMTRa.gif br/brMTRab.gif
1633 60* MTR fr/frMTR60.gif fr/frMTR60b.gif
1648 ATR fr/frATR.gif fr/frATRb.gif
1665 81* MTR mle 27/31 fr/gun/frMTR81s.gif fr/gun/frMTR81sb.gif
1667 81* MTR(f) mle 27/31 fr/gun/frMTR81sf.gif fr/gun/frMTR81sfb.gif
1669 25LL AT SA-L mle 34 fr/gun/frAT25LL.gif fr/gun/frAT25LLb.gif
1670 47L AT SA mle 37 APX <41 fr/gun/frAT47L-40.gif fr/gun/frAT47Lb.gif
@ -1281,6 +1281,7 @@ GPID Name Front images
12687 OML 2in MTR (KW) br/brMTR.gif br/brMTRb.gif
12689 60* MTR M2 (KW) am/amMTR.gif am/amMTRb.gif
12730 IP Carrier AOV br/vehicles/ipcaov.gif
12830 50 MTR ff/ffMTR50.png ff/ffMTR50b.png
3b5:10093 SL truck us/veh/usSearchlight(KFW).png
3b5:10114 57LL AT PTP obr. 43 cc/gun/ccAT57LL(KFW).png cc/gun/ccAT57LLm(KFW).png
3b5:10115 70* INF Type 92 cc/gun/ccINF70(KFW).png cc/gun/ccINF70m(KFW).png

@ -412,9 +412,9 @@ GPID Name Front images
1575 2-1/2 ton Truck(a) br/vehicles/ton212.gif
1577 7-1/2 ton Truck(a) br/vehicles/ton712.gif
1632 37* INF fr/frINF.gif fr/frINFb.gif
1636 50 MTR(f) fr/frMTR.gif fr/frMTRb.gif
1641 60* MTR(a) br/brMTRa.gif br/brMTRab.gif
1633 60* MTR fr/frMTR60.gif fr/frMTR60b.gif
1648 ATR fr/frATR.gif fr/frATRb.gif
1665 81* MTR mle 27/31 fr/gun/frMTR81s.gif fr/gun/frMTR81sb.gif
1667 81* MTR(f) mle 27/31 fr/gun/frMTR81sf.gif fr/gun/frMTR81sfb.gif
1669 25LL AT SA-L mle 34 fr/gun/frAT25LL.gif fr/gun/frAT25LLb.gif
1670 47L AT SA mle 37 APX <41 fr/gun/frAT47L-40.gif fr/gun/frAT47Lb.gif
@ -1281,6 +1281,7 @@ GPID Name Front images
12687 OML 2in MTR (KW) br/brMTR.gif br/brMTRb.gif
12689 60* MTR M2 (KW) am/amMTR.gif am/amMTRb.gif
12730 IP Carrier AOV br/vehicles/ipcaov.gif
12830 50 MTR ff/ffMTR50.png ff/ffMTR50b.png
3b5:10093 SL truck us/veh/usSearchlight(KFW).png
3b5:10114 57LL AT PTP obr. 43 cc/gun/ccAT57LL(KFW).png cc/gun/ccAT57LLm(KFW).png
3b5:10115 70* INF Type 92 cc/gun/ccINF70(KFW).png cc/gun/ccINF70m(KFW).png

@ -412,9 +412,9 @@ GPID Name Front images
1575 2-1/2 ton Truck(a) br/vehicles/ton212.gif
1577 7-1/2 ton Truck(a) br/vehicles/ton712.gif
1632 37* INF fr/frINF.gif fr/frINFb.gif
1636 50 MTR(f) fr/frMTR.gif fr/frMTRb.gif
1641 60* MTR(a) br/brMTRa.gif br/brMTRab.gif
1633 60* MTR fr/frMTR60.gif fr/frMTR60b.gif
1648 ATR fr/frATR.gif fr/frATRb.gif
1665 81* MTR mle 27/31 fr/gun/frMTR81s.gif fr/gun/frMTR81sb.gif
1667 81* MTR(f) mle 27/31 fr/gun/frMTR81sf.gif fr/gun/frMTR81sfb.gif
1669 25LL AT SA-L mle 34 fr/gun/frAT25LL.gif fr/gun/frAT25LLb.gif
1670 47L AT SA mle 37 APX <41 fr/gun/frAT47L-40.gif fr/gun/frAT47Lb.gif
@ -1281,6 +1281,7 @@ GPID Name Front images
12687 OML 2in MTR (KW) br/brMTR.gif br/brMTRb.gif
12689 60* MTR M2 (KW) am/amMTR.gif am/amMTRb.gif
12730 IP Carrier AOV br/vehicles/ipcaov.gif
12830 50 MTR ff/ffMTR50.png ff/ffMTR50b.png
13832 81* MTR sv/gun/svMTR81s.gif sv/gun/svMTR81sb.gif
13835 37L AT sv/gun/svAT37L.gif sv/gun/svAT37Lb.gif
13836 84* ART sv/gun/svART84s.gif sv/gun/svART84sb.gif

@ -412,9 +412,9 @@ GPID Name Front images
1575 2-1/2 ton Truck(a) br/vehicles/ton212.gif
1577 7-1/2 ton Truck(a) br/vehicles/ton712.gif
1632 37* INF fr/frINF.gif fr/frINFb.gif
1636 50 MTR(f) fr/frMTR.gif fr/frMTRb.gif
1641 60* MTR(a) br/brMTRa.gif br/brMTRab.gif
1633 60* MTR fr/frMTR60.gif fr/frMTR60b.gif
1648 ATR fr/frATR.gif fr/frATRb.gif
1665 81* MTR mle 27/31 fr/gun/frMTR81s.gif fr/gun/frMTR81sb.gif
1667 81* MTR(f) mle 27/31 fr/gun/frMTR81sf.gif fr/gun/frMTR81sfb.gif
1669 25LL AT SA-L mle 34 fr/gun/frAT25LL.gif fr/gun/frAT25LLb.gif
1670 47L AT SA mle 37 APX <41 fr/gun/frAT47L-40.gif fr/gun/frAT47Lb.gif
@ -1281,6 +1281,7 @@ GPID Name Front images
12687 OML 2in MTR (KW) br/brMTR.gif br/brMTRb.gif
12689 60* MTR M2 (KW) am/amMTR.gif am/amMTRb.gif
12730 IP Carrier AOV br/vehicles/ipcaov.gif
12830 50 MTR ff/ffMTR50.png ff/ffMTR50b.png
13832 81* MTR sv/gun/svMTR81s.gif sv/gun/svMTR81sb.gif
13835 37L AT sv/gun/svAT37L.gif sv/gun/svAT37Lb.gif
13836 84* ART sv/gun/svART84s.gif sv/gun/svART84sb.gif

@ -412,9 +412,9 @@ GPID Name Front images
1575 2-1/2 ton Truck(a) br/vehicles/ton212.gif
1577 7-1/2 ton Truck(a) br/vehicles/ton712.gif
1632 37* INF fr/frINF.gif fr/frINFb.gif
1636 50 MTR(f) fr/frMTR.gif fr/frMTRb.gif
1641 60* MTR(a) br/brMTRa.gif br/brMTRab.gif
1633 60* MTR fr/frMTR60.gif fr/frMTR60b.gif
1648 ATR fr/frATR.gif fr/frATRb.gif
1665 81* MTR mle 27/31 fr/gun/frMTR81s.gif fr/gun/frMTR81sb.gif
1667 81* MTR(f) mle 27/31 fr/gun/frMTR81sf.gif fr/gun/frMTR81sfb.gif
1669 25LL AT SA-L mle 34 fr/gun/frAT25LL.gif fr/gun/frAT25LLb.gif
1670 47L AT SA mle 37 APX <41 fr/gun/frAT47L-40.gif fr/gun/frAT47Lb.gif
@ -1281,6 +1281,7 @@ GPID Name Front images
12687 OML 2in MTR (KW) br/brMTR.gif br/brMTRb.gif
12689 60* MTR M2 (KW) am/amMTR.gif am/amMTRb.gif
12730 IP Carrier AOV br/vehicles/ipcaov.gif
12830 50 MTR ff/ffMTR50.png ff/ffMTR50b.png
13832 81* MTR sv/gun/svMTR81s.gif sv/gun/svMTR81sb.gif
13835 37L AT sv/gun/svAT37L.gif sv/gun/svAT37Lb.gif
13836 84* ART sv/gun/svART84s.gif sv/gun/svART84sb.gif

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,6 +1,6 @@
""" Injected functions for SetDataDirRequest. """
from .generated.control_tests_pb2 import SetDataDirRequest
from .generated.control_tests_pb2 import SetDataDirRequest #pylint: disable=no-name-in-module
from .utils import enum_to_string
# ---------------------------------------------------------------------

@ -1,6 +1,6 @@
""" Injected functions for SetDefaultTemplatePackRequest. """
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import SetDefaultTemplatePackRequest
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import SetDefaultTemplatePackRequest #pylint: disable=no-name-in-module
from .utils import enum_to_string
# ---------------------------------------------------------------------

@ -1,6 +1,6 @@
""" Injected functions for SetVaslVersionRequest. """
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import SetVaslVersionRequest
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import SetVaslVersionRequest #pylint: disable=no-name-in-module
from .utils import enum_to_string
# ---------------------------------------------------------------------

@ -1,6 +1,6 @@
""" Injected functions for SetVehOrdNotesDirRequest. """
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import SetVehOrdNotesDirRequest
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import SetVehOrdNotesDirRequest #pylint: disable=no-name-in-module
from .utils import enum_to_string
# ---------------------------------------------------------------------

@ -63,7 +63,7 @@ def test_counter_images( webapp, webdriver ): #pylint: disable=too-many-locals
return (code == 200 and data) or (code == 404 and not data)
# initialize
check_dir = os.path.join( os.path.split(__file__)[0], "fixtures" )
check_dir = os.path.join( os.path.split(__file__)[0], "fixtures/counters" )
save_dir = os.environ.get( "COUNTERS_SAVEDIR" ) # nb: define this to save the generated reports
if save_dir:
if os.path.isdir( save_dir ):
@ -87,7 +87,7 @@ def test_counter_images( webapp, webdriver ): #pylint: disable=too-many-locals
init_webapp( webapp, webdriver )
# figure out what we're expecting to see
fname = os.path.join( check_dir, "vasl-pieces-{}.txt".format(
fname = os.path.join( check_dir, "{}.txt".format(
aliases.get( vasl_version, vasl_version )
) )
with open( fname, "r", encoding="utf-8" ) as fp:

@ -328,8 +328,8 @@ def test_time_based_national_capabilities( webapp, webdriver ):
comments = [ "Plentiful Ammo included (KMC)", "ROK: 6B/3R" ]
)
check_oba( "kfw-rok", "Korea", 10, 1950, "10B", "3R", plentiful=True )
check_th_color( "kfw-rok", "Korea", 8, 1950, "Red TH#" )
check_th_color( "kfw-rok", "Korea", 9, 1950, "Red TH# (ROK) ; Black (KMC)" )
check_th_color( "kfw-rok", "Korea", 7, 1950, "Red TH#" )
check_th_color( "kfw-rok", "Korea", 8, 1950, "Red TH# (ROK) ; Black (KMC)" )
check_th_color( "kfw-rok", "Korea", 5, 1951, "Black TH#" )
# test the CPVA national Capabilities

@ -15,7 +15,8 @@ from vasl_templates.webapp.tests.test_vassal import run_vassal_tests
from vasl_templates.webapp.tests.utils import init_webapp, select_tab, new_scenario, \
set_player, set_template_params, set_scenario_date, get_player_nat, get_theater, set_theater, \
get_turn_track_nturns, \
wait_for, wait_for_elem, find_child, find_children, get_css_classes, set_stored_msg, click_dialog_button
wait_for, wait_for_elem, find_child, find_children, get_css_classes, set_stored_msg, click_dialog_button, \
remove_url_params
# ---------------------------------------------------------------------
@ -833,7 +834,7 @@ def _unload_scenario_card(): #pylint: disable=too-many-branches,too-many-locals
# unload the icons
icons = set(
c.get_attribute( "src" )
remove_url_params( c.get_attribute( "src" ) )
for c in find_children( ".info .icons img", card )
)
if icons:

@ -448,7 +448,7 @@ def _check_warning_msgs( webapp, expected ):
warnings = webapp.control_tests.get_vasl_mod_warnings()
if expected:
assert len(warnings) == 1
if isinstance( expected, typing.re.Pattern ):
if isinstance( expected, typing.Pattern ):
assert expected.search( warnings[0] )
else:
assert warnings[0].startswith( expected )

@ -1,11 +1,11 @@
""" Test VASSAL integration. """
import os
import traceback
import re
import json
import base64
import random
import typing.re #pylint: disable=import-error
from vasl_templates.webapp.vassal import VassalShim
from vasl_templates.webapp.utils import TempFile, change_extn, compare_version_strings
@ -878,14 +878,32 @@ def run_vassal_tests( webapp, func, vasl_extns_type=None,
assert False, "Can't find a valid combination of VASSAL and VASL."
# run the test for each VASSAL+VASL
for vassal_version in vassal_versions:
for vasl_version in vasl_versions:
if not VassalShim.is_compatible_version( vassal_version, vasl_version ):
continue
webapp.control_tests \
.set_vassal_version( vassal_version ) \
.set_vasl_version( vasl_version, vasl_extns_type )
func()
log_fname = os.environ.get( "RUN_VASSAL_TESTS_LOG" ) # nb: define this to log activity
log_file = open( log_fname, "w", encoding="utf-8" ) if log_fname else None #pylint: disable=consider-using-with
def log( fmt, *args, **kwargs ):
if log_file:
print( fmt.format( *args, **kwargs ), file=log_file )
log_file.flush()
try:
for vassal_version in vassal_versions:
for vasl_version in vasl_versions:
if not VassalShim.is_compatible_version( vassal_version, vasl_version ):
continue
log( "Running tests for VASSAL {}, VASL {}...", vassal_version, vasl_version )
webapp.control_tests \
.set_vassal_version( vassal_version ) \
.set_vasl_version( vasl_version, vasl_extns_type )
try:
func()
log ( "- OK.\n" )
except Exception as exc: #pylint: disable=broad-except
log( "- Failed: {}\n{}", exc,
re.sub( "^", " ", traceback.format_exc(), flags=re.MULTILINE )
)
#raise
finally:
if log_file:
log_file.close()
# ---------------------------------------------------------------------
@ -963,7 +981,7 @@ def _check_vsav_dump( vsav_dump, expected, ignore=None ):
# compare what we extracted from the dump with what's expected
for snippet_id in expected:
if isinstance( expected[snippet_id], typing.re.Pattern ):
if isinstance( expected[snippet_id], re.Pattern ):
rc = expected[snippet_id].search( labels[snippet_id] ) is not None
else:
assert isinstance( expected[snippet_id], str )

@ -671,7 +671,7 @@ def wait_for_clipboard( timeout, expected, contains=None, transform=None ):
if transform:
clipboard = transform( clipboard )
if contains is None:
if isinstance( expected, typing.re.Pattern ):
if isinstance( expected, typing.Pattern ):
return expected.search( clipboard ) is not None
else:
return expected == clipboard
@ -749,6 +749,13 @@ def get_css_classes( elem ):
classes = elem.get_attribute( "class" )
return classes.split() if classes else []
def remove_url_params( url ):
"""Remove parameters from a URL."""
pos = url.find( "?" )
if pos < 0:
return url
return url[:pos]
# ---------------------------------------------------------------------
def get_all_loggers():

@ -16,8 +16,8 @@ from vasl_templates.webapp.config.constants import DATA_DIR
from vasl_templates.webapp.vo import get_vo_listings
from vasl_templates.webapp.utils import compare_version_strings
SUPPORTED_VASL_MOD_VERSIONS = [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6" ]
SUPPORTED_VASL_MOD_VERSIONS_DISPLAY = "6.6.0-.6, 6.6.3.1"
SUPPORTED_VASL_MOD_VERSIONS = [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6", "6.6.7" ]
SUPPORTED_VASL_MOD_VERSIONS_DISPLAY = "6.6.0-.7, 6.6.3.1"
_zip_file_lock = threading.Lock()

@ -39,6 +39,7 @@ from vasl_templates.webapp.vasl_mod import get_reverse_remapped_gpid
# 6.6.4 | 3.6.6 17.0.2+8-LTS
# 6.6.5 | 3.6.7 18.0.1
# 6.6.6 | 3.6.14 19.0.2+7
# 6.6.7 | 3.7.5 21 (2023-09-19 LTS)
# NOTE: VASSAL+VASL back-compat has gone out the window :-/ We have to tie versions of VASL
# to specific versions of VASSAL. Sigh...
SUPPORTED_VASSAL_VERSIONS = {
@ -46,9 +47,10 @@ SUPPORTED_VASSAL_VERSIONS = {
"3.4.6": [ "6.6.0", "6.6.1" ],
"3.5.5": [ "6.6.0", "6.6.1", "6.6.2" ],
"3.5.8": [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1" ],
"3.6.6": [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6" ],
"3.6.7": [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6" ],
"3.6.14": [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6" ],
"3.6.6": [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6", "6.6.7" ],
"3.6.7": [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6", "6.6.7" ],
"3.6.14": [ "6.6.0", "6.6.1", "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6", "6.6.7" ],
"3.7.5": [ "6.6.2", "6.6.3", "6.6.3.1", "6.6.4", "6.6.5", "6.6.6", "6.6.7" ],
}
SUPPORTED_VASSAL_VERSIONS_DISPLAY = "3.4.2, 3.4.6, 3.5.5, 3.5.8, 3.6.6, 3.6.7, 3.6.14"
@ -435,6 +437,8 @@ class VassalShim:
if compare_version_strings( mo.group(), "3.3.0" ) < 0:
# we're using a legacy version of VASSAL - use Java 8
java_path = java8_path
if not java_path:
raise SimpleError( "Java has not been configured." )
# prepare the command
class_path = app.config.get( "JAVA_CLASS_PATH" )

@ -61,7 +61,7 @@ class WebDriver:
log_fname = globvars.user_profile.webdriver_log_fname
if "chromedriver" in webdriver_path:
options = webdriver.ChromeOptions()
options.headless = True
options.add_argument( "--headless" )
options.add_argument( "--no-sandbox" ) # nb: need this on the rPi 4
options.add_argument( "--no-proxy-server" )
# OMG! The chromedriver looks for Chrome/Chromium in a hard-coded, fixed location (the default
@ -70,23 +70,23 @@ class WebDriver:
if chrome_path:
options.binary_location = chrome_path
service = webdriver.chrome.service.Service(
webdriver_path, log_path=log_fname
webdriver_path, log_output=log_fname
)
if is_windows():
service.creationflags = 0x8000000 # win32process.CREATE_NO_WINDOW
service.creation_flags = 0x8000000 # win32process.CREATE_NO_WINDOW
self.driver = webdriver.Chrome(
options=options, service=service
)
elif "geckodriver" in webdriver_path:
options = webdriver.FirefoxOptions()
options.headless = True
options.add_argument( "--headless" )
service = webdriver.firefox.service.Service(
webdriver_path, log_path=log_fname
webdriver_path, log_output=log_fname
)
if is_windows():
service.creationflags = 0x8000000 # win32process.CREATE_NO_WINDOW
service.creation_flags = 0x8000000 # win32process.CREATE_NO_WINDOW
self.driver = webdriver.Firefox(
options=options, proxy=None, service=service
options=options, service=service
)
else:
raise SimpleError( "Can't identify webdriver: {}".format( webdriver_path ) )

@ -61,6 +61,7 @@ import VASSAL.counters.Decorator ;
import VASSAL.counters.DynamicProperty ;
import VASSAL.counters.PieceCloner ;
import VASSAL.preferences.Prefs ;
import VASSAL.configure.StringConfigurer ;
import VASSAL.tools.DataArchive ;
import VASSAL.tools.DialogUtils ;
@ -1131,6 +1132,13 @@ public class VassalShim
private Command loadScenario( String scenarioFilename ) throws IOException
{
// FUDGE! Need this for VASL 6.6.7 (but for some reason, older VASSAL+VASL combinations that used to work
// started breaking, as well :-/).
Prefs prefs = GameModule.getGameModule().getPrefs() ;
String SHOW_MARK_MOVED = "showMarkMoved" ;
prefs.addOption( null, new StringConfigurer( SHOW_MARK_MOVED, null ) ) ;
prefs.setValue( SHOW_MARK_MOVED, false ) ;
// load the scenario
disableBoardWarnings() ;
logger.info( "Loading scenario: {}", scenarioFilename ) ;

Loading…
Cancel
Save