From 09a41da590582d125027b608f9816efb7ec1b1cc Mon Sep 17 00:00:00 2001 From: Taka Date: Fri, 13 Jul 2018 10:10:08 +0000 Subject: [PATCH] Added the ability to load/save/reset scenarios. --- vasl_templates/main.py | 2 +- vasl_templates/webapp/main.py | 4 +- vasl_templates/webapp/static/css/main.css | 9 + .../webapp/static/download/download.min.js | 2 + vasl_templates/webapp/static/generate.js | 208 +++++++++++++++--- vasl_templates/webapp/static/main.js | 42 +++- .../popmenu/jquery.popmenu-1.0.0.min.js | 46 ++++ .../webapp/static/popmenu/jquery.popmenu.css | 140 ++++++++++++ .../templates/{main.html => index.html} | 9 + vasl_templates/webapp/tests/test_generate.py | 20 +- vasl_templates/webapp/tests/test_ob_setup.py | 66 +++--- vasl_templates/webapp/tests/test_players.py | 14 +- .../webapp/tests/test_scenario_persistence.py | 138 ++++++++++++ vasl_templates/webapp/tests/test_ssr.py | 74 ++++--- vasl_templates/webapp/tests/utils.py | 79 +++++-- 15 files changed, 699 insertions(+), 154 deletions(-) create mode 100644 vasl_templates/webapp/static/download/download.min.js create mode 100644 vasl_templates/webapp/static/popmenu/jquery.popmenu-1.0.0.min.js create mode 100644 vasl_templates/webapp/static/popmenu/jquery.popmenu.css rename vasl_templates/webapp/templates/{main.html => index.html} (94%) create mode 100644 vasl_templates/webapp/tests/test_scenario_persistence.py diff --git a/vasl_templates/main.py b/vasl_templates/main.py index fb2dd21..e889bac 100755 --- a/vasl_templates/main.py +++ b/vasl_templates/main.py @@ -70,7 +70,7 @@ def main(): # run the application app = QApplication( sys.argv ) - url = "http://localhost:{}/main".format( port ) + url = "http://localhost:{}".format( port ) main_window = MainWindow( url ) main_window.show() ret_code = app.exec_() diff --git a/vasl_templates/webapp/main.py b/vasl_templates/webapp/main.py index 6209865..435ad47 100644 --- a/vasl_templates/webapp/main.py +++ b/vasl_templates/webapp/main.py @@ -6,10 +6,10 @@ from vasl_templates.webapp import app # --------------------------------------------------------------------- -@app.route( "/main" ) +@app.route( "/" ) def main(): """Return the main page.""" - return render_template( "main.html" ) + return render_template( "index.html" ) # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/static/css/main.css b/vasl_templates/webapp/static/css/main.css index eec2488..3110670 100644 --- a/vasl_templates/webapp/static/css/main.css +++ b/vasl_templates/webapp/static/css/main.css @@ -4,6 +4,15 @@ body { height: 100% ; } /* -------------------------------------------------------------------- */ +#menu { position: absolute ; top: 15px ; right: 15px ; z-index: 1 ; } +#menu { height: 30px ; } + +.PopMenu-Item { width: 8em } +.PopMenu-Item a { padding: 5px 10px 5px 10px ; } +.PopMenu-Icon { display: none ; } + +/* -------------------------------------------------------------------- */ + #tabs { display: none ; position: absolute ; top: 5px ; bottom: 5px ; left: 5px ; right: 5px ; diff --git a/vasl_templates/webapp/static/download/download.min.js b/vasl_templates/webapp/static/download/download.min.js new file mode 100644 index 0000000..7acea27 --- /dev/null +++ b/vasl_templates/webapp/static/download/download.min.js @@ -0,0 +1,2 @@ +//download.js v4.21, by dandavis; 2008-2018. [MIT] see http://danml.com/download.html for tests/usage +;(function(root,factory){typeof define=="function"&&define.amd?define([],factory):typeof exports=="object"?module.exports=factory():root.download=factory()})(this,function(){return function download(data,strFileName,strMimeType){var self=window,defaultMime="application/octet-stream",mimeType=strMimeType||defaultMime,payload=data,url=!strFileName&&!strMimeType&&payload,anchor=document.createElement("a"),toString=function(a){return String(a)},myBlob=self.Blob||self.MozBlob||self.WebKitBlob||toString,fileName=strFileName||"download",blob,reader;myBlob=myBlob.call?myBlob.bind(self):Blob,String(this)==="true"&&(payload=[payload,mimeType],mimeType=payload[0],payload=payload[1]);if(url&&url.length<2048){fileName=url.split("/").pop().split("?")[0],anchor.href=url;if(anchor.href.indexOf(url)!==-1){var ajax=new XMLHttpRequest;return ajax.open("GET",url,!0),ajax.responseType="blob",ajax.onload=function(e){download(e.target.response,fileName,defaultMime)},setTimeout(function(){ajax.send()},0),ajax}}if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){if(!(payload.length>2096103.424&&myBlob!==toString))return navigator.msSaveBlob?navigator.msSaveBlob(dataUrlToBlob(payload),fileName):saver(payload);payload=dataUrlToBlob(payload),mimeType=payload.type||defaultMime}else if(/([\x80-\xff])/.test(payload)){var i=0,tempUiArr=new Uint8Array(payload.length),mx=tempUiArr.length;for(i;i0?3:2,decoder=parts[indexDecoder]=="base64"?atob:decodeURIComponent,binData=decoder(parts.pop()),mx=binData.length,i=0,uiArr=new Uint8Array(mx);for(i;i 0) ) - missing_params.push( _MANDATORY_PARAMS[template_id][param_id] ) ; - } - if ( missing_params.length > 0 ) { - var buf = [ "Missing parameters:
    " ] ; - for ( var i=0 ; i < missing_params.length ; ++i ) - buf.push( "
  • " + escapeHTML(missing_params[i]) ) ; - buf.push( "
" ) ; - showWarningMsg( buf.join("") ) ; - } - } // extract the scenario date components var scenario_date = $("input[name='scenario_date']").datepicker( "getDate" ) ; @@ -90,10 +62,6 @@ function generate_snippet( $btn ) params.PF_CHECK_DRM = "" ; params.PF_CHECK_DR = 3 ; } - if ( template_id === "pf" ) { - if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1942 || (params.SCENARIO_YEAR == 1943 && params.SCENARIO_MONTH <= 9) ) - showWarningMsg( "PF are only available after September 1943." ) ; - } // generate BAZ parameters if ( params.SCENARIO_YEAR >= 1945 ) { @@ -113,6 +81,28 @@ function generate_snippet( $btn ) params.BAZ_TOKILL = 13 ; params.BAZ_RANGE = 4 ; } + + // check for mandatory parameters + if ( template_id in _MANDATORY_PARAMS ) { + var missing_params = [] ; + for ( var param_id in _MANDATORY_PARAMS[template_id] ) { + if ( ! (param_id in params && params[param_id].length > 0) ) + missing_params.push( _MANDATORY_PARAMS[template_id][param_id] ) ; + } + if ( missing_params.length > 0 ) { + var buf = [ "Missing parameters:
    " ] ; + for ( var i=0 ; i < missing_params.length ; ++i ) + buf.push( "
  • " + escapeHTML(missing_params[i]) ) ; + buf.push( "
" ) ; + showWarningMsg( buf.join("") ) ; + } + } + + // check for date-specific parameters + if ( template_id === "pf" ) { + if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1942 || (params.SCENARIO_YEAR == 1943 && params.SCENARIO_MONTH <= 9) ) + showWarningMsg( "PF are only available after September 1943." ) ; + } if ( template_id === "baz" ) { if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1941 || (params.SCENARIO_YEAR == 1942 && params.SCENARIO_MONTH < 11) ) showWarningMsg( "BAZ are only available from November 1942." ) ; @@ -154,3 +144,153 @@ function generate_snippet( $btn ) } showInfoMsg( "The HTML snippet has been copied to the clipboard." ) ; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +function unload_params() +{ + // collect all the template parameters + var params = {} ; + add_param = function($elem) { params[ $elem.attr("name").toUpperCase() ] = $elem.val() ; } ; + $("input[type='text'].param").each( function() { add_param($(this)) ; } ) ; + $("textarea.param").each( function() { add_param($(this)) ; } ) ; + $("select.param").each( function() { add_param($(this)) ; } ) ; + + // collect the SSR's + params.SSR = [] ; + $("#ssr-sortable li").each( function() { + params.SSR.push( $(this).text() ) ; + } ) ; + + return params ; +} + +// -------------------------------------------------------------------- + +function on_load_scenario() +{ + // FIXME! confirm this operation if the scenario is dirty + + // FOR TESTING PORPOISES! We can't control a file upload from Selenium (since + // the browser will use native controls), so we store the result in a
). + var $elem ; + if ( getUrlParam( "scenario_persistence" ) ) { + $elem = $( "#scenario_persistence" ) ; // nb: must have already been created + do_load_scenario( JSON.parse( $elem.val() ) ) ; + return ; + } + + // ask the user to upload the scenario file + $("#load-scenario").trigger( "click" ) ; +} + +function on_load_scenario_file_selected() +{ + // read the selected file + var fileReader = new FileReader() ; + fileReader.onload = function() { + var data ; + try { + data = JSON.parse( fileReader.result ) ; + } catch( ex ) { + showErrorMsg( "Can't load the scenario file:
" + escapeHTML(ex) + "
" ) ; + return ; + } + do_load_scenario( data ) ; + showInfoMsg( "The scenario was loaded." ) ; + } ; + fileReader.readAsText( $("#load-scenario").prop("files")[0] ) ; +} + +function do_load_scenario( params ) +{ + // load the scenario parameters + on_new_scenario( false ) ; + var params_loaded = {} ; + var set_param = function( $elem, key ) { $elem.val(params[key]) ; params_loaded[key]=true ; return $elem ; } ; + for ( var key in params ) { + if ( key === "SSR" ) { + for ( var i=0 ; i < params[key].length ; ++i ) { + var $ssr = $( "
  • " ) ; + $ssr.text( params[key][i] ) ; + $("#ssr-sortable").append( $ssr ) ; + init_ssr( $ssr ) ; + } + update_ssr_hint() ; + params_loaded[key] = true ; + continue ; + } + //jshint loopfunc: true + $elem = $("input[type='text'][name='"+key.toLowerCase()+"'].param").each( function() { + set_param( $(this), key ) ; + } ) ; + $elem = $("textarea[type='text'][name='"+key.toLowerCase()+"'].param").each( function() { + set_param( $(this), key ) ; + } ) ; + $elem = $("select[name='"+key.toLowerCase()+"'].param").each( function() { + set_param( $(this), key ).trigger( "change" ) ; + } ) ; + } + + // look for unrecognized keys + var buf = [] ; + for ( key in params ) { + if ( ! (key in params_loaded) ) + buf.push( "
  • " + key + " = '" + escapeHTML(params[key]) + "'" ) ; + } + if ( buf.length > 0 ) + showWarningMsg( "Unknown keys in the scenario file:
      " + buf.join("") + "
    " ) ; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +function on_save_scenario() +{ + // unload the template parameters + var params = unload_params() ; + var data = JSON.stringify( params ) ; + + // FOR TESTING PORPOISES! We can't control a file download from Selenium (since + // the browser will use native controls), so we store the result in a
    ). + if ( getUrlParam( "scenario_persistence" ) ) { + var $elem = $( "#scenario_persistence" ) ; + if ( $elem.length === 0 ) { + // NOTE: The
    we store the message in must be visible, otherwise + // Selenium doesn't return any text for it :-/ + $elem = $( "" ) ; + $("body").append( $elem ) ; + } + $elem.val( data ) ; + return ; + } + + // return the parameters to the user as a downloadable file + download( data, "scenario.json", "application/json" ) ; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +function on_new_scenario( verbose ) +{ + // FIXME! confirm this operation if the scenario is dirty + + // reset all the template parameters + $("input[type='text'].param").each( function() { $(this).val("") ; } ) ; + $("textarea.param").each( function() { $(this).val("") ; } ) ; + + // reset all the template parameters + on_player_change( $("select[name='player_1']").val( "german" ) ) ; + $("select[name='player_1_elr']").val( 5 ) ; + $("select[name='player_1_san']").val( 2 ) ; + on_player_change( $("select[name='player_2']").val( "russian" ) ) ; + $("select[name='player_2_elr']").val( 5 ) ; + $("select[name='player_2_san']").val( 2 ) ; + + // reset all the template parameters + $("#ssr-sortable li").each( function() { + $(this).remove() ; + } ) ; + update_ssr_hint() ; + if ( verbose ) + showInfoMsg( "The scenario was reset." ) ; +} diff --git a/vasl_templates/webapp/static/main.js b/vasl_templates/webapp/static/main.js index 9618bf1..4422015 100644 --- a/vasl_templates/webapp/static/main.js +++ b/vasl_templates/webapp/static/main.js @@ -12,6 +12,31 @@ var _NATIONALITY_SPECIFIC_BUTTONS = { $(document).ready( function () { + // initialize + var $menu = $("#menu input") ; + $menu.popmenu( { + new: { label: "New scenario", action: on_new_scenario }, + load: { label: "Load scenario", action: on_load_scenario }, + save: { label: "Save scenario", action: on_save_scenario }, + } ) ; + // nb: we only show the popmenu on left click (not the normal right-click) + $menu.off( "contextmenu" ) ; + $menu.click( function() { + var pos = $(this).offset() ; + $(this).data( "PopMenu.contextmenu" ).data( "PopMenu.instance" ).show( + pos.left+$(this).width()+4, pos.top+$(this).height()+4, "fade", 200 + ) ; + } ) ; + // nb: we dismiss the popmenu on ESCAPE + $(document).keydown( function(evt) { + if ( evt.keyCode == 27 ) + $menu.popmenu( "hide" ) ; + } ) ; + // add a handler for when the "load scenario" file has been selected + $("#load-scenario").change( on_load_scenario_file_selected ) ; + // all done - we can show the menu now + $("#menu").show() ; + // initialize $("#tabs").tabs( { heightStyle: "fill", @@ -45,14 +70,14 @@ $(document).ready( function () { for ( var i=0 ; i <= 5 ; ++i ) // nb: A19.1: ELR is 0-5 buf.push( "" ) ; buf = buf.join( "" ) ; - $("select[name='player_1_elr']").html( buf ).val( 5 ) ; - $("select[name='player_2_elr']").html( buf ).val( 5 ) ; + $("select[name='player_1_elr']").html( buf ) ; + $("select[name='player_2_elr']").html( buf ) ; buf = [ "" ] ; // nb: allow scenarios that have no SAN for ( i=2 ; i <= 7 ; ++i ) // nb: A14.1: SAN is 2-7 buf.push( "" ) ; buf = buf.join( "" ) ; - $("select[name='player_1_san']").html( buf ).val( 2 ) ; - $("select[name='player_2_san']").html( buf ).val( 2 ) ; + $("select[name='player_1_san']").html( buf ) ; + $("select[name='player_2_san']").html( buf ) ; // load the nationalities $.getJSON( gGetNationalitiesUrl, function(data) { @@ -60,12 +85,9 @@ $(document).ready( function () { var buf = [] ; for ( var id in gNationalities ) buf.push( "" ) ; - on_player_change( - $("select[name='player_1']").html( buf ).val( "german" ) - ) ; - on_player_change( - $("select[name='player_2']").html( buf ).val( "russian" ) - ) ; + $("select[name='player_1']").html( buf ) ; + $("select[name='player_2']").html( buf ) ; + on_new_scenario( false ) ; } ).fail( function( xhr, status, errorMsg ) { showErrorMsg( "Can't get the nationalities:
    " + escapeHTML(errorMsg) + "
    " ) ; } ) ; diff --git a/vasl_templates/webapp/static/popmenu/jquery.popmenu-1.0.0.min.js b/vasl_templates/webapp/static/popmenu/jquery.popmenu-1.0.0.min.js new file mode 100644 index 0000000..51a433e --- /dev/null +++ b/vasl_templates/webapp/static/popmenu/jquery.popmenu-1.0.0.min.js @@ -0,0 +1,46 @@ +/** + * jQuery PopMenu + * + * Context menu (popup menu) plugin for web applications. + * + * Copyright © 2014 Fajar Yoseph Chandra. All rights reserved. + * + * @version 1.0.0 + * @author Fajar Chandra + * @since 2014.05.29 + */ +PopMenu=function(menu,options){this.id="PopMenu_"+Math.round(Math.random()*1E7);this.data=PopMenu.defaults;this.$=$('");this.$menu=this.$.children(".PopMenu-Menu");this.populateMenu(menu);this.$.on("click",function(){var instance=$(this).data("PopMenu.instance");instance.hide()});this.$.on("contextmenu",function(){return false});this.$.on("mousewheel DOMMouseScroll",function(e){e.stopPropagation(); +e.preventDefault();return false});this.$menu.on("mousewheel DOMMouseScroll",function(e){if(!$(this).hasClass("overflows"))return false;var marginTop=parseInt($(this).css("margin-top"));if((e.type="DOMMouseScroll"&&e.originalEvent.detail<0)||typeof WheelEvent!="undefined"&&e.originalEvent instanceof WheelEvent&&e.originalEvent.wheelDeltaY>0||typeof MouseEvent!="undefined"&&e.originalEvent instanceof MouseEvent&&e.originalEvent.detail<0||typeof Event!="undefined"&&e.originalEvent instanceof Event&& +e.originalEvent.wheelDelta>0){marginTop+=72;if(marginTop>0)marginTop=0}else if((e.type="DOMMouseScroll")||typeof WheelEvent!="undefined"&&e.originalEvent instanceof WheelEvent||typeof MouseEvent!="undefined"&&e.originalEvent instanceof MouseEvent||typeof Event!="undefined"&&e.originalEvent instanceof Event){marginTop-=72;if(marginTop+$(this).data("menu-height")<$(window).height())marginTop=$(window).height()-$(this).data("menu-height")}$(this).stop();$(this).animate({"margin-top":marginTop+"px"}, +100,"linear")});this.$menu.on("mousemove",function(e){if(!$(this).hasClass("overflows"))return false;var $instance=$(this).parent().data("PopMenu.instance");var marginTop=parseInt($(this).css("margin-top"));var duration=0;if(e.pageY-$(window).scrollTop()<$instance.data.scrollerSize){duration=-marginTop;marginTop=0}else if(e.pageY-$(window).scrollTop()>$(window).height()-$instance.data.scrollerSize){duration=marginTop-($(window).height()-$(this).data("menu-height"));marginTop=$(window).height()-$(this).data("menu-height")}else{if($(this).data("is-scrolling-with-scroller")){$(this).stop(); +$(this).data("is-scrolling-with-scroller",false)}return}$(this).stop();$(this).data("is-scrolling-with-scroller",true);$(this).animate({"margin-top":marginTop+"px"},duration*3,"linear",function(){$(this).data("is-scrolling-with-scroller",false)})});this.$.data("PopMenu.instance",this);this.options(options)};PopMenu.direction={RIGHT:2,LEFT:0,TOP:0,BOTTOM:1,HORIZONTAL:2,VERTICAL:1,RIGHT_BOTTOM:3,RIGHT_TOP:2,LEFT_BOTTOM:1,LEFT_TOP:0}; +PopMenu.defaults={effect:"fade",duration:200,contextMenu:false,tmpFx:undefined,direction:PopMenu.direction.RIGHT_BOTTOM,scrollerSize:24};PopMenu.prototype.options=function(options){if(arguments.length==0)return this.data;this.data=$.extend(true,{},this.data,options);this.effect(this.data.effect);this.duration(this.data.duration);this.contextMenu(this.data.contextMenu)}; +PopMenu.prototype.effect=function(effect){if(arguments.length==0)return this.data.effect;switch(effect){case "fade":case "slide":case "none":this.data.effect=effect;return true;default:return false}};PopMenu.prototype.duration=function(duration){if(arguments.length==0)return this.data.duration;this.data.duration=duration}; +PopMenu.prototype.contextMenu=function(value){if(arguments.length==0)return this.data.contextMenu;this.data.contextMenu=value;this.$.removeClass("PopMenu");this.$.removeClass("PopMenu-TopMenu");if(this.data.contextMenu){this.$.addClass("PopMenu");this.$menu.addClass("PopMenu-TopMenu");$("body").append(this.$)}}; +PopMenu.prototype.show=function(x,y,effect,duration){if(!this.contextMenu())return false;if(effect==null||effect=="default")effect=this.data.effect;if(duration===undefined)duration=this.data.duration;this.data.tmpFx=effect;this.$.css("visibility","hidden");this.$.show();var menuW=this.$menu.outerWidth();var menuH=this.$menu.outerHeight();this.$.hide();this.$.css("visibility","visible");if(x<0)x=0;if(x+menuW>$(window).width()){x-=menuW;if(x+menuW>$(window).width())x=$(window).width()-menuW}if(y<0)y= +0;if(y+menuH>$(window).height()){y-=menuH;if(y+menuH>$(window).height())y=$(window).height()-menuH}this.$menu.css("left",x+"px");this.$menu.css("top",y+"px");this.updateLabels();switch(effect){default:case "none":this.$.show();break;case "fade":this.$.fadeIn(duration);break;case "slide":this.$.slideDown(duration);break}}; +PopMenu.prototype.showAsSubmenu=function(direction,effect,duration){var $parent=this.$.parent(".PopMenu-Item");if($parent.length==0)return false;if(effect==null||effect=="default")effect=this.data.effect;if(duration===undefined)duration=this.data.duration;if(direction===undefined)direction=this.data.direction;this.data.tmpFx=effect;this.$menu.removeClass("overflows");this.$menu.css("margin-top",0);if((direction&PopMenu.direction.HORIZONTAL)==PopMenu.direction.RIGHT){this.$menu.css("left","100%"); +this.$menu.css("right","auto")}else{this.$menu.css("left","auto");this.$menu.css("right","100%")}if((direction&PopMenu.direction.VERTICAL)==PopMenu.direction.BOTTOM){this.$menu.css("top","0");this.$menu.css("bottom","auto")}else{this.$menu.css("top","auto");this.$menu.css("bottom","0")}this.$.css("visibility","hidden");this.$.show();var menuW=this.$menu.outerWidth();var menuH=this.$menu.outerHeight();var x=this.$menu.offset().left-$(window).scrollLeft();var y=this.$menu.offset().top-$(window).scrollTop(); +this.$menu.data("menu-height",menuH);this.$menu.data("menu-width",menuW);this.$.hide();this.$.css("visibility","visible");var parentH=$parent.outerHeight();if((direction&PopMenu.direction.HORIZONTAL)==PopMenu.direction.RIGHT&&x+menuW>$(window).width()){this.$menu.css("left","auto");this.$menu.css("right","100%");this.data.direction=PopMenu.direction.direction&~PopMenu.direction.HORIZONTAL|PopMenu.direction.LEFT}if((direction&PopMenu.direction.HORIZONTAL)==PopMenu.direction.LEFT&&x<0){this.$menu.css("left", +"100%");this.$menu.css("right","auto");this.data.direction=PopMenu.direction.direction&~PopMenu.direction.HORIZONTAL|PopMenu.direction.RIGHT}if((direction&PopMenu.direction.VERTICAL)==PopMenu.direction.BOTTOM&&y+menuH>$(window).height()){this.$menu.css("top","auto");this.$menu.css("bottom","0");if(y+parentH-menuH<0){this.$menu.css("top",-y+"px");this.$menu.css("bottom","auto");this.$menu.addClass("overflows")}}if((direction&PopMenu.direction.VERTICAL)==PopMenu.direction.TOP&&y<0){this.$menu.css("top", +"0");this.$menu.css("bottom","auto");if(y+parentH>$(window).height()){this.$menu.css("top",-y+"px");this.$menu.css("bottom","auto");this.$menu.addClass("overflows")}}$parent.addClass("selected");switch(effect){default:case "none":this.$.show();break;case "fade":this.$.fadeIn(duration);break;case "slide":this.$.slideDown(duration);break}}; +PopMenu.prototype.hide=function(effect,duration){if(effect==null)effect=this.data.tmpFx;if(duration===undefined)duration=this.data.duration;this.hideSubmenus(effect,duration);switch(effect){default:case "none":this.$.hide();break;case "fade":this.$.fadeOut(duration);break;case "slide":this.$.slideUp(duration);break}}; +PopMenu.prototype.hideSubmenus=function(effect,duration){this.$.find(".PopMenu-Item.selected").removeClass("selected");switch(effect){default:case "none":this.$.find(".PopMenu-Container").hide();break;case "fade":this.$.find(".PopMenu-Container").fadeOut(duration);break;case "slide":this.$.find(".PopMenu-Container").slideUp(duration);break}};PopMenu.prototype.populateMenu=function(menu){var instance=this;$.each(menu,function(index,item){instance.append(index,item)})}; +PopMenu.prototype.find=function(id){var $find=this.$menu.find("a[data-id="+id+"]");if($find.length==0)return null;else return $find.parent().data("PopMenu.instance")};PopMenu.prototype.append=function(id,item){item.id=id;var menuItem=new PopMenuItem(item);this.$menu.append(menuItem.$);return menuItem};PopMenu.prototype.insert=function(id,item,after){item.id=id;var $after=this.find(after).$;var menuItem=new PopMenuItem(item);$after.after(menuItem.$);return menuItem}; +PopMenu.prototype.prepend=function(id,item){item.id=id;var menuItem=new PopMenuItem(item);this.$menu.prepend(menuItem.$);return menuItem};PopMenu.prototype.remove=function(id){var item=this.find(id);if(item!=null){item.$.remove();delete item}};PopMenu.prototype.updateLabels=function(force){this.$.find("a.PopMenu-Link").each(function(){var instance=$(this).parent().data("PopMenu.instance");instance.updateLabel(force)})}; +PopMenu.prototype.selectedItem=function(){var selected=this.$.find(".PopMenu-Item.selected");if(selected.length>0)return selected.data("PopMenu.instance");else return null}; +PopMenuItem=function(options){this.data=PopMenuItem.defaults;this.callbacks={};this.$=$('
  • '+''+""+''+"
  • ");this.$link=this.$.children(".PopMenu-Link");this.$icon=this.$.children(".PopMenu-Icon");this.options(options);this.$link.click(function(e){if($(this).parent().hasClass("disabled"))return false;$(this).trigger("action",e);if($(this).parent().hasClass("PopMenu-HasMenu"))e.stopPropagation()}); +this.$.mouseover(function(e){var instance=$(this).data("PopMenu.instance");var $container=$(this).parents(".PopMenu-Container:eq(0)");if($container.length>0){var container=$container.data("PopMenu.instance");if(container.selectedItem()!=instance){container.hideSubmenus();if(instance.submenu!=null)instance.submenu.showAsSubmenu(container.data.direction,container.data.effect,container.data.duration)}}else if(instance.submenu!=null)instance.submenu.showAsSubmenu(container.data.direction,container.data.effect, +container.data.duration);e.stopPropagation()});this.$.data("PopMenu.instance",this)};PopMenuItem.defaults={id:undefined,type:"item",label:undefined,key:undefined,action:undefined,href:undefined,target:undefined,icon:undefined,disabled:false,submenu:undefined,visible:true}; +PopMenuItem.prototype.on=function(event,callback){var instance=this;if(typeof this.callbacks[event]!="object"){this.callbacks[event]=[];this.$link.on(event,function(){var args=arguments;$.each(instance.callbacks[event],function(index,item){item.apply(instance,args)})})}this.callbacks[event].push(callback)}; +PopMenuItem.prototype.off=function(event){this.$link.off(event);delete this.callbacks[event];if(event=="click")this.$link.click(function(e){if($(this).parent().hasClass("disabled"))return false;$(this).trigger("action",e);if($(this).parent().hasClass("PopMenu-HasMenu"))e.stopPropagation()})}; +PopMenuItem.prototype.options=function(options){if(arguments.length==0)return this.data;this.data=$.extend(true,{},this.data,options);this.id(this.data.id);this.key(this.data.key);this.type(this.data.type);this.icon(this.data.icon);this.label(this.data.label);this.disable(this.data.disabled);this.visible(this.data.visible);this.href(this.data.href);this.target(this.data.target);if(typeof this.data.action=="function"){this.off("action");this.on("action",this.data.action)}this.data.action=undefined; +if(typeof this.data.submenu=="object"){if(this.data.submenu instanceof PopMenu==false)this.data.submenu=new PopMenu(this.data.submenu);this.$.append(this.data.submenu.$);this.$.addClass("PopMenu-HasMenu");this.submenu=this.data.submenu}else{this.$.removeClass("PopMenu-HasMenu");delete this.submenu}}; +PopMenuItem.prototype.icon=function(icon){if(arguments.length==0)return this.data.icon;this.$icon.css("background-image","none");this.$icon.attr("data-icon",null);if(icon==null)return;if(icon.indexOf(".")==-1)this.$icon.attr("data-icon",icon);else this.$icon.css("background-image",'url("'+icon+'")')};PopMenuItem.prototype.show=function(){this.data.visible=true;this.$.show()};PopMenuItem.prototype.hide=function(){this.data.visible=false;this.$.hide()}; +PopMenuItem.prototype.visible=function(value){if(arguments.length==0)return this.data.visible;if(value)return this.show();else return this.hide()};PopMenuItem.prototype.id=function(id){if(arguments.length==0)return this.data.id;this.data.id=id;this.$link.attr("data-id",this.data.id)};PopMenuItem.prototype.key=function(key){if(arguments.length==0)return this.data.key;this.data.key=key;this.$link.attr("data-key",this.data.key)}; +PopMenuItem.prototype.href=function(url){if(arguments.length==0)return this.data.href;this.data.href=url;if(this.data.href)this.$link.attr("href",this.data.href);else this.$link.attr("href",null)};PopMenuItem.prototype.target=function(target){if(arguments.length==0)return this.data.target;this.data.target=target;if(this.data.target)this.$link.attr("target",this.data.target);else this.$link.attr("target",null)}; +PopMenuItem.prototype.label=function(label){if(arguments.length==0)return this.data.label;this.data.label=label;this.updateLabel(true)};PopMenuItem.prototype.type=function(type){if(arguments.length==0)return this.data.type;var invalid=false;switch(type){case "item":this.$.removeClass();this.$.addClass("PopMenu-Item");break;case "separator":this.$.removeClass();this.$.addClass("PopMenu-Separator");break;default:invalid=true;break}if(!invalid)this.data.type=type}; +PopMenuItem.prototype.disable=function(disabled){if(disabled===undefined)return this.$.hasClass("disabled");if(disabled)this.$.addClass("disabled");else this.$.removeClass("disabled")};PopMenuItem.prototype.updateLabel=function(force){if(typeof this.data.label!="function"&&!force)return;if(typeof this.data.label=="function")var label=this.data.label();else var label=this.data.label;if(this.data.key!=null){pattern=new RegExp("("+this.data.key+")","i");label=label.replace(pattern,"$1")}this.$link.html(label)}; +(function($){var methods={};var defaults={effect:"default",duration:null};$.fn.popmenu=function(){if(arguments.length==0){if(this.data("PopMenu.contextmenu")==null)methods.init.call(this,{});return this.data("PopMenu.contextmenu").data("PopMenu.instance")}if(typeof arguments[0]==="object")return methods.init.call(this,arguments[0]);else if($.isFunction(methods[arguments[0]])){var shift=[].shift;var firstArg=shift.apply(arguments);return methods[firstArg].apply(this,arguments)}};methods.init=function(menu, +options){var $this=this;if(menu instanceof PopMenu==false)var menu=new PopMenu(menu);menu.contextMenu(true);return $this.each(function(){var $this=$(this);var newOptions=$.extend(true,{},defaults,options);$this.data("PopMenu.contextmenu",menu.$);$this.data("PopMenu.options",newOptions);$this.on("contextmenu",methods.show)})};methods.show=function(e){var $elm=$(this);var $menu=$elm.data("PopMenu.contextmenu");var instance=$menu.data("PopMenu.instance");var options=$elm.data("PopMenu.options");if(e!= +null){var pageX=e.pageX;var pageY=e.pageY}else{var pageX=$elm.offset().left;var pageY=$elm.offset().top}var scrollLeft=$(window).scrollLeft();var scrollTop=$(window).scrollTop();var menuX=pageX-scrollLeft;var menuY=pageY-scrollTop;instance.show(menuX,menuY,options.effect,options.duration);return false};methods.hide=function(e){var $menu=$(this).data("PopMenu.contextmenu");var options=$(this).data("PopMenu.options");var instance=$menu.data("PopMenu.instance");instance.hide(options.effect,options.duration)}})(jQuery); diff --git a/vasl_templates/webapp/static/popmenu/jquery.popmenu.css b/vasl_templates/webapp/static/popmenu/jquery.popmenu.css new file mode 100644 index 0000000..b49d85f --- /dev/null +++ b/vasl_templates/webapp/static/popmenu/jquery.popmenu.css @@ -0,0 +1,140 @@ +/** + * jQuery PopMenu + * + * Default stylesheet for jQuery PopMenu. + * + * @version 1.0.0 + * @author Fajar Chandra + * @since 2014.05.29 + */ + +/* CONTAINER */ + +.PopMenu { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 100000; +} + +/* MENU */ + +.PopMenu-Menu { + box-shadow: 2px 2px 2px #000; + box-shadow: 2px 2px 2px rgba(0,0,0,.5); + background: #eee; + background: linear-gradient(#fff, #e0e0e0); + border: 1px solid #aaa; + color: #333; + list-style: none; + margin: 0; + padding: 0; + font-size: 12px; + font-family: 'Arial', sans-serif; + cursor: default; + line-height: 1.2em; + position: absolute; + top: 0; + left: 100%; + z-index: 100000; +} + + +/* TOP LEVEL MENU */ + +.PopMenu-TopMenu { + top: 0; + left: 0; +} + + +/* MENU ITEM */ + +.PopMenu-Item { + position: relative; +} + +.PopMenu-Item > a { + position: relative; + color: #333; + text-decoration: none; + cursor: default; + display: block; + padding: 5px 15px 5px 30px; + min-width: 100px; + z-index: 1; + white-space: nowrap; +} + +.PopMenu-Item.selected, +.PopMenu-Item:hover { + background: #43ace8; +} + +.PopMenu-Item.selected > a, +.PopMenu-Item:hover > a { + color: #fff; + text-decoration: none; +} + +.PopMenu-Item.disabled { + background: transparent; +} + +.PopMenu-Item.disabled > a { + color: #aaa; +} + +/* MENU ICON */ + +.PopMenu-Icon { + position: absolute; + left: 5px; + top: 50%; + margin-top: -8px; + height: 16px; + width: 16px; + font-size: 16px; + line-height: 16px; + color: #333; + display: block; + text-align: center; + z-index: 0; + background-repeat: no-repeat; + background-position: center center; +} + +.PopMenu-Item.disabled .PopMenu-Icon { + color: #aaa; +} + +/* MENU SEPARATOR */ + +.PopMenu-Separator { + display: block; + height: 1px; + background: #ccc; + margin: 0 2px; +} + +.PopMenu-Separator * { + display: none; +} + +/* SUBMENU */ + +.PopMenu-Item.PopMenu-HasMenu > a:after { + content: '▶'; + display: block; + position: absolute; + right: 5px; + top: 4px; + height: 14px; + width: 14px; + text-align: center; + vertical-align: middle; + font-size: 8px; + line-height: 14px; +} diff --git a/vasl_templates/webapp/templates/main.html b/vasl_templates/webapp/templates/index.html similarity index 94% rename from vasl_templates/webapp/templates/main.html rename to vasl_templates/webapp/templates/index.html index c6372c0..fa3e111 100644 --- a/vasl_templates/webapp/templates/main.html +++ b/vasl_templates/webapp/templates/index.html @@ -7,12 +7,19 @@ + + + +
      @@ -150,6 +157,8 @@ + +