Allow images in scenarios to be loaded from the internet.

master
Pacman Ghost 5 years ago
parent 0a91f820f3
commit 13e6709420
  1. 6
      vasl_templates/webapp/data/expected-multiple-images.json
  2. 42
      vasl_templates/webapp/data/vasl-overrides.json
  3. 13
      vasl_templates/webapp/files.py
  4. 20
      vasl_templates/webapp/main.py
  5. 8
      vasl_templates/webapp/static/css/user-settings-dialog.css
  6. 0
      vasl_templates/webapp/static/images/bullet.png
  7. BIN
      vasl_templates/webapp/static/images/warning.gif
  8. 17
      vasl_templates/webapp/static/main.js
  9. 16
      vasl_templates/webapp/static/snippets.js
  10. 45
      vasl_templates/webapp/static/user_settings.js
  11. 9
      vasl_templates/webapp/static/utils.js
  12. 72
      vasl_templates/webapp/static/vo.js
  13. 2
      vasl_templates/webapp/static/vo2.js
  14. 121
      vasl_templates/webapp/templates/counter-image-urls.html
  15. 1
      vasl_templates/webapp/templates/index.html
  16. 23
      vasl_templates/webapp/templates/user-settings-dialog.html
  17. 2276
      vasl_templates/webapp/tests/fixtures/vasl-pieces.txt
  18. 4
      vasl_templates/webapp/tests/remote.py
  19. 162
      vasl_templates/webapp/tests/test_online_images.py
  20. 12
      vasl_templates/webapp/tests/test_user_settings.py
  21. 5
      vasl_templates/webapp/tests/utils.py
  22. 22
      vasl_templates/webapp/vasl_mod.py
  23. 4
      vasl_templates/webapp/vassal.py

@ -2,14 +2,14 @@
"1555": {
"name": "2pdr Portee",
"front_images": [ "br/vehicles/portee", "br/vehicles/portee0" ],
"front_images": [ "br/vehicles/portee.gif", "br/vehicles/portee0.gif" ],
"back_images": null
},
"2212": {
"name": "76* INF FRC",
"front_images": [ "al/gun/alINF76", "al/gun/alINF76u" ],
"back_images": "al/gun/alINF76b"
"front_images": [ "al/gun/alINF76.gif", "al/gun/alINF76u.gif" ],
"back_images": "al/gun/alINF76b.gif"
},
"adf:1828": {

@ -3,22 +3,22 @@
"2474": {
"expected": {
"name": "Goliath",
"front_images": [ "ge/gegol", "ge/gegolb" ],
"front_images": [ "ge/gegol.gif", "ge/gegolb.gif" ],
"back_images": null
},
"updated": {
"front_images": "ge/gegol"
"front_images": "ge/gegol.gif"
}
},
"1555": {
"expected": {
"name": "2pdr Portee",
"front_images": "br/vehicles/portee",
"back_images": [ "br/vehicles/portee", "br/vehicles/portee0" ]
"front_images": "br/vehicles/portee.gif",
"back_images": [ "br/vehicles/portee.gif", "br/vehicles/portee0.gif" ]
},
"updated": {
"front_images": [ "br/vehicles/portee", "br/vehicles/portee0" ],
"front_images": [ "br/vehicles/portee.gif", "br/vehicles/portee0.gif" ],
"back_images": null
}
},
@ -26,34 +26,34 @@
"3463": {
"expected": {
"name": "75L AA 75/46",
"front_images": [ "it/gun/itAA7546", "it/gun/itAA7546b" ],
"back_images": [ "it/gun/itAA7546b", "it/gun/itAA7546lb" ]
"front_images": [ "it/gun/itAA7546.gif", "it/gun/itAA7546b.gif" ],
"back_images": [ "it/gun/itAA7546b.gif", "it/gun/itAA7546lb.gif" ]
},
"updated": {
"front_images": "it/gun/itAA7546",
"back_images": "it/gun/itAA7546b"
"front_images": "it/gun/itAA7546.gif",
"back_images": "it/gun/itAA7546b.gif"
}
},
"3776": {
"expected": {
"name": "37* INF Skoda IG",
"front_images": [ "ax/gun/buIN37s", "ax/gun/buIN37s2" ],
"back_images": "ax/gun/buIN37sb"
"front_images": [ "ax/gun/buIN37s.gif", "ax/gun/buIN37s2.gif" ],
"back_images": "ax/gun/buIN37sb.gif"
},
"updated": {
"front_images": "ax/gun/buIN37s"
"front_images": "ax/gun/buIN37s.gif"
}
},
"3777": {
"expected": {
"name": "70* INF Skoda IG",
"front_images": [ "ax/gun/buIN37s", "ax/gun/buIN37s2" ],
"back_images": "ax/gun/buIN37sb"
"front_images": [ "ax/gun/buIN37s.gif", "ax/gun/buIN37s2.gif" ],
"back_images": "ax/gun/buIN37sb.gif"
},
"updated": {
"front_images": "ax/gun/buIN37s2"
"front_images": "ax/gun/buIN37s2.gif"
}
},
@ -104,8 +104,8 @@
"adf:1824": {
"expected": {
"name": "37L AT PTP obr. 30",
"front_images": "ru/gun/ruAT37L",
"back_images": "ru/gun/ruAT37Lb"
"front_images": "ru/gun/ruAT37L.gif",
"back_images": "ru/gun/ruAT37Lb.gif"
},
"updated": {
"front_images": "ru/gun/ru37LPTPobr30.png"
@ -114,8 +114,8 @@
"adf:1822": {
"expected": {
"name": "37* INF PP obr. 15R",
"front_images": "ru/gun/ruINF37s",
"back_images": "ru/gun/ruINF37sb"
"front_images": "ru/gun/ruINF37s.gif",
"back_images": "ru/gun/ruINF37sb.gif"
},
"updated": {
"front_images": "ru/gun/ru37PPobr15R.png"
@ -124,8 +124,8 @@
"adf:1823": {
"expected": {
"name": "76* INF PP obr. 27",
"front_images": "ru/gun/ruINF76s",
"back_images": "ru/gun/ruINF76sb"
"front_images": "ru/gun/ruINF76s.gif",
"back_images": "ru/gun/ruINF76sb.gif"
},
"updated": {
"front_images": "ru/gun/ru76PPobr27.png"

@ -6,7 +6,7 @@ import urllib.request
import urllib.parse
import mimetypes
from flask import send_file, send_from_directory, jsonify, redirect, url_for, abort
from flask import send_file, send_from_directory, jsonify, redirect, url_for, abort, render_template
from vasl_templates.webapp import app, globvars
from vasl_templates.webapp.utils import resize_image_response, is_empty_file
@ -99,3 +99,14 @@ def get_vasl_piece_info():
# return the VASL piece info
return jsonify( globvars.vasl_mod.get_piece_info() )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@app.route( "/counter-image-urls/<nat>/<vo_type>" )
def get_counter_image_urls( nat, vo_type ):
"""Get the URL's for each counter image (for testing porpoises)."""
return render_template( "counter-image-urls.html",
NATIONALITY = nat,
VO_TYPE = vo_type,
VO_TYPE0 = vo_type[:-1] if vo_type.endswith("s") else vo_type,
)

@ -48,6 +48,26 @@ def get_startup_msgs():
# ---------------------------------------------------------------------
_APP_CONFIG_DEFAULTS = { # Bodhgaya, India (APR/19)
# NOTE: We use HTTP for static images, since VASSAL is already insanely slow loading images (done in serial?),
# so I don't even want to think about what it might be doing during a TLS handshake... :-/
"ONLINE_IMAGES_URL_BASE": "http://vasl-templates.org/services/static-images",
# NOTE: We would rather use https://github.com/vasl-developers/vasl/raw/develop/dist/images/ in the template,
# but VASSAL is already so slow to load images, and doing everything twice would make it that much worse :-/
"ONLINE_COUNTER_IMAGES_URL_TEMPLATE": "https://raw.githubusercontent.com/vasl-developers/vasl/develop/dist/images/{{PATH}}", #pylint: disable=line-too-long
"ONLINE_EXTN_COUNTER_IMAGES_URL_TEMPLATE": "http://vasl-templates.org/services/counter/{{EXTN_ID}}/{{PATH}}",
}
@app.route( "/app-config" )
def get_app_config():
"""Get the application config."""
return jsonify( {
key: app.config.get( key, default )
for key,default in _APP_CONFIG_DEFAULTS.items()
} )
# ---------------------------------------------------------------------
@app.route( "/help" )
def show_help():
"""Show the help page."""

@ -1,6 +1,8 @@
.ui-dialog.user-settings .ui-dialog-titlebar { background: #80d0ff ; }
.ui-dialog.user-settings .ui-dialog-buttonpane { border: none ; margin-top: 0 !important ; padding-top: 0 !important ; }
.ui-dialog.user-settings fieldset { margin: 1em 0 0 0 ; padding-top: 0.25em ; border-radius: 0 ; }
.ui-dialog.user-settings .run-as-server-note { margin-bottom: 0.5em ; font-size: 80% ; font-style: italic ; color: #666 ; }
.ui-dialog.user-settings .run-as-server-note img { float: left ; margin-right: 0.25em ; }
.ui-dialog.user-settings fieldset { margin: 0.5em 0 0 0 ; padding-top: 0.5em ; border-radius: 0 ; }
.ui-dialog.user-settings img.need-localhost { display: inline-block ; height: 0.75em ; }
.ui-dialog.user-settings div.need-localhost { float: left ; width: 290px ; font-size: 80% ; font-style: italic ; color: #c02020 ; }
.ui-dialog.user-settings div.need-localhost img { float: left ; height: 1.5em ; margin-right: 0.25em ; }

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,5 +1,6 @@
APP_URL_BASE = window.location.origin ;
gAppConfig = {} ;
gDefaultTemplatePack = null ;
gTemplatePack = {} ;
gValidTemplateIds = [] ;
@ -198,7 +199,7 @@ $(document).ready( function () {
function format_player_droplist_item( opt ) {
if ( ! opt.id )
return opt.text ;
var url = make_player_flag_url( opt.id ) ;
var url = make_player_flag_url( opt.id, false ) ;
return $( "<div style='display:flex;align-items:center;'>" +
"<img src='" + url + "' style='height:0.9em;margin-right:0.25em;'>" +
" " + opt.text +
@ -241,6 +242,14 @@ $(document).ready( function () {
$sel.data( "select2" ).$results.css( "max-height", "15em" ) ;
}
// get the application config
$.getJSON( gAppConfigUrl, function(data) {
gAppConfig = data ;
update_page_load_status( "app-config" ) ;
} ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the application config:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ;
} ) ;
// get the vehicle/ordnance listings
$.getJSON( gVehicleListingsUrl, function(data) {
gVehicleOrdnanceListings.vehicles = data ;
@ -452,7 +461,7 @@ function init_snippet_button( $btn )
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
gPageLoadStatus = [
"main",
"main", "app-config",
"vehicle-listings", "ordnance-listings", "reset-scenario",
"vehicle-notes", "ordnance-notes",
"vasl-piece-info", "template-pack", "default-scenario"
@ -519,7 +528,7 @@ function update_page_load_status( id )
// preload the flag images (so that the player droplist renders immediately)
for ( var nat in gTemplatePack.nationalities ) {
$("body").append( $(
"<img src='" + make_player_flag_url(nat) + "' style='display:none;'>"
"<img src='" + make_player_flag_url(nat,false) + "' style='display:none;'>"
) ) ;
}
}
@ -716,7 +725,7 @@ function update_ob_tab_header( player_no )
// update the OB tab header for the specified player
var player_nat = $( "select[name='PLAYER_" + player_no + "']" ).val() ;
var display_name = get_nationality_display_name( player_nat ) ;
var image_url = make_player_flag_url( player_nat ) ;
var image_url = make_player_flag_url( player_nat, false ) ;
var $elem = $( "#tabs .ui-tabs-nav a[href='#tabs-ob" + player_no + "']" ) ;
$elem.html(
"<img src='" + image_url + "'>&nbsp;" +

@ -125,7 +125,9 @@ function make_snippet( $btn, params, extra_params, show_date_warnings )
var snippet_save_name = null ;
// add simple parameters
params.IMAGES_BASE_URL = APP_URL_BASE + gImagesBaseUrl ;
params.IMAGES_BASE_URL = gUserSettings["use-online-images"] ?
gAppConfig.ONLINE_IMAGES_URL_BASE :
APP_URL_BASE + gImagesBaseUrl ;
if ( gUserSettings["custom-list-bullets"] )
params.CUSTOM_LIST_BULLETS = true ;
@ -137,7 +139,7 @@ function make_snippet( $btn, params, extra_params, show_date_warnings )
params.OB_COLOR = colors[0] ;
params.OB_COLOR_2 = colors[2] ;
if ( gUserSettings["include-flags-in-snippets"] )
params.PLAYER_FLAG = make_player_flag_url( get_player_nat( player_no ) ) ;
params.PLAYER_FLAG = make_player_flag_url( get_player_nat(player_no), true ) ;
}
// set the snippet ID
@ -288,8 +290,8 @@ function make_snippet( $btn, params, extra_params, show_date_warnings )
params.PLAYER_1_NAME = get_nationality_display_name( params.PLAYER_1 ) ;
params.PLAYER_2_NAME = get_nationality_display_name( params.PLAYER_2 ) ;
if ( gUserSettings["include-flags-in-snippets"] ) {
params.PLAYER_FLAG_1 = make_player_flag_url( get_player_nat( 1 ) ) ;
params.PLAYER_FLAG_2 = make_player_flag_url( get_player_nat( 2 ) ) ;
params.PLAYER_FLAG_1 = make_player_flag_url( get_player_nat(1), true ) ;
params.PLAYER_FLAG_2 = make_player_flag_url( get_player_nat(2), true ) ;
}
// pass through all the player colors and names
@ -300,7 +302,7 @@ function make_snippet( $btn, params, extra_params, show_date_warnings )
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 ) ;
params.PLAYER_FLAGS[nat] = make_player_flag_url( nat, true ) ;
} ) ;
// generate PF parameters
@ -746,9 +748,9 @@ function unload_snippet_params( unpack_scenario_date, template_id )
if ( vo_entry.extn_id )
obj.extn_id = vo_entry.extn_id ;
if ( gUserSettings["include-vasl-images-in-snippets"] ) {
var url = get_vo_image_url( vo_entry, vo_image_id ) ;
var url = get_vo_image_url( vo_entry, vo_image_id, false, true ) ;
if ( url )
obj.image = APP_URL_BASE + url ;
obj.image = url ;
if ( $(this).find( ".vo-entry" ).hasClass( "small-piece" ) )
obj.small_piece = true ;
}

@ -2,6 +2,7 @@ gUserSettings = Cookies.getJSON( "user-settings" ) || {} ;
USER_SETTINGS = {
"date-format": "droplist",
"use-online-images": "checkbox",
"hide-unavailable-ma-notes": "checkbox",
"include-vasl-images-in-snippets": "checkbox",
"include-flags-in-snippets": "checkbox",
@ -20,6 +21,7 @@ function user_settings()
var func = handlers[ "load_" + USER_SETTINGS[name] ] ;
func( $elem, gUserSettings[name] ) ;
}
update_ui() ;
}
function unload_settings() {
@ -33,6 +35,27 @@ function user_settings()
return settings ;
}
function update_ui() {
// update the UI
var use_online_images = $( ".ui-dialog.user-settings input[name='use-online-images']" ).prop( "checked" ) ;
$( ".ui-dialog.user-settings img.need-localhost.sometimes" ).css(
"display", use_online_images ? "none" : "inline-block"
) ;
// update the UI
var rc = false ;
$( ".ui-dialog.user-settings input.need-localhost:checked" ).each( function() {
if ( $(this).hasClass( "sometimes" ) ) {
if ( ! use_online_images )
rc = true ;
}
else
rc = true ;
} ) ;
$( ".ui-dialog.user-settings div.need-localhost" ).css(
"display", rc ? "block" : "none"
) ;
}
var handlers = {
load_checkbox: function( $elem, val ) { $elem.prop( "checked", val?true:false ) ; },
unload_checkbox: function( $elem ) { return $elem.prop( "checked" ) ; },
@ -46,10 +69,30 @@ function user_settings()
dialogClass: "user-settings",
modal: true,
width: 440,
height: 315,
height: 305,
resizable: false,
create: function() {
init_dialog( $(this), "OK", true ) ;
// initialize the "this program must be running" warnings
$( "input.need-localhost" ).each( function() {
var $img = $( "<img src='" + gImagesBaseUrl+"/warning.gif" + "'class='need-localhost'>" ) ;
if ( $(this).hasClass( "sometimes" ) )
$img.addClass( "sometimes" ) ;
$img.attr( "title", "If you turn this option on, this program must be running\nbefore you load the scenario into VASSAL." ) ;
$(this).next().before( $img ) ;
} ) ;
var $btn_pane = $(".ui-dialog.user-settings .ui-dialog-buttonpane") ;
$btn_pane.prepend( $(
"<div class='need-localhost'><img src='" + gImagesBaseUrl+"/warning.gif" + "'>" +
"This program must be running before<br>you load the scenario into VASSAL.</div>"
) ) ;
// install handlers to keep the UI updated
for ( var name in USER_SETTINGS ) {
if ( USER_SETTINGS[name] === "checkbox" ) {
var $elem = $( ".ui-dialog.user-settings [name='" + name + "']" ) ;
$elem.click( update_ui ) ;
}
}
},
open: function() {
on_dialog_open( $(this) ) ;

@ -33,8 +33,11 @@ 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 make_player_flag_url( nat, for_snippet ) {
if ( for_snippet && gUserSettings["use-online-images"] )
return gAppConfig.ONLINE_IMAGES_URL_BASE + "/flags/" + nat + ".png" ;
else
return APP_URL_BASE + "/flags/" + nat ;
}
function get_player_no_for_element( $elem )
@ -334,7 +337,7 @@ function add_flag_to_dialog_titlebar( $dlg, player_no )
if ( ! player_nat )
return ;
var $titlebar = $dlg.dialog( "instance" ).uiDialogTitlebar ;
var url = make_player_flag_url( player_nat ) ;
var url = make_player_flag_url( player_nat, false ) ;
$titlebar.find( ".ui-dialog-title" ).prepend(
$( "<img src='" + url + "' class='flag'>" )
).css( { display: "flex", "align-items": "center" } ) ;

@ -21,7 +21,7 @@ function add_vo( vo_type, player_no )
if ( is_small_vasl_piece( vo_entry ) )
div_class += " small-piece" ;
var buf2 = [ "<div class='" + div_class + "' data-index='" + opt.id + "'>",
"<img src='" + get_vo_image_url(vo_entry,null,true) + "' class='vasl-image'>",
"<img src='" + get_vo_image_url(vo_entry,null,true,false) + "' class='vasl-image'>",
"<div class='content'><div>",
vo_entry.name,
vo_entry.type ? "&nbsp;<span class='vo-type'>("+vo_entry.type+")</span>" : "",
@ -230,7 +230,7 @@ function update_vo_sortable2_entry( $entry, snippet_params )
}
// update the vehicle/ordnance's sortable2 entry
var url = get_vo_image_url( vo_entry, vo_image_id, true ) ;
var url = get_vo_image_url( vo_entry, vo_image_id, true, false ) ;
var $content = $entry.children( ".vo-entry" ) ;
$content.find( "img.vasl-image" ).attr( "src", url ) ;
var name = vo_entry.name ;
@ -318,7 +318,7 @@ function on_select_vo_image( $btn, on_ok ) {
for ( var i=0 ; i < vo_images.length ; ++i ) {
var $elem = $( "<img data-index='" + i + "'>" )
.bind( "load", on_image_loaded )
.attr( "src", get_vo_image_url( null, vo_images[i], true ) ) ;
.attr( "src", get_vo_image_url( null, vo_images[i], true, false ) ) ;
$images.append( $elem ) ;
}
@ -344,7 +344,7 @@ function on_select_vo_image( $btn, on_ok ) {
// handle image selection
$images.children( "img" ).click( function() {
vo_image_id = vo_images[ $(this).data("index") ] ;
$img.attr( "src", get_vo_image_url(null,vo_image_id,true) ) ;
$img.attr( "src", get_vo_image_url(null,vo_image_id,true,false) ) ;
$img.data( "vo-image-id", vo_image_id ) ;
$dlg.dialog( "close" ) ;
if ( on_ok )
@ -379,18 +379,64 @@ function _find_vo_image_id( vo_images, vo_image_id )
return -1 ;
}
function get_vo_image_url( vo_entry, vo_image_id, allow_missing_image )
function get_vo_image_url( vo_entry, vo_image_id, allow_missing_image, for_snippet )
{
if ( vo_image_id )
return "/counter/" + vo_image_id[0] + "/front/" + vo_image_id[1] ;
else {
// generate the image URL for the specified vehicle/ordnance
var gpid, index=null ;
if ( vo_image_id ) {
gpid = vo_image_id[0] ;
index = vo_image_id[1] ;
} else {
// no V/O image ID was provided, just use the first available image
if ( $.isArray( vo_entry.gpid ) )
return "/counter/" + vo_entry.gpid[0] + "/front" ;
if ( vo_entry.gpid )
return "/counter/" + vo_entry.gpid + "/front" ;
gpid = $.isArray( vo_entry.gpid ) ? vo_entry.gpid[0] : vo_entry.gpid ;
}
if ( gpid ) {
if ( for_snippet && gUserSettings["use-online-images"] )
return make_online_counter_image_url( gpid, index ) ;
else
return make_local_counter_image_url( gpid, index ) ;
}
// couldn't find an image
if ( allow_missing_image ) {
if ( for_snippet && gUserSettings["use-online-images"] )
return gAppConfig.ONLINE_IMAGES_URL_BASE + "/missing-image.png" ;
else
return gImagesBaseUrl + "/missing-image.png" ;
}
return allow_missing_image ? gImagesBaseUrl + "/missing-image.png" : null ;
return null ;
}
function make_local_counter_image_url( gpid, index )
{
url = APP_URL_BASE + "/counter/" + gpid + "/front" ;
if ( index !== null )
url += "/" + index ;
return url ;
}
function make_online_counter_image_url( gpid, index )
{
// check if we have a piece from the core VASL module or an extension
var url, extn_id ;
var pos = gpid.toString().indexOf( ":" ) ;
if ( pos === -1 )
url = gAppConfig.ONLINE_COUNTER_IMAGES_URL_TEMPLATE ;
else {
url = gAppConfig.ONLINE_EXTN_COUNTER_IMAGES_URL_TEMPLATE ;
extn_id = gpid.substr( 0, pos ) ;
}
// generate the URL
url = strReplaceAll( url, "{{GPID}}", gpid ) ;
if ( index === null )
index = 0 ;
url = strReplaceAll( url, "{{INDEX}}", index ) ;
url = strReplaceAll( url, "{{PATH}}", gVaslPieceInfo[gpid].paths[index] ) ;
if ( extn_id )
url = strReplaceAll( url, "{{EXTN_ID}}", extn_id ) ;
return url ;
}
function is_small_vasl_piece( vo_entry )

@ -58,7 +58,7 @@ function _do_edit_ob_vo( $entry, player_no, vo_type )
// load the dialog
var vo_image_id = $entry.data( "sortable2-data" ).vo_image_id ;
var url = get_vo_image_url( vo_entry, vo_image_id, true ) ;
var url = get_vo_image_url( vo_entry, vo_image_id, true, false ) ;
var buf = [ "<div class='header'>",
"<img src='" + url + "' class='vasl-image'>",
"<div class='content'>",

@ -0,0 +1,121 @@
<!doctype html> <!-- NOTE: For testing porpoises only! -->
<html lang="en">
<head>
<meta charset="utf-8">
<title> Counter image URL's ({{NATIONALITY}}) </title>
<style>
th, td { text-align: left ; vertical-align: top ; padding-right: 1em ; }
th { background: #eee ; }
td { border-bottom: 1px solid #eee ; font-size: 90% ; }
</style>
</head>
<body>
<div id="results" style="display:none;"></div>
</body>
<script src="{{url_for('static',filename='jquery/jquery-3.3.1.min.js')}}"></script>
<script src="{{url_for('static',filename='vo.js')}}"></script>
<script src="{{url_for('static',filename='utils.js')}}"></script>
<script>
APP_URL_BASE = window.location.origin ;
gAppConfig = null ;
gVaslPieceInfo = null ;
$(document).ready( function () {
// initialize
var counter_image_urls ;
var on_load_counter = 3 ;
function on_data_loaded() {
if ( --on_load_counter == 0 )
show_counter_image_urls( counter_image_urls ) ;
}
// get the app config
$.getJSON( "{{url_for('get_app_config')}}", function(data) {
gAppConfig = data ;
on_data_loaded() ;
} ).fail( function( xhr, status, errorMsg ) {
alert( "Can't get the application config:\n\n" + errorMsg ) ;
} ) ;
// get the VASL piece info
$.getJSON( "{{url_for('get_vasl_piece_info')}}", function(data) {
gVaslPieceInfo = data ;
on_data_loaded() ;
} ).fail( function( xhr, status, errorMsg ) {
alert( "Can't get the VASL piece info:\n\n" + errorMsg ) ;
} ) ;
// get the vehicle/ordnance listings
var url ;
if ( "{{VO_TYPE}}" == "ordnance" )
url = "{{url_for( 'get_ordnance_listings', report=1 )}}" ;
else
url = "{{url_for( 'get_vehicle_listings', report=1 )}}" ; // nb: includes landing craft
url += "&merge_common=1" ;
$.getJSON( url, function(data) {
counter_image_urls = data[ "{{NATIONALITY}}" ] ;
on_data_loaded() ;
} ).fail( function( xhr, status, errorMsg ) {
alert( "Can't get the {{VO_TYPE0}} listings:\n\n" + errorMsg ) ;
} ) ;
} ) ;
function show_counter_image_urls( counters )
{
// initialize
if ( ! counters )
counters = [] ;
var buf = [] ;
buf.push( "<table>" ) ;
buf.push( "<tr>", "<th>ID", "<th>Name", "<th>GPID", "<th>Local URL's", "<th>Online URL's" ) ;
// process each counter
for ( var i=0 ; i < counters.length ; ++i ) {
var counter = counters[i] ;
buf.push( "<tr>",
"<td>", counter.id,
"<td>", counter.name,
) ;
// process each counter variant
var gpids = $.isArray( counter.gpid ) ? counter.gpid : [ counter.gpid ] ;
var gpids2=[] , local_urls=[] , online_urls=[] ;
for ( var j=0 ; j < gpids.length ; ++j ) {
if ( gpids[j] === null || [7140,7146].indexOf( gpids[j] ) !== -1 ) {
buf.push( "<td>", "<td>", "<td>" ) ;
continue ;
}
gpids2.push( gpids[j] ) ;
// NOTE: We don't handle the case where there are multiple images available for a GPID,
// but this happens so infrequently, we can live with it (see expected-multiple-images.json).
var index = 0 ;
// generate the counter image URL's
var url = make_local_counter_image_url( gpids[j], index ) ;
local_urls.push( "<a href='" + url + "' target='_blank'>" + url + "</a>" ) ;
url = make_online_counter_image_url( gpids[j], index ) ;
online_urls.push( "<a href='" + url + "' target='_blank'>" + url + "</a>" ) ;
}
buf.push(
"<td>", gpids2.join( "<br>" ),
"<td>", local_urls.join( "<br>" ),
"<td>", online_urls.join( "<br>" ),
) ;
}
buf.push( "</table>" ) ;
$("#results").html( buf.join("") ).show() ;
}
</script>
</html>

@ -92,6 +92,7 @@
gAppName = "{{APP_NAME}}" ;
gAppVersion = "{{APP_VERSION}}" ;
gImagesBaseUrl = "{{url_for('static',filename='images')}}" ;
gAppConfigUrl = "{{url_for('get_app_config')}}" ;
gGetStartupMsgsUrl = "{{url_for('get_startup_msgs')}}" ;
gGetTemplatePackUrl = "{{url_for('get_template_pack')}}" ;
gGetDefaultScenarioUrl = "{{url_for('get_default_scenario')}}" ;

@ -6,21 +6,18 @@
<option value="dd/mm/yy">DD/MM/YYYY</option>
<option value="yy-mm-dd">YYYY-MM-DD</option>
</select>
<div style="margin-top:0.5em;">
<input type="checkbox" name="hide-unavailable-ma-notes">&nbsp;Hide unavailable multi-applicable notes <br>
<div style="margin-top:0.25em;">
<input type="checkbox" name="hide-unavailable-ma-notes"> Hide unavailable multi-applicable notes <br>
</div>
<fieldset> <legend> Provide services to VASL </legend>
<div class="run-as-server-note">
<img src="{{url_for('static',filename='images/info.gif')}}">
If you enable any of these options, this program must be running before you load the scenario in VASL.
</div>
<input type="checkbox" name="include-vasl-images-in-snippets">&nbsp;Include VASL counter images in snippets <br>
<input type="checkbox" name="include-flags-in-snippets">&nbsp;Include flags in snippets <br>
<input type="checkbox" name="custom-list-bullets">&nbsp;Use custom list bullets <br>
<div style="height:0.25em;"> &nbsp; </div>
<input type="checkbox" name="vo-notes-as-images">&nbsp;Show Chapter H vehicle/ordnance notes as images
<fieldset> <legend> Images in scenarios </legend>
<input type="checkbox" name="use-online-images"> Get images from the internet <br>
<div style="height:0.25em;"></div>
<input type="checkbox" name="include-vasl-images-in-snippets" class="need-localhost sometimes"> Include VASL counter images in snippets <br>
<input type="checkbox" name="include-flags-in-snippets" class="need-localhost sometimes"> Include flags in snippets <br>
<input type="checkbox" name="custom-list-bullets" class="need-localhost sometimes"> Use custom list bullets <br>
<div style="height:0.25em;"></div>
<input type="checkbox" name="vo-notes-as-images" class="need-localhost"> Show Chapter H vehicle/ordnance notes as images <br>
</fieldset>
</div>

File diff suppressed because it is too large Load Diff

@ -104,7 +104,9 @@ class ControlTests:
def _set_default_template_pack( self, dname=None ):
"""Set the default template pack."""
if dname:
if dname == "real":
dname = os.path.join( os.path.split(__file__)[0], "../data/default-template-pack" )
elif dname:
dname2 = os.path.join( os.path.split(__file__)[0], "fixtures" )
dname = os.path.join( dname2, dname )
_logger.info( "Setting default template pack: %s", dname )

@ -0,0 +1,162 @@
"""Test using online images in VASL scenarios."""
import re
from selenium.webdriver.common.action_chains import ActionChains
from vasl_templates.webapp.tests.utils import init_webapp, select_tab, \
find_child, find_children, click_dialog_button, wait_for_clipboard, wait_for_elem
from vasl_templates.webapp.tests.test_user_settings import set_user_settings
from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario
# ---------------------------------------------------------------------
def test_online_images( webapp, webdriver ):
"""Test using online images in VASL scenarios."""
# initialize
init_webapp( webapp, webdriver, scenario_persistence=1,
reset = lambda ct:
ct.set_data_dir( dtype="real" ) \
.set_vasl_mod( vmod="random" ) \
.set_default_template_pack( dname="real" )
)
# load the test scenario
load_scenario( {
"PLAYER_1": "german",
"OB_VEHICLES_1": [ { "name": "PzKpfw IVH" } ],
} )
# configure the user settings
set_user_settings( {
"include-flags-in-snippets": True,
"custom-list-bullets": True,
"include-vasl-images-in-snippets": True,
} )
def do_test( snippet_id, expected1, expected2 ): #pylint: disable=missing-docstring
# generate the snippet with online images enabled
set_user_settings( { "use-online-images": True } )
btn = find_child( "button[data-id='{}']".format( snippet_id ) )
btn.click()
wait_for_clipboard( 2, expected1 )
# generate the snippet with online images disabled
set_user_settings( { "use-online-images": False } )
btn.click()
wait_for_clipboard( 2, expected2 )
# test player flags
do_test( "players",
re.compile( r'<img src="http://vasl-templates.org/.+/flags/german.png">' ),
re.compile( r'<img src="http://[a-z0-9.]+:\d+/flags/german">' )
)
# test custom list bullets
do_test( "ssr",
re.compile( r'url\("http://vasl-templates.org/.+/bullet.png"\)' ),
re.compile( r'url\("http://[a-z0-9.]+:\d+/.+/bullet.png"\)')
)
# test VASL counter images
select_tab( "ob1" )
do_test( "ob_vehicles_1",
re.compile( r'<img src="https://raw.githubusercontent.com/.+/ge/veh/pzivh.gif">' ),
re.compile( r'<img src="http://[a-z0-9.]+:\d+/counter/2584/front">' )
)
# ---------------------------------------------------------------------
def test_multiple_images( webapp, webdriver ):
"""Test handling of VASL counters that have multiple images."""
# initialize
init_webapp( webapp, webdriver, scenario_persistence=1,
reset = lambda ct:
ct.set_data_dir( dtype="real" ) \
.set_vasl_mod( vmod="random" ) \
.set_default_template_pack( dname="real" )
)
# load the test scenario
load_scenario( {
"PLAYER_1": "british",
"OB_VEHICLES_1": [ { "name": "2pdr Portee" } ],
} )
# configure the user settings
set_user_settings( {
"use-online-images": True,
"include-vasl-images-in-snippets": True,
} )
# generate a snippet for the vehicle (using the default image)
select_tab( "ob1" )
btn = find_child( "button[data-id='ob_vehicles_1']" )
btn.click()
wait_for_clipboard( 2,
re.compile( r'<img src="https://raw.githubusercontent.com/.+/br/vehicles/portee.gif">')
)
# select the second image for the vehicle
sortable = find_child( "#ob_vehicles-sortable_1" )
elems = find_children( "li", sortable )
assert len(elems) == 1
ActionChains(webdriver).double_click( elems[0] ).perform()
btn = wait_for_elem( 2, "#edit-vo input.select-vo-image" )
btn.click()
images = find_children( ".ui-dialog.select-vo-image .vo-images img" )
assert len(images) == 2
images[1].click()
click_dialog_button( "OK" )
# generate a snippet for the vehicle (using the new image)
btn = find_child( "button[data-id='ob_vehicles_1']" )
btn.click()
wait_for_clipboard( 2,
re.compile( r'<img src="https://raw.githubusercontent.com/.+/br/vehicles/portee0.gif">')
)
# ---------------------------------------------------------------------
def test_extensions( webapp, webdriver ):
"""Test handling of VASL counters in extensions."""
# initialize
init_webapp( webapp, webdriver, scenario_persistence=1,
reset = lambda ct:
ct.set_data_dir( dtype="real" ) \
.set_vasl_mod( vmod="random", extns_dtype="real" ) \
.set_default_template_pack( dname="real" )
)
# load the test scenario
load_scenario( {
"PLAYER_1": "russian",
"OB_VEHICLES_1": [
{ "id": "ru/v:078", "image_id": "f97:178/0" }, # Matilda II(b) (4FP variant)
{ "id": "ru/v:078", "image_id": "f97:184/0" }, # Matilda II(b) (6FP variant)
{ "id": "ru/v:004", "image_id": "547/0" }, # T-60 M40 (core module)
{ "id": "ru/v:004", "image_id": "f97:186/0" }, # T-60 M40 (KGS variant)
],
} )
# configure the user settings
set_user_settings( {
"use-online-images": True,
"include-vasl-images-in-snippets": True,
} )
# generate a snippet for the vehicles
select_tab( "ob1" )
btn = find_child( "button[data-id='ob_vehicles_1']" )
btn.click()
wait_for_clipboard( 2, re.compile(
'<img src="http://vasl-templates.org/.+/f97/matii2-4cmg.gif">'
'.+'
'<img src="http://vasl-templates.org/.+/f97/matii2-6cmg.gif">'
'.+'
'<img src="https://raw.githubusercontent.com/.+/ru/veh/T60M40.gif">'
'.+'
'<img src="http://vasl-templates.org/.+/f97/T60M40.gif">'
, re.DOTALL ) )

@ -284,6 +284,18 @@ def test_vo_notes_as_images( webapp, webdriver ):
# ---------------------------------------------------------------------
def set_user_settings( opts ):
"""Configure the user settings."""
select_menu_option( "user_settings" )
for key,val in opts.items():
assert isinstance( val, bool ) # nb: we currently only support checkboxes
elem = find_child( ".ui-dialog.user-settings input[name='{}']".format( key ) )
if (val and not elem.is_selected()) or (not val and elem.is_selected()):
elem.click()
click_dialog_button( "OK" )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _check_cookies( webdriver, name, expected ):
"""Check that a user setting was stored in the cookies correctly."""
cookies = [ c for c in webdriver.get_cookies() if c["name"] == "user-settings" ]

@ -77,6 +77,9 @@ def init_webapp( webapp, webdriver, **options ):
webdriver.get( webapp.url_for( "main", **options ) )
_wait_for_webapp()
# reset the user settings
webdriver.delete_all_cookies()
return control_tests
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -538,7 +541,7 @@ def wait_for_elem( timeout, elem_id, parent=None ):
args = { "elem": None }
def check_elem(): #pylint: disable=missing-docstring
args["elem"] = find_child( elem_id, parent )
return args["elem"] is not None
return args["elem"] is not None and args["elem"].is_displayed()
wait_for( timeout, check_elem )
return args["elem"]

@ -165,8 +165,6 @@ class VaslMod:
if not isinstance( image_paths, list ):
image_paths = [ image_paths ]
image_path = image_paths[ index ]
if not os.path.splitext( image_path )[1]:
image_path += ".gif"
# load the image data
image_path = os.path.join( "images", image_path )
@ -182,11 +180,16 @@ class VaslMod:
if not piece[key]:
return 0
return len(piece[key]) if isinstance( piece[key], list ) else 1
def get_image_paths( piece ):
"""Return the piece's image paths."""
paths = piece[ "front_images" ]
return paths if isinstance(paths,list) else [paths]
return {
p["gpid"]: {
"name": p["name"],
"front_images": image_count( p, "front_images" ),
"back_images": image_count( p, "back_images" ),
"paths": get_image_paths( p ),
"is_small": p["is_small"],
}
for p in self._pieces.values()
@ -375,11 +378,18 @@ class VaslMod:
front_images.pop()
assert not back_images
def delistify( val ): #pylint: disable=missing-docstring
if val is None:
def tidy_paths( paths ):
"""Tidy up image paths."""
if paths is None:
return None
return val[0] if len(val) == 1 else val
return delistify(front_images), delistify(back_images)
assert isinstance( paths, list )
# ensure every path has an extension
for i,path in enumerate(paths):
if not os.path.splitext( path )[1]:
paths[i] += ".gif"
# de-listify the paths
return paths[0] if len(paths) == 1 else paths
return tidy_paths(front_images), tidy_paths(back_images)
# ---------------------------------------------------------------------

@ -287,7 +287,9 @@ class VassalShim:
args2.extend( args[1:] )
# figure out how long to the let the VASSAL shim run
timeout = int( app.config.get( "VASSAL_SHIM_TIMEOUT", 120 ) )
# NOTE: This used to be 2 minutes, but adding the ability to load images from the internet
# slows the process down, since VASSAL loads images insanely slowly :-/
timeout = int( app.config.get( "VASSAL_SHIM_TIMEOUT", 5*60 ) )
if timeout <= 0:
timeout = None

Loading…
Cancel
Save