diff --git a/vasl_templates/webapp/static/css/main.css b/vasl_templates/webapp/static/css/main.css
index c807825..254e865 100644
--- a/vasl_templates/webapp/static/css/main.css
+++ b/vasl_templates/webapp/static/css/main.css
@@ -43,21 +43,25 @@ body { height: 100% ; }
#tabs-ob1, #tabs-ob2 {
display: grid ; display: -ms-grid ;
- grid-template-rows: 10em 1fr ; -ms-grid-rows: 10em 1fr ;
+ grid-template-rows: 1fr 1fr ; -ms-grid-rows: 1fr 1fr ;
grid-template-columns: 1fr 1fr ; -ms-grid-columns: 1fr 1fr ;
}
-#tabs-ob1 fieldset.tl, #tab-ob2 fieldset.tl {
+#tabs-ob1 fieldset.tl, #tabs-ob2 fieldset.tl {
grid-row-start: 1 ; -ms-grid-row: 1 ;
- grid-column-start: 1 ; -ms-grid-row: 1 ;
+ grid-column-start: 1 ; -ms-grid-column: 1 ;
}
-#tabs-ob1 fieldset.r, #tabs-ob2 fieldset.r {
+#tabs-ob1 fieldset.bl, #tabs-ob2 fieldset.bl {
+ grid-row-start: 2 ; -ms-grid-row: 2 ;
+ grid-column-start: 1 ; -ms-grid-column: 1 ;
+}
+#tabs-ob1 fieldset.tr, #tabs-ob2 fieldset.tr {
grid-row-start: 1 ; -ms-grid-row: 1 ;
- grid-row-end: 3 ; -ms-grid-row-span: 2 ;
grid-column-start: 2 ; -ms-grid-column: 2 ;
}
-#tabs-ob1 fieldset.bl, #tabs-ob2 fieldset.bl {
- -ms-grid-row: 2 ; -ms-grid-column: 1 ;
+#tabs-ob1 fieldset.br, #tabs-ob2 fieldset.br {
+ grid-row-start: 2 ; -ms-grid-row: 2 ;
+ grid-column-start: 2 ; -ms-grid-column: 2 ;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -107,6 +111,12 @@ input[type="text"] { margin-bottom: 0.25em ; }
.ui-dialog.edit-ssr textarea { resize: none ; width: calc(100% - 4px) ; height: calc(100% - 1.25em) ; }
.ui-dialog.edit-ssr button { margin: 0 0 0 5px ; padding: 0.1em 0.2em ; }
+.ui-dialog.edit-ob_setup .ui-dialog-titlebar { display: none ; }
+.ui-dialog.edit-ob_setup .ui-dialog-buttonpane { border: none ; padding: 0 ; font-size: 75% ; }
+#edit-ob_setup { padding: 2px ; }
+.ui-dialog.edit-ob_setup textarea { resize: none ; width: calc(100% - 4px) ; height: calc(100% - 3em) ; }
+.ui-dialog.edit-ob_setup button { margin: 0 0 0 5px ; padding: 0.1em 0.2em ; }
+
#select-vo { overflow: hidden ; }
#select-vo .header { height: 2em ; }
#select-vo select { width: 100% ; top: 2em ; height: calc(100% - 2em) ; }
diff --git a/vasl_templates/webapp/static/css/tabs-ob.css b/vasl_templates/webapp/static/css/tabs-ob.css
index 153e18e..34bf172 100644
--- a/vasl_templates/webapp/static/css/tabs-ob.css
+++ b/vasl_templates/webapp/static/css/tabs-ob.css
@@ -1,19 +1,37 @@
/* -------------------------------------------------------------------- */
-.panel-obsetup {
+.panel-ob_setup {
height: 100% ;
display: grid ; display: -ms-grid ;
- grid-template-rows: 1fr 2.5em ; -ms-grid-rows: 1fr 2.5em ;
+ grid-template-rows: 1fr 2em ; -ms-grid-rows: 1fr 2em ;
+ grid-template-columns: 1fr ; -ms-grid-columns: 1fr ;
+}
+/* FUDGE! IE hackamathon follows... */
+.panel-ob_setup .content { -ms-grid-row: 1 ; -ms-grid-column: 1 ; }
+.panel-ob_setup .footer { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
+
+.panel-ob_setup ul.sortable li input[type="button"] { float: right ; }
+.panel-ob_setup .footer { text-align: right ; font-size: 75% ; }
+.panel-ob_setup .footer .l { float: left ; }
+.ob_setup-trash { margin-left: 5px ; height: 2em ; }
+
+.ob_setup-hint { width:100% ; height: calc(100% - 1.5em) ; font-size: 80% ; font-style: italic ; }
+.ob_setup-hint p { margin-bottom: 1em ; }
+
+/* -------------------------------------------------------------------- */
+
+.panel-ob_notes {
+ height: 100% ;
+ display: grid ; display: -ms-grid ;
+ grid-template-rows: 1fr 2em ; -ms-grid-rows: 1fr 2em ;
grid-template-columns: 1fr ; -ms-grid-columns: 1fr ;
}
/* FUDGE! IE hackamathon follows... */
-.panel-obsetup .footer { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
+.panel-ob_notes .content { -ms-grid-row: 1 ; -ms-grid-column: 1 ; }
+.panel-ob_notes .footer { -ms-grid-row: 2 ; -ms-grid-column: 1 ; }
-.panel-obsetup textarea { width: 100% ; height: 100% ; resize: none ; }
-.panel-obsetup .footer { font-size: 75% ; }
-.panel-obsetup div.snippet-control { float: left ; margin: 0.25em 0.25em 0 0 ; }
-.panel-obsetup .footer .r { display: block-inline ; float: right ; }
-.panel-obsetup .footer .r div.snippet-control { float: none ; margin-right: 0 ; }
+.panel-ob_notes .footer { font-size: 75% ; }
+.panel-ob_notes div.snippet-control { float: left ; margin: 0.25em 0.25em 0 0 ; }
/* -------------------------------------------------------------------- */
diff --git a/vasl_templates/webapp/static/main.js b/vasl_templates/webapp/static/main.js
index caf07f9..f5b361a 100644
--- a/vasl_templates/webapp/static/main.js
+++ b/vasl_templates/webapp/static/main.js
@@ -86,12 +86,24 @@ $(document).ready( function () {
$("#ssr-trash").sortable( {
receive: function( evt, ui ) { ui.item.remove() ; update_ssr_hint() ; }
} ) ;
- $("#edit-ssr textarea").keydown( function(evt) {
- if ( evt.keyCode == 13 && evt.ctrlKey ) {
- $(".ui-dialog.edit-ssr button:contains('OK')").click() ;
- evt.preventDefault() ;
- }
+ enable_ctrl_enter( $("#edit-ssr"), "OK" ) ;
+
+ // initialize OB setup controls
+ init_sortable( $("#ob_setup-sortable_1"),
+ function() { add_ob_setup(1) ; },
+ edit_ob_setup
+ ) ;
+ $("#panel-ob_setup1 input[type='button'][data-id='ob_setup']").click( function() {
+ edit_template( "ob_setup" ) ;
+ } ) ;
+ init_sortable( $("#ob_setup-sortable_2"),
+ function() { add_ob_setup(2) ; },
+ edit_ob_setup
+ ) ;
+ $("#panel-ob_setup2 input[type='button'][data-id='ob_setup']").click( function() {
+ edit_template( "ob_setup" ) ;
} ) ;
+ enable_ctrl_enter( $("#edit-ob_setup"), "OK" ) ;
// initialize vehicle controls (1)
$("#vehicle-sortable_1").sortable( { connectWith: "#vehicle-trash_1", cursor: "move" } ) ;
@@ -241,11 +253,12 @@ $(document).ready( function () {
// handle requests to generate/edit HTML snippets
$("input[type='button'].generate").click( function() {
- generate_snippet( $(this) ) ;
+ generate_snippet( $(this), null ) ;
} ) ;
$("div.snippet-control select").on( "selectmenuselect", function() {
edit_template( $(this).attr("data-id") ) ;
} ) ;
+ enable_ctrl_enter( $("#edit-template"), "Close" ) ;
// initialize hotkeys
init_hotkeys() ;
@@ -347,7 +360,7 @@ function on_player_change( $select )
for ( var nat in _NATIONALITY_SPECIFIC_BUTTONS ) {
for ( var i=0 ; i < _NATIONALITY_SPECIFIC_BUTTONS[nat].length ; ++i ) {
var button_id = _NATIONALITY_SPECIFIC_BUTTONS[nat][i] ;
- $elem = $( "#panel-obsetup" + player_id + " div.snippet-control[data-id='" + button_id + "']" ) ;
+ $elem = $( "#panel-ob_notes" + player_id + " div.snippet-control[data-id='" + button_id + "']" ) ;
$elem.css( "display", nat == player_nat ? "block" : "none" ) ;
}
}
diff --git a/vasl_templates/webapp/static/ob_setup.js b/vasl_templates/webapp/static/ob_setup.js
new file mode 100644
index 0000000..fbdd804
--- /dev/null
+++ b/vasl_templates/webapp/static/ob_setup.js
@@ -0,0 +1,87 @@
+
+// --------------------------------------------------------------------
+
+function add_ob_setup( player_id )
+{
+ // add a new OB setup
+ edit_ob_setup( $("#ob_setup-sortable_"+player_id), null ) ;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+function edit_ob_setup( $sortable, $entry )
+{
+ var $caption, $width ;
+
+ // let the user edit the OB setup
+ $("#edit-ob_setup").dialog( {
+ dialogClass: "edit-ob_setup",
+ modal: true,
+ minWidth: 400,
+ minHeight: 150,
+ open: function() {
+ $caption = $(this).children( "textarea" ) ;
+ $width = $(this).children( "input[type='text']" ) ;
+ if ( $entry ) {
+ var data = $entry.data( "sortable-data" ) ;
+ $caption.val( data.caption ) ;
+ $width.val( data.width ) ;
+ }
+ else {
+ $caption.val( "" ) ;
+ $width.val( "" ) ;
+ }
+ $(this).height( $(this).height() ) ; // fudge: force the textarea to resize
+ },
+ buttons: {
+ OK: function() {
+ var caption = $caption.val().trim() ;
+ var width = $width.val().trim() ;
+ if ( $entry ) {
+ // update the existing OB setup
+ if ( caption === "" )
+ delete_sortable_entry( $entry ) ;
+ else {
+ $entry.data("sortable-data").caption = caption ;
+ $entry.data("sortable-data").width = width ;
+ $entry.empty().append( _make_sortable_entry( caption ) ) ;
+ }
+ }
+ else {
+ // create a new OB setup
+ if ( caption !== "" ) {
+ data = { caption: caption, width: width } ;
+ do_add_ob_setup( $sortable, data ) ;
+ }
+ }
+ $(this).dialog( "close" ) ;
+ },
+ Cancel: function() { $(this).dialog( "close" ) ; },
+ },
+ } ) ;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+function do_add_ob_setup( $sortable, data )
+{
+ // add a new sortable entry
+ add_sortable( $sortable, _make_sortable_entry(data.caption), data ) ;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+function _make_sortable_entry( caption )
+{
+ // generate the sortable entry
+ var $content = $( "
" + caption + "
" ) ;
+
+ // add a handler for the snippet button
+ $content.children("input[type='button']").click( function() {
+ var data = $(this).parent().parent().data( "sortable-data" ) ;
+ var extra_params = { OB_SETUP: data.caption, OB_SETUP_WIDTH: data.width } ;
+ generate_snippet( $(this), extra_params ) ;
+ } ) ;
+
+ return $content ;
+}
diff --git a/vasl_templates/webapp/static/snippets.js b/vasl_templates/webapp/static/snippets.js
index 6ee40d3..62a0539 100644
--- a/vasl_templates/webapp/static/snippets.js
+++ b/vasl_templates/webapp/static/snippets.js
@@ -16,7 +16,7 @@ var _DAY_OF_MONTH_POSTFIXES = { // nb: we assume English :-/
// --------------------------------------------------------------------
-function generate_snippet( $btn )
+function generate_snippet( $btn, extra_params )
{
// initialize
storeMsgForTestSuite( "_last-info_", "" ) ;
@@ -44,18 +44,6 @@ function generate_snippet( $btn )
unload_params( params, true ) ;
// set player-specific parameters
- // NOTE: We used to delete the player-specific parameters (e.g. OB_SETUP_1/2)
- // and just return a generic player-independent one (e.g. OB_SETUP), but now,
- // we just leave them in place, in case a user-defined template wants them both.
- if ( template_id === "ob_setup_1" ) {
- template_id = "ob_setup" ;
- params.OB_SETUP = params.OB_SETUP_1 ;
- params.OB_SETUP_WIDTH = params.OB_SETUP_WIDTH_1 ;
- } else if ( template_id === "ob_setup_2" ) {
- template_id = "ob_setup" ;
- params.OB_SETUP = params.OB_SETUP_2 ;
- params.OB_SETUP_WIDTH = params.OB_SETUP_WIDTH_2 ;
- }
var nationalities = gTemplatePack.nationalities ;
var curr_tab = $("#tabs .ui-tabs-active a").attr( "href" ) ;
if ( curr_tab === "#tabs-ob1" ) {
@@ -154,6 +142,10 @@ function generate_snippet( $btn )
showWarningMsg( "ATMM are only available from 1944." ) ;
}
+ // add in any extra parameters
+ if ( extra_params )
+ $.extend( true, params, extra_params ) ;
+
// check that the players have different nationalities
if ( params.PLAYER_1 === params.PLAYER_2 )
showWarningMsg( "Both players have the same nationality!" ) ;
@@ -511,8 +503,16 @@ function do_load_scenario( params )
params_loaded[key] = true ;
continue ;
}
+ var player_id ;
+ if ( key === "OB_SETUP_1" || key === "OB_SETUP_2" ) {
+ player_id = key.substring( key.length-1 ) ;
+ var $sortable = $( "#ob_setup-sortable_" + player_id ) ;
+ for ( i=0 ; i < params[key].length ; ++i )
+ do_add_ob_setup( $sortable, params[key][i] ) ;
+ params_loaded[key] = true ;
+ }
if ( key === "VEHICLES_1" || key === "ORDNANCE_1" || key === "VEHICLES_2" || key === "ORDNANCE_2" ) {
- var player_id = key.substring( key.length-1 ) ;
+ player_id = key.substring( key.length-1 ) ;
var nat = params[ "PLAYER_" + player_id ] ;
var vo_type = key.substring(0,9) === "VEHICLES_" ? "vehicle" : "ordnance" ;
for ( i=0 ; i < params[key].length ; ++i ) {
@@ -562,6 +562,13 @@ function do_load_scenario( params )
function on_save_scenario()
{
// unload the template parameters
+ function unload_ob_setups( $sortable ) {
+ var entries = [] ;
+ $sortable.children("li").each( function() {
+ entries.push( $(this).data( "sortable-data" ) ) ;
+ } ) ;
+ return entries ;
+ }
function extract_vo_names( key ) { // nb: we only need to save the vehicle/ordnance name
if ( !(key in params) )
return ;
@@ -570,8 +577,10 @@ function on_save_scenario()
names.push( params[key][i].name ) ;
params[key] = names ;
}
- var params = {};
+ var params = {} ;
unload_params( params, false ) ;
+ params.OB_SETUP_1 = unload_ob_setups( $("#ob_setup-sortable_1") ) ;
+ params.OB_SETUP_2 = unload_ob_setups( $("#ob_setup-sortable_2") ) ;
extract_vo_names( "VEHICLES_1" ) ;
extract_vo_names( "ORDNANCE_1" ) ;
extract_vo_names( "VEHICLES_2" ) ;
@@ -619,10 +628,11 @@ function on_new_scenario( verbose )
update_ssr_hint() ;
// reset all the template parameters
- delete_all_vo( "vehicle", 1 ) ;
- delete_all_vo( "ordnance", 1 ) ;
- delete_all_vo( "vehicle", 2 ) ;
- delete_all_vo( "ordnance", 2 ) ;
+ for ( var i=1 ; i <= 2 ; ++i ) {
+ delete_all_sortable_entries( $("#ob_setup-sortable_"+i) ) ;
+ delete_all_vo( "vehicle", i ) ;
+ delete_all_vo( "ordnance", i ) ;
+ }
// provide some feedback to the user
if ( verbose )
diff --git a/vasl_templates/webapp/static/sortable.js b/vasl_templates/webapp/static/sortable.js
new file mode 100644
index 0000000..3f08e8a
--- /dev/null
+++ b/vasl_templates/webapp/static/sortable.js
@@ -0,0 +1,110 @@
+
+// --------------------------------------------------------------------
+
+function init_sortable( $sortable, on_add, on_edit )
+{
+ // initialize the support elements
+ var $add = _find_sortable_helper( $sortable, "add" ) ;
+ $add.click( on_add ) ;
+ $sortable.data( "on_edit", on_edit ) ;
+
+ // handle dragging entries to the trash
+ var $trash = _find_sortable_helper( $sortable, "trash" ) ;
+ $sortable.sortable( { connectWith: $trash, cursor: "move" } ) ;
+ $trash.sortable( {
+ receive: function( evt, ui ) {
+ ui.item.remove() ;
+ update_sortable_hint($sortable) ;
+ }
+ } ) ;
+}
+
+// --------------------------------------------------------------------
+
+function add_sortable( $sortable, $content, sortable_data )
+{
+ // add a new entry to the sortable
+ var $entry = $( "" ) ;
+ $entry.append( $content ) ;
+ $entry.data( "sortable-data", sortable_data ) ;
+ $sortable.append( $entry ) ;
+ init_sortable_entry( $entry ) ;
+
+ // update the hint
+ update_sortable_hint( $sortable ) ;
+
+ return $entry ;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+function init_sortable_entry( $entry )
+{
+ // initialize the sortable entry
+ var $sortable = $entry.parent() ;
+ $entry.dblclick( function() {
+ $sortable.data("on_edit")( $sortable, $entry ) ;
+ } ) ;
+ $entry.click( function( evt ) {
+ if ( evt.ctrlKey )
+ delete_sortable_entry( $(this) ) ;
+ } ) ;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+function update_sortable_hint( $sortable )
+{
+ // show/hide the hint
+ var $hint = _find_sortable_helper( $sortable, "hint" ) ;
+ if ( $sortable.children("li").length === 0 ) {
+ $sortable.hide() ;
+ $hint.show() ;
+ } else {
+ $sortable.show() ;
+ $hint.hide() ;
+ }
+}
+
+// --------------------------------------------------------------------
+
+function delete_sortable_entry( $entry )
+{
+ // initialize
+ var $sortable = $entry.parent() ;
+
+ // ask if it's OK to delete the entry
+ $entry.addClass( "highlighted" ) ;
+ var caption = $entry.data("sortable-data").caption ;
+ if ( ! caption )
+ caption = $entry.html() ;
+ ask( "OK to delete?", escapeHTML(caption), {
+ "ok": function() {
+ // yup - make it so
+ $entry.remove() ;
+ update_sortable_hint( $sortable ) ;
+ },
+ "close": function() { $entry.removeClass("highlighted") ; },
+ } ) ;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+function delete_all_sortable_entries( $sortable )
+{
+ // delete all entries from the sortable
+ $sortable.children("li").each( function() {
+ $(this).remove() ;
+ } ) ;
+ update_sortable_hint( $sortable ) ;
+}
+
+// --------------------------------------------------------------------
+
+function _find_sortable_helper( $sortable, type )
+{
+ // find a support element for the specified sortable
+ var id = $sortable.prop( "id" ) ;
+ var pos = id.indexOf( "sortable" ) ;
+ return $( "#" + id.substring(0,pos) + type+ id.substring(pos+8) ) ;
+}
diff --git a/vasl_templates/webapp/static/utils.js b/vasl_templates/webapp/static/utils.js
index 1e2d1bc..d82b603 100644
--- a/vasl_templates/webapp/static/utils.js
+++ b/vasl_templates/webapp/static/utils.js
@@ -77,6 +77,23 @@ jQuery.fn.filterByText = function( $textbox ) {
// --------------------------------------------------------------------
+function enable_ctrl_enter( $dlg, btn_text )
+{
+ // allow Ctrl-Enter to dismiss a dialog
+ var dismiss_dialog = function( evt ) {
+ if ( evt.keyCode == 13 && evt.ctrlKey ) {
+ // locate the OK button (nb: we assume the dialog was created with a class the same as its ID)
+ var id = $(this).parent().prop( "id" ) ;
+ $( ".ui-dialog" + "."+id + " button:contains('"+btn_text+"')" ).click() ;
+ evt.preventDefault() ;
+ }
+ } ;
+ $dlg.find("input[type='text']").keydown( dismiss_dialog ) ;
+ $dlg.find("textarea").keydown( dismiss_dialog ) ;
+}
+
+// --------------------------------------------------------------------
+
function ask( title, msg, args )
{
// ask a question
diff --git a/vasl_templates/webapp/templates/index.html b/vasl_templates/webapp/templates/index.html
index 8ee19f2..22a3303 100644
--- a/vasl_templates/webapp/templates/index.html
+++ b/vasl_templates/webapp/templates/index.html
@@ -88,25 +88,36 @@