diff --git a/vasl_templates/webapp/__init__.py b/vasl_templates/webapp/__init__.py index b2e29a6..374e115 100644 --- a/vasl_templates/webapp/__init__.py +++ b/vasl_templates/webapp/__init__.py @@ -17,6 +17,10 @@ from vasl_templates.webapp.config.constants import BASE_DIR def _on_startup(): """Do startup initialization.""" + # load the default template_pack + from vasl_templates.webapp.snippets import load_default_template_pack + load_default_template_pack() + # configure the VASL module fname = app.config.get( "VASL_MOD" ) if fname: diff --git a/vasl_templates/webapp/data/default-template-pack/ma_note.css b/vasl_templates/webapp/data/default-template-pack/ma_note.css new file mode 100644 index 0000000..ccbe731 --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/ma_note.css @@ -0,0 +1,16 @@ +.ma-note { text-align: justify ; } +.ma-note .key { font-weight: bold ; } +.ma-note table { margin-left: 10px ; } +.ma-note td { padding: 0 ; } +.ma-note ul { padding-left: 10px ; list-style-image: url("{{IMAGES_BASE_URL}}/bullet.png") ; } +.ma-note li { margin-bottom: 2px ; } +.ma-note .example { font-size: 90% ; font-style: italic ; } +.ma-note table { margin-left: 10px ; margin-top: -5px ; } +.ma-note table th { padding: 2px 10px 2px 5px ; text-align: left ; background: #f0f0f0 ; } +.ma-note table td { padding: 0 10px 0 5px ; } + +.extra-notes-caption { border: 1px solid #e0e0e0 ; background: #fcfcfc ; font-weight: bold ; padding: 2px 5px ; } +.slashed { text-decoration: line-through ; } + +ul { margin: 0 0 0 15px ; padding: 0 ; } +sup { font-size: 75% ; } diff --git a/vasl_templates/webapp/data/default-template-pack/mol-p.j2 b/vasl_templates/webapp/data/default-template-pack/mol-p.j2 index 053f103..8916f47 100644 --- a/vasl_templates/webapp/data/default-template-pack/mol-p.j2 +++ b/vasl_templates/webapp/data/default-template-pack/mol-p.j2 @@ -6,7 +6,7 @@ td { margin: 0 ; padding: 0 ; } td.c { text-align: center ; } td.r { text-align: right ; } -ul { margin: 0 0 0 10px ; padding: 0 ; } +ul { margin: 0 ; padding-left: 10px ; list-style-image: url("{{IMAGES_BASE_URL}}/bullet.png") ; } diff --git a/vasl_templates/webapp/data/default-template-pack/mol.j2 b/vasl_templates/webapp/data/default-template-pack/mol.j2 index be5bdee..69b2b57 100644 --- a/vasl_templates/webapp/data/default-template-pack/mol.j2 +++ b/vasl_templates/webapp/data/default-template-pack/mol.j2 @@ -4,7 +4,7 @@ diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 index 45fb9c0..3720142 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 @@ -2,12 +2,7 @@ - + - +
+
@@ -16,7 +17,7 @@ {%if PLAYER_FLAG%} {%endif%}{{ORDNANCE_NAME}} -
+ {{ORDNANCE_NOTE_HTML}}
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 index 26eeed3..a40e0b9 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 @@ -2,6 +2,7 @@ + @@ -16,7 +17,7 @@ {%if PLAYER_FLAG%} {%endif%}{{VEHICLE_NAME}} -
+ {{VEHICLE_NOTE_HTML}}
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 index 74af6df..eb0a0d0 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 @@ -2,12 +2,7 @@ - + - +
+
' ; + else { + if ( gUserSettings["vo-notes-as-images"] ) { + // show the vehicle/ordnance note as an image + var nat = params[ "PLAYER_" + player_no ] ; + var url = APP_URL_BASE + "/" + vo_type + "/" + nat + "/note/" + get_vo_note_key(data.vo_entry) ; + params[ key + "_NOTE_HTML" ] = '' ; + } else { + // insert the raw HTML into the snippet + params[ key + "_NOTE_HTML" ] = data.vo_note ; + } + } snippet_save_name = data.vo_entry.name ; } if ( template_id === "ob_vehicle_note" ) @@ -182,6 +197,18 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) else if ( template_id === "ob_ordnance_note" ) set_vo_note( "ordnance" ) ; + // install the CSS + function install_css( key ) { + if ( gTemplatePack.css[ key ] ) { + params[ key.toUpperCase() + "_CSS" ] = strReplaceAll( + gTemplatePack.css[key], "{{IMAGES_BASE_URL}}", params.IMAGES_BASE_URL + ) ; + } + } + install_css( "vo" ) ; + install_css( "vo_note" ) ; + install_css( "ma_note" ) ; + // generate snippets for multi-applicable vehicle/ordnance notes var pos ; function add_ma_notes( ma_notes, keys, param_name, nat, vo_type ) { @@ -335,7 +362,6 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) // add in any extra parameters if ( extra_params ) $.extend( true, params, extra_params ) ; - params.IMAGES_BASE_URL = APP_URL_BASE + gImagesBaseUrl ; // check that the players have different nationalities if ( params.PLAYER_1 === params.PLAYER_2 ) @@ -376,7 +402,8 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) } // fixup any user file URL's - snippet = snippet.replace( "{{USER_FILES}}", APP_URL_BASE + "/user" ) ; + snippet = strReplaceAll( snippet, "{{USER_FILES}}", APP_URL_BASE+"/user" ) ; + snippet = strReplaceAll( snippet, "{{CHAPTER_H}}", APP_URL_BASE+"/chapter-h" ) ; return { content: snippet, @@ -401,7 +428,7 @@ function get_vo_note_key( vo_entry ) return key ; } -function make_vo_note_key_url( vo_type, nat, key ) +function get_vo_note( vo_type, nat, key ) { if ( ! key ) return null ; @@ -421,8 +448,13 @@ function make_vo_note_key_url( vo_type, nat, key ) if ( !( key in gVehicleOrdnanceNotes[ vo_type ][ nat ] ) ) return null ; - // generate the URL - return APP_URL_BASE + "/" + vo_type + "/" + nat + "/note/" + key ; + var vo_note = gVehicleOrdnanceNotes[ vo_type ][ nat ][ key ] ; + // FUDGE! We need to detect between a full HTML note and an image-based one. + // This is not great, but it'll do... :-/ + if ( vo_note.substr( 0, nat.length+1 ) === nat+"/" ) + return APP_URL_BASE + "/" + vo_type + "/" + nat + "/note/" + key ; + else + return vo_note ; } function get_ma_notes_keys( nat, vo_entries, vo_type ) @@ -1625,7 +1657,7 @@ function on_template_pack() var pos = data.indexOf( "|" ) ; var fname = data.substring( 0, pos ).trim() ; data = data.substring( pos+1 ).trim() ; - if ( fname.substring(fname.length-4) === ".zip" ) + if ( getFilenameExtn( fname ) === ".zip" ) data = atob( data ) ; do_load_template_pack( fname, data ) ; return ; @@ -1664,6 +1696,7 @@ function do_load_template_pack( fname, data ) var template_pack = { nationalities: $.extend( true, {}, gDefaultTemplatePack.nationalities ), templates: {}, + css: {}, } ; // NOTE: We always start with the default extras templates; user-defined template packs @@ -1687,17 +1720,22 @@ function do_load_template_pack( fname, data ) $.extend( true, template_pack.nationalities, nationalities ) ; return ; } - if ( fname.substring(fname.length-3) != ".j2" ) { + var extn = getFilenameExtn( fname ) ; + if ( [".j2",".css"].indexOf( extn ) === -1 ) { invalid_filename_extns.push( fname ) ; return ; } - var template_id = fname.substring( 0, fname.length-3 ).toLowerCase() ; - if ( gValidTemplateIds.indexOf( template_id ) === -1 && template_id.substr(0,7) !== "extras/" ) { - unknown_template_ids.push( fname ) ; - return ; - } // save the template pack file - template_pack.templates[template_id] = data ; + var template_id = fname.substring( 0, fname.length-extn.length ).toLowerCase() ; + if ( extn === ".css" ) + template_pack.css[template_id] = data ; + else { + if ( gValidTemplateIds.indexOf( template_id ) === -1 && template_id.substr(0,7) !== "extras/" ) { + unknown_template_ids.push( fname ) ; + return ; + } + template_pack.templates[template_id] = data ; + } } // initialize @@ -1746,7 +1784,7 @@ function do_load_template_pack( fname, data ) // check if we have a ZIP file fname = fname.toLowerCase() ; - if ( fname.substring(fname.length-4) === ".zip" ) { + if ( getFilenameExtn( fname ) === ".zip" ) { // yup - process each file in the ZIP var nFiles = 0 ; JSZip.loadAsync( data ).then( function( zip ) { diff --git a/vasl_templates/webapp/static/user_settings.js b/vasl_templates/webapp/static/user_settings.js index 934f457..e39e993 100644 --- a/vasl_templates/webapp/static/user_settings.js +++ b/vasl_templates/webapp/static/user_settings.js @@ -5,6 +5,7 @@ USER_SETTINGS = { "hide-unavailable-ma-notes": "checkbox", "include-vasl-images-in-snippets": "checkbox", "include-flags-in-snippets": "checkbox", + "vo-notes-as-images": "checkbox", } ; // -------------------------------------------------------------------- @@ -44,7 +45,7 @@ function user_settings() dialogClass: "user-settings", modal: true, width: 440, - height: 270, + height: 300, resizable: false, create: function() { init_dialog( $(this), "OK", true ) ; diff --git a/vasl_templates/webapp/static/utils.js b/vasl_templates/webapp/static/utils.js index 83a1ac1..309a3b7 100644 --- a/vasl_templates/webapp/static/utils.js +++ b/vasl_templates/webapp/static/utils.js @@ -365,6 +365,30 @@ function pluralString( n, str1, str2 ) return (n == 1) ? str1 : str2 ; } +function strReplaceAll( val, searchFor, replaceWith ) +{ + // str.replace() only replaces a single instance!?!? :wtf: + if ( ! searchFor ) + return val ; + var pos = 0 ; + for ( ; ; ) { + pos = val.indexOf( searchFor, pos ) ; + if ( pos === -1 ) + return val ; + val = val.substr(0,pos) + replaceWith + val.substr(pos+searchFor.length) ; + } +} + +function getFilenameExtn( fname ) +{ + // get the filename extension + var pos = fname.lastIndexOf( "." ) ; + if ( pos !== -1 ) + return fname.substr( pos ) ; + else + return null ; +} + function isIE() { // check if we're running in IE :-/ diff --git a/vasl_templates/webapp/static/vo.js b/vasl_templates/webapp/static/vo.js index e34e91d..c596876 100644 --- a/vasl_templates/webapp/static/vo.js +++ b/vasl_templates/webapp/static/vo.js @@ -165,17 +165,17 @@ function do_add_vo( vo_type, player_no, vo_entry, vo_image_id, elite, custom_cap "" ] ; var vo_note_key = get_vo_note_key( vo_entry ) ; - var vo_note_url = make_vo_note_key_url( vo_type, nat, vo_note_key ) ; - if ( ! vo_note_url ) { + var vo_note = get_vo_note( vo_type, nat, vo_note_key ) ; + if ( ! vo_note ) { // NOTE: Note numbers seem to be distinct across all Allied Minor or all Axis Minor vehicles/ordnance, // so if we don't find a note in a given nationality's normal vehicles/ordnance, we can get away with // just checking their corresponding common vehicles/ordnance. var nat_type = gTemplatePack.nationalities[ nat ].type ; if ( ["allied-minor","axis-minor"].indexOf( nat_type ) !== -1 ) { - vo_note_url = make_vo_note_key_url( vo_type, nat_type, vo_note_key ) ; + vo_note = get_vo_note( vo_type, nat_type, vo_note_key ) ; } } - if ( vo_note_url ) { + if ( vo_note ) { var template_id = (vo_type === "vehicles") ? "ob_vehicle_note" : "ob_ordnance_note" ; if ( is_template_available( template_id ) ) { buf.push( @@ -183,7 +183,7 @@ function do_add_vo( vo_type, player_no, vo_entry, vo_image_id, elite, custom_cap " class='snippet' data-id='" + template_id + "' title='" + GENERATE_SNIPPET_HINT + "'>" ) ; } - data.vo_note_url = vo_note_url ; + data.vo_note = vo_note ; } buf.push( "" ) ; var $content = $( buf.join("") ) ; diff --git a/vasl_templates/webapp/templates/user-settings-dialog.html b/vasl_templates/webapp/templates/user-settings-dialog.html index d660152..2b7c8bb 100644 --- a/vasl_templates/webapp/templates/user-settings-dialog.html +++ b/vasl_templates/webapp/templates/user-settings-dialog.html @@ -17,7 +17,9 @@ If you enable any of these options, this program must be running before you load the scenario in VASL.  Include VASL images in snippets
-  Include flags in snippets +  Include flags in snippets
+
 
+  Show Chapter H vehicle/ordnance notes as images diff --git a/vasl_templates/webapp/templates/vo-notes-report.html b/vasl_templates/webapp/templates/vo-notes-report.html index c743fc8..66a4235 100644 --- a/vasl_templates/webapp/templates/vo-notes-report.html +++ b/vasl_templates/webapp/templates/vo-notes-report.html @@ -146,9 +146,10 @@ function load_vo_notes( vo_entries ) for ( var i=0 ; i < keys.length ; ++i ) { if ( keys[i] === "multi-applicable" ) continue ; + var vo_note = vo_notes[ keys[i] ] ; buf.push( "", "
", keys[i]+":", - "", vo_notes[keys[i]] + "", vo_note.substr(vo_note.length-4) === ".png" ? vo_note : "(HTML content)" ) ; } buf.push( "
" ) ; @@ -181,7 +182,7 @@ function load_vo_notes( vo_entries ) buf.push( "", vo_entry.note_number) ; var vo_note_key = get_vo_note_key( vo_entry ) ; if ( vo_note_key ) { - if ( ! make_vo_note_key_url( vo_type, nat, vo_note_key ) ) + if ( ! get_vo_note( vo_type, nat, vo_note_key ) ) vo_note_key += " (missing)" ; } buf.push( "", vo_note_key ) ; diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json index ac796d1..a02d4c7 100644 --- a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json +++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/nationalities.json @@ -45,6 +45,11 @@ "ob_colors": [ "OBCOL:dutch","OBCOL2:dutch", "OBCOL-BORDER:dutch" ], "type": "allied-minor" }, +"greek": { + "display_name": "Greek", + "ob_colors": [ "OBCOL:greek","OBCOL2:greek", "OBCOL-BORDER:greek" ], + "type": "allied-minor" +}, "romanian": { "display_name": "Romanian", diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2 b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2 index eec4f24..b1b3e0a 100644 --- a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2 +++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_ordnance_note.j2 @@ -1 +1 @@ -{{ORDNANCE_NAME}}: {{ORDNANCE_NOTE_URL}} +{{ORDNANCE_NAME}}: {{ORDNANCE_NOTE_HTML}} diff --git a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2 b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2 index a3936d9..85cfbce 100644 --- a/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2 +++ b/vasl_templates/webapp/tests/fixtures/data/default-template-pack/ob_vehicle_note.j2 @@ -1 +1 @@ -{{VEHICLE_NAME}}: {{VEHICLE_NOTE_URL}} +{{VEHICLE_NAME}}: {{VEHICLE_NOTE_HTML}} diff --git a/vasl_templates/webapp/tests/fixtures/data/vehicles/allied-minor/greek.json b/vasl_templates/webapp/tests/fixtures/data/vehicles/allied-minor/greek.json new file mode 100644 index 0000000..ea4f8f7 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/data/vehicles/allied-minor/greek.json @@ -0,0 +1,16 @@ +[ + +{ "name": "PNG note", + "note_number": "201", + "id": "gr/v:001" +}, +{ "name": "HTML note", + "note_number": "202", + "id": "gr/v:002" +}, +{ "name": "PNG + HTML notes", + "note_number": "203", + "id": "gr/v:003" +} + +] diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/201.png b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/201.png new file mode 100644 index 0000000..62b2678 Binary files /dev/null and b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/201.png differ diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/202.html b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/202.html new file mode 100644 index 0000000..03bfaba --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/202.html @@ -0,0 +1 @@ +This is an HTML vehicle note (202). diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.html b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.html new file mode 100644 index 0000000..1003497 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.html @@ -0,0 +1 @@ +This is an HTML vehicle note (203). diff --git a/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.png b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.png new file mode 100644 index 0000000..62b2678 Binary files /dev/null and b/vasl_templates/webapp/tests/fixtures/vo-notes/allied-minor/vehicles/203.png differ diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt index b7c440c..dac8d58 100644 --- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt +++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1940.txt @@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL -Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL -Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL +Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL +Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt index 75b2033..4c99ef9 100644 --- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt +++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1941.txt @@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL -Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL -Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL +Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL +Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt index 9eb51f2..f8fad0f 100644 --- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt +++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1942.txt @@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL -Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL -Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL +Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL +Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt index 3d4cb50..2f31f5a 100644 --- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt +++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1943.txt @@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL -Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL -Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL +Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL +Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 sM8† CS 7 53† N LL M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt index b3f7e3c..d0ea793 100644 --- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt +++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1944.txt @@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] s8 CS 5[bre Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL -Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL -Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL +Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL +Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 HE8† sD6 sM8† CS 7 53† N LL M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL diff --git a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt index 712b8fd..65da51f 100644 --- a/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt +++ b/vasl_templates/webapp/tests/fixtures/vo-reports/vehicles/russian/1945.txt @@ -82,8 +82,8 @@ Sherman III(a) WP6[J4+]† s8 CS 5[brewup] WP6† s8 C Sherman III(L)(a) WP7 s5 sM8 CS 6[brewup] WP7 s5 sM8 CS 6[brewup] 50.1 N O R† LL Matilda II(b) sD6 CS 5 sD6 CS 5 51† M†1 N LL Valentine II(b) sM8 CS 4 sM8 CS 4 52 Br N LL -Valentine V(b) sM8 CS 4 sM8 CS 4 52† Br K†1 Br NT LL -Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52† Br NT LL +Valentine V(b) sM8 CS 4 sM8 CS 4 52.1† Br K†1 Br NT LL +Valentine VIII(b) HE7 sD6 CS 4 HE7 sD6 CS 4 52.2† Br NT LL Churchill III(b) D6[J4]7[5]† HE7[F3]8[4+]† sD6[4+] sM8† CS 7 D7† HE8† sD6 sM8† CS 7 53† N LL M3A1 Scout Car(a) CS 4 CS 4 54 US E† US H US I† US N LL M5(a) cs 5†[1] cs 5†[1] 55 Br A Br I†1 Br N LL diff --git a/vasl_templates/webapp/tests/remote.py b/vasl_templates/webapp/tests/remote.py index 5c9da1c..9087097 100644 --- a/vasl_templates/webapp/tests/remote.py +++ b/vasl_templates/webapp/tests/remote.py @@ -47,7 +47,7 @@ class ControlTests: def __getattr__( self, name ): """Generic entry point for handling control requests.""" - if name.startswith( ("get_","set_") ): + if name.startswith( ("get_","set_","reset_") ): # check if we are talking to a local or remote server if self.server_url: # remote: return a function that will invoke the handler function on the remote server @@ -281,3 +281,9 @@ class ControlTests: """Get the vasl_mod startup warnings.""" _logger.info( "Returning the vasl_mod startup warnings: %s", vasl_mod_module.warnings ) return vasl_mod_module.warnings + + def _reset_template_pack( self ): + """Force the default template pack to be reloaded.""" + _logger.info( "Reseting the default template pack." ) + globvars.template_pack = None + return self diff --git a/vasl_templates/webapp/tests/test_user_settings.py b/vasl_templates/webapp/tests/test_user_settings.py index 316d67c..e26e779 100644 --- a/vasl_templates/webapp/tests/test_user_settings.py +++ b/vasl_templates/webapp/tests/test_user_settings.py @@ -1,12 +1,13 @@ """ Test the user settings. """ import json +import re from selenium.webdriver.support.ui import Select from selenium.webdriver.common.keys import Keys from vasl_templates.webapp.tests.utils import \ - init_webapp, find_child, wait_for_clipboard, \ + init_webapp, find_child, find_children, wait_for_clipboard, \ select_tab, select_menu_option, set_player, click_dialog_button, add_simple_note from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.test_scenario_persistence import save_scenario, load_scenario @@ -231,6 +232,58 @@ def test_hide_unavailable_ma_notes( webapp, webdriver ): # --------------------------------------------------------------------- +def test_vo_notes_as_images( webapp, webdriver ): + """Test showing vehicle/ordnance notes as HTML/images.""" + + # initialize + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: ct.set_vo_notes_dir( dtype="test" ) + ) + + # load the test vehicle + load_scenario( { + "PLAYER_1": "greek", + "OB_VEHICLES_1": [ { "name": "HTML note" } ], + } ) + select_tab( "ob1" ) + + def check_snippet( expected ): + """Generate and check the vehicle note snippet.""" + sortable = find_child( "#ob_vehicles-sortable_1" ) + elems = find_children( "li", sortable ) + assert len(elems) == 1 + btn = find_child( "img.snippet", elems[0] ) + btn.click() + contains = True if isinstance( expected, str ) else None + wait_for_clipboard( 2, expected, contains=contains ) + + # generate the vehicle snippet (should get the raw HTML) + check_snippet( "This is an HTML vehicle note (202)." ) + + # enable "show vehicle/ordnance notes as images" + select_menu_option( "user_settings" ) + elem = find_child( ".ui-dialog.user-settings input[name='vo-notes-as-images']" ) + assert not elem.is_selected() + elem.click() + click_dialog_button( "OK" ) + _check_cookies( webdriver, "vo-notes-as-images", True ) + + # generate the vehicle snippet (should get a link to return an image) + check_snippet( re.compile( r"http://.+?:\d+/vehicles/greek/note/202" ) ) + + # disable "show vehicle/ordnance notes as images" + select_menu_option( "user_settings" ) + elem = find_child( ".ui-dialog.user-settings input[name='vo-notes-as-images']" ) + assert elem.is_selected() + elem.click() + click_dialog_button( "OK" ) + _check_cookies( webdriver, "vo-notes-as-images", False ) + + # generate the vehicle snippet (should get the raw HTML) + check_snippet( "This is an HTML vehicle note (202)." ) + +# --------------------------------------------------------------------- + def _check_cookies( webdriver, name, expected ): """Check that a user setting was stored in the cookies correctly.""" cookies = [ c for c in webdriver.get_cookies() if c["name"] == "user-settings" ] diff --git a/vasl_templates/webapp/tests/test_vasl_extensions.py b/vasl_templates/webapp/tests/test_vasl_extensions.py index 5faa5b4..8927ec8 100644 --- a/vasl_templates/webapp/tests/test_vasl_extensions.py +++ b/vasl_templates/webapp/tests/test_vasl_extensions.py @@ -124,7 +124,7 @@ def test_dedupe_ma_notes( webapp, webdriver ): # do the tests do_test( [ "Type 92A (Tt)", "M3(a) (LT)" ], [ - ( False, "A", "The MA and allall MG" ), ( True, "A", "The (a) indicates U." ), ( True, "B", "This vehicle uses Re" ), ( True, "C", "Although a captured " ), @@ -132,7 +132,7 @@ def test_dedupe_ma_notes( webapp, webdriver ): ( True, "US B", "Due to two of the MG" ), ] ) do_test( [ "Type 92A (Tt)", "Type 98 MCT (AAtr)" ], [ - ( False, "A", "The MA and allall MG" ), ( True, "Br H", 'As signified by "Inf' ), ( True, "Ge A", "MA and CMG (if so eq" ), # nb: this is "Ge A", which is different to the Japanese "A" ] ) @@ -142,11 +142,11 @@ def test_dedupe_ma_notes( webapp, webdriver ): ( True, "C", "Although a captured " ), ( True, "Br H", 'As signified by "Inf' ), ( True, "Ge A", "MA and CMG (if so eq" ), - ( True, "Jp A", "The MA and allall MG" ), ( True, "US B", "Due to two of the MG" ), ] ) do_test( [ "Type 92A (Tt)", "M3(a) (LT)", "Type 98 MCT (AAtr)" ], [ - ( False, "A", "The MA and allall MG" ), ( True, "A", "The (a) indicates U." ), ( True, "B", "This vehicle uses Re" ), ( True, "C", "Although a captured " ), @@ -285,7 +285,7 @@ def test_bfp_extensions( webapp, webdriver ): ( True, "A", "The (a) indicates U." ), ( True, "C", "Although a captured " ), ( True, "Ch F", "This vehicle, despit" ), - ( True, "Jp A", "The MA and allall MG" ), ], transform=_extract_extn_ma_notes ) # test the Chapter H note @@ -293,9 +293,9 @@ def test_bfp_extensions( webapp, webdriver ): elems = find_children( "li img.snippet", vehicles_sortable ) assert len(elems) == 2 elems[0].click() - wait_for_clipboard( 2, re.compile( r'' ) ) + wait_for_clipboard( 2, "By 1935 the latest European tanks", contains=True ) elems[1].click() - wait_for_clipboard( 2, re.compile( r'' ) ) + wait_for_clipboard( 2, "The Japanese captured hundreds of vehicles", contains=True ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vasl_templates/webapp/tests/test_vassal.py b/vasl_templates/webapp/tests/test_vassal.py index 9ca2cad..7c6e526 100644 --- a/vasl_templates/webapp/tests/test_vassal.py +++ b/vasl_templates/webapp/tests/test_vassal.py @@ -277,7 +277,9 @@ def test_latw_update( webapp, webdriver ): # update the scenario (German/Russian, no date) load_scenario_params( { "scenario": { "PLAYER_1": "german", "PLAYER_2": "russian", "SCENARIO_DATE": "" } } ) - updated_vsav_dump = _update_vsav_and_dump( fname, { "created": 3, "updated": 2, "deleted": 2 } ) + # NOTE: We changed the MOL-P template (to add custom list bullets), so the snippet is different + # to when this test was originally written, and so #updated changed from 2 to 3. + updated_vsav_dump = _update_vsav_and_dump( fname, { "created": 3, "updated": 3, "deleted": 2 } ) _check_vsav_dump( updated_vsav_dump, { "pf": "Panzerfaust", "psk": "Panzerschrek", "atmm": "ATMM check:", # nb: the PF label now has a snippet ID "mol": "Kindling Attempt:", "mol-p": "TH#", # nb: the MOL label now has a snippet ID diff --git a/vasl_templates/webapp/tests/test_vo_notes.py b/vasl_templates/webapp/tests/test_vo_notes.py index b25ba0e..a0f157c 100644 --- a/vasl_templates/webapp/tests/test_vo_notes.py +++ b/vasl_templates/webapp/tests/test_vo_notes.py @@ -112,6 +112,33 @@ def test_ma_notes( webapp, webdriver ): # --------------------------------------------------------------------- +def test_ma_html_notes( webapp, webdriver ): + """Test how we load vehicle/ordnance notes (HTML vs. PNG).""" + + # initialize + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: ct.set_vo_notes_dir( dtype="test" ) + ) + + # load the test scenario + load_scenario( { + "PLAYER_1": "greek", + "OB_VEHICLES_1": [ + { "name": "PNG note" }, + { "name": "HTML note" }, + { "name": "PNG + HTML notes" } + ], + } ) + + # check the snippets + _check_vo_snippets( 1, "vehicles", [ + ( "PNG note", "vehicles/allied-minor/note/201" ), + "HTML note:
\nThis is an HTML vehicle note (202).\n
", + "PNG + HTML notes:
\nThis is an HTML vehicle note (203).\n
", + ] ) + +# --------------------------------------------------------------------- + def test_common_vo_notes( webapp, webdriver ): """Test handling of Allied/Axis Minor common vehicles/ordnance.""" @@ -550,5 +577,8 @@ def _check_vo_snippets( player_no, vo_type, expected ): def _extract_vo_note( clipboard ): """Extract the details from a vehicle/ordnance note snippet.""" - mo = re.search( "^(.+?): http://.+?/(.*)$", clipboard ) - return ( mo.group(1), mo.group(2) ) + mo = re.search( r'^(.+?): \$', clipboard ) + if mo: + return ( mo.group(1), mo.group(2) ) + else: + return clipboard diff --git a/vasl_templates/webapp/tests/utils.py b/vasl_templates/webapp/tests/utils.py index 25b7acc..7046d07 100644 --- a/vasl_templates/webapp/tests/utils.py +++ b/vasl_templates/webapp/tests/utils.py @@ -46,9 +46,12 @@ _webdriver = None def init_webapp( webapp, webdriver, **options ): """Initialize the webapp.""" + + # initialize global _webapp, _webdriver _webapp = webapp _webdriver = webdriver + # reset the server # NOTE: We have to do this manually, since we can't use pytest's monkeypatch'ing, # since we could be talking to a remote server (see ControlTests for more details). @@ -65,6 +68,10 @@ def init_webapp( webapp, webdriver, **options ): if "reset" in options: options.pop( "reset" )( control_tests ) + # force the default template pack to be reloaded (using the new settings) + control_tests.reset_template_pack() + + # load the webapp webdriver.get( webapp.url_for( "main", **options ) ) wait_for( 5, lambda: find_child("#_page-loaded_") is not None ) diff --git a/vasl_templates/webapp/vo_notes.py b/vasl_templates/webapp/vo_notes.py index 63a5436..9f0dc16 100644 --- a/vasl_templates/webapp/vo_notes.py +++ b/vasl_templates/webapp/vo_notes.py @@ -2,13 +2,17 @@ # Pokhara, Nepal (DEC/18). import os +import pathlib +import io +import re import logging from collections import defaultdict -from flask import render_template, jsonify, abort +from flask import request, render_template, jsonify, send_file, abort, Response, url_for from vasl_templates.webapp import app, globvars from vasl_templates.webapp.files import FileServer +from vasl_templates.webapp.webdriver import WebDriver from vasl_templates.webapp.utils import resize_image_response, is_image_file, is_empty_file # --------------------------------------------------------------------- @@ -32,7 +36,7 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma dname = app.config.get( "CHAPTER_H_NOTES_DIR" ) if not dname: globvars.vo_notes = { "vehicles": {}, "ordnance": {} } - globvars.file_server = None + globvars.vo_notes_file_server = None return dname = os.path.abspath( dname ) if not os.path.isdir( dname ): @@ -68,9 +72,12 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma # multi-applicable notes, so we force them to appear in the final results. vo_notes["vehicles"]["anzac"] = {} vo_notes["ordnance"]["indonesian"] = {} + vo_note_layout_width = app.config.get( "VO_NOTE_LAYOUT_WIDTH", 500 ) # load the vehicle/ordnance notes for root,_,fnames in os.walk( dname, followlinks=True ): + + # initialize dname2, vo_type2 = os.path.split( root ) if vo_type2 in extn_ids: extn_id = vo_type2 @@ -86,38 +93,77 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma vo_type2, nat2 = "vehicles", "landing-craft" else: nat2 = nat + + # process each file in the next directory ma_notes = {} for fname in fnames: + + # ignore placeholder files + fname = os.path.join( root, fname ) + if is_empty_file( fname ): + continue + + # figure out what kind of file we have extn = os.path.splitext( fname )[1].lower() if is_image_file( extn ): - key = os.path.splitext(fname)[0] - if not all( ch.isdigit() or ch in (".") for ch in key ): - logging.warning( "Unexpected vehicle/ordnance note key: %s", key ) - fname = os.path.join( root, fname ) - if is_empty_file( fname ): - continue # nb: ignore placeholder files - prefix = os.path.commonpath( [ dname, fname ] ) - if prefix: - if extn_id: - key = "{}:{}".format( extn_id, key ) - vo_notes[vo_type2][nat2][key] = fname[len(prefix)+1:] - else: - logging.warning( "Unexpected vehicle/ordnance note path: %s", fname ) - elif extn == ".html": - key = get_ma_note_key( nat2, fname ) + + # image file - check if this looks like a vehicle/ordnance note + key = os.path.splitext( os.path.split( fname )[1] )[0] + if not all( ch.isdigit() or ch == "." for ch in key ): + # nope (this could be e.g. an image that's part of an HTML vehicle/ordnance note) + continue + + # yup - save it as a vehicle/ordnance note if extn_id: key = "{}:{}".format( extn_id, key ) + # NOTE: We only do this if we don't already have an HTML version. + if not vo_notes.get( vo_type2, {} ).get( nat2, {} ).get( key ): + rel_path = pathlib.PosixPath( fname ).relative_to( dname ) + vo_notes[vo_type2][nat2][key] = str(rel_path) + + elif extn == ".html": + + # HTML file - read the content fname = os.path.join( root, fname ) with open( fname, "r" ) as fp: - buf = fp.read().strip() - if not buf: - continue # nb: ignore placeholder files - if buf.startswith( "

" ): - buf = buf[3:].strip() - if "½" in buf: - # NOTE: VASSAL doesn't like this, use "frac12;" :-/ - logging.warning( "Found ½ in HTML: %s", fname ) - ma_notes[key] = buf + html_content = fp.read().strip() + if "½" in html_content: + # NOTE: VASSAL doesn't like this, use "frac12;" :-/ + logging.warning( "Found ½ in HTML: %s", fname ) + + # check what kind of file we have + key = get_ma_note_key( nat2, os.path.split(fname)[1] ) + if re.search( r"^\d+(\.\d+)?$", key ): + + # check if the content is specifying its own layout + if "" not in html_content: + # nope - use the default one + html_content = "
\n{}\n
".format( + vo_note_layout_width, html_content + ) + + # save it as a vehicle/ordnance note + if extn_id: + key = "{}:{}".format( extn_id, key ) + rel_path = pathlib.PosixPath( os.path.split(fname)[0] ).relative_to( dname ) + vo_notes[ vo_type2 ][ nat2 ][ key ] = _fixup_urls( + html_content, + "{{CHAPTER_H}}/" + str(rel_path) + "/" + ) + + else: + + # save it as a multi-applicable note + if extn_id: + key = "{}:{}".format( extn_id, key ) + if html_content.startswith( "

" ): + html_content = html_content[3:].strip() + rel_path = pathlib.PosixPath( os.path.split(fname)[0] ).relative_to( dname ) + ma_notes[ key ] = _fixup_urls( + html_content, + "{{CHAPTER_H}}/" + str(rel_path) + "/" + ) + if "multi-applicable" in vo_notes[ vo_type2 ][ nat2 ]: vo_notes[ vo_type2 ][ nat2 ][ "multi-applicable" ].update( ma_notes ) else: @@ -131,7 +177,14 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma # install the vehicle/ordnance notes globvars.vo_notes = { k: dict(v) for k,v in vo_notes.items() } - globvars.file_server = file_server + globvars.vo_notes_file_server = file_server + +def _fixup_urls( html, url_stem ): + """Fixup URL's to Chapter H files.""" + matches = list( re.finditer( r"]*src=(['\"])(.*?)\1", html ) ) + for mo in reversed(matches): + html = html[:mo.start(2)] + url_stem+ html[mo.start(2):] + return html # --------------------------------------------------------------------- @@ -139,19 +192,69 @@ def load_vo_notes(): #pylint: disable=too-many-statements,too-many-locals,too-ma def get_vo_note( vo_type, nat, key ): """Return a Chapter H vehicle/ordnance note.""" - # locate the file + # get the vehicle/ordnance note vo_notes = globvars.vo_notes[ vo_type ] - fname = vo_notes.get( nat, {} ).get( key ) - if not fname: - abort( 404 ) - if not globvars.file_server: + vo_note = vo_notes.get( nat, {} ).get( key ) + if not vo_note: abort( 404 ) - resp = globvars.file_server.serve_file( fname, ignore_empty=True ) - if not resp: + if not globvars.vo_notes_file_server: abort( 404 ) - default_scaling = app.config.get( "CHAPTER_H_IMAGE_SCALING", 100 ) - return resize_image_response( resp, default_scaling=default_scaling ) + # serve the file + if is_image_file( vo_note ): + resp = globvars.vo_notes_file_server.serve_file( vo_note, ignore_empty=True ) + if not resp: + abort( 404 ) + default_scaling = app.config.get( "CHAPTER_H_IMAGE_SCALING", 100 ) + return resize_image_response( resp, default_scaling=default_scaling ) + else: + buf = _make_vo_note_html( vo_note ) + if request.args.get( "f" ) == "html": + # return the content as HTML + return Response( buf, mimetype="text/html" ) + else: + # return the content as an image + # NOTE: We offer this option since VASSAL's HTML engine is so ancient, it doesn't support + # floating images (which we really need), either via CSS "float", or the HTML "align" attribute. + # NOTE: We need our own WebDriver instance in case the user is trying to generate a snippet image, + # which will use the shared instance (thus locking it), but vehicle/ordnance notes can contain + # a link that calls us here to generate the Chapter H content as an image, and if this 2nd request + # gets handled in a different thread (which it certainly will, since the 1st request is still + # in progress), we will deadlock waiting for the shared instance to become available. + with WebDriver.get_instance( "vo_note" ) as webdriver: + img = webdriver.get_snippet_screenshot( None, buf ) + buf = io.BytesIO() + img.save( buf, format="PNG" ) + buf.seek( 0 ) + return send_file( buf, mimetype="image/png" ) + +def _make_vo_note_html( vo_note ): + """Generate the HTML for a vehicle/ordnance note.""" + + # initialize + url_root = request.url_root + if url_root.endswith( "/" ): + url_root = url_root[:-1] + + # inject the CSS (we do it like this since VASSAL doesn't support :-/) + css = globvars.template_pack.get( "css", {} ).get( "vo_note" ) + if css: + vo_note = "\n\n\n\n{}".format( css, vo_note ) + + # update any parameters + vo_note = vo_note.replace( "{{CHAPTER_H}}", url_root+"/chapter-h" ) + vo_note = vo_note.replace( "{{IMAGES_BASE_URL}}", url_root+url_for("static",filename="images") ) + + return vo_note + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +@app.route( "/chapter-h/" ) +def get_chapter_h_file( path ): + """Return a Chapter H file.""" + if not globvars.vo_notes_file_server: + abort( 404 ) + return globvars.vo_notes_file_server.serve_file( path, ignore_empty=True ) # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/webdriver.py b/vasl_templates/webapp/webdriver.py index a1082ac..6cd9a2d 100644 --- a/vasl_templates/webapp/webdriver.py +++ b/vasl_templates/webapp/webdriver.py @@ -19,10 +19,10 @@ _logger = logging.getLogger( "webdriver" ) class WebDriver: """Wrapper for a Selenium webdriver.""" - # NOTE: The thread-safety lock controls access to the _shared_instance variable, + # NOTE: The thread-safety lock controls access to the _shared_instances variable, # not the WebDriver it points to (it has its own lock). - _shared_instance_lock = threading.RLock() - _shared_instance = None + _shared_instances_lock = threading.RLock() + _shared_instances = {} def __init__( self ): self.driver = None @@ -159,7 +159,7 @@ class WebDriver: return self.get_screenshot( snippet, window_size, window_size2 ) @staticmethod - def get_instance(): + def get_instance( key="default" ): """Return the shared WebDriver instance. A Selenium webdriver has a hefty startup time, so we create one on first use, and then re-use it. @@ -178,26 +178,26 @@ class WebDriver: if app.config.get( "DISABLE_SHARED_WEBDRIVER" ): return WebDriver() - with WebDriver._shared_instance_lock: + with WebDriver._shared_instances_lock: # check if we've already created the shared WebDriver - if WebDriver._shared_instance: + if key in WebDriver._shared_instances: # yup - just return it (nb: the caller is responsible for locking it) - _logger.info( "Returning shared WebDriver: %x", id(WebDriver._shared_instance) ) + _logger.info( "Returning shared WebDriver (%s): %x", key, id(WebDriver._shared_instances[key]) ) - return WebDriver._shared_instance + return WebDriver._shared_instances[ key ] # nope - create a new WebDriver instance # NOTE: We start it here to keep it alive even after the caller has finished with it, # and take steps to make sure it gets stopped and cleaned up when the program exits. wdriver = WebDriver() - _logger.info( "Created shared WebDriver: %x", id(wdriver) ) + _logger.info( "Created shared WebDriver (%s): %x", key, id(wdriver) ) wdriver._do_start() #pylint: disable=protected-access - WebDriver._shared_instance = wdriver + WebDriver._shared_instances[ key ] = wdriver # make sure the shared WebDriver gets cleaned up def cleanup(): #pylint: disable=missing-docstring - _logger.info( "Cleaning up shared WebDriver: %x", id(wdriver) ) + _logger.info( "Cleaning up shared WebDriver (%s): %x", key, id(wdriver) ) wdriver._do_stop() #pylint: disable=protected-access atexit.register( cleanup ) globvars.cleanup_handlers.append( cleanup )