", vo_note_key ) ;
diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json
index ac796d1..a02d4c7 100644
--- a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json
+++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json
@@ -45,6 +45,11 @@
"ob_colors": [ "OBCOL:dutch","OBCOL2:dutch", "OBCOL-BORDER:dutch" ],
"type": "allied-minor"
},
+"greek": {
+ "display_name": "Greek",
+ "ob_colors": [ "OBCOL:greek","OBCOL2:greek", "OBCOL-BORDER:greek" ],
+ "type": "allied-minor"
+},
"romanian": {
"display_name": "Romanian",
diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2 b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2
index eec4f24..b1b3e0a 100644
--- a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2
+++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2
@@ -1 +1 @@
-{{ORDNANCE_NAME}}: {{ORDNANCE_NOTE_URL}}
+{{ORDNANCE_NAME}}: {{ORDNANCE_NOTE_HTML}}
diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2 b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2
index a3936d9..85cfbce 100644
--- a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2
+++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2
@@ -1 +1 @@
-{{VEHICLE_NAME}}: {{VEHICLE_NOTE_URL}}
+{{VEHICLE_NAME}}: {{VEHICLE_NOTE_HTML}}
diff --git a/vasl_templates/webapp/tests/fixtures/data/vehicles/allied-minor/greek.json b/vasl_templates/webapp/tests/fixtures/data/vehicles/allied-minor/greek.json
new file mode 100644
index 0000000..ea4f8f7
--- /dev/null
+++ b/vasl_templates/webapp/tests/fixtures/data/vehicles/allied-minor/greek.json
@@ -0,0 +1,16 @@
+[
+
+{ "name": "PNG note",
+ "note_number": "201",
+ "id": "gr/v:001"
+},
+{ "name": "HTML note",
+ "note_number": "202",
+ "id": "gr/v:002"
+},
+{ "name": "PNG + HTML notes",
+ "note_number": "203",
+ "id": "gr/v:003"
+}
+
+]
diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/201.png b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/201.png
new file mode 100644
index 0000000..62b2678
Binary files /dev/null and b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/201.png differ
diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/202.html b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/202.html
new file mode 100644
index 0000000..03bfaba
--- /dev/null
+++ b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/202.html
@@ -0,0 +1 @@
+This is an HTML vehicle note (202).
diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.html b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.html
new file mode 100644
index 0000000..1003497
--- /dev/null
+++ b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.html
@@ -0,0 +1 @@
+This is an HTML vehicle note (203).
diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.png b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.png
new file mode 100644
index 0000000..62b2678
Binary files /dev/null and b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.png differ
diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt
index b7c440c..dac8d58 100644
--- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt
+++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt
@@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre
Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL
Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL
Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL
-Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL
-Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL
+Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL
+Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL
Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL
M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL
M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL
diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt
index 75b2033..4c99ef9 100644
--- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt
+++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt
@@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre
Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL
Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL
Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL
-Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL
-Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL
+Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL
+Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL
Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL
M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL
M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL
diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt
index 9eb51f2..f8fad0f 100644
--- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt
+++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt
@@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre
Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL
Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL
Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL
-Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL
-Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL
+Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL
+Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL
Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL
M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL
M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL
diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt
index 3d4cb50..2f31f5a 100644
--- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt
+++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt
@@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre
Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL
Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL
Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL
-Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL
-Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL
+Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL
+Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL
Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL
M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL
M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL
diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt
index b3f7e3c..d0ea793 100644
--- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt
+++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt
@@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre
Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL
Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL
Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL
-Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL
-Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL
+Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL
+Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL
Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 HE8† sD6 sM8† CS 7 53† N LL
M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL
M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL
diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt
index 712b8fd..65da51f 100644
--- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt
+++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt
@@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] WP6† s8 C
Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL
Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL
Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL
-Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL
-Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL
+Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL
+Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL
Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 D7† HE8† sD6 sM8† CS 7 53† N LL
M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL
M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL
diff --git a/vasl_templates/webapp/tests/remote.py b/vasl_templates/webapp/tests/remote.py
index 5c9da1c..9087097 100644
--- a/vasl_templates/webapp/tests/remote.py
+++ b/vasl_templates/webapp/tests/remote.py
@@ -47,7 +47,7 @@ class ControlTests:
def __getattr__( self, name ):
"""Generic entry point for handling control requests."""
- if name.startswith( ("get_","set_") ):
+ if name.startswith( ("get_","set_","reset_") ):
# check if we are talking to a local or remote server
if self.server_url:
# remote: return a function that will invoke the handler function on the remote server
@@ -281,3 +281,9 @@ class ControlTests:
"""Get the vasl_mod startup warnings."""
_logger.info( "Returning the vasl_mod startup warnings: %s", vasl_mod_module.warnings )
return vasl_mod_module.warnings
+
+ def _reset_template_pack( self ):
+ """Force the default template pack to be reloaded."""
+ _logger.info( "Reseting the default template pack." )
+ globvars.template_pack = None
+ return self
diff --git a/vasl_templates/webapp/tests/test_user_settings.py b/vasl_templates/webapp/tests/test_user_settings.py
index 316d67c..e26e779 100644
--- a/vasl_templates/webapp/tests/test_user_settings.py
+++ b/vasl_templates/webapp/tests/test_user_settings.py
@@ -1,12 +1,13 @@
""" Test the user settings. """
import json
+import re
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.keys import Keys
from vasl_templates.webapp.tests.utils import \
- init_webapp, find_child, wait_for_clipboard, \
+ init_webapp, find_child, find_children, wait_for_clipboard, \
select_tab, select_menu_option, set_player, click_dialog_button, add_simple_note
from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo
from vasl_templates.webapp.tests.test_scenario_persistence import save_scenario, load_scenario
@@ -231,6 +232,58 @@ def test_hide_unavailable_ma_notes( webapp, webdriver ):
# ---------------------------------------------------------------------
+def test_vo_notes_as_images( webapp, webdriver ):
+ """Test showing vehicle/ordnance notes as HTML/images."""
+
+ # initialize
+ init_webapp( webapp, webdriver, scenario_persistence=1,
+ reset = lambda ct: ct.set_vo_notes_dir( dtype="test" )
+ )
+
+ # load the test vehicle
+ load_scenario( {
+ "PLAYER_1": "greek",
+ "OB_VEHICLES_1": [ { "name": "HTML note" } ],
+ } )
+ select_tab( "ob1" )
+
+ def check_snippet( expected ):
+ """Generate and check the vehicle note snippet."""
+ sortable = find_child( "#ob_vehicles-sortable_1" )
+ elems = find_children( "li", sortable )
+ assert len(elems) == 1
+ btn = find_child( "img.snippet", elems[0] )
+ btn.click()
+ contains = True if isinstance( expected, str ) else None
+ wait_for_clipboard( 2, expected, contains=contains )
+
+ # generate the vehicle snippet (should get the raw HTML)
+ check_snippet( "This is an HTML vehicle note (202)." )
+
+ # enable "show vehicle/ordnance notes as images"
+ select_menu_option( "user_settings" )
+ elem = find_child( ".ui-dialog.user-settings input[name='vo-notes-as-images']" )
+ assert not elem.is_selected()
+ elem.click()
+ click_dialog_button( "OK" )
+ _check_cookies( webdriver, "vo-notes-as-images", True )
+
+ # generate the vehicle snippet (should get a link to return an image)
+ check_snippet( re.compile( r"http://.+?:\d+/vehicles/greek/note/202" ) )
+
+ # disable "show vehicle/ordnance notes as images"
+ select_menu_option( "user_settings" )
+ elem = find_child( ".ui-dialog.user-settings input[name='vo-notes-as-images']" )
+ assert elem.is_selected()
+ elem.click()
+ click_dialog_button( "OK" )
+ _check_cookies( webdriver, "vo-notes-as-images", False )
+
+ # generate the vehicle snippet (should get the raw HTML)
+ check_snippet( "This is an HTML vehicle note (202)." )
+
+# ---------------------------------------------------------------------
+
def _check_cookies( webdriver, name, expected ):
"""Check that a user setting was stored in the cookies correctly."""
cookies = [ c for c in webdriver.get_cookies() if c["name"] == "user-settings" ]
diff --git a/vasl_templates/webapp/tests/test_vasl_extensions.py b/vasl_templates/webapp/tests/test_vasl_extensions.py
index 5faa5b4..8927ec8 100644
--- a/vasl_templates/webapp/tests/test_vasl_extensions.py
+++ b/vasl_templates/webapp/tests/test_vasl_extensions.py
@@ -124,7 +124,7 @@ def test_dedupe_ma_notes( webapp, webdriver ):
# do the tests
do_test( [ "Type 92A (Tt)", "M3(a) (LT)" ], [
- ( False, "A", "The MA and allall MG" ),
( True, "A", "The (a) indicates U." ),
( True, "B", "This vehicle uses Re" ),
( True, "C", "Although a captured " ),
@@ -132,7 +132,7 @@ def test_dedupe_ma_notes( webapp, webdriver ):
( True, "US B", "Due to two of the MG" ),
] )
do_test( [ "Type 92A (Tt)", "Type 98 MCT (AAtr)" ], [
- ( False, "A", "The MA and allall MG" ),
( True, "Br H", 'As signified by "Inf' ),
( True, "Ge A", "MA and CMG (if so eq" ), # nb: this is "Ge A", which is different to the Japanese "A"
] )
@@ -142,11 +142,11 @@ def test_dedupe_ma_notes( webapp, webdriver ):
( True, "C", "Although a captured " ),
( True, "Br H", 'As signified by "Inf' ),
( True, "Ge A", "MA and CMG (if so eq" ),
- ( True, "Jp A", "The MA and allall MG" ),
( True, "US B", "Due to two of the MG" ),
] )
do_test( [ "Type 92A (Tt)", "M3(a) (LT)", "Type 98 MCT (AAtr)" ], [
- ( False, "A", "The MA and allall MG" ),
( True, "A", "The (a) indicates U." ),
( True, "B", "This vehicle uses Re" ),
( True, "C", "Although a captured " ),
@@ -285,7 +285,7 @@ def test_bfp_extensions( webapp, webdriver ):
( True, "A", "The (a) indicates U." ),
( True, "C", "Although a captured " ),
( True, "Ch F", "This vehicle, despit" ),
- ( True, "Jp A", "The MA and allall MG" ),
], transform=_extract_extn_ma_notes )
# test the Chapter H note
@@ -293,9 +293,9 @@ def test_bfp_extensions( webapp, webdriver ):
elems = find_children( "li img.snippet", vehicles_sortable )
assert len(elems) == 2
elems[0].click()
- wait_for_clipboard( 2, re.compile( r'' ) )
+ wait_for_clipboard( 2, "By 1935 the latest European tanks", contains=True )
elems[1].click()
- wait_for_clipboard( 2, re.compile( r'' ) )
+ wait_for_clipboard( 2, "The Japanese captured hundreds of vehicles", contains=True )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/vasl_templates/webapp/tests/test_vassal.py b/vasl_templates/webapp/tests/test_vassal.py
index 9ca2cad..7c6e526 100644
--- a/vasl_templates/webapp/tests/test_vassal.py
+++ b/vasl_templates/webapp/tests/test_vassal.py
@@ -277,7 +277,9 @@ def test_latw_update( webapp, webdriver ):
# update the scenario (German/Russian, no date)
load_scenario_params( { "scenario": { "PLAYER_1": "german", "PLAYER_2": "russian", "SCENARIO_DATE": "" } } )
- updated_vsav_dump = _update_vsav_and_dump( fname, { "created": 3, "updated": 2, "deleted": 2 } )
+ # NOTE: We changed the MOL-P template (to add custom list bullets), so the snippet is different
+ # to when this test was originally written, and so #updated changed from 2 to 3.
+ updated_vsav_dump = _update_vsav_and_dump( fname, { "created": 3, "updated": 3, "deleted": 2 } )
_check_vsav_dump( updated_vsav_dump, {
"pf": "Panzerfaust", "psk": "Panzerschrek", "atmm": "ATMM check:", # nb: the PF label now has a snippet ID
"mol": "Kindling Attempt:", "mol-p": "TH#", # nb: the MOL label now has a snippet ID
diff --git a/vasl_templates/webapp/tests/test_vo_notes.py b/vasl_templates/webapp/tests/test_vo_notes.py
index b25ba0e..a0f157c 100644
--- a/vasl_templates/webapp/tests/test_vo_notes.py
+++ b/vasl_templates/webapp/tests/test_vo_notes.py
@@ -112,6 +112,33 @@ def test_ma_notes( webapp, webdriver ):
# ---------------------------------------------------------------------
+def test_ma_html_notes( webapp, webdriver ):
+ """Test how we load vehicle/ordnance notes (HTML vs. PNG)."""
+
+ # initialize
+ init_webapp( webapp, webdriver, scenario_persistence=1,
+ reset = lambda ct: ct.set_vo_notes_dir( dtype="test" )
+ )
+
+ # load the test scenario
+ load_scenario( {
+ "PLAYER_1": "greek",
+ "OB_VEHICLES_1": [
+ { "name": "PNG note" },
+ { "name": "HTML note" },
+ { "name": "PNG + HTML notes" }
+ ],
+ } )
+
+ # check the snippets
+ _check_vo_snippets( 1, "vehicles", [
+ ( "PNG note", "vehicles/allied-minor/note/201" ),
+ "HTML note:
\nThis is an HTML vehicle note (202).\n
",
+ "PNG + HTML notes:
\nThis is an HTML vehicle note (203).\n
",
+ ] )
+
+# ---------------------------------------------------------------------
+
def test_common_vo_notes( webapp, webdriver ):
"""Test handling of Allied/Axis Minor common vehicles/ordnance."""
@@ -550,5 +577,8 @@ def _check_vo_snippets( player_no, vo_type, expected ):
def _extract_vo_note( clipboard ):
"""Extract the details from a vehicle/ordnance note snippet."""
- mo = re.search( "^(.+?): http://.+?/(.*)$", clipboard )
- return ( mo.group(1), mo.group(2) )
+ mo = re.search( r'^(.+?): \$', clipboard )
+ if mo:
+ return ( mo.group(1), mo.group(2) )
+ else:
+ return clipboard
diff --git a/vasl_templates/webapp/tests/utils.py b/vasl_templates/webapp/tests/utils.py
index 25b7acc..7046d07 100644
--- a/vasl_templates/webapp/tests/utils.py
+++ b/vasl_templates/webapp/tests/utils.py
@@ -46,9 +46,12 @@ _webdriver = None
def init_webapp( webapp, webdriver, **options ):
"""Initialize the webapp."""
+
+ # initialize
global _webapp, _webdriver
_webapp = webapp
_webdriver = webdriver
+
# reset the server
# NOTE: We have to do this manually, since we can't use pytest's monkeypatch'ing,
# since we could be talking to a remote server (see ControlTests for more details).
@@ -65,6 +68,10 @@ def init_webapp( webapp, webdriver, **options ):
if "reset" in options:
options.pop( "reset" )( control_tests )
+ # force the default template pack to be reloaded (using the new settings)
+ control_tests.reset_template_pack()
+
+ # load the webapp
webdriver.get( webapp.url_for( "main", **options ) )
wait_for( 5, lambda: find_child("#_page-loaded_") is not None )
diff --git a/vasl_templates/webapp/vo_notes.py b/vasl_templates/webapp/vo_notes.py
index 63a5436..9f0dc16 100644
--- a/vasl_templates/webapp/vo_notes.py
+++ b/vasl_templates/webapp/vo_notes.py
@@ -2,13 +2,17 @@
# Pokhara, Nepal (DEC/18).
import os
+import pathlib
+import io
+import re
import logging
from collections import defaultdict
-from flask import render_template, jsonify, abort
+from flask import request, render_template, jsonify, send_file, abort, Response, url_for
from vasl_templates.webapp import app, globvars
from vasl_templates.webapp.files import FileServer
+from vasl_templates.webapp.webdriver import WebDriver
from vasl_templates.webapp.utils import resize_image_response, is_image_file, is_empty_file
# ---------------------------------------------------------------------
@@ -32,7 +36,7 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma
dname = app.config.get( "CHAPTER_H_NOTES_DIR" )
if not dname:
globvars.vo_notes = { "vehicles": {}, "ordnance": {} }
- globvars.file_server = None
+ globvars.vo_notes_file_server = None
return
dname = os.path.abspath( dname )
if not os.path.isdir( dname ):
@@ -68,9 +72,12 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma
# multi-applicable notes, so we force them to appear in the final results.
vo_notes["vehicles"]["anzac"] = {}
vo_notes["ordnance"]["indonesian"] = {}
+ vo_note_layout_width = app.config.get( "VO_NOTE_LAYOUT_WIDTH", 500 )
# load the vehicle/ordnance notes
for root,_,fnames in os.walk( dname, followlinks=True ):
+
+ # initialize
dname2, vo_type2 = os.path.split( root )
if vo_type2 in extn_ids:
extn_id = vo_type2
@@ -86,38 +93,77 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma
vo_type2, nat2 = "vehicles", "landing-craft"
else:
nat2 = nat
+
+ # process each file in the next directory
ma_notes = {}
for fname in fnames:
+
+ # ignore placeholder files
+ fname = os.path.join( root, fname )
+ if is_empty_file( fname ):
+ continue
+
+ # figure out what kind of file we have
extn = os.path.splitext( fname )[1].lower()
if is_image_file( extn ):
- key = os.path.splitext(fname)[0]
- if not all( ch.isdigit() or ch in (".") for ch in key ):
- logging.warning( "Unexpected vehicle/ordnance note key: %s", key )
- fname = os.path.join( root, fname )
- if is_empty_file( fname ):
- continue # nb: ignore placeholder files
- prefix = os.path.commonpath( [ dname, fname ] )
- if prefix:
- if extn_id:
- key = "{}:{}".format( extn_id, key )
- vo_notes[vo_type2][nat2][key] = fname[len(prefix)+1:]
- else:
- logging.warning( "Unexpected vehicle/ordnance note path: %s", fname )
- elif extn == ".html":
- key = get_ma_note_key( nat2, fname )
+
+ # image file - check if this looks like a vehicle/ordnance note
+ key = os.path.splitext( os.path.split( fname )[1] )[0]
+ if not all( ch.isdigit() or ch == "." for ch in key ):
+ # nope (this could be e.g. an image that's part of an HTML vehicle/ordnance note)
+ continue
+
+ # yup - save it as a vehicle/ordnance note
if extn_id:
key = "{}:{}".format( extn_id, key )
+ # NOTE: We only do this if we don't already have an HTML version.
+ if not vo_notes.get( vo_type2, {} ).get( nat2, {} ).get( key ):
+ rel_path = pathlib.PosixPath( fname ).relative_to( dname )
+ vo_notes[vo_type2][nat2][key] = str(rel_path)
+
+ elif extn == ".html":
+
+ # HTML file - read the content
fname = os.path.join( root, fname )
with open( fname, "r" ) as fp:
- buf = fp.read().strip()
- if not buf:
- continue # nb: ignore placeholder files
- if buf.startswith( "
" ):
- buf = buf[3:].strip()
- if "½" in buf:
- # NOTE: VASSAL doesn't like this, use "frac12;" :-/
- logging.warning( "Found ½ in HTML: %s", fname )
- ma_notes[key] = buf
+ html_content = fp.read().strip()
+ if "½" in html_content:
+ # NOTE: VASSAL doesn't like this, use "frac12;" :-/
+ logging.warning( "Found ½ in HTML: %s", fname )
+
+ # check what kind of file we have
+ key = get_ma_note_key( nat2, os.path.split(fname)[1] )
+ if re.search( r"^\d+(\.\d+)?$", key ):
+
+ # check if the content is specifying its own layout
+ if "" not in html_content:
+ # nope - use the default one
+ html_content = "
\n{}\n
".format(
+ vo_note_layout_width, html_content
+ )
+
+ # save it as a vehicle/ordnance note
+ if extn_id:
+ key = "{}:{}".format( extn_id, key )
+ rel_path = pathlib.PosixPath( os.path.split(fname)[0] ).relative_to( dname )
+ vo_notes[ vo_type2 ][ nat2 ][ key ] = _fixup_urls(
+ html_content,
+ "{{CHAPTER_H}}/" + str(rel_path) + "/"
+ )
+
+ else:
+
+ # save it as a multi-applicable note
+ if extn_id:
+ key = "{}:{}".format( extn_id, key )
+ if html_content.startswith( "
" ):
+ html_content = html_content[3:].strip()
+ rel_path = pathlib.PosixPath( os.path.split(fname)[0] ).relative_to( dname )
+ ma_notes[ key ] = _fixup_urls(
+ html_content,
+ "{{CHAPTER_H}}/" + str(rel_path) + "/"
+ )
+
if "multi-applicable" in vo_notes[ vo_type2 ][ nat2 ]:
vo_notes[ vo_type2 ][ nat2 ][ "multi-applicable" ].update( ma_notes )
else:
@@ -131,7 +177,14 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma
# install the vehicle/ordnance notes
globvars.vo_notes = { k: dict(v) for k,v in vo_notes.items() }
- globvars.file_server = file_server
+ globvars.vo_notes_file_server = file_server
+
+def _fixup_urls( html, url_stem ):
+ """Fixup URL's to Chapter H files."""
+ matches = list( re.finditer( r"]*src=(['\"])(.*?)\1", html ) )
+ for mo in reversed(matches):
+ html = html[:mo.start(2)] + url_stem+ html[mo.start(2):]
+ return html
# ---------------------------------------------------------------------
@@ -139,19 +192,69 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma
def get_vo_note( vo_type, nat, key ):
"""Return a Chapter H vehicle/ordnance note."""
- # locate the file
+ # get the vehicle/ordnance note
vo_notes = globvars.vo_notes[ vo_type ]
- fname = vo_notes.get( nat, {} ).get( key )
- if not fname:
- abort( 404 )
- if not globvars.file_server:
+ vo_note = vo_notes.get( nat, {} ).get( key )
+ if not vo_note:
abort( 404 )
- resp = globvars.file_server.serve_file( fname, ignore_empty=True )
- if not resp:
+ if not globvars.vo_notes_file_server:
abort( 404 )
- default_scaling = app.config.get( "CHAPTER_H_IMAGE_SCALING", 100 )
- return resize_image_response( resp, default_scaling=default_scaling )
+ # serve the file
+ if is_image_file( vo_note ):
+ resp = globvars.vo_notes_file_server.serve_file( vo_note, ignore_empty=True )
+ if not resp:
+ abort( 404 )
+ default_scaling = app.config.get( "CHAPTER_H_IMAGE_SCALING", 100 )
+ return resize_image_response( resp, default_scaling=default_scaling )
+ else:
+ buf = _make_vo_note_html( vo_note )
+ if request.args.get( "f" ) == "html":
+ # return the content as HTML
+ return Response( buf, mimetype="text/html" )
+ else:
+ # return the content as an image
+ # NOTE: We offer this option since VASSAL's HTML engine is so ancient, it doesn't support
+ # floating images (which we really need), either via CSS "float", or the HTML "align" attribute.
+ # NOTE: We need our own WebDriver instance in case the user is trying to generate a snippet image,
+ # which will use the shared instance (thus locking it), but vehicle/ordnance notes can contain
+ # a link that calls us here to generate the Chapter H content as an image, and if this 2nd request
+ # gets handled in a different thread (which it certainly will, since the 1st request is still
+ # in progress), we will deadlock waiting for the shared instance to become available.
+ with WebDriver.get_instance( "vo_note" ) as webdriver:
+ img = webdriver.get_snippet_screenshot( None, buf )
+ buf = io.BytesIO()
+ img.save( buf, format="PNG" )
+ buf.seek( 0 )
+ return send_file( buf, mimetype="image/png" )
+
+def _make_vo_note_html( vo_note ):
+ """Generate the HTML for a vehicle/ordnance note."""
+
+ # initialize
+ url_root = request.url_root
+ if url_root.endswith( "/" ):
+ url_root = url_root[:-1]
+
+ # inject the CSS (we do it like this since VASSAL doesn't support :-/)
+ css = globvars.template_pack.get( "css", {} ).get( "vo_note" )
+ if css:
+ vo_note = "
\n\n\n\n{}".format( css, vo_note )
+
+ # update any parameters
+ vo_note = vo_note.replace( "{{CHAPTER_H}}", url_root+"/chapter-h" )
+ vo_note = vo_note.replace( "{{IMAGES_BASE_URL}}", url_root+url_for("static",filename="images") )
+
+ return vo_note
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+@app.route( "/chapter-h/" )
+def get_chapter_h_file( path ):
+ """Return a Chapter H file."""
+ if not globvars.vo_notes_file_server:
+ abort( 404 )
+ return globvars.vo_notes_file_server.serve_file( path, ignore_empty=True )
# ---------------------------------------------------------------------
diff --git a/vasl_templates/webapp/webdriver.py b/vasl_templates/webapp/webdriver.py
index a1082ac..6cd9a2d 100644
--- a/vasl_templates/webapp/webdriver.py
+++ b/vasl_templates/webapp/webdriver.py
@@ -19,10 +19,10 @@ _logger = logging.getLogger( "webdriver" )
class WebDriver:
"""Wrapper for a Selenium webdriver."""
- # NOTE: The thread-safety lock controls access to the _shared_instance variable,
+ # NOTE: The thread-safety lock controls access to the _shared_instances variable,
# not the WebDriver it points to (it has its own lock).
- _shared_instance_lock = threading.RLock()
- _shared_instance = None
+ _shared_instances_lock = threading.RLock()
+ _shared_instances = {}
def __init__( self ):
self.driver = None
@@ -159,7 +159,7 @@ class WebDriver:
return self.get_screenshot( snippet, window_size, window_size2 )
@staticmethod
- def get_instance():
+ def get_instance( key="default" ):
"""Return the shared WebDriver instance.
A Selenium webdriver has a hefty startup time, so we create one on first use, and then re-use it.
@@ -178,26 +178,26 @@ class WebDriver:
if app.config.get( "DISABLE_SHARED_WEBDRIVER" ):
return WebDriver()
- with WebDriver._shared_instance_lock:
+ with WebDriver._shared_instances_lock:
# check if we've already created the shared WebDriver
- if WebDriver._shared_instance:
+ if key in WebDriver._shared_instances:
# yup - just return it (nb: the caller is responsible for locking it)
- _logger.info( "Returning shared WebDriver: %x", id(WebDriver._shared_instance) )
+ _logger.info( "Returning shared WebDriver (%s): %x", key, id(WebDriver._shared_instances[key]) )
- return WebDriver._shared_instance
+ return WebDriver._shared_instances[ key ]
# nope - create a new WebDriver instance
# NOTE: We start it here to keep it alive even after the caller has finished with it,
# and take steps to make sure it gets stopped and cleaned up when the program exits.
wdriver = WebDriver()
- _logger.info( "Created shared WebDriver: %x", id(wdriver) )
+ _logger.info( "Created shared WebDriver (%s): %x", key, id(wdriver) )
wdriver._do_start() #pylint: disable=protected-access
- WebDriver._shared_instance = wdriver
+ WebDriver._shared_instances[ key ] = wdriver
# make sure the shared WebDriver gets cleaned up
def cleanup(): #pylint: disable=missing-docstring
- _logger.info( "Cleaning up shared WebDriver: %x", id(wdriver) )
+ _logger.info( "Cleaning up shared WebDriver (%s): %x", key, id(wdriver) )
wdriver._do_stop() #pylint: disable=protected-access
atexit.register( cleanup )
globvars.cleanup_handlers.append( cleanup )