diff --git a/Dockerfile b/Dockerfile index 7a98f73..3c7e41c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,8 @@ # -p 5010:5010 \ # -v .../vasl-6.4.3.vmod:/data/vasl.vmod \ # vasl-templates +# If you have Chapter H data, add the following: +# -v .../chapter-h-notes:/data/chapter-h-notes FROM python:alpine3.6 diff --git a/conftest.py b/conftest.py index a593fbd..510a16e 100644 --- a/conftest.py +++ b/conftest.py @@ -62,6 +62,13 @@ def pytest_addoption( parser ): help="Directory containing VASSAL installation(s)." ) + # NOTE: Some tests require Chapter H vehicle/ordnance notes. This is copyrighted material, + # so it is kept in a private repo. + parser.addoption( + "--vo-notes", action="store", dest="vo_notes", default=None, + help="Directory containing Chapter H vehicle/ordnance notes and test results." + ) + # NOTE: It's not good to have the code run differently to how it will normally, # but using the clipboard to retrieve snippets causes more trouble than it's worth :-/ # since any kind of clipboard activity while the tests are running could cause them to fail diff --git a/docker/config/site.cfg b/docker/config/site.cfg index 27869ca..54816ee 100644 --- a/docker/config/site.cfg +++ b/docker/config/site.cfg @@ -3,3 +3,4 @@ FLASK_HOST = 0.0.0.0 VASL_MOD = /data/vasl.vmod +CHAPTER_H_NOTES = /data/chapter-h-notes/ diff --git a/vasl_templates/webapp/__init__.py b/vasl_templates/webapp/__init__.py index 9d1715c..1ab2a5d 100644 --- a/vasl_templates/webapp/__init__.py +++ b/vasl_templates/webapp/__init__.py @@ -60,6 +60,7 @@ import vasl_templates.webapp.vo #pylint: disable=cyclic-import import vasl_templates.webapp.snippets #pylint: disable=cyclic-import import vasl_templates.webapp.files #pylint: disable=cyclic-import import vasl_templates.webapp.vassal #pylint: disable=cyclic-import +import vasl_templates.webapp.vo_notes #pylint: disable=cyclic-import if app.config.get( "ENABLE_REMOTE_TEST_CONTROL" ): print( "*** WARNING: Remote test control enabled! ***" ) import vasl_templates.webapp.testing #pylint: disable=cyclic-import diff --git a/vasl_templates/webapp/config/site.cfg.example b/vasl_templates/webapp/config/site.cfg.example index ef2e777..f17cc08 100644 --- a/vasl_templates/webapp/config/site.cfg.example +++ b/vasl_templates/webapp/config/site.cfg.example @@ -8,3 +8,5 @@ VASSAL_DIR = ...configure the VASSAL installation directory... BOARDS_DIR = ...configure the VASL boards directory... WEBDRIVER_PATH = ...configure either geckodriver or chromedriver here... ; JAVA_PATH = ...configure the Java executable here (optional, must be in the PATH otherwise)... + +CHAPTER_H_NOTES = ...configure your Chapter H vehicle/ordnance images and multi-applicable notes... diff --git a/vasl_templates/webapp/data/default-scenario.json b/vasl_templates/webapp/data/default-scenario.json index f43d380..09f52f6 100644 --- a/vasl_templates/webapp/data/default-scenario.json +++ b/vasl_templates/webapp/data/default-scenario.json @@ -12,6 +12,11 @@ "VICTORY_CONDITIONS_WIDTH": "300px", "SSR_WIDTH": "300px", +"OB_VEHICLES_MA_NOTES_WIDTH_1": "300px", +"OB_ORDNANCE_MA_NOTES_WIDTH_1": "300px", +"OB_VEHICLES_MA_NOTES_WIDTH_2": "300px", +"OB_ORDNANCE_MA_NOTES_WIDTH_2": "300px", + "_SCENARIO_NOTE_WIDTH": "200px" } diff --git a/vasl_templates/webapp/data/default-template-pack/nationalities.json b/vasl_templates/webapp/data/default-template-pack/nationalities.json index 6583428..402fde0 100644 --- a/vasl_templates/webapp/data/default-template-pack/nationalities.json +++ b/vasl_templates/webapp/data/default-template-pack/nationalities.json @@ -47,48 +47,58 @@ "polish": { "display_name": "Polish", - "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ] + "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ], + "type": "allied-minor" }, "belgian": { "display_name": "Belgian", - "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ] + "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ], + "type": "allied-minor" }, "yugoslavian": { "display_name": "Yugoslavian", - "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ] + "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ], + "type": "allied-minor" }, "danish": { "display_name": "Danish", - "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ] + "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ], + "type": "allied-minor" }, "dutch": { "display_name": "Dutch", - "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ] + "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ], + "type": "allied-minor" }, "greek": { "display_name": "Greek", - "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ] + "ob_colors": [ "#a3ecd1","#82e3bd", "#61d8a6" ], + "type": "allied-minor" }, - "romanian": { "display_name": "Romanian", - "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ] + "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ], + "type": "axis-minor" }, "hungarian": { "display_name": "Hungarian", - "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ] + "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ], + "type": "axis-minor" }, "slovakian": { "display_name": "Slovakian", - "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ] + "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ], + "type": "axis-minor" }, "croatian": { "display_name": "Croatian", - "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ] + "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ], + "type": "axis-minor" }, "bulgarian": { "display_name": "Bulgarian", - "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ] + "ob_colors": [ "#3ceb7c","#1de256", "#0ed93c" ], + "type": "axis-minor" } } diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 new file mode 100644 index 0000000..2024f61 --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 @@ -0,0 +1,43 @@ + + + + + + + + + + +
+ {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance Notes + +{%if OB_ORDNANCE_MA_NOTES%} +
+{%for ma_note in OB_ORDNANCE_MA_NOTES%} +
{{ma_note}}
+{%endfor%} +{%endif%} + +{%if OB_ORDNANCE_EXTRA_MA_NOTES%} +
+{%if OB_ORDNANCE_EXTRA_MA_NOTES_CAPTION%}
{{OB_ORDNANCE_EXTRA_MA_NOTES_CAPTION}}
{%endif%} +{%for ma_note in OB_ORDNANCE_EXTRA_MA_NOTES%} +
{{ma_note}}
+{%endfor%} +{%endif%} + +
+ + diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 new file mode 100644 index 0000000..f87db92 --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 @@ -0,0 +1,23 @@ + + + + + + + + + + +
+ {%if PLAYER_FLAG%} {%endif%}{{ORDNANCE_NAME}} + +
+ +
+ + diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 new file mode 100644 index 0000000..85eb718 --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 @@ -0,0 +1,23 @@ + + + + + + + + + + +
+ {%if PLAYER_FLAG%} {%endif%}{{VEHICLE_NAME}} + +
+ +
+ + diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 new file mode 100644 index 0000000..724dfca --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 @@ -0,0 +1,43 @@ + + + + + + + + + + +
+ {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicle Notes + +{%if OB_VEHICLES_MA_NOTES%} +
+{%for ma_note in OB_VEHICLES_MA_NOTES%} +
{{ma_note}}
+{%endfor%} +{%endif%} + +{%if OB_VEHICLES_EXTRA_MA_NOTES%} +
+{%if OB_VEHICLES_EXTRA_MA_NOTES_CAPTION%}
{{OB_VEHICLES_EXTRA_MA_NOTES_CAPTION}}
{%endif%} +{%for ma_note in OB_VEHICLES_EXTRA_MA_NOTES%} +
{{ma_note}}
+{%endfor%} +{%endif%} + +
+ + diff --git a/vasl_templates/webapp/data/ordnance/chinese.json b/vasl_templates/webapp/data/ordnance/chinese.json index c3837a9..047415a 100644 --- a/vasl_templates/webapp/data/ordnance/chinese.json +++ b/vasl_templates/webapp/data/ordnance/chinese.json @@ -167,7 +167,7 @@ "capabilities": [ "NT", "QSU", "h-d" ], "capabilities2": { "WP": 7, "C": "5\u20201" }, "note_number": "10\u2020", - "notes": [ "A", "C \u20201" ], + "notes": [ "A", "C\u20201" ], "id": "ch/o:019", "gpid": 2037 }, diff --git a/vasl_templates/webapp/files.py b/vasl_templates/webapp/files.py index 5d1e099..e285e5a 100644 --- a/vasl_templates/webapp/files.py +++ b/vasl_templates/webapp/files.py @@ -15,6 +15,27 @@ if app.config.get( "VASL_MOD" ): # --------------------------------------------------------------------- +class FileServer: + """Serve static files.""" + + def __init__( self, base_dir ): + self.base_dir = os.path.abspath( base_dir ) + + def get_file( self, fname ): + """Serve a file.""" + if not fname: + return None + fname = os.path.join( self.base_dir, fname ) + fname = os.path.abspath( fname ) + if not os.path.isfile( fname ): + return None + prefix = os.path.commonpath( [ self.base_dir, fname ] ) + if prefix != self.base_dir: + return None # nb: files must be sub-ordinate to the configured directory + return fname + +# --------------------------------------------------------------------- + def install_vasl_mod( new_vasl_mod ): """Install a new VASL module.""" global vasl_mod diff --git a/vasl_templates/webapp/static/css/sortable.css b/vasl_templates/webapp/static/css/sortable.css index f5bed89..fd6f886 100644 --- a/vasl_templates/webapp/static/css/sortable.css +++ b/vasl_templates/webapp/static/css/sortable.css @@ -17,4 +17,4 @@ img.sortable-reset { vertical-align: middle ; height: 15px ; margin-right: 0.25e .sortable-hint .instructions { margin: 1em 0 0 1em ; font-size: 80% ; font-style: italic ; color: #888 ; } .sortable-hint .instructions li { margin-top: 0.5em ; } -.sortable-trash { margin: 3px 5px ; height: 24px ; } +.sortable-trash { margin: 3px 5px 0 5px ; height: 24px ; } diff --git a/vasl_templates/webapp/static/css/tabs-ob.css b/vasl_templates/webapp/static/css/tabs-ob.css index 52e7ca2..e6e3f9f 100644 --- a/vasl_templates/webapp/static/css/tabs-ob.css +++ b/vasl_templates/webapp/static/css/tabs-ob.css @@ -2,33 +2,32 @@ .panel-ob_setups { height: 100% ; display: flex ; flex-direction: column ; } .panel-ob_setups .content { flex-grow: 1 ; } -.panel-ob_setups .sortable { font-size: 90% ; } -.panel-ob_setups .footer { margin-top: 0.5em ; display: flex ; align-items: center ; } +.panel-ob_setups .footer { margin-top: 0.5em ; padding-bottom: 1px ; display: flex ; align-items: center ; } /* -------------------------------------------------------------------- */ .panel-ob_notes { height: 100% ; display: flex ; flex-direction: column ; } .panel-ob_notes .content { flex-grow: 1 ; } -.panel-ob_notes .sortable { font-size: 90% ; } -.panel-ob_notes .footer { margin-top: 0.5em ; display: flex ; align-items: center ; } +.panel-ob_notes .footer { margin-top: 0.5em ; padding-bottom: 1px ; display: flex ; align-items: center ; } /* -------------------------------------------------------------------- */ .panel-ob_vehicles { height: 100% ; display: flex ; flex-direction: column ; } .panel-ob_vehicles .content { flex-grow: 1 ; } -.panel-ob_vehicles .sortable { font-size: 90% ; } .panel-ob_vehicles .footer { margin-top: 0.5em ; display: flex ; align-items: center ; } .panel-ob_ordnance { height: 100% ; display: flex ; flex-direction: column ; } .panel-ob_ordnance .content { flex-grow: 1 ; } -.panel-ob_ordnance .sortable { font-size: 90% ; } .panel-ob_ordnance .footer { margin-top: 0.5em ; display: flex ; align-items: center ; } /* nb: the following CSS is shared by vehicles and ordnance */ -.panel-ob_vo .sortable .vo-entry { display: flex ; font-size: 90% ; } -.panel-ob_vo .sortable .vo-entry img.vasl-image { display: inline-block ; vertical-align: middle ; height: 3.5em ; margin-right: 0.5em ; } -.panel-ob_vo .sortable .vo-entry.small-piece img.vasl-image { height: 2.5em ; margin-left: 0.5em ; margin-right: 1em ; } +.panel-ob_vo .sortable .vo-entry { display: flex ; } +.panel-ob_vo .sortable .vo-entry img.vasl-image { display: inline-block ; vertical-align: middle ; height: 3.25em ; margin-right: 0.5em ; } +.panel-ob_vo .sortable .vo-entry.small-piece img.vasl-image { height: 2.25em ; margin-left: 0.5em ; margin-right: 0.75em ; } .panel-ob_vo .sortable .vo-entry .detail { flex-grow: 1 ; display: flex ; flex-direction: column ; justify-content: center ; } -.panel-ob_vo .sortable .vo-entry .detail .vo-name { font-size: 110% ; } -.panel-ob_vo .sortable .vo-entry .detail .vo-capabilities { max-height: 2.5em ; overflow: hidden ; font-size: 90% ; font-style: italic ; } +.panel-ob_vo .sortable .vo-entry .detail .vo-name { font-size: 90% ; } +.panel-ob_vo .sortable .vo-entry .detail .vo-capabilities { max-height: 2.5em ; overflow: hidden ; font-size: 80% ; font-style: italic ; } .panel-ob_vo .sortable .vo-entry .detail .vo-capability { margin-right: 0.5em ; color: #444 ; } +.panel-ob_vo label.header { font-weight: bold ; display: inline-block ; width: 3.25em ; } +.panel-ob_vo .snippet-admin { align-self: flex-end ; } +.panel-ob_vo .snippets-notes { margin-top: 2px ; } diff --git a/vasl_templates/webapp/static/css/tabs.css b/vasl_templates/webapp/static/css/tabs.css index dd8010b..4317ba0 100644 --- a/vasl_templates/webapp/static/css/tabs.css +++ b/vasl_templates/webapp/static/css/tabs.css @@ -34,7 +34,7 @@ .ui-tabs-panel.tabs-ob { display: flex ; } .ui-tabs-panel.tabs-ob .left { flex-grow: 1 ; min-width: 32em ; } -.ui-tabs-panel.tabs-ob .right { width: 25em ; min-width: 25em ; } +.ui-tabs-panel.tabs-ob .right { width: 28em ; min-width: 26em ; } .ui-tabs-panel.tabs-ob .left { display: flex ; flex-direction: column ; } .ui-tabs-panel.tabs-ob .tl { height: 100% ; flex-grow: 1 } diff --git a/vasl_templates/webapp/static/main.js b/vasl_templates/webapp/static/main.js index 4b98b74..0e670c0 100644 --- a/vasl_templates/webapp/static/main.js +++ b/vasl_templates/webapp/static/main.js @@ -4,6 +4,7 @@ gDefaultTemplatePack = null ; gTemplatePack = {} ; gValidTemplateIds = [] ; gVehicleOrdnanceListings = {} ; +gVehicleOrdnanceNotes = {} ; gVaslPieceInfo = {} ; gWebChannelHandler = null ; @@ -239,6 +240,18 @@ $(document).ready( function () { } ).fail( function( xhr, status, errorMsg ) { showErrorMsg( "Can't get the ordnance listings:
" + escapeHTML(errorMsg) + "
" ) ; } ) ; + $.getJSON( gVehicleNotesUrl, function(data) { + gVehicleOrdnanceNotes.vehicles = data ; + update_page_load_status( "vehicle-notes" ) ; + } ).fail( function( xhr, status, errorMsg ) { + showErrorMsg( "Can't get the vehicle notes:
" + escapeHTML(errorMsg) + "
" ) ; + } ) ; + $.getJSON( gOrdnanceNotesUrl, function(data) { + gVehicleOrdnanceNotes.ordnance = data ; + update_page_load_status( "ordnance-notes" ) ; + } ).fail( function( xhr, status, errorMsg ) { + showErrorMsg( "Can't get the ordnance notes:
" + escapeHTML(errorMsg) + "
" ) ; + } ) ; // get the VASL piece info $.getJSON( gGetVaslPieceInfoUrl, function(data) { @@ -355,6 +368,10 @@ $(document).ready( function () { var template_id = $(this).attr( "data-id" ) ; if ( template_id.substring(0,9) === "ob_setup_" ) template_id = "ob_setup" ; + else if ( template_id.substring(0,21) === "ob_vehicles_ma_notes_" ) + template_id = "ob_vehicles_ma_notes" ; + else if ( template_id.substring(0,21) === "ob_ordnance_ma_notes_" ) + template_id = "ob_ordnance_ma_notes" ; else if ( template_id.substring(0,12) === "ob_vehicles_" ) template_id = "ob_vehicles" ; else if ( template_id.substring(0,12) === "ob_ordnance_" ) @@ -380,6 +397,10 @@ function init_snippet_button( $btn ) var template_id2 ; if ( template_id.substring(0,9) === "ob_setup_" ) template_id2 = "ob_setup" ; + else if ( template_id.substring(0,21) == "ob_vehicles_ma_notes_" ) + template_id2 = "ob_vehicles_ma_notes" ; + else if ( template_id.substring(0,21) == "ob_ordnance_ma_notes_" ) + template_id2 = "ob_ordnance_ma_notes" ; else if ( template_id.substring(0,12) == "ob_vehicles_" ) template_id2 = "ob_vehicles" ; else if ( template_id.substring(0,12) == "ob_ordnance_" ) @@ -426,7 +447,7 @@ function init_snippet_button( $btn ) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -gPageLoadStatus = [ "main", "vehicle-listings", "ordnance-listings", "vasl-piece-info", "template-pack", "default-scenario" ] ; +gPageLoadStatus = [ "main", "vehicle-listings", "ordnance-listings", "vehicle-notes", "ordnance-notes", "vasl-piece-info", "template-pack", "default-scenario" ] ; function update_page_load_status( id ) { @@ -587,6 +608,18 @@ function on_player_change( player_no ) } } + // show/hide the vehicle/ordnance multi-applicable notes controls + function update_ma_notes_controls( vo_type ) { + var show = ( gVehicleOrdnanceNotes[vo_type] && gVehicleOrdnanceNotes[vo_type][player_nat] ) || + ["allied-minor","axis-minor"].indexOf( gTemplatePack.nationalities[ player_nat ].type ) !== -1 ; + var $fieldset = $( "#tabs-ob" + player_no + " fieldset[name='ob_" + vo_type + "_" + player_no ) ; + $fieldset.find( ".snippets-notes" ).css( "display", show?"block":"none" ) ; + $fieldset.find( "label[for='ob']" ).css( "display", show?"inline-block":"none" ) ; + } + update_ma_notes_controls( "vehicles" ) ; + update_ma_notes_controls( "ordnance" ) ; + // TO DO: We should also show a button that lets the ob_vehicle/ordnance_note template to be edited. + // reset the OB params $( "#ob_setups-sortable_" + player_no ).sortable2( "delete-all" ) ; $("input[name='OB_SETUP_WIDTH_"+player_no+"']").val( "" ) ; diff --git a/vasl_templates/webapp/static/simple_notes.js b/vasl_templates/webapp/static/simple_notes.js index c12fd10..d618b0d 100644 --- a/vasl_templates/webapp/static/simple_notes.js +++ b/vasl_templates/webapp/static/simple_notes.js @@ -93,7 +93,7 @@ function _do_edit_simple_note( $sortable2, $entry, default_width ) $sortable2.find( "li" ).each( function() { usedIds[ $(this).data("sortable2-data").id ] = true ; } ) ; - data.id = auto_assign_id( usedIds ) ; + data.id = auto_assign_id( usedIds, "id" ) ; } _do_add_simple_note( $sortable2, data ) ; } @@ -126,7 +126,7 @@ function _make_simple_note( note_type, caption ) if ( ["scenario_notes","ob_setups","ob_notes"].indexOf( note_type ) !== -1 ) { var note_type0 = note_type.substring( 0, note_type.length-1 ) ; buf.push( - "" ) ; } diff --git a/vasl_templates/webapp/static/snippets.js b/vasl_templates/webapp/static/snippets.js index 120b9e1..23d2edb 100644 --- a/vasl_templates/webapp/static/snippets.js +++ b/vasl_templates/webapp/static/snippets.js @@ -24,7 +24,9 @@ var gScenarioCreatedTime = null ; function generate_snippet( $btn, extra_params ) { // generate the snippet - var snippet = make_snippet( $btn, extra_params, true ) ; + var template_id = $btn.data( "id" ) ; + var params = unload_snippet_params( true, template_id ) ; + var snippet = make_snippet( $btn, params, extra_params, true ) ; // copy the snippet to the clipboard try { @@ -37,11 +39,10 @@ function generate_snippet( $btn, extra_params ) showInfoMsg( "The HTML snippet has been copied to the clipboard." ) ; } -function make_snippet( $btn, extra_params, show_date_warnings ) +function make_snippet( $btn, params, extra_params, show_date_warnings ) { // initialize var template_id = $btn.data( "id" ) ; - var params = unload_snippet_params( true, template_id ) ; // set player-specific parameters var player_no = get_player_no_for_element( $btn ) ; @@ -56,7 +57,7 @@ function make_snippet( $btn, extra_params, show_date_warnings ) // set the snippet ID var data ; - if ( template_id === "ob_setup" || template_id === "ob_note" ) { + if ( ["ob_setup","ob_note","ob_vehicle_note","ob_ordnance_note"].indexOf( template_id ) !== -1 ) { data = $btn.parent().parent().data( "sortable2-data" ) ; params.SNIPPET_ID = template_id + "_" + player_no + "." + data.id ; } else if ( template_id === "scenario_note" ) { @@ -66,25 +67,101 @@ function make_snippet( $btn, extra_params, show_date_warnings ) params.SNIPPET_ID = template_id ; // set player-specific parameters - if ( template_id == "ob_vehicles_1" ) { + if ( template_id === "ob_vehicles_1" ) { template_id = "ob_vehicles" ; params.OB_VEHICLES = params.OB_VEHICLES_1 ; params.OB_VEHICLES_WIDTH = params.OB_VEHICLES_WIDTH_1 ; - } else if ( template_id == "ob_vehicles_2" ) { + } else if ( template_id === "ob_vehicles_2" ) { template_id = "ob_vehicles" ; params.OB_VEHICLES = params.OB_VEHICLES_2 ; params.OB_VEHICLES_WIDTH = params.OB_VEHICLES_WIDTH_2 ; } - if ( template_id == "ob_ordnance_1" ) { + if ( template_id === "ob_ordnance_1" ) { template_id = "ob_ordnance" ; params.OB_ORDNANCE = params.OB_ORDNANCE_1 ; params.OB_ORDNANCE_WIDTH = params.OB_ORDNANCE_WIDTH_1 ; - } else if ( template_id == "ob_ordnance_2" ) { + } else if ( template_id === "ob_ordnance_2" ) { template_id = "ob_ordnance" ; params.OB_ORDNANCE = params.OB_ORDNANCE_2 ; params.OB_ORDNANCE_WIDTH = params.OB_ORDNANCE_WIDTH_2 ; } + // set vehicle/ordnance note parameters + function set_vo_note( vo_type ) { + var data = $btn.parent().parent().data( "sortable2-data" ) ; + var key = (vo_type === "vehicles") ? "VEHICLE" : "ORDNANCE" ; + params[ key + "_NAME" ] = data.vo_entry.name ; + params[ key + "_NOTE_URL" ] = data.vo_note_url ; + } + if ( template_id === "ob_vehicle_note" ) + set_vo_note( "vehicles" ) ; + else if ( template_id === "ob_ordnance_note" ) + set_vo_note( "ordnance" ) ; + + // generate snippets for multi-applicable vehicle/ordnance notes + function add_ma_notes( ma_notes, keys, param_name, nat, vo_type ) { + if ( ! keys ) + return ; + params[ param_name ] = [] ; + for ( var i=0 ; i < keys.length ; ++i ) { + var ma_note = ma_notes[ keys[i] ] ; + params[ param_name ].push( + "" + + (nat === "italian" && vo_type === "ordnance" && keys[i] === "R" ? "R" : keys[i]) + ":" + + " " + + (ma_note || "Unavailable.") + ) ; + } + } + function get_ma_notes( vo_type, player_no, param_name ) { + var nat = params[ "PLAYER_" + player_no ] ; + var vo_entries = params[ "OB_" + vo_type.toUpperCase() + "_" + player_no ] ; + var result = get_ma_notes_keys( nat, vo_entries, vo_type, null ) ; + if ( ! result ) + return ; + // NOTE: If the V/O entries contain landing craft or common vehicles/ordnance, we get: + // [ m/a note keys, m/a note keys for the extras, nat ID for the extras, display caption for the extras, unrecognized keys ] + // where "extras" = landing craft or common vehicles/ordnance. Otherwise, we get: + // [ m/a note keys, null, null, null, unrecognized keys ] + add_ma_notes( get_ma_notes_for_nat(nat,vo_type), result[0], param_name, nat, vo_type ) ; + if ( result[1] ) { + // there are extras, show their multi-applicable notes separately + add_ma_notes( get_ma_notes_for_nat(result[2],vo_type), result[1], param_name.replace("_MA_NOTES_","_EXTRA_MA_NOTES_"), result[2], vo_type ) ; + if ( result[0] ) { + var param_name2 = "OB_" + vo_type.toUpperCase() + "_EXTRA_MA_NOTES_CAPTION_" + player_no ; + params[param_name2] = result[3] ; + } + } + } + function get_ma_notes_for_nat( nat, vo_type ) { + if ( nat === "landing-craft" && nat in gVehicleOrdnanceNotes.vehicles ) + return gVehicleOrdnanceNotes.vehicles[ nat ][ "multi-applicable" ] ; + if ( vo_type in gVehicleOrdnanceNotes && nat in gVehicleOrdnanceNotes[vo_type] ) + return gVehicleOrdnanceNotes[ vo_type ][ nat ][ "multi-applicable" ] ; + return {} ; + } + get_ma_notes( "vehicles", 1, "OB_VEHICLES_MA_NOTES_1" ) ; + get_ma_notes( "ordnance", 1, "OB_ORDNANCE_MA_NOTES_1" ) ; + get_ma_notes( "vehicles", 2, "OB_VEHICLES_MA_NOTES_2" ) ; + get_ma_notes( "ordnance", 2, "OB_ORDNANCE_MA_NOTES_2" ) ; + function set_params( vo_type, player_no ) { + template_id = "ob_" + vo_type + "_ma_notes" ; + var vo_type_uc = vo_type.toUpperCase() ; + var postfixes = [ "MA_NOTES", "MA_NOTES_WIDTH", "EXTRA_MA_NOTES", "EXTRA_MA_NOTES_CAPTION" ] ; + for ( var i=0 ; i < postfixes.length ; ++i ) { + var stem = "OB_" + vo_type_uc + "_" + postfixes[i] ; + params[ stem ] = params[ stem + "_" + player_no ] ; + } + } + if ( template_id === "ob_vehicles_ma_notes_1" ) + set_params( "vehicles", 1 ) ; + else if ( template_id === "ob_ordnance_ma_notes_1" ) + set_params( "ordnance", 1 ) ; + else if ( template_id === "ob_vehicles_ma_notes_2" ) + set_params( "vehicles", 2 ) ; + else if ( template_id === "ob_ordnance_ma_notes_2" ) + set_params( "ordnance", 2 ) ; + // include the player display names and flags params.PLAYER_1_NAME = get_nationality_display_name( params.PLAYER_1 ) ; params.PLAYER_2_NAME = get_nationality_display_name( params.PLAYER_2 ) ; @@ -105,13 +182,13 @@ function make_snippet( $btn, extra_params, show_date_warnings ) } ) ; // generate PF parameters - if ( params.SCENARIO_YEAR < 1944 || (params.SCENARIO_YEAR == 1944 && params.SCENARIO_MONTH < 6) ) + if ( params.SCENARIO_YEAR < 1944 || (params.SCENARIO_YEAR === 1944 && params.SCENARIO_MONTH < 6) ) params.PF_RANGE = 1 ; - else if ( params.SCENARIO_YEAR == 1944 ) + else if ( params.SCENARIO_YEAR === 1944 ) params.PF_RANGE = 2 ; else params.PF_RANGE = 3 ; - if ( params.SCENARIO_YEAR < 1943 || (params.SCENARIO_YEAR == 1943 && params.SCENARIO_MONTH <= 9) ) { + if ( params.SCENARIO_YEAR < 1943 || (params.SCENARIO_YEAR === 1943 && params.SCENARIO_MONTH <= 9) ) { params.PF_CHECK_DRM = "+1" ; params.PF_CHECK_DR = 2 ; } else if ( params.SCENARIO_YEAR >= 1945 ) { @@ -134,7 +211,7 @@ function make_snippet( $btn, extra_params, show_date_warnings ) params.BAZ_BREAKDOWN = 11 ; params.BAZ_TOKILL = 16 ; params.BAZ_RANGE = 4 ; - } else if ( params.SCENARIO_YEAR == 1943 || (params.SCENARIO_YEAR == 1942 && params.SCENARIO_MONTH >= 11) ) { + } else if ( params.SCENARIO_YEAR === 1943 || (params.SCENARIO_YEAR === 1942 && params.SCENARIO_MONTH >= 11) ) { params.BAZ_TYPE = 43 ; params.BAZ_BREAKDOWN = 10 ; params.BAZ_TOKILL = 13 ; @@ -209,6 +286,131 @@ function make_snippet( $btn, extra_params, show_date_warnings ) return snippet ; } +function get_vo_note_key( vo_entry ) +{ + // get the note number for the specified vehicle/ordnance + if ( ! vo_entry.note_number ) + return null ; + // nb: there are some note numbers of the form "1.2" :-/ + var match = vo_entry.note_number.match( new RegExp( "^([0-9.]+)" ) ) ; + return match ? match[1] : null ; +} + +function is_known_vo_note_key( vo_type, nat, key ) +{ + // check if the vehicle/ordnance note key is known to us + return vo_type in gVehicleOrdnanceNotes && + nat in gVehicleOrdnanceNotes[ vo_type ] && + key in gVehicleOrdnanceNotes[ vo_type ][ nat ] ; +} + +function get_ma_notes_keys( nat, vo_entries, vo_type ) +{ + // figure out which multi-applicable notes are being referenced + if ( ! vo_entries ) + return null ; + // NOTE: We need to return 2 sets of referenced keys, one for the normal vehicle/ordnance notes + // and one for any landing craft/common vehicles, since they share common keys. + var keys = [ {}, {} ] ; + var unrecognized = [] ; + var regexes = [ + new RegExp( "^([A-Z]{1,2})$" ), + new RegExp( "^([A-Z]{1,2})\\u2020" ), + new RegExp( "^([a-z])$" ), + new RegExp( "^([a-z])\\u2020" ), + new RegExp( "^([A-Z][a-z])$" ), + new RegExp( "^([A-Za-z])" ), + new RegExp( "^([A-Za-z])$" ), + ] ; + var EXTRA_NOTES_INFO = { + "alc/v": [ "allied-minor", "Allied Minor Common Vehicles" ], + "alc/o": [ "allied-minor", "Allied Minor Common Ordnance" ], + "axc/v": [ "axis-minor", "Axis Minor Common Vehicles" ], + "axc/o": [ "axis-minor", "Axis Minor Common Ordnance" ], + "sh/v": [ "landing-craft", "Landing Craft" ], + } ; + var extra_notes_info = [ null, null ] ; + var i, j, k ; + for ( i=0 ; i < vo_entries.length ; ++i ) { + var vo_entry = vo_entries[i] ; + if ( ! vo_entry.notes ) + continue ; + for ( j=0 ; j < vo_entry.notes.length ; ++j ) { + var rc = false ; + for ( k=0 ; k < regexes.length ; ++k ) { + var match = vo_entry.notes[j].match( regexes[k] ) ; + if ( match ) { + var vo_id = vo_entry.id.split( ":", 1 )[0] ; + var is_extra = ["allied-minor","axis-minor","landing-craft"].indexOf( nat ) === -1 && + ["alc/v","alc/o","axc/v","axc/o","sh/v"].indexOf( vo_id ) !== -1 ; + keys[ is_extra?1:0 ][ match[1] ] = true ; + if ( is_extra ) { + // NOTE: Only the Americans/British and Japanese have landing craft, while Axis Minor Powers + // will never have Allied Minor common vehicles/ordnance (and vice versa), so if we have + // extra notes, they should be all of the same type. + extra_notes_info = EXTRA_NOTES_INFO[ vo_id ] ; + } + rc = true ; + break ; + } + } + if ( ! rc ) { + unrecognized.push( [ vo_entry, vo_entry.notes[j] ] ) ; + console.log( "Couldn't recognize multi-applicable note keys for '" + vo_entry.name + "':", vo_entry.notes[j] ) ; + } + } + } + + return [ + sort_ma_notes_keys( nat, Object.keys(keys[0]) ), + sort_ma_notes_keys( nat, Object.keys(keys[1]) ), + extra_notes_info[0], extra_notes_info[1], + unrecognized + ] ; +} + +function sort_ma_notes_keys( nat, keys ) +{ + // NOTE: I tried sorting the multi-applicable notes on the server side, but it got very messy very quickly + // e.g. we get an ordered list of notes, so we can no longer access them via the key; we have references + // to notes that may not be defined e.g. because the user hasn't set them up. + + if ( ! keys || keys.length === 0 ) + return null ; + + function isUpperCase( ch ) { return ch === ch.toUpperCase() ; } + function isLowerCase( ch ) { return ch === ch.toLowerCase() ; } + + // FUDGE! The sort rules don't apply for the special mixed-case keys in the Allied Minor ordnance. + // NOTE: There are a few other cases that have two-character mixed-case keys :-/ + function isSpecialKey( key ) { return key.length === 2 && isUpperCase(key[0]) && isLowerCase(key[1]) ; } + + // sort the multi-applicable note keys + keys.sort( function( lhs, rhs ) { + if ( ! isSpecialKey(lhs) && ! isSpecialKey(rhs) ) { + // upper-case sorts lower than lower-case (so that "AA" appears before "a") + if ( isUpperCase(lhs[0]) && isLowerCase(rhs[0]) ) + return -1 ; + if ( isLowerCase(lhs[0]) && isUpperCase(rhs[0]) ) + return +1 ; + // shorter strings sort lower (e.g. so that "A" appears before "AA") + if ( lhs.length < rhs.length ) + return -1 ; + else if ( lhs.length > rhs.length ) + return +1 ; + } + // return the natural sort order (only for strings with the same case and length) + if ( lhs < rhs ) + return -1 ; + else if ( lhs > rhs ) + return +1 ; + else + return 0 ; + } ) ; + + return keys ; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function unload_snippet_params( unpack_scenario_date, template_id ) @@ -260,6 +462,7 @@ function unload_snippet_params( unpack_scenario_date, template_id ) var vo_image_id = $(this).data( "sortable2-data" ).vo_image_id ; var obj = { id: vo_entry.id, + seq_id: $(this).data( "sortable2-data" ).id, image_id: (vo_image_id !== null) ? vo_image_id[0]+"/"+vo_image_id[1] : null, name: vo_entry.name, note_number: vo_entry.note_number, @@ -330,7 +533,7 @@ function make_capabilities( raw, vo_entry, nat, scenario_theater, scenario_year, var no_if = "no IF" ; if ( typeof(vo_entry.no_if) === "string" ) { // nb: only for the French B1-bis :-/ no_if = vo_entry.no_if ; - if ( no_if.substring(no_if.length-1) == "\u2020" ) + if ( no_if.substring(no_if.length-1) === "\u2020" ) no_if = "no IF" + no_if.substring(0,no_if.length-1) + "\u2020" ; else no_if = "no IF" + no_if + "" ; @@ -355,9 +558,9 @@ function make_capabilities( raw, vo_entry, nat, scenario_theater, scenario_year, continue ; } // check for LF - if ( key == "LF" ) { + if ( key === "LF" ) { var caps = $.extend( true, [], vo_entry.capabilities2[key] ) ; - if ( caps[caps.length-1] == "\u2020" ) { + if ( caps[caps.length-1] === "\u2020" ) { caps.pop() ; capabilities.push( "LF\u2020" ) ; } else @@ -381,7 +584,7 @@ function make_capabilities( raw, vo_entry, nat, scenario_theater, scenario_year, var cap = _select_capability_by_date( vo_entry.capabilities2[key], nat, scenario_theater, scenario_year, scenario_month ) ; if ( cap === null ) continue ; - if ( cap == "" ) { + if ( cap === "" ) { invalid_caps.push( vo_entry.name + ": " + key + ": " + vo_entry.capabilities2[key] ) ; continue ; } @@ -528,7 +731,7 @@ function _check_capability_timestamp( capabilities, timestamp, nat, scenario_the } // remove any trailing "+" (FIXME! What does it even mean? Doesn't make sense :-/) - if ( timestamp.substring( timestamp.length-1 ) == "+" ) + if ( timestamp.substring( timestamp.length-1 ) === "+" ) timestamp = timestamp.substring( 0, timestamp.length-1 ) ; // check if there is anything left @@ -547,7 +750,7 @@ function _check_capability_timestamp( capabilities, timestamp, nat, scenario_the // check if the capabilitity is available if ( scenario_year > 1940 + timestamp ) return capabilities[0] ; - else if ( scenario_year == 1940 + timestamp ) { + else if ( scenario_year === 1940 + timestamp ) { if( !month || scenario_month >= month ) return capabilities[0] ; } @@ -623,10 +826,6 @@ function get_template( template_id, fixup ) function edit_template( template_id ) { // get the specified template - if ( template_id.substring(0,12) == "ob_ordnance_" ) - template_id = "ob_ordnance" ; - else if ( template_id.substring(0,12) == "ob_vehicles_" ) - template_id = "ob_vehicles" ; var template = get_template( template_id, false ) ; if ( template === null ) return ; @@ -737,11 +936,25 @@ function do_load_scenario_data( params ) // auto-assign ID's to the OB setup notes and notes // NOTE: We do this here to handle scenarios that were created before these ID's were implemented. - auto_assign_ids( params.SCENARIO_NOTES ) ; - auto_assign_ids( params.OB_SETUPS_1 ) ; - auto_assign_ids( params.OB_NOTES_1 ) ; - auto_assign_ids( params.OB_SETUPS_2 ) ; - auto_assign_ids( params.OB_NOTES_2 ) ; + auto_assign_ids( params.SCENARIO_NOTES, "id" ) ; + auto_assign_ids( params.OB_SETUPS_1, "id" ) ; + auto_assign_ids( params.OB_NOTES_1, "id" ) ; + auto_assign_ids( params.OB_VEHICLES_1, "seq_id" ) ; + auto_assign_ids( params.OB_ORDNANCE_1, "seq_id" ) ; + auto_assign_ids( params.OB_SETUPS_2, "id" ) ; + auto_assign_ids( params.OB_NOTES_2, "id" ) ; + auto_assign_ids( params.OB_VEHICLES_2, "seq_id" ) ; + auto_assign_ids( params.OB_ORDNANCE_2, "seq_id" ) ; + + // set default values + function set_default_val( key, val ) { + if ( ! (key in params) ) + params[key] = val ; + } + set_default_val( "OB_VEHICLES_MA_NOTES_WIDTH_1", "300px" ) ; + set_default_val( "OB_ORDNANCE_MA_NOTES_WIDTH_1", "300px" ) ; + set_default_val( "OB_VEHICLES_MA_NOTES_WIDTH_2", "300px" ) ; + set_default_val( "OB_ORDNANCE_MA_NOTES_WIDTH_2", "300px" ) ; // load the scenario parameters var params_loaded = {} ; @@ -830,7 +1043,7 @@ function do_load_scenario_data( params ) warnings.push( "Invalid V/O image ID for '" + params[key][i].name + "': " + params[key][i].image_id ) ; } if ( vo_entry ) - do_add_vo( vo_type, player_no, vo_entry, vo_image_id, params[key][i].custom_capabilities ) ; + do_add_vo( vo_type, player_no, vo_entry, vo_image_id, params[key][i].custom_capabilities, params[key][i].seq_id ) ; else unknown_vo.push( vo_id || "(not set)" ) ; } @@ -871,7 +1084,7 @@ function do_load_scenario_data( params ) } // show any other warnings - if ( warnings.length == 1 ) + if ( warnings.length === 1 ) showWarningMsg( warnings[0] ) ; else if ( warnings.length > 1 ) { showWarningMsg( makeBulletListMsg( @@ -890,7 +1103,7 @@ function do_load_scenario_data( params ) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -function auto_assign_ids( vals ) +function auto_assign_ids( vals, key ) { if ( ! vals ) return ; @@ -910,14 +1123,14 @@ function auto_assign_ids( vals ) // identify which ID's are currently in use var usedIds = {} ; for ( var i=0 ; i < vals.length ; ++i ) { - if ( vals[i].id ) - usedIds[ vals[i].id ] = true ; + if ( vals[i][key] ) + usedIds[ vals[i][key] ] = true ; } // assign ID's to entries that don't have one for ( i=0 ; i < vals.length ; ++i ) { - if ( ! vals[i].id ) - vals[i].id = auto_assign_id( usedIds ) ; + if ( ! vals[i][key] ) + vals[i][key] = auto_assign_id( usedIds ) ; } } @@ -976,12 +1189,13 @@ function on_save_scenario() function unload_params_for_save( user_requested ) { function extract_vo_entries( key ) { - if ( !(key in params) ) + if ( !( key in params ) ) return ; var entries = [] ; for ( var i=0 ; i < params[key].length ; ++i ) { var entry = { id: params[key][i].id, + seq_id: params[key][i].seq_id, name: params[key][i].name, // nb: not necessary, but convenient } ; if ( params[key][i].image_id !== null ) @@ -1123,7 +1337,7 @@ function on_template_pack() var pos = data.indexOf( "|" ) ; var fname = data.substring( 0, pos ).trim() ; data = data.substring( pos+1 ).trim() ; - if ( fname.substring(fname.length-4) == ".zip" ) + if ( fname.substring(fname.length-4) === ".zip" ) data = atob( data ) ; do_load_template_pack( fname, data ) ; return ; @@ -1244,7 +1458,7 @@ function do_load_template_pack( fname, data ) // check if we have a ZIP file fname = fname.toLowerCase() ; - if ( fname.substring(fname.length-4) == ".zip" ) { + if ( fname.substring(fname.length-4) === ".zip" ) { // yup - process each file in the ZIP var nFiles = 0 ; JSZip.loadAsync( data ).then( function( zip ) { diff --git a/vasl_templates/webapp/static/vassal.js b/vasl_templates/webapp/static/vassal.js index 253d167..18aa32e 100644 --- a/vasl_templates/webapp/static/vassal.js +++ b/vasl_templates/webapp/static/vassal.js @@ -180,30 +180,37 @@ function _generate_snippets() // the snippet, which is more trouble than it's worth, at this point. return ; } + var params = unload_snippet_params( true, template_id ) ; var snippet_id = template_id ; var extra_params = {} ; var player_no = get_player_no_for_element( $btn ) ; + var data ; if ( ["scenario_note","ob_setup","ob_note"].indexOf( template_id ) !== -1 ) { - var data = $btn.parent().parent().data( "sortable2-data" ) ; + data = $btn.parent().parent().data( "sortable2-data" ) ; if ( player_no ) snippet_id = template_id + "_" + player_no + "." + data.id ; else snippet_id = template_id + "." + data.id ; extra_params = get_simple_note_snippet_extra_params( $btn ) ; } - var raw_content = _get_raw_content( snippet_id, $btn ) ; + if ( ["ob_vehicle_note","ob_ordnance_note"].indexOf( template_id ) !== -1 ) { + data = $btn.parent().parent().data( "sortable2-data" ) ; + snippet_id = template_id + "_" + player_no + "." + data.id ; + } + var raw_content = _get_raw_content( snippet_id, $btn, params ) ; if ( ["scenario","players","victory_conditions"].indexOf( snippet_id ) === -1 ) { // NOTE: We don't pass through a snippet for things that have no content, // except for important stuff, such as the scenario name and victory conditions. - if ( raw_content === null || raw_content.length === 0 ) { + if ( raw_content === false || raw_content === null || raw_content.length === 0 ) { return ; } } snippets[snippet_id] = { - content: make_snippet( $btn, extra_params, false ), + content: make_snippet( $btn, params, extra_params, false ), auto_create: ! no_autocreate[template_id] && ! inactive, - raw_content: raw_content, } ; + if ( raw_content !== true ) + snippets[snippet_id].raw_content = raw_content ; if ( player_no ) snippets[snippet_id].label_area = "player" + player_no ; } @@ -219,7 +226,7 @@ function _generate_snippets() return snippets ; } -function _get_raw_content( snippet_id, $btn ) +function _get_raw_content( snippet_id, $btn, params ) { // NOTE: We pass the raw content, as entered by the user into the UI, through to the VASSAL shim, // so that it can locate legacy labels, that were created before we added snippet ID's to the templates. @@ -270,21 +277,56 @@ function _get_raw_content( snippet_id, $btn ) if ( snippet_id === "baz" ) return [ "Bazooka", "Range", "TH#" ] ; + // handle vehicle/ordnance notes + // NOTE: These were implemented after we added snippet ID's, so there's no need to support legacy labels. + // NOTE: We get called in response to an img.snippet button, which implies there is a Chapter H snippet available, + // so we don't have to check anything and just always return true. + if ( snippet_id.substring(0,16) === "ob_vehicle_note_" ) + return true ; + if ( snippet_id.substring(0,17) === "ob_ordnance_note_" ) + return true ; + // handle simple notes if ( $btn.prop( "tagName" ).toLowerCase() == "img" ) { var data = $btn.parent().parent().data( "sortable2-data" ) ; return [ data.caption ] ; } - // handle vehicles/ordnance - if ( snippet_id.substring(0,11) === "ob_vehicles" || snippet_id.substring(0,11) === "ob_ordnance" ) { - var id = snippet_id.substring(0,11) + "-sortable" + snippet_id.substring(11) ; + function get_vo_entries( vo_type, player_no, names_only ) { + var vo_entries = [] ; + var id = "ob_" + vo_type + "-sortable_" + player_no ; $( "#"+id + " > li" ).each( function() { var vo_entry = $(this).data( "sortable2-data" ).vo_entry ; - raw_content.push( vo_entry.name ) ; + vo_entries.push( names_only ? vo_entry.name : vo_entry ) ; } ) ; - return raw_content ; + return vo_entries ; } + // handle multi-applicable vehicle/ordnance notes + // NOTE: These were implemented after we added snippet ID's, so there's no need to support legacy labels. + function check_ma_notes( vo_type, player_no ) { + var nat = params[ "PLAYER_" + player_no ] ; + // NOTE: The following test has to handle a number of subtleties: + // - if no Chapter H data has been configured, we don't create the label + // However, if Chapter data has been configured, we always create the label, even if: + // - there are no notes whatsoever (e.g. Romania). + // - there are notes, but no multi-applicable notes (e.g. Belgium) + // It's tempting to think that it might be better to skip creating the label if there are no available + // multi-applicable notes, but this will be confusing for the user, since the label will not appear + // in the VASL scenario, and it won't be immediately clear why. + if ( !( vo_type in gVehicleOrdnanceNotes && Object.keys(gVehicleOrdnanceNotes[vo_type]).length > 0 ) ) + return false ; + vo_entries = get_vo_entries( vo_type, player_no, false ) ; + var result = get_ma_notes_keys( nat, vo_entries, vo_type ) ; + return (result[0] && result[0].length > 0) || (result[1] && result[1].length > 0) ; + } + var player_no, nat, vo_entries, keys ; + if ( snippet_id.substring(0,21) === "ob_vehicles_ma_notes_" || snippet_id.substring(0,21) === "ob_ordnance_ma_notes_" ) + return check_ma_notes( snippet_id.substring(3,11), snippet_id.substring(21) ) ; + + // handle vehicles/ordnance + if ( snippet_id.substring(0,12) === "ob_vehicles_" || snippet_id.substring(0,12) === "ob_ordnance_" ) + return get_vo_entries( snippet_id.substring(3,11), snippet_id.substring(12), true ) ; + return null ; } diff --git a/vasl_templates/webapp/static/vo.js b/vasl_templates/webapp/static/vo.js index 97a8eb0..977c2a2 100644 --- a/vasl_templates/webapp/static/vo.js +++ b/vasl_templates/webapp/static/vo.js @@ -112,7 +112,12 @@ function add_vo( vo_type, player_no ) var sel_index = $elem.children( ".vo-entry" ).data( "index" ) ; var $img = $elem.find( "img[class='vasl-image']" ) ; var vo_image_id = $img.data( "vo-image-id" ) ; - do_add_vo( vo_type, player_no, entries[sel_index], vo_image_id, null ) ; + var usedIds = {}; + $sortable2.find( "li" ).each( function() { + usedIds[ $(this).data( "sortable2-data" ).id ] = true ; + } ) ; + var seq_id = auto_assign_id( usedIds, "seq_id" ) ; + do_add_vo( vo_type, player_no, entries[sel_index], vo_image_id, null, seq_id ) ; $(this).dialog( "close" ) ; }, Cancel: function() { $(this).dialog( "close" ) ; }, @@ -122,11 +127,12 @@ function add_vo( vo_type, player_no ) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -function do_add_vo( vo_type, player_no, vo_entry, vo_image_id, custom_capabilities ) +function do_add_vo( vo_type, player_no, vo_entry, vo_image_id, custom_capabilities, seq_id ) { // add the specified vehicle/ordnance // NOTE: We set a fixed height for the sortable2 entries (based on the CSS settings in tabs-ob.css), // so that the vehicle/ordnance images won't get truncated if there are a lot of them. + var nat = get_player_nat( player_no ) ; var $sortable2 = $( "#ob_" + vo_type + "-sortable_" + player_no ) ; var div_tag = "
", "
", "
", "
", - "
", - "
" ] ; + "" + ] ; + var vo_note_key = get_vo_note_key( vo_entry ) ; + var vo_nat ; + if ( is_known_vo_note_key( vo_type, nat, vo_note_key ) ) + vo_nat = nat ; + else { + // NOTE: Note numbers seem to be distinct across all Allied Minor or all Axis Minor vehicles/ordnance, + // so if we don't find a note in a given nationality's normal vehicles/ordnance, we can get away with + // just checking their corresponding common vehicles/ordnance. + var nat_type = gTemplatePack.nationalities[ nat ].type ; + if ( ["allied-minor","axis-minor"].indexOf( nat_type ) !== -1 ) { + if ( is_known_vo_note_key( vo_type, nat_type, vo_note_key ) ) + vo_nat = nat_type ; + } + } + if ( vo_nat ) { + var template_id = (vo_type === "vehicles") ? "ob_vehicle_note" : "ob_ordnance_note" ; + buf.push( + "" + ) ; + data.vo_note_url = APP_URL_BASE + "/" + vo_type + "/" + vo_nat + "/note/" + vo_note_key ; + } + buf.push( "" ) ; + var $content = $( buf.join("") ) ; var $entry = $sortable2.sortable2( "add", { - content: $( buf.join("") ), + content: $content, data: data, } ) ; update_vo_sortable2_entry( $entry ) ; + + // add a handler for the snippet button + $content.children("img.snippet").click( function() { + generate_snippet( $(this), {} ) ; + } ) ; } function update_vo_sortable2_entry( $entry, snippet_params ) diff --git a/vasl_templates/webapp/templates/index.html b/vasl_templates/webapp/templates/index.html index 092290e..4f5a6a1 100644 --- a/vasl_templates/webapp/templates/index.html +++ b/vasl_templates/webapp/templates/index.html @@ -94,6 +94,8 @@ gGetTemplatePackUrl = "{{url_for('get_template_pack')}}" ; 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')}}" ; gUpdateVsavUrl = "{{url_for('update_vsav')}}" ; gHelpUrl = "{{url_for('show_help')}}" ; diff --git a/vasl_templates/webapp/templates/tabs-ob1.html b/vasl_templates/webapp/templates/tabs-ob1.html index 5bb6a92..3b9093f 100644 --- a/vasl_templates/webapp/templates/tabs-ob1.html +++ b/vasl_templates/webapp/templates/tabs-ob1.html @@ -52,13 +52,24 @@ @@ -71,13 +82,24 @@ diff --git a/vasl_templates/webapp/templates/vo-notes-report.html b/vasl_templates/webapp/templates/vo-notes-report.html new file mode 100644 index 0000000..a428bbc --- /dev/null +++ b/vasl_templates/webapp/templates/vo-notes-report.html @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/vasl_templates/webapp/templates/vo-report.html b/vasl_templates/webapp/templates/vo-report.html index df9749b..c2d39f2 100644 --- a/vasl_templates/webapp/templates/vo-report.html +++ b/vasl_templates/webapp/templates/vo-report.html @@ -22,7 +22,7 @@ td { padding: 0.2em 0.5em ; }