diff --git a/vasl_templates/webapp/data/default-template-pack/extras/blank-space.j2 b/vasl_templates/webapp/data/default-template-pack/extras/blank-space.j2 new file mode 100644 index 0000000..6832123 --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/extras/blank-space.j2 @@ -0,0 +1,11 @@ + + + + + + + +
  +
+ + diff --git a/vasl_templates/webapp/data/default-template-pack/extras/hip-guns.j2 b/vasl_templates/webapp/data/default-template-pack/extras/hip-guns.j2 new file mode 100755 index 0000000..1c5013e --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/extras/hip-guns.j2 @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
Hidden Guns
+ +
  + Hidden + Possible +
  + Fires + Flip + Fires + Remove + +
H H H + 2-5 6+     +
H H P + 2-6 7+ 2-3 4-11 +
H P P + 2-7 8+ 2-4 5-11 +
H H + 2-6 7+     +
H P + 2-8 9+ 2-4 5-11 +
P P P +     2-5 6-10 +
P P +     2-6 7-10 + +
+ +Leadership DRM's apply. + + + diff --git a/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 b/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 new file mode 100644 index 0000000..999f7f3 --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + +
+ {%if PLAYER_FLAGS["german"]%} {%endif%}Grenade Bundles + +
+ -2 CC Attack DRM
+ ATMM check: dr ≤ 3 (△)
+ + + + + +
+1 + HS/crew +
+2 + SMC +
+1 + CX +
+1 + vs. non-armored vehicle +
+ original 6 = pinned (CCV reduced by 1) + +
+ + + diff --git a/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 b/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 new file mode 100644 index 0000000..dfff9ab --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + +
+ {%if PLAYER_FLAGS["german"]%} {%endif%}Molotov Cocktails + +
+ vs. AFV only
+ MOL check: dr ≤ 3 (△)
+ + + + +
+1 + HS/crew +
+2 + SMC +
+1 + CX +
+ IFT DR original colored dr: +
    +
  • 1 = Flame in target Location +
  • 6 = thrower breaks, Flame in their Location +
+ Kindling Attempt: +2 DRM + +
+ + + diff --git a/vasl_templates/webapp/data/default-template-pack/extras/pf-count.j2 b/vasl_templates/webapp/data/default-template-pack/extras/pf-count.j2 new file mode 100644 index 0000000..cda7253 --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/extras/pf-count.j2 @@ -0,0 +1,7 @@ + + + + + + +
{{PF_COUNT:/3|Number of PF shots}} diff --git a/vasl_templates/webapp/data/default-template-pack/extras/turn-track-shading.j2 b/vasl_templates/webapp/data/default-template-pack/extras/turn-track-shading.j2 new file mode 100644 index 0000000..f76afee --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/extras/turn-track-shading.j2 @@ -0,0 +1,11 @@ + + + + + + + +
  +
+ + diff --git a/vasl_templates/webapp/snippets.py b/vasl_templates/webapp/snippets.py index a357b9e..e498d85 100644 --- a/vasl_templates/webapp/snippets.py +++ b/vasl_templates/webapp/snippets.py @@ -36,6 +36,14 @@ def get_template_pack(): with open(fname,"r") as fp: data["nationalities"] = json.load( fp ) + # NOTE: Similarly, we always load the default extras templates, and user-defined template packs + # can add to them, or modify existing ones, but not remove them. + dname = os.path.join( base_dir, "extras" ) + if os.path.isdir( dname ): + _, extra_templates = _do_get_template_pack( dname ) + for key,val in extra_templates.items(): + data["templates"]["extras/"+key] = val + # check if a default template pack has been configured if default_template_pack: dname = default_template_pack @@ -60,11 +68,13 @@ def get_template_pack(): if fname.endswith( "/" ): continue fdata = zip_file.read( fname ).decode( "utf-8" ) - fname = os.path.split(fname)[1] - if fname.lower() == "nationalities.json": + fname2 = os.path.split(fname)[1] + if fname2.lower() == "nationalities.json": data["nationalities"].update( json.loads( fdata ) ) continue - data["templates"][ os.path.splitext(fname)[0] ] = fdata + if fname.startswith( "extras" + os.sep ): + fname2 = "extras/" + fname2 + data["templates"][ fname2 ] = fdata return jsonify( data ) @@ -72,19 +82,25 @@ def get_template_pack(): def _do_get_template_pack( dname ): """Get the specified template pack.""" + dname = os.path.abspath( dname ) if not os.path.isdir( dname ): abort( 404 ) nationalities, templates = {}, {} for root,_,fnames in os.walk(dname): for fname in fnames: # add the next file to the results - with open( os.path.join(root,fname), "r" ) as fp: + words = os.path.splitext( fname ) + fname = os.path.join( root, fname ) + with open( fname, "r" ) as fp: if fname.lower() == "nationalities.json": nationalities = json.load( fp ) continue - words = os.path.splitext( fname ) if words[1] == ".j2": - templates[words[0]] = fp.read() + fname2 = words[0] + relpath = os.path.relpath( os.path.abspath(fname), dname ) + if relpath.startswith( "extras" + os.sep ): + fname2 = "extras/" + fname2 + templates[fname2] = fp.read() return nationalities, templates # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/static/css/select-vo-dialog.css b/vasl_templates/webapp/static/css/select-vo-dialog.css index 3e08342..c5ff016 100644 --- a/vasl_templates/webapp/static/css/select-vo-dialog.css +++ b/vasl_templates/webapp/static/css/select-vo-dialog.css @@ -11,7 +11,7 @@ #select-vo .select2-dropdown .vo-entry .content { display: flex ; flex-direction: column ; justify-content: center ; } #select-vo .select2-dropdown .vo-entry.small-piece img { height: 2.7em ; margin-left: 0.4em ; margin-right: 0.9em ; } #select-vo .select2-dropdown .vo-entry .vo-type { font-size: 80% ; font-style: italic ; color: #888 ; } -#select-vo .select2-results__option--highlighted[aria-selected] .vo-type { color: #fff ; } +#select-vo .select2-results__option--highlighted[aria-selected] .vo-type { color: #eee ; } #select-vo .select2-dropdown .vo-entry input.select-vo-image { width: 15px ; position: relative ; top: 10px ; } .ui-dialog.select-vo .ui-dialog-buttonpane { border: none ; padding: 0 ; } diff --git a/vasl_templates/webapp/static/css/tabs-extras.css b/vasl_templates/webapp/static/css/tabs-extras.css new file mode 100644 index 0000000..02322c1 --- /dev/null +++ b/vasl_templates/webapp/static/css/tabs-extras.css @@ -0,0 +1,22 @@ +/* -------------------------------------------------------------------- */ + +#tabs-extras { height: 100% ; display: flex ; flex-direction: row ; overflow: hidden ; } +#tabs-extras .left-panel { flex: 0 0 auto ; overflow: hidden auto ; min-width: 13em ; border-right: 2px dotted #aaa; margin-right: 5px ; padding-right: 5px ; } +#tabs-extras .right-panel { flex: 1 1 auto ; width: 100%; bottom: 0 ; } + +#tabs-extras .left-panel ul { list-style-type: none ; margin: 0 ; } +#tabs-extras .left-panel li { margin-bottom: 0.25em ; padding: 5px 8px ; border: 1px solid #ccc ; background: #eee ; cursor: pointer ; } +#tabs-extras .left-panel .ui-selected { background: #fff ; border: 1px solid #888 ; text-color: #666 ; } + +#tabs-extras .left-panel .name { font-weight: bold ; color: #666 ; } +#tabs-extras .left-panel .caption { margin-top: 0.25em ; font-size: 80% ; font-style: italic ; color: #888 ; } + +#tabs-extras .right-panel { overflow-y: hidden ; padding: 5px ; border: 1px solid #888 ; border-radius: 0 10px 0 0 ; background: #fff ; } + +#tabs-extras .right-panel div.name { font-size: 150% ; font-weight: bold ; } +#tabs-extras .right-panel div.caption { font-style: italic ; } +#tabs-extras .right-panel div.description { margin-top: 0.5em ; } + +#tabs-extras .right-panel table { margin-top: 1em ; } +#tabs-extras .right-panel td.caption { font-weight: bold ; padding-right: 0.25em ; } +#tabs-extras .right-panel .snippet-control { margin-top: 0.5em ; } diff --git a/vasl_templates/webapp/static/css/tabs-scenario.css b/vasl_templates/webapp/static/css/tabs-scenario.css index fd19f96..0c50634 100644 --- a/vasl_templates/webapp/static/css/tabs-scenario.css +++ b/vasl_templates/webapp/static/css/tabs-scenario.css @@ -7,7 +7,8 @@ #panel-scenario input[name='SCENARIO_ID'] { margin-left: 0.25em ; width: 80px ; flex-grow: 0 ; } #panel-scenario input[name='SCENARIO_DATE'] { width: 6em ; flex-grow: 0 ; } -#panel-scenario label[for='PLAYER_1'], label[for='PLAYER_2'] { margin-top: 2px ; } +#panel-scenario label[for='PLAYER_1'] { margin-top: 2px ; } +#panel-scenario label[for='PLAYER_2'] { margin-top: 2px ; } #panel-scenario label { font-weight: bold ; width: 5em ; } #panel-scenario label.header { font-weight: bold ; width: 3em ; text-align: center ; } @@ -15,7 +16,7 @@ #panel-scenario .select2-container { margin: 2px ; } #panel-scenario .select2-selection__rendered { height: 24px ; margin-top: -3px ; } -#panel-scenario .select2-selection__arrow { margin-top: -2px ; } +#panel-scenario .select2-selection__arrow { margin-top: -3px ; } #panel-scenario .select2-selection { height: 24px !important ; border-radius: 0 !important ; } #panel-scenario .select2-container[name="SCENARIO_THEATER"] .select2-selection { height: 22px !important ; margin-top: -4px ;} diff --git a/vasl_templates/webapp/static/extras.js b/vasl_templates/webapp/static/extras.js new file mode 100644 index 0000000..2b6647f --- /dev/null +++ b/vasl_templates/webapp/static/extras.js @@ -0,0 +1,166 @@ + +// -------------------------------------------------------------------- + +function init_extras() +{ + // initialize the layout + $( "#tabs-extras .left-panel" ).resizable( { + resizeHeight: false, + handles: "e", + create: function( event, ui ) { + $( ".ui-resizable-e" ).css( "cursor", "ew-resize" ) ; + }, + } ) ; + + // identify the extras templates + var extra_templates = [] ; + for ( var template_id in gTemplatePack.templates ) { + if ( template_id.substr( 0, 7 ) === "extras/" ) { + extra_templates.push( + _parse_extra_template( template_id, gTemplatePack.templates[template_id] ) + ) ; + } + } + + // sort the extras templates by name + extra_templates.sort( function( lhs, rhs ) { + return lhs.name.localeCompare( rhs.name, "en", { sensitivity: "base" } ) ; + } ) ; + + // build the side-panel showing the available templates + var $index = $( "" ) ; + for ( var i=0 ; i < extra_templates.length ; ++i ) { + var buf = [] ; + buf.push( "
  • ", + "
    ", extra_templates[i].name, "
    " + ) ; + if ( extra_templates[i].caption ) + buf.push( "
    ", extra_templates[i].caption, "
    " ) ; + buf.push( "
  • ", "" ) ; + var $entry = $( buf.join("") ) ; + if ( i === 0 ) + $entry.addClass( "ui-selecting" ) ; + $entry.data( "template_id", extra_templates[i].template_id ) ; + $index.append( $entry ) ; + } + $index.selectable( { + selected: function( evt, ui ) { + _show_extra_template( $(ui.selected).data( "template_id" ) ) ; + }, + selecting: function( evt, ui ) { // nb: prevent multiple selections + if ( $index.find( ".ui-selected, .ui-selecting" ).length > 1 ) + $(ui.selecting).removeClass( "ui-selecting" ) ; + }, + } ) ; + $index.data( "ui-selectable" )._mouseStop( null ) ; // nb: trigger the selection + $( "#tabs-extras .left-panel .content" ).empty().append( $index ) ; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +function _show_extra_template( template_id ) +{ + // parse the template (nb: we do this every time since the user may have changed it in the UI) + var template_info = _parse_extra_template( template_id, gTemplatePack.templates[template_id] ) ; + + // generate the form for entering the template parameters + var buf = [ "
    " ] ; + buf.push( "
    ", template_info.name, "
    " ) ; + if ( template_info.caption ) + buf.push( "
    ", template_info.caption, "
    " ) ; + if ( template_info.description ) + buf.push( "
    ", template_info.description, "
    " ) ; + if ( template_info.params.length > 0 ) { + buf.push( "" ) ; + for ( var i=0 ; i < template_info.params.length ; ++i ) { + buf.push( "" ) ; + var display_name = template_info.params[i].caption || template_info.params[i].name ; + buf.push( "
    ", escapeHTML(display_name)+":" ) ; + buf.push( "", "" ) ; + } + buf.push( "
    " ) ; + } + buf.push( "" ) ; + buf.push( "
    " ) ; + var $form = $( buf.join("") ) ; + + // initialize the "generate" button + init_snippet_button( $form.find( "button.generate" ) ) ; + + // install the form + $( "#tabs-extras .right-panel" ).empty().append( $form ) ; +} + +// -------------------------------------------------------------------- + +function _parse_extra_template( template_id, template ) +{ + // extract the main details + var result = { template_id: template_id, name: template_id } ; + function extract_val( key ) { + var match = template.match( new RegExp( "" ) ) ; + if ( match ) + result[key] = match[1] ; + } + extract_val( "name" ) ; + extract_val( "caption" ) ; + extract_val( "description" ) ; + + // extract the template parameters + result.params = [] ; + var params_regex = new RegExp( /\{\{(.*?)\}\}/g ) ; + while( (match = params_regex.exec( template )) !== null ) { + // extract the parameter name and default value + var words = match[1].split( "|" ) ; + var param = { name: words[0] } ; + var pos = param.name.indexOf( ":" ) ; + if ( pos === -1 ) + continue ; + param.default = param.name.substr( pos+1 ) ; + param.name = param.name.substr( 0, pos ) ; + // extract the field width + pos = param.default.indexOf( "/" ) ; + if ( pos !== -1 ) { + param.width = param.default.substr( pos+1 ) ; + param.default = param.default.substr( 0, pos ) ; + } + // extract the caption and description + if ( words.length >= 2 ) + param.caption = words[1] ; + if ( words.length >= 3 ) + param.description = words[2] ; + result.params.push( param ) ; + } + + return result ; +} + +// -------------------------------------------------------------------- + +function fixup_template_parameters( template ) +{ + // identify any non-standard template parameters + var matches = [] ; + var regex = /\{\{([A-Z0-9_]+?):.*?\}\}/g ; + var match ; + while( (match = regex.exec( template )) !== null ) + matches.push( [ regex.lastIndex-match[0].length, match[0].length, match[1] ] ) ; + + // fix them up + if ( matches.length > 0 ) { + for ( var i=matches.length-1 ; i >= 0 ; --i ) + template = template.substr(0,matches[i][0]) + "{{"+matches[i][2]+"}}" + template.substr(matches[i][0]+matches[i][1]) ; + } + + // remove comments + template = template.replace( /\n*/g, "" ) ; + + return template ; +} diff --git a/vasl_templates/webapp/static/images/extras.png b/vasl_templates/webapp/static/images/extras.png new file mode 100755 index 0000000..e38d9cf Binary files /dev/null and b/vasl_templates/webapp/static/images/extras.png differ diff --git a/vasl_templates/webapp/static/images/scenario.png b/vasl_templates/webapp/static/images/scenario.png index 2fd56bd..3c73c78 100755 Binary files a/vasl_templates/webapp/static/images/scenario.png and b/vasl_templates/webapp/static/images/scenario.png differ diff --git a/vasl_templates/webapp/static/main.js b/vasl_templates/webapp/static/main.js index cdd4ec9..f0a2b7a 100644 --- a/vasl_templates/webapp/static/main.js +++ b/vasl_templates/webapp/static/main.js @@ -1,7 +1,7 @@ APP_URL_BASE = window.location.origin ; +gDefaultTemplatePack = null ; gTemplatePack = {} ; -gDefaultNationalities = {} ; gValidTemplateIds = [] ; gVehicleOrdnanceListings = {} ; gVaslPieceInfo = {} ; @@ -92,7 +92,7 @@ $(document).ready( function () { // initialize the tabs $("#tabs").tabs( { heightStyle: "fill", - disabled: [1, 2], // nb: we enable these when the page has finished loading + disabled: [1, 2, 3], // nb: we enable these when the page has finished loading activate: on_tab_activate, } ).show() ; var navHeight = $("#tabs .ui-tabs-nav").height() ; @@ -257,8 +257,8 @@ $(document).ready( function () { delete data._path_ ; } } + gDefaultTemplatePack = $.extend( true, {}, data ) ; install_template_pack( data ) ; - gDefaultNationalities = $.extend( true, {}, data.nationalities ) ; // NOTE: If we are loading a user-defined template pack, then what we think // is the set of valid template ID's will depend on what's in it :-/ gValidTemplateIds = Object.keys( data.templates ) ; @@ -291,49 +291,7 @@ $(document).ready( function () { $(window).trigger( "resize" ) ; // replace all the "generate" buttons with "generate/edit" button/droplist's - $("button.generate").each( function() { - var template_id = $(this).attr( "data-id" ) ; - var template_id2 ; - if ( template_id.substring(0,9) === "ob_setup_" ) - template_id2 = "ob_setup" ; - else if ( template_id.substring(0,12) == "ob_vehicles_" ) - template_id2 = "ob_vehicles" ; - else if ( template_id.substring(0,12) == "ob_ordnance_" ) - template_id2 = "ob_ordnance" ; - else - template_id2 = template_id ; - var buf = [ "
    ", - $(this).prop( "outerHTML" ), - "", - "
    " - ] ; - var $newElem = $( buf.join("") ) ; - $newElem.find( "button" ).prepend( - $( "" ) - ) ; - $newElem.controlgroup() ; - $newElem.children("select").each( function() { - $(this).selectmenu( { - classes: { - "ui-selectmenu-button": "ui-button-icon-only", - "ui-selectmenu-menu": "snippet-control-menu-item", - }, - } ) ; - } ) ; - $newElem.children(".ui-button-icon-only").css( "width", "1em" ) ; - $newElem.children(".ui-selectmenu-button").click( function() { $(this).blur() ; } ) ; - $(this).replaceWith( $newElem ) ; - } ) ; - - // handle requests to generate/edit HTML snippets - $("button.generate").click( function() { - generate_snippet( $(this), null ) ; - } ).attr( "title", "Generate a snippet." ) ; - $("div.snippet-control select").on( "selectmenuselect", function() { - edit_template( $(this).attr("data-id") ) ; - } ) ; + $("button.generate").each( function() { init_snippet_button( $(this) ) ; } ) ; // handle requests to edit the templates $("button.edit-template").click( function() { @@ -399,6 +357,59 @@ $(document).ready( function () { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +function init_snippet_button( $btn ) +{ + // figure out what template we're dealing with + var template_id = $btn.attr( "data-id" ) ; + var template_id2 ; + if ( template_id.substring(0,9) === "ob_setup_" ) + template_id2 = "ob_setup" ; + else if ( template_id.substring(0,12) == "ob_vehicles_" ) + template_id2 = "ob_vehicles" ; + else if ( template_id.substring(0,12) == "ob_ordnance_" ) + template_id2 = "ob_ordnance" ; + else + template_id2 = template_id ; + + // create the new button + var buf = [ "
    ", + $btn.prop( "outerHTML" ), + "", + "
    " + ] ; + var $newBtn = $( buf.join("") ) ; + $newBtn.find( "button" ).prepend( + $( "" ) + ).click( function() { + generate_snippet( $(this), null ) ; + } ).attr( "title", "Generate a snippet." ) ; + + // add in the droplist + $newBtn.controlgroup() ; + $newBtn.children( "select" ).each( function() { + $(this).selectmenu( { + classes: { + "ui-selectmenu-button": "ui-button-icon-only", + "ui-selectmenu-menu": "snippet-control-menu-item", + }, + } ) ; + } ) ; + $newBtn.children( ".ui-button-icon-only" ).css( "width", "1em" ) ; + $newBtn.children( ".ui-selectmenu-button" ).click( function() { $btn.blur() ; } ) ; + + // handle requests to edit the template + $newBtn.children( "select" ).on( "selectmenuselect", function() { + edit_template( $(this).attr("data-id") ) ; + } ) ; + + // replace the existing button with the new replacement button + $btn.replaceWith( $newBtn ) ; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + gPageLoadStatus = [ "main", "vehicle-listings", "ordnance-listings", "vasl-piece-info", "template-pack", "default-scenario" ] ; function update_page_load_status( id ) @@ -420,6 +431,9 @@ function update_page_load_status( id ) if ( gPageLoadStatus.length === 0 ) { // yup - update the UI apply_user_settings() ; + $( "a[href='#tabs-extras'] div" ).html( + " Extras" + ) ; $("#tabs").tabs({ disabled: [] }) ; $("#loader").fadeOut( 500 ) ; adjust_footer_vspacers() ; @@ -447,7 +461,8 @@ function init_hotkeys() var curr_tab = $("#tabs .ui-tabs-active a").attr( "href" ) ; if ( curr_tab !== tab ) $("#tabs .ui-tabs-nav a[href='"+tab+"']").trigger( "click" ) ; - $ctrl.focus() ; + if ( $ctrl ) + $ctrl.focus() ; } $(document).bind( "keydown", "alt+c", function() { set_focus_to( "#tabs-scenario", $("input[name='SCENARIO_NAME']") ) ; @@ -467,6 +482,9 @@ function init_hotkeys() $(document).bind( "keydown", "alt+2", function() { set_focus_to( "#tabs-ob2", $("textarea[name='OB_SETUP_2']") ) ; } ) ; + $(document).bind( "keydown", "alt+x", function() { + set_focus_to( "#tabs-extras" ) ; + } ) ; } // -------------------------------------------------------------------- @@ -475,6 +493,7 @@ function install_template_pack( data ) { // install the template pack gTemplatePack = data ; + init_extras() ; // update the player droplists var curSel = { diff --git a/vasl_templates/webapp/static/snippets.js b/vasl_templates/webapp/static/snippets.js index 27cbe38..c01526a 100644 --- a/vasl_templates/webapp/static/snippets.js +++ b/vasl_templates/webapp/static/snippets.js @@ -27,10 +27,6 @@ function generate_snippet( $btn, extra_params ) var params = unload_snippet_params( true, template_id ) ; // set player-specific parameters - function make_player_flag_url( player_no ) { - var player_nat = get_player_nat( player_no ) ; - return APP_URL_BASE + "/flags/" + player_nat ; - } var curr_tab = $("#tabs .ui-tabs-active a").attr( "href" ) ; var colors ; if ( curr_tab === "#tabs-ob1" ) { @@ -39,14 +35,14 @@ function generate_snippet( $btn, extra_params ) params.OB_COLOR = colors[0] ; params.OB_COLOR_2 = colors[2] ; if ( gUserSettings["include-flags-in-snippets"] ) - params.PLAYER_FLAG = make_player_flag_url( 1 ) ; + params.PLAYER_FLAG = make_player_flag_url( get_player_nat( 1 ) ) ; } else if ( curr_tab === "#tabs-ob2" ) { params.PLAYER_NAME = get_nationality_display_name( params.PLAYER_2 ) ; colors = get_player_colors( 2 ) ; params.OB_COLOR = colors[0] ; params.OB_COLOR_2 = colors[2] ; if ( gUserSettings["include-flags-in-snippets"] ) - params.PLAYER_FLAG = make_player_flag_url( 2 ) ; + params.PLAYER_FLAG = make_player_flag_url( get_player_nat( 2 ) ) ; } // set player-specific parameters @@ -73,6 +69,17 @@ function generate_snippet( $btn, extra_params ) params.PLAYER_1_NAME = get_nationality_display_name( params.PLAYER_1 ) ; params.PLAYER_2_NAME = get_nationality_display_name( params.PLAYER_2 ) ; + // pass through all the player colors and names + params.PLAYER_NAMES = {} ; + params.PLAYER_COLORS = {} ; + params.PLAYER_FLAGS = {} ; + $.each( gTemplatePack.nationalities, function( nat ) { + params.PLAYER_NAMES[nat] = gTemplatePack.nationalities[nat].display_name ; + params.PLAYER_COLORS[nat] = gTemplatePack.nationalities[nat].ob_colors ; + if ( gUserSettings["include-flags-in-snippets"] ) + params.PLAYER_FLAGS[nat] = make_player_flag_url( nat ) ; + } ) ; + // generate PF parameters if ( params.SCENARIO_YEAR < 1944 || (params.SCENARIO_YEAR == 1944 && params.SCENARIO_MONTH < 6) ) params.PF_RANGE = 1 ; @@ -140,7 +147,7 @@ function generate_snippet( $btn, extra_params ) showWarningMsg( "Both players have the same nationality!" ) ; // get the template to generate the snippet from - var templ = get_template( template_id ) ; + var templ = get_template( template_id, true ) ; if ( templ === null ) return ; var func ; @@ -184,7 +191,7 @@ function generate_snippet( $btn, extra_params ) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -function unload_snippet_params( unpack_scenario_date, show_warnings_for ) +function unload_snippet_params( unpack_scenario_date, template_id ) { var params = {} ; @@ -208,7 +215,14 @@ function unload_snippet_params( unpack_scenario_date, show_warnings_for ) // collect all the template parameters add_param = function($elem) { params[ $elem.attr("name") ] = $elem.val() ; } ; - $("input[type='text'].param").each( function() { add_param($(this)) ; } ) ; + $("input[type='text'].param").each( function() { + // NOTE: We only unload parameters on the EXTRAS tab if we're processing an extras template. + if ( $.contains( $("#tabs-extras")[0], $(this)[0] ) ) { + if ( template_id === null || template_id.substr(0,7) !== "extras/" ) + return ; + } + add_param( $(this) ) ; + } ) ; $("textarea.param").each( function() { add_param($(this)) ; } ) ; $("select.param").each( function() { add_param($(this)) ; } ) ; @@ -274,10 +288,10 @@ function unload_snippet_params( unpack_scenario_date, show_warnings_for ) if ( objs.length > 0 ) params[key] = objs ; } - get_vo( "vehicles", 1, "OB_VEHICLES_1", show_warnings_for === "ob_vehicles_1" ) ; - get_vo( "vehicles", 2, "OB_VEHICLES_2", show_warnings_for === "ob_vehicles_2" ) ; - get_vo( "ordnance", 1, "OB_ORDNANCE_1", show_warnings_for === "ob_ordnance_1" ) ; - get_vo( "ordnance", 2, "OB_ORDNANCE_2", show_warnings_for === "ob_ordnance_2" ) ; + get_vo( "vehicles", 1, "OB_VEHICLES_1", template_id === "ob_vehicles_1" ) ; + get_vo( "vehicles", 2, "OB_VEHICLES_2", template_id === "ob_vehicles_2" ) ; + get_vo( "ordnance", 1, "OB_ORDNANCE_1", template_id === "ob_ordnance_1" ) ; + get_vo( "ordnance", 2, "OB_ORDNANCE_2", template_id === "ob_ordnance_2" ) ; return params ; } @@ -570,11 +584,17 @@ function make_crew_survival( vo_entry ) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -function get_template( template_id ) +function get_template( template_id, fixup ) { // get the specified template - if ( template_id in gTemplatePack.templates ) - return gTemplatePack.templates[template_id] ; + if ( template_id in gTemplatePack.templates ) { + var template = gTemplatePack.templates[ template_id ] ; + if ( fixup ) { + if ( template_id.substr(0,7) === "extras/" ) + template = fixup_template_parameters( template ) ; + } + return template ; + } showErrorMsg( "Unknown template: " + escapeHTML(template_id) + "" ) ; return null ; } @@ -588,7 +608,7 @@ function edit_template( template_id ) template_id = "ob_ordnance" ; else if ( template_id.substring(0,12) == "ob_vehicles_" ) template_id = "ob_vehicles" ; - var template = get_template( template_id ) ; + var template = get_template( template_id, false ) ; if ( template === null ) return ; @@ -967,7 +987,10 @@ function do_on_new_scenario( verbose ) { function reset_scenario() { // reset all the template parameters - $("input[type='text'].param").each( function() { $(this).val("") ; } ) ; + $("input[type='text'].param").each( function() { + if ( ! $.contains( $("#tabs-extras")[0], $(this)[0] ) ) + $(this).val( "" ) ; + } ) ; $("textarea.param").each( function() { $(this).val("") ; } ) ; // reset all the template parameters @@ -1047,10 +1070,17 @@ function do_load_template_pack( fname, data ) var invalid_filename_extns = [] ; var unknown_template_ids = [] ; var template_pack = { - nationalities: $.extend( true, {}, gDefaultNationalities ), + nationalities: $.extend( true, {}, gDefaultTemplatePack.nationalities ), templates: {}, } ; + // NOTE: We always start with the default extras templates; user-defined template packs + // can add to them, or modify existing ones, but not remove them. + for ( var template_id in gDefaultTemplatePack.templates ) { + if ( template_id.substr( 0, 7 ) === "extras/" ) + template_pack.templates[template_id] = gDefaultTemplatePack.templates[template_id].slice() ; + } + // initialize function on_template_pack_file( fname, data ) { // make sure the filename is valid @@ -1070,7 +1100,7 @@ function do_load_template_pack( fname, data ) return ; } var template_id = fname.substring( 0, fname.length-3 ).toLowerCase() ; - if ( gValidTemplateIds.indexOf( template_id ) === -1 ) { + if ( gValidTemplateIds.indexOf( template_id ) === -1 && template_id.substr(0,7) !== "extras/" ) { unknown_template_ids.push( fname ) ; return ; } @@ -1136,7 +1166,7 @@ function do_load_template_pack( fname, data ) var pos = Math.max( fname.lastIndexOf("/"), fname.lastIndexOf("\\") ) ; if ( pos === fname.length-1 ) return ; // nb: ignore directory entries - if ( pos !== -1 ) + if ( pos !== -1 && fname.substr(0,7) !== "extras/" ) fname = fname.substring( pos+1 ) ; on_template_pack_file( fname, data ) ; } ).then( function() { diff --git a/vasl_templates/webapp/static/utils.js b/vasl_templates/webapp/static/utils.js index 68e395a..8f98f34 100644 --- a/vasl_templates/webapp/static/utils.js +++ b/vasl_templates/webapp/static/utils.js @@ -31,6 +31,10 @@ function get_player_colors_for_element( $elem ) return get_player_colors( player_no ) ; } +function make_player_flag_url( player_nat ) { + return APP_URL_BASE + "/flags/" + player_nat ; +} + function get_player_no_for_element( $elem ) { // get the player colors (if any) for the specified element diff --git a/vasl_templates/webapp/templates/index.html b/vasl_templates/webapp/templates/index.html index 7519bba..409d1d2 100644 --- a/vasl_templates/webapp/templates/index.html +++ b/vasl_templates/webapp/templates/index.html @@ -15,6 +15,7 @@ + @@ -93,6 +94,7 @@ gHelpUrl = "{{url_for('show_help')}}" ; + diff --git a/vasl_templates/webapp/templates/tabs-extras.html b/vasl_templates/webapp/templates/tabs-extras.html new file mode 100644 index 0000000..a665170 --- /dev/null +++ b/vasl_templates/webapp/templates/tabs-extras.html @@ -0,0 +1,9 @@ +
    + +
    +
    +
    + +
    + +
    diff --git a/vasl_templates/webapp/templates/tabs-scenario.html b/vasl_templates/webapp/templates/tabs-scenario.html index ab6a8f3..7039fcd 100644 --- a/vasl_templates/webapp/templates/tabs-scenario.html +++ b/vasl_templates/webapp/templates/tabs-scenario.html @@ -28,11 +28,11 @@ -
    +
    - - + +
    diff --git a/vasl_templates/webapp/templates/tabs.html b/vasl_templates/webapp/templates/tabs.html index b84115e..9b2723e 100644 --- a/vasl_templates/webapp/templates/tabs.html +++ b/vasl_templates/webapp/templates/tabs.html @@ -1,18 +1,22 @@
    + + {%include "tabs-scenario.html"%} {%include "tabs-ob1.html"%} {%include "tabs-ob2.html"%} +{%include "tabs-extras.html"%} {%include "tabs-help.html"%}
    diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/full.j2 b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/full.j2 new file mode 100644 index 0000000..8bb3bad --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/full.j2 @@ -0,0 +1,14 @@ + + + + + + + +{{FOO}} + +
    +param = {{PARAM:default-val/10|The parameter|This is the parameter description.}} +
    + + diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/minimal.j2 b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/minimal.j2 new file mode 100644 index 0000000..324526a --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/minimal.j2 @@ -0,0 +1,7 @@ + + +
    +param = {{PARAM:}} +
    + + diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/full.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/full.j2 new file mode 100644 index 0000000..bf90c5b --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/full.j2 @@ -0,0 +1,15 @@ + + + + + + + +{{FOO}} + +
    +modified-param = {{MODIFIED-PARAM:modified-default-val/10|The modified parameter|This is the modified parameter description.}} +new-param = {{NEW-PARAM:}} +
    + + diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/subdir/new.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/subdir/new.j2 new file mode 100644 index 0000000..0e2726a --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/subdir/new.j2 @@ -0,0 +1,9 @@ + + + + +
    +new-param = {{NEW-PARAM:}} +
    + + diff --git a/vasl_templates/webapp/tests/test_extras_templates.py b/vasl_templates/webapp/tests/test_extras_templates.py new file mode 100644 index 0000000..2af8eab --- /dev/null +++ b/vasl_templates/webapp/tests/test_extras_templates.py @@ -0,0 +1,182 @@ +""" Test the extras templates. """ + +from selenium.webdriver.common.keys import Keys + +from vasl_templates.webapp.tests.utils import \ + init_webapp, find_child, find_children, wait_for, wait_for_clipboard, select_tab +from vasl_templates.webapp.tests.test_template_packs import make_zip_from_files, upload_template_pack_zip + +# --------------------------------------------------------------------- + +def test_extras_templates( webapp, webdriver ): + """Test the extras templates.""" + + # initialize + init_webapp( webapp, webdriver ) + select_tab( "extras" ) + + # check that the extras templates were loaded correctly + assert _get_extras_template_index() == [ + ( "extras/minimal", None ), + ( "Full template", "This is the caption." ) + ] + + # check that the "full" template was loaded correctly + _select_extras_template( webdriver, "extras/full" ) + content = find_child( "#tabs-extras .right-panel" ) + assert find_child( "div.name", content ).text == "Full template" + assert find_child( "div.caption", content ).text == "This is the caption." + assert find_child( "div.description", content ).text == "This is the description." + params = find_children( "tr", content ) + assert len(params) == 1 + assert find_child( "td.caption", params[0] ).text == "The parameter:" + textbox = find_child( "td.value input", params[0] ) + assert textbox.get_attribute( "value" ) == "default-val" + assert textbox.get_attribute( "size" ) == "10" + assert textbox.get_attribute( "title" ) == "This is the parameter description." + + # generate the snippet + snippet_btn = find_child( "button.generate", content ) + snippet_btn.click() + clipboard = wait_for_clipboard( 2, "param = default-val", contains=True ) + assert "vasl-templates:comment" not in clipboard # nb: check that the comment was removed + + # check that the "minimal" template was loaded correctly + _select_extras_template( webdriver, "extras/minimal" ) + assert find_child( "div.name", content ).text == "extras/minimal" + assert find_child( "div.caption", content ) is None + assert find_child( "div.description", content ) is None + params = find_children( "tr", content ) + assert len(params) == 1 + assert find_child( "td.caption", params[0] ).text == "PARAM:" + textbox = find_child( "td.value input", params[0] ) + assert textbox.get_attribute( "value" ) == "" + + # generate the snippet + textbox.send_keys( "boo!" ) + snippet_btn = find_child( "button.generate", content ) + snippet_btn.click() + clipboard = wait_for_clipboard( 2, "param = boo!", contains=True ) + +# --------------------------------------------------------------------- + +def test_template_pack( webapp, webdriver ): + """Test uploading a template pack that contains extras templates.""" + + # initialize + init_webapp( webapp, webdriver, template_pack_persistence=1 ) + select_tab( "extras" ) + + # check that the extras templates were loaded correctly + assert _get_extras_template_index() == [ + ( "extras/minimal", None ), + ( "Full template", "This is the caption." ) + ] + + # upload the template pack + zip_data = make_zip_from_files( "extras" ) + upload_template_pack_zip( zip_data, False ) + + # check that the templates were updated correctly + assert _get_extras_template_index() == [ + ( "extras/minimal", None ), + ( "Full template (modified)", "This is the caption (modified)." ), + ( "New template", None ), + ] + + # check that the modified "full" template is being used + _select_extras_template( webdriver, "extras/full" ) + content = find_child( "#tabs-extras .right-panel" ) + assert find_child( "div.name", content ).text == "Full template (modified)" + assert find_child( "div.caption", content ).text == "This is the caption (modified)." + params = find_children( "tr", content ) + assert len(params) == 2 + assert find_child( "td.caption", params[0] ).text == "The modified parameter:" + textbox = find_child( "td.value input", params[0] ) + assert textbox.get_attribute( "value" ) == "modified-default-val" + assert textbox.get_attribute( "size" ) == "10" + assert textbox.get_attribute( "title" ) == "This is the modified parameter description." + assert find_child( "td.caption", params[1] ).text == "NEW-PARAM:" + textbox = find_child( "td.value input", params[1] ) + +# --------------------------------------------------------------------- + +def test_edit_extras_template( webapp, webdriver ): + """Test editing an extras templates.""" + + # initialize + init_webapp( webapp, webdriver ) + select_tab( "extras" ) + + # edit the "minimal" template + _select_extras_template( webdriver, "extras/minimal" ) + content = find_child( "#tabs-extras .right-panel" ) + assert find_child( "div.caption", content ) is None + webdriver.execute_script( "edit_template('extras/minimal')", content ) + textarea = find_child( "#edit-template textarea" ) + template = textarea.get_attribute( "value" ) \ + .replace( "", "\n" ) \ + .replace( "
    ", "
    \nadded = {{ADDED:added-val}}" ) + textarea.clear() + textarea.send_keys( template ) + textarea.send_keys( Keys.ESCAPE ) + + # generate the template (we should still be using the old template) + snippet_btn = find_child( "button.generate", content ) + snippet_btn.click() + wait_for_clipboard( 2, "param = ", contains=True ) + + # switch to another template, then back again + _select_extras_template( webdriver, "extras/full" ) + _select_extras_template( webdriver, "extras/minimal" ) + + # make sure the new template was loaded + assert find_child( "div.caption", content ).text == "Modified minimal." + params = find_children( "tr", content ) + assert len(params) == 2 + assert find_child( "td.caption", params[0] ).text == "ADDED:" + textbox = find_child( "td.value input", params[0] ) + assert textbox.get_attribute( "value" ) == "added-val" + + # generate the template (we should be using the new template) + snippet_btn = find_child( "button.generate", content ) + snippet_btn.click() + wait_for_clipboard( 2, "added = added-val\nparam = ", contains=True ) + +# --------------------------------------------------------------------- + +def _get_extras_template_index(): + """Get the list of extras templates from the sidebar.""" + def get_child_text( child_class, elem ): #pylint: disable=missing-docstring + elem = find_child( child_class, elem ) + return elem.text if elem else None + return [ + ( get_child_text(".name",elem), get_child_text(".caption",elem) ) + for elem in _get_extras_templates() + ] + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def _get_extras_templates(): + """Get the available extras templates.""" + return find_children( "#tabs-extras .left-panel li" ) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def _select_extras_template( webdriver, template_id ): + """Select the specified extras template.""" + + # find the specified template in the index + elems = [ + e for e in _get_extras_templates() + if webdriver.execute_script( "return $(arguments[0]).data('template_id')", e ) == template_id + ] + assert len(elems) == 1 + template_name = find_child( ".name", elems[0] ).text + + # select the template and wait for it to load + elems[0].click() + def is_template_loaded(): #pylint: disable=missing-docstring + elem = find_child( "#tabs-extras .right-panel .name" ) + return elem and elem.text == template_name + wait_for( 2, is_template_loaded ) diff --git a/vasl_templates/webapp/tests/test_template_packs.py b/vasl_templates/webapp/tests/test_template_packs.py index 2780bef..f69e05e 100644 --- a/vasl_templates/webapp/tests/test_template_packs.py +++ b/vasl_templates/webapp/tests/test_template_packs.py @@ -48,22 +48,22 @@ def test_zip_files( webapp, webdriver ): init_webapp( webapp, webdriver, template_pack_persistence=1 ) # upload a template pack that contains a full set of templates - zip_data = _make_zip_from_files( "full" ) - _, marker = _upload_template_pack_zip( zip_data, False ) + zip_data = make_zip_from_files( "full" ) + _, marker = upload_template_pack_zip( zip_data, False ) assert get_stored_msg( "_last-error_" ) == marker # check that the uploaded templates are being used _check_snippets( lambda tid: "Customized {}.".format( tid.upper() ) ) # upload only part of template pack - _ = _upload_template_pack_zip( + _ = upload_template_pack_zip( zip_data[ : int(len(zip_data)/2) ], True ) assert get_stored_msg( "_last-error_" ).startswith( "Can't unpack the ZIP:" ) # try uploading an empty template pack - _ = _upload_template_pack_zip( b"", True ) + _ = upload_template_pack_zip( b"", True ) assert get_stored_msg( "_last-error_" ).startswith( "Can't unpack the ZIP:" ) # NOTE: We can't test the limit on template pack size, since it happens after the browser's @@ -107,8 +107,8 @@ def test_nationality_data( webapp, webdriver ): assert droplist_vals["british"] == "British" # upload a template pack that contains nationality data - zip_data = _make_zip_from_files( "with-nationality-data" ) - _, marker = _upload_template_pack_zip( zip_data, False ) + zip_data = make_zip_from_files( "with-nationality-data" ) + _, marker = upload_template_pack_zip( zip_data, False ) assert get_stored_msg( "_last-error_" ) == marker # check that the UI was updated correctly @@ -136,10 +136,11 @@ def _make_zip( files ): zip_file.writestr( fname, fdata ) return open( temp_file.name, "rb" ).read() -def _make_zip_from_files( dname ): +def make_zip_from_files( dname ): """Generate a ZIP file from files in a directory.""" files = {} dname = os.path.join( os.path.split(__file__)[0], "fixtures/template-packs/"+dname ) + assert os.path.isdir( dname ) for root,_,fnames in os.walk(dname): for fname in fnames: fname = os.path.join( root, fname ) @@ -186,7 +187,7 @@ def _generate_snippet( template_id, orig_template_id ): # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def _upload_template_pack_zip( zip_data, error_expected ): +def upload_template_pack_zip( zip_data, error_expected ): """Upload a template pack ZIP.""" return _do_upload_template_pack( "{} | {}".format(