Added support for extras templates.

master
Pacman Ghost 6 years ago
parent 675629dc14
commit b524af57c2
  1. 11
      vasl_templates/webapp/data/default-template-pack/extras/blank-space.j2
  2. 60
      vasl_templates/webapp/data/default-template-pack/extras/hip-guns.j2
  3. 42
      vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2
  4. 45
      vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2
  5. 7
      vasl_templates/webapp/data/default-template-pack/extras/pf-count.j2
  6. 11
      vasl_templates/webapp/data/default-template-pack/extras/turn-track-shading.j2
  7. 28
      vasl_templates/webapp/snippets.py
  8. 2
      vasl_templates/webapp/static/css/select-vo-dialog.css
  9. 22
      vasl_templates/webapp/static/css/tabs-extras.css
  10. 5
      vasl_templates/webapp/static/css/tabs-scenario.css
  11. 166
      vasl_templates/webapp/static/extras.js
  12. BIN
      vasl_templates/webapp/static/images/extras.png
  13. BIN
      vasl_templates/webapp/static/images/scenario.png
  14. 113
      vasl_templates/webapp/static/main.js
  15. 72
      vasl_templates/webapp/static/snippets.js
  16. 4
      vasl_templates/webapp/static/utils.js
  17. 2
      vasl_templates/webapp/templates/index.html
  18. 9
      vasl_templates/webapp/templates/tabs-extras.html
  19. 6
      vasl_templates/webapp/templates/tabs-scenario.html
  20. 4
      vasl_templates/webapp/templates/tabs.html
  21. 14
      vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/full.j2
  22. 7
      vasl_templates/webapp/tests/fixtures/data/default-template-pack/extras/minimal.j2
  23. 15
      vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/full.j2
  24. 9
      vasl_templates/webapp/tests/fixtures/template-packs/extras/extras/subdir/new.j2
  25. 182
      vasl_templates/webapp/tests/test_extras_templates.py
  26. 17
      vasl_templates/webapp/tests/test_template_packs.py

@ -0,0 +1,11 @@
<html>
<!-- vasl-templates:name Blank space -->
<!-- vasl-templates:description Generates a white label that can be used to cover up and hide things in your scenario. -->
<table>
<tr>
<td style="width:{{WIDTH:60px/5|Width}};height:{{HEIGHT:60px/5|Height}};background:white;"> &nbsp;
</table>
</html>

@ -0,0 +1,60 @@
<html>
<!-- vasl-templates:name Hidden Guns -->
<!-- vasl-templates:description HIP Guns for Solo Play, taken from <a href="http://vftt.co.uk/vfttpdfs.asp" target="_blank"><i>View From The Trenches</i></a>, Issue 34/35. -->
<head>
<meta charset="utf-8">
<style>
td { margin: 0 ; padding: 0 5px ; text-align: center ; }
td.header { font-weight: bold ; }
td.header2 { background: #f8f8f8 ; padding: 2px 5px ; font-weight: bold ; }
td.status { font-weight: bold ; text-align: left ; }
</style>
</head>
<table>
<tr>
<td colspan="5" style="background: #f0f0f0 ; border-bottom: 1px solid #c0c0c0 ; padding: 2px 5px ; font-weight: bold ;">
<center> Hidden Guns </center>
<tr>
<td> &nbsp;
<td class="header2" colspan=2> Hidden
<td class="header2" colspan=2> Possible
<tr>
<td> &nbsp;
<td class="header"> Fires
<td class="header"> Flip
<td class="header"> Fires
<td class="header"> Remove
<tr>
<td class="status"> H H H
<td> 2-5 <td> 6+ <td> &nbsp; <td> &nbsp;
<tr>
<td class="status"> H H P
<td> 2-6 <td> 7+ <td> 2-3 <td> 4-11
<tr>
<td class="status"> H P P
<td> 2-7 <td> 8+ <td> 2-4 <td> 5-11
<tr>
<td class="status"> H H
<td> 2-6 <td> 7+ <td> &nbsp; <td> &nbsp;
<tr>
<td class="status"> H P
<td> 2-8 <td> 9+ <td> 2-4 <td> 5-11
<tr>
<td class="status"> P P P
<td> &nbsp; <td> &nbsp; <td> 2-5 <td> 6-10
<tr>
<td class="status"> P P
<td> &nbsp; <td> &nbsp; <td> 2-6 <td> 7-10
</table>
Leadership DRM's apply.
</html>

@ -0,0 +1,42 @@
<html>
<!-- vasl-templates:name KGS Grenade Bundles -->
<!-- vasl-templates:description Data chart for Grenade Bundles in <i>Kampfgruppe Scherer</i>. -->
<head>
<meta charset="utf-8">
<style>
td { margin: 0 ; padding: 0 ; }
</style>
</head>
<table>
<tr>
<td colspan="2" style="background: {{PLAYER_COLORS["german"][0]}} ; border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; padding: 2px 5px ; font-weight: bold ;">
{%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}" height=15 width=15>&nbsp;{%endif%}Grenade Bundles
<tr>
<td style="padding:2px 5px;">
-2 CC Attack DRM <br>
ATMM check: dr &le; 3 (&#9651;) <br>
<table style="margin-left:10px;">
<tr>
<td style="width:20px;"> +1
<td> HS/crew
<tr>
<td> +2
<td> SMC
<tr>
<td> +1
<td> CX
<tr>
<td> +1
<td> vs. non-armored vehicle
</table>
original 6 = pinned (CCV reduced by 1)
</table>
</html>

@ -0,0 +1,45 @@
<html>
<!-- vasl-templates:name KGS Molotov Cocktails -->
<!-- vasl-templates:description Data chart for Molotov Cocktails in <i>Kampfgruppe Scherer</i>. -->
<head>
<meta charset="utf-8">
<style>
td { margin: 0 ; padding: 0 ; }
ul { margin: 0 0 0 10px ; padding: 0 ; }
</style>
</head>
<table>
<tr>
<td colspan="2" style="background: {{PLAYER_COLORS["german"][0]}} ; border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; padding: 2px 5px ; font-weight: bold ;">
{%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}" height=15 width=15>&nbsp;{%endif%}Molotov Cocktails
<tr>
<td style="padding:0 5px;">
vs. AFV only <br>
MOL check: dr &le; 3 (&#9651;) <br>
<table style="margin-left:10px;">
<tr>
<td style="width:20px;"> +1
<td> HS/crew
<tr>
<td> +2
<td> SMC
<tr>
<td> +1
<td> CX
</table>
IFT DR original colored dr:
<ul>
<li> 1 = Flame in target Location
<li> 6 = thrower breaks, Flame in their Location
</ul>
Kindling Attempt: +2 DRM
</table>
</html>

@ -0,0 +1,7 @@
<html>
<!-- vasl-templates:name PF count -->
<!-- vasl-templates:description Add the snippet as the label of a Panzerfaust counter, then press <i>Ctrl-L</i> when you need to update the number of remaining shots. -->
<!-- vasl-templates:comment The HTML is deliberately malformed, so that the number of remaining shots is the last thing in snippet, which makes it easier to change during the course of a game. -->
<div style="font-size:12px;font-weight:bold;"> {{PF_COUNT:/3|Number of PF shots}}

@ -0,0 +1,11 @@
<html>
<!-- vasl-templates:name Turn Track shading -->
<!-- vasl-templates:description Generates a shaded square that you can place behind the Turn Track to indicate an LV Hindrance e.g. because of dusk/dawn. -->
<table>
<tr>
<td style="width:{{WIDTH:45px/5|Width}};height:{{HEIGHT:45px/5|Height}};background:#f0f0f0;"> &nbsp;
</table>
</html>

@ -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
# ---------------------------------------------------------------------

@ -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 ; }

@ -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 ; }

@ -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 ;}

@ -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 = $( "<ul></ul>" ) ;
for ( var i=0 ; i < extra_templates.length ; ++i ) {
var buf = [] ;
buf.push( "<li class='ui-widget-content'>",
"<div class='name'>", extra_templates[i].name, "</div>"
) ;
if ( extra_templates[i].caption )
buf.push( "<div class='caption'>", extra_templates[i].caption, "</div>" ) ;
buf.push( "</div>", "</li>" ) ;
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 = [ "<div>" ] ;
buf.push( "<div class='name'>", template_info.name, "</div>" ) ;
if ( template_info.caption )
buf.push( "<div class='caption'>", template_info.caption, "</div>" ) ;
if ( template_info.description )
buf.push( "<div class='description'>", template_info.description, "</div>" ) ;
if ( template_info.params.length > 0 ) {
buf.push( "<table>" ) ;
for ( var i=0 ; i < template_info.params.length ; ++i ) {
buf.push( "<tr>" ) ;
var display_name = template_info.params[i].caption || template_info.params[i].name ;
buf.push( "<td class='caption'>", escapeHTML(display_name)+":" ) ;
buf.push( "<td class='value'>", "<input class='param' name='" + escapeHTML(template_info.params[i].name) + "' type='text'" ) ;
if ( template_info.params[i].width )
buf.push( " size='" + escapeHTML(template_info.params[i].width) + "'" ) ;
if ( template_info.params[i].default )
buf.push( " value='" + escapeHTML(template_info.params[i].default) + "'" ) ;
if ( template_info.params[i].description )
buf.push( " title='" + escapeHTML(template_info.params[i].description) + "'" ) ;
buf.push( ">" ) ;
}
buf.push( "</table>" ) ;
}
buf.push( "<button class='generate' data-id='" + template_info.template_id + "'>Snippet</button>" ) ;
buf.push( "</div>" ) ;
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( "<!-- vasl-templates:" + key + " (.*?) -->" ) ) ;
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( /<!-- vasl-templates:.*? -->\n*/g, "" ) ;
return template ;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 871 B

@ -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 = [ "<div class='snippet-control' data-id='" + template_id + "'>",
$(this).prop( "outerHTML" ),
"<select data-id='" + template_id2 + "'>",
"<option value='edit' class='edit-template' title='Edit the template that will generate this snippet.'>Edit</option>",
"</select>",
"</div>"
] ;
var $newElem = $( buf.join("") ) ;
$newElem.find( "button" ).prepend(
$( "<img src='" + gImagesBaseUrl + "/snippet.png'>" )
) ;
$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 = [ "<div class='snippet-control' data-id='" + template_id + "'>",
$btn.prop( "outerHTML" ),
"<select data-id='" + template_id2 + "'>",
"<option value='edit' class='edit-template' title='Edit the template that will generate this snippet.'>Edit</option>",
"</select>",
"</div>"
] ;
var $newBtn = $( buf.join("") ) ;
$newBtn.find( "button" ).prepend(
$( "<img src='" + gImagesBaseUrl + "/snippet.png'>" )
).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(
"<img src='" + gImagesBaseUrl + "/extras.png'>&nbsp;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 = {

@ -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: <span class='pre'>" + escapeHTML(template_id) + "</span>" ) ;
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() {

@ -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

@ -15,6 +15,7 @@
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs-scenario.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs-ob.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs-extras.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/sortable.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/edit-template-dialog.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/edit-simple-note-dialog.css')}}" />
@ -93,6 +94,7 @@ gHelpUrl = "{{url_for('show_help')}}" ;
<script src="{{url_for('static',filename='main.js')}}"></script>
<script src="{{url_for('static',filename='snippets.js')}}"></script>
<script src="{{url_for('static',filename='extras.js')}}"></script>
<script src="{{url_for('static',filename='simple_notes.js')}}"></script>
<script src="{{url_for('static',filename='vo.js')}}"></script>
<script src="{{url_for('static',filename='vo2.js')}}"></script>

@ -0,0 +1,9 @@
<div id="tabs-extras">
<div class="left-panel">
<div class='content'></div>
</div>
<div class="right-panel"></div>
</div>

@ -28,11 +28,11 @@
<button class="generate" data-id="scenario">Snippet</button>
</span>
</div>
<div class='row'>
<div class='row' style="margin-top:0.5em;">
<label></label>
<span style='width:9.45em'></span>
<label class="header">ELR</label>
<label class="header">SAN</label>
<label class="header" for="ELR">ELR</label>
<label class="header" for="SAN">SAN</label>
</div>
<div class='row'>
<label for="PLAYER_1"><u>P</u>layer 1:</label>

@ -1,18 +1,22 @@
<div id="tabs">
<img src="{{url_for('static',filename='images/flags/scenario.png')}}" style="display:none;">
<img src="{{url_for('static',filename='images/flags/german.png')}}" style="display:none;">
<img src="{{url_for('static',filename='images/flags/russian.png')}}" style="display:none;">
<img src="{{url_for('static',filename='images/extras.png')}}" style="display:none;">
<ul>
<li> <a href="#tabs-scenario"><img src="{{url_for('static',filename='images/scenario.png')}}">&nbsp;Scenario</a>
<li> <a href="#tabs-ob1"><div style="width:7em;">&nbsp;</div></a>
<li> <a href="#tabs-ob2"><div style="width:7em;">&nbsp;</div></a>
<li> <a href="#tabs-extras"><div style="width:4em;">&nbsp;</div></a>
<li style="display:none;"> <a href="#tabs-help"><img src="{{url_for('static',filename='images/help.png')}}">&nbsp;Help</a>
</ul>
{%include "tabs-scenario.html"%}
{%include "tabs-ob1.html"%}
{%include "tabs-ob2.html"%}
{%include "tabs-extras.html"%}
{%include "tabs-help.html"%}
</div> <!-- #tabs -->

@ -0,0 +1,14 @@
<html>
<!-- vasl-templates:name Full template -->
<!-- vasl-templates:caption This is the caption. -->
<!-- vasl-templates:description This is the description. -->
<!-- vasl-templates:comment This is the comment (shouldn't appear anywhere, including the snippet). -->
{{FOO}} <!-- nb: this is a normal parameter, not one of ours (no embedded colon) -->
<div>
param = {{PARAM:default-val/10|The parameter|This is the parameter description.}}
</div>
</html>

@ -0,0 +1,7 @@
<html>
<div>
param = {{PARAM:}}
</div>
</html>

@ -0,0 +1,15 @@
<html>
<!-- vasl-templates:name Full template (modified) -->
<!-- vasl-templates:caption This is the caption (modified). -->
<!-- vasl-templates:description This is the description. -->
<!-- vasl-templates:comment This is the comment (shouldn't appear anywhere, including the snippet). -->
{{FOO}} <!-- nb: this is a normal parameter, not one of ours (no embedded colon) -->
<div>
modified-param = {{MODIFIED-PARAM:modified-default-val/10|The modified parameter|This is the modified parameter description.}}
new-param = {{NEW-PARAM:}}
</div>
</html>

@ -0,0 +1,9 @@
<html>
<!-- vasl-templates:name New template -->
<div>
new-param = {{NEW-PARAM:}}
</div>
</html>

@ -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( "<html>", "<html>\n<!-- vasl-templates:caption Modified minimal. -->" ) \
.replace( "<div>", "<div>\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 )

@ -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(

Loading…
Cancel
Save