@ -0,0 +1,20 @@ |
||||
.ui-dialog.scenario-downloads { border-radius: 5px ; } |
||||
.ui-dialog.scenario-downloads .ui-dialog-titlebar { background: none ; border: none ; cursor: auto ; } |
||||
.ui-dialog.scenario-downloads .ui-dialog-titlebar-close { display: none; } |
||||
#scenario-downloads-dialog { padding-top: 0 !important ; } |
||||
|
||||
#scenario-downloads-dialog .fgroups { margin: 0 ; } |
||||
#scenario-downloads-dialog .fgroup { |
||||
border: 1px solid #ccc ; border-radius: 5px ; |
||||
margin-bottom: 0.5em ; |
||||
padding: 0.5em ; |
||||
display: flex ; |
||||
list-style-type: none ; |
||||
} |
||||
#scenario-downloads-dialog .fgroup:last-of-type { margin-bottom: 0 ; } |
||||
#scenario-downloads-dialog .fgroup .screenshot { width: 5em ; max-height: 4em ; margin-right: 1em ; text-align: center ; } |
||||
#scenario-downloads-dialog .fgroup .screenshot img { max-width: 100% ; max-height: 4em ; } |
||||
#scenario-downloads-dialog .fgroup .user { font-style: italic ; } |
||||
#scenario-downloads-dialog .fgroup .timestamp { font-size: 80% ; font-style: italic ; color: #888 ; } |
||||
#scenario-downloads-dialog .fgroup button { float: left ; margin: 0.5em 0.5em 0 0 ; padding: 2px 5px ; display: flex ; align-items: center ; font-size: 80% ; } |
||||
#scenario-downloads-dialog .fgroup button img { height: 1em ; margin-right: 0.5em ; } |
@ -0,0 +1,56 @@ |
||||
.ui-dialog.scenario-upload .ui-dialog-titlebar { background: #e0e0a0 ; } |
||||
#scenario-upload-dialog { display: flex ; flex-direction: column ; padding: 1em !important ; overflow-y: hidden ; } |
||||
|
||||
#scenario-upload-dialog label { display: inline-block ; width: 5.75em ; font-weight: bold ; } |
||||
#scenario-upload-dialog .row { margin-bottom: 2px ; } |
||||
#scenario-upload-dialog .hint { font-style: italic ; color: #888 ; } |
||||
#scenario-upload-dialog .row .hint { margin-left: 0.5em ; font-size: 80% ; font-style: italic ; color: #666 ; } |
||||
#scenario-upload-dialog .row .hint a { color: #666 ; } |
||||
|
||||
#scenario-upload-dialog .scenario-id { font-size: 80% ; font-style: italic ; color: #666 ; } |
||||
#scenario-upload-dialog .asa-id { font-size: 70% ; font-style: italic ; color: #666 ; } |
||||
#scenario-upload-dialog .auth { margin: 0.25em 0 1em 0 ; } |
||||
#scenario-upload-dialog .disclaimer { margin-top: 1em ; font-size: 80% ; font-style: italic ; color: #444 ; } |
||||
|
||||
#scenario-upload-dialog img.remove { height: 12px ; position: absolute ; top: 5px ; right: 5px ; z-index: 5 ; cursor: pointer ; } |
||||
|
||||
#scenario-upload-dialog .grid { flex: 1 ; display: flex ; } |
||||
#scenario-upload-dialog .grid .left { flex: 4 ; margin-right: 1em ; } |
||||
#scenario-upload-dialog .grid .right { flex: 6 ; } |
||||
|
||||
#scenario-upload-dialog .vsav-container { |
||||
position: relative ; |
||||
padding: 1em ; |
||||
border: 1px solid #888 ; border-radius: 5px ; |
||||
background: #f8f8f8 ; |
||||
} |
||||
#scenario-upload-dialog .vsav-container .hint { display: flex ; } |
||||
#scenario-upload-dialog .vsav-container .hint img { |
||||
margin-right: 0.5em ; height: 2em ; opacity: 0.7 ; |
||||
} |
||||
#scenario-upload-dialog .vsav-container .file-info { display: flex ; } |
||||
#scenario-upload-dialog .vsav-container .file-info img { |
||||
margin-right: 0.5em ; height: 2em ; opacity: 0.8 ; |
||||
} |
||||
#scenario-upload-dialog .vsav-container .file-info .name { font-size: 80% ; font-family: monospace ; color: #444 ; } |
||||
|
||||
#scenario-upload-dialog .screenshot-container { |
||||
display: flex ; flex-direction: column ; justify-content: center ; |
||||
height: 100% ; |
||||
border: 1px solid #888 ; border-radius: 5px ; |
||||
background: #f8f8f8 ; |
||||
position: relative ; |
||||
} |
||||
#scenario-upload-dialog .screenshot-container .hint { display: flex ; justify-content: center ; align-items: center ; } |
||||
#scenario-upload-dialog .screenshot-container .hint img { |
||||
float: left ; margin-right: 0.5em ; height: 2.5em ; opacity: 0.6 ; |
||||
} |
||||
#scenario-upload-dialog .screenshot-container .preview { |
||||
display: flex ; flex-direction: column ; align-items: center ; justify-content: center ; |
||||
height: calc( 100% - 10px ) ; |
||||
margin: 5px ; |
||||
} |
||||
#scenario-upload-dialog .screenshot-container .preview img { |
||||
object-fit: scale-down ; max-width: 100% ; max-height: 100% ; |
||||
border: 1px dotted #aaa ; |
||||
} |
@ -1,48 +0,0 @@ |
||||
div.jquery-image-zoom { |
||||
line-height: 0; |
||||
font-size: 0; |
||||
|
||||
z-index: 1000; |
||||
|
||||
border: 5px solid #fff; |
||||
background: #eee; /* TM 25jan15: Added this to make it easier to see images with transparent backgrounds. */ |
||||
margin: -5px; |
||||
|
||||
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); |
||||
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); |
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); |
||||
} |
||||
|
||||
div.jquery-image-zoom a { |
||||
background: url(/static/imageZoom/jquery.imageZoom.png) no-repeat; |
||||
|
||||
display: block; |
||||
width: 25px; |
||||
height: 25px; |
||||
|
||||
position: absolute; |
||||
left: -17px; |
||||
top: -17px; |
||||
/* IE-users are prolly used to close-link in right-hand corner */ |
||||
*left: auto; |
||||
*right: -17px; |
||||
|
||||
text-decoration: none; |
||||
text-indent: -100000px; |
||||
outline: 0; |
||||
|
||||
z-index: 11; |
||||
} |
||||
|
||||
div.jquery-image-zoom a:hover { |
||||
background-position: left -25px; |
||||
} |
||||
|
||||
div.jquery-image-zoom img, |
||||
div.jquery-image-zoom embed, |
||||
div.jquery-image-zoom object, |
||||
div.jquery-image-zoom div { |
||||
width: 100%; |
||||
height: 100%; |
||||
margin: 0; |
||||
} |
@ -1 +0,0 @@ |
||||
jQuery.fn.imageZoom=function(c,b){var a=c.extend({speed:200,dontFadeIn:1,hideClicked:1,imageMargin:30,className:"jquery-image-zoom",loading:"Loading..."},b);a.doubleSpeed=a.speed/4;c(document).keydown(function(d){if(d.keyCode==27){c("div.jquery-image-zoom a").click()}});return this.click(function(k){var h=c(k.target);var g=h.is("a")?h:h.parents("a");g=(g&&g.is("a")&&g.attr("href").search(/(.*)\.(jpg|jpeg|gif|png|bmp|tif|tiff)$/gi)!=-1)?g:false;var i=(g&&g.find("img").length)?g.find("img"):false;c("div.jquery-image-zoom a").click();if(g){g.oldText=g.text();g.setLoadingImg=function(){if(i){i.css({opacity:"0.5"})}else{g.text(a.loading)}};g.setNotLoadingImg=function(){if(i){i.css({opacity:"1"})}else{g.text(g.oldText)}};var d=g.attr("href");if(c("div."+a.className+' img[src="'+d+'"]').length){return false}var j=function(l){g.setNotLoadingImg();var u=i?i:g;var q=i?a.hideClicked:0;var p=u.offset();var n={width:u.outerWidth(),height:u.outerHeight(),left:p.left,top:p.top};var o=c('<div><img src="'+d+'" alt=""/></div>').css("position","absolute").appendTo(document.body);var m={width:l.width,height:l.height};var s={width:c(window).width(),height:c(window).height()};if(m.width>(s.width-a.imageMargin*2)){var r=s.width-a.imageMargin*2;m.height=(r/m.width)*m.height;m.width=r}if(m.height>(s.height-a.imageMargin*2)){var t=s.height-a.imageMargin*2;m.width=(t/m.height)*m.width;m.height=t}m.left=(s.width-m.width)/2+c(window).scrollLeft();m.top=(s.height-m.height)/2+c(window).scrollTop();var e=c('<a href="#">Close</a>').appendTo(o).hide();if(q){g.css("visibility","hidden")}o.addClass(a.className).css(n).animate(m,a.speed,function(){e.fadeIn(a.doubleSpeed)});var v=function(){e.fadeOut(a.doubleSpeed,function(){o.animate(n,a.speed,function(){g.css("visibility","visible");o.remove()})});return false};o.click(v);e.click(v)};var f=new Image();f.src=d;if(f.complete){j(f)}else{g.setLoadingImg();f.onload=function(){j(f)}}return false}})}; |
Before Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 4.1 KiB |
@ -0,0 +1,4 @@ |
||||
/*! lg-rotate - v1.2.0 - 2020-09-19 |
||||
* http://sachinchoolur.github.io/lightGallery
|
||||
* Copyright (c) 2020 Sachin N; Licensed GPLv3 */ |
||||
!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){!function(){"use strict";var b={rotate:!0,rotateLeft:!0,rotateRight:!0,flipHorizontal:!0,flipVertical:!0},c=function(c){return this.core=a(c).data("lightGallery"),this.core.s=a.extend({},b,this.core.s),this.core.s.rotate&&this.core.doCss()&&this.init(),this};c.prototype.buildTemplates=function(){var a="";this.core.s.flipVertical&&(a+='<button aria-label="Flip vertical" class="lg-flip-ver lg-icon"></button>'),this.core.s.flipHorizontal&&(a+='<button aria-label="flip horizontal" class="lg-flip-hor lg-icon"></button>'),this.core.s.rotateLeft&&(a+='<button aria-label="Rotate left" class="lg-rotate-left lg-icon"></button>'),this.core.s.rotateRight&&(a+='<button aria-label="Rotate right" class="lg-rotate-right lg-icon"></button>'),this.core.$outer.find(".lg-toolbar").append(a)},c.prototype.init=function(){var a=this;this.buildTemplates(),this.rotateValuesList={},this.core.$el.on("onAferAppendSlide.lg.tm.rotate",function(b,c){a.core.$slide.eq(c).find(".lg-img-wrap").wrap('<div class="lg-img-rotate"></div>')}),this.core.$outer.find(".lg-rotate-left").on("click.lg",this.rotateLeft.bind(this)),this.core.$outer.find(".lg-rotate-right").on("click.lg",this.rotateRight.bind(this)),this.core.$outer.find(".lg-flip-hor").on("click.lg",this.flipHorizontal.bind(this)),this.core.$outer.find(".lg-flip-ver").on("click.lg",this.flipVertical.bind(this)),this.core.$el.on("onBeforeSlide.lg.tm.rotate",function(b,c,d){a.rotateValuesList[d]||(a.rotateValuesList[d]={rotate:0,flipHorizontal:1,flipVertical:1})})},c.prototype.applyStyles=function(){this.core.$slide.eq(this.core.index).find(".lg-img-rotate").css("transform","rotate("+this.rotateValuesList[this.core.index].rotate+"deg) scale3d("+this.rotateValuesList[this.core.index].flipVertical+", "+this.rotateValuesList[this.core.index].flipHorizontal+", 1)")},c.prototype.rotateLeft=function(){this.rotateValuesList[this.core.index].rotate-=90,this.applyStyles()},c.prototype.rotateRight=function(){this.rotateValuesList[this.core.index].rotate+=90,this.applyStyles()},c.prototype.flipHorizontal=function(){this.rotateValuesList[this.core.index].flipVertical*=-1,this.applyStyles()},c.prototype.flipVertical=function(){this.rotateValuesList[this.core.index].flipHorizontal*=-1,this.applyStyles()},c.prototype.destroy=function(){this.core.$el.off(".lg.tm.rotate"),this.rotateValuesList={}},a.fn.lightGallery.modules.rotate=c}()}); |
@ -0,0 +1,454 @@ |
||||
/* jshint esnext: true */ |
||||
|
||||
( function() { // nb: put the entire file into its own local namespace, global stuff gets added to window.
|
||||
|
||||
var gVsavData, gScreenshotData ; |
||||
var $gDialog, $gVsavContainer, $gScreenshotContainer ; |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
window.uploadScenario = function() { |
||||
|
||||
// initialize
|
||||
var asaScenarioId = $( "input[name='ASA_ID']" ).val() ; |
||||
|
||||
function onAddVsavFile() { |
||||
if ( ! canAddVsavFile() ) |
||||
return ; |
||||
if ( getUrlParam( "vsav_persistence" ) ) { |
||||
// FOR TESTING PORPOISES! We can't control a file upload from Selenium (since
|
||||
// the browser will use native controls), so we get the data from a <textarea>).
|
||||
var $elem = $( "#_vsav-persistence_" ) ; |
||||
var vsavData = $elem.val() ; |
||||
$elem.val( "" ) ; // nb: let the test suite know we've received the data
|
||||
doSelectVsavData( "test.vsav", vsavData ) ; |
||||
return ; |
||||
} |
||||
$( "#select-vsav-for-upload" ).trigger( "click" ) ; // nb: onSelectVsavFile() will be called
|
||||
} |
||||
function canAddVsavFile() { |
||||
return $gVsavContainer.find( ".hint" ).css( "display" ) !== "none" ; |
||||
} |
||||
function onSelectVsavFile( file ) { |
||||
// read the selected file
|
||||
var fileReader = new FileReader() ; |
||||
fileReader.onload = function() { |
||||
var vsavData = fileReader.result ; |
||||
var fname = file.name ; |
||||
// check the file size
|
||||
var maxBytes = gAppConfig.ASA_MAX_VASL_SETUP_SIZE ; |
||||
if ( maxBytes <= 0 ) |
||||
doSelectVsavData( fname, vsavData ) ; |
||||
else { |
||||
var vsavDataDecoded = atob( removeBase64Prefix( vsavData ) ) ; |
||||
if ( vsavDataDecoded.length <= 1024*maxBytes ) |
||||
doSelectVsavData( fname, vsavData ) ; |
||||
else { |
||||
ask( "VASL scenario", "VASL scenario files should be less than " + maxBytes + " KB.", { |
||||
ok: function() { doSelectVsavData( fname, vsavData ) ; }, |
||||
} ) ; |
||||
} |
||||
} |
||||
} ; |
||||
fileReader.readAsDataURL( file ) ; |
||||
} |
||||
function doSelectVsavData( fname, vsavData ) { |
||||
// show the file details in the UI
|
||||
$gVsavContainer.find( ".hint" ).hide() ; |
||||
var $fileInfo = $gVsavContainer.find( ".file-info" ) ; |
||||
$fileInfo.find( ".name" ).text( fname ) ; |
||||
$fileInfo.show() ; |
||||
// prepare the upload
|
||||
// NOTE: We do this here (rather than when the user clicks the "Upload" button),
|
||||
// so that we can show the generated screenshot.
|
||||
vsavData = removeBase64Prefix( vsavData ) ; |
||||
prepareUploadFiles( fname, vsavData ) ; |
||||
} |
||||
|
||||
function onAddScreenshotFile() { |
||||
if ( ! canAddScreenshotFile() ) |
||||
return ; |
||||
$( "#select-screenshot-for-upload" ).trigger( "click" ) ; // nb: onSelectScreenshotFile() will be called
|
||||
} |
||||
function canAddScreenshotFile() { |
||||
return $gScreenshotContainer.find( ".hint" ).css( "display" ) !== "none" ; |
||||
} |
||||
function onSelectScreenshotFile( file ) { |
||||
// read the selected image file
|
||||
var fileReader = new FileReader() ; |
||||
fileReader.onload = function() { |
||||
var imageData = fileReader.result ; |
||||
var fname = file.name ; |
||||
// check the file size
|
||||
var maxBytes = gAppConfig.ASA_MAX_SCREENSHOT_SIZE ; |
||||
if ( maxBytes <= 0 ) |
||||
doSelectScreenshotFile() ; |
||||
else { |
||||
var imageDataDecoded = atob( removeBase64Prefix( imageData ) ) ; |
||||
if ( imageDataDecoded.length <= 1024*maxBytes ) |
||||
doSelectScreenshotFile() ; |
||||
else { |
||||
ask( "VASL screenshot", "Screenshots should be less than " + maxBytes + " KB.", { |
||||
ok: doSelectScreenshotFile |
||||
} ) ; |
||||
} |
||||
} |
||||
function doSelectScreenshotFile() { |
||||
// show the image preview
|
||||
setScreenshotPreview( imageData, true ) ; |
||||
imageData = removeBase64Prefix( imageData ) ; |
||||
gScreenshotData = [ fname, atob(imageData) ] ; |
||||
} |
||||
} ; |
||||
fileReader.readAsDataURL( file ) ; |
||||
} |
||||
|
||||
function initExternalDragDrop() { |
||||
// disable events we're not interested in
|
||||
[ $gVsavContainer, $gScreenshotContainer ].forEach( function( $elem ) { |
||||
$elem.on( "dragenter", stopEvent ) ; |
||||
$elem.on( "dragleave", stopEvent ) ; |
||||
$elem.on( "dragover", stopEvent ) ; |
||||
} ) ; |
||||
// add handlers for files dropped in
|
||||
$gVsavContainer.on( "drop", function( evt ) { |
||||
if ( ! canAddVsavFile() ) |
||||
return ; |
||||
onSelectVsavFile( evt.originalEvent.dataTransfer.files[0] ) ; |
||||
stopEvent( evt ) ; |
||||
} ) ; |
||||
$gScreenshotContainer.on( "drop", function( evt ) { |
||||
if ( ! canAddScreenshotFile() ) |
||||
return ; |
||||
onSelectScreenshotFile( evt.originalEvent.dataTransfer.files[0] ) ; |
||||
stopEvent( evt ) ; |
||||
} ) ; |
||||
} |
||||
|
||||
function updateUi() |
||||
{ |
||||
// update the UI
|
||||
var userName = $gDialog.find( ".auth .user" ).val().trim() ; |
||||
var apiToken = $gDialog.find( ".auth .token" ).val().trim() ; |
||||
$( ".ui-dialog.scenario-upload button.upload" ).button( |
||||
userName !== "" && apiToken !== "" ? "enable" : "disable" |
||||
) ; |
||||
} |
||||
|
||||
// shpw the upload dialog
|
||||
$( "#scenario-upload-dialog" ).dialog( { |
||||
title: "Upload to the ASL Scenario Archive", |
||||
dialogClass: "scenario-upload", |
||||
modal: true, |
||||
width: 800, minWidth: 800, |
||||
height: 500, minHeight: 500, |
||||
create: function() { |
||||
// add handlers to add files to be uploaded
|
||||
$gVsavContainer = $(this).find( ".vsav-container" ) ; |
||||
$gVsavContainer.on( "click", onAddVsavFile ) ; |
||||
$( "#select-vsav-for-upload" ).on( "change", function() { |
||||
onSelectVsavFile( $( "#select-vsav-for-upload" ).prop("files")[0] ) ; |
||||
} ) ; |
||||
$gScreenshotContainer = $(this).find( ".screenshot-container" ) ; |
||||
$gScreenshotContainer.on( "click", onAddScreenshotFile ) ; |
||||
$( "#select-screenshot-for-upload" ).on( "change", function() { |
||||
onSelectScreenshotFile( $( "#select-screenshot-for-upload" ).prop("files")[0] ) ; |
||||
} ) ; |
||||
// add handlers to remove files to be uploaded
|
||||
$gVsavContainer.find( ".remove" ).on( "click", function( evt ) { |
||||
gVsavData = null ; |
||||
$gVsavContainer.find( ".file-info" ).hide() ; |
||||
$gVsavContainer.find( ".remove" ).hide() ; |
||||
$gVsavContainer.find( ".hint" ).show() ; |
||||
setScreenshotPreview( null ) ; |
||||
stopEvent( evt ) ; |
||||
} ) ; |
||||
$gScreenshotContainer.find( ".remove" ).on( "click", function( evt ) { |
||||
setScreenshotPreview( null ) ; |
||||
stopEvent( evt ) ; |
||||
} ) ; |
||||
// add keyboard handlers
|
||||
$(this).find( ".auth .user" ).on( "keyup", updateUi ) ; |
||||
$(this).find( ".auth .token" ).on( "keyup", updateUi ) ; |
||||
// initialize
|
||||
initExternalDragDrop() ; |
||||
}, |
||||
open: function() { |
||||
// initialize
|
||||
$gDialog = $(this) ; |
||||
$gDialog.find( ".auth .user" ).val( gUserSettings["asa-user-name"] ) ; |
||||
$gDialog.find( ".auth .token" ).val( gUserSettings["asa-api-token"] ? atob(gUserSettings["asa-api-token"]) : "" ) ; |
||||
$gVsavContainer.find( ".hint" ).show() ; |
||||
$gVsavContainer.find( ".file-info" ).hide() ; |
||||
$gScreenshotContainer.find( ".hint" ).show() ; |
||||
$gScreenshotContainer.find( ".preview" ).hide() ; |
||||
// initialize
|
||||
gVsavData = gScreenshotData = null ; |
||||
// load the dialog
|
||||
var scenarioName = $("input[name='SCENARIO_NAME']").val().trim() ; |
||||
if ( scenarioName ) |
||||
$gDialog.find( ".scenario-name" ).text( scenarioName ) ; |
||||
var scenarioId = $("input[name='SCENARIO_ID']").val().trim() ; |
||||
if ( scenarioId ) |
||||
$gDialog.find( ".scenario-id" ).text( "(" + scenarioId + ")" ) ; |
||||
$gDialog.find( ".asa-id" ).text( "(#" + asaScenarioId + ")" ) ; |
||||
var url = gAppConfig.ASA_SCENARIO_URL.replace( "{ID}", asaScenarioId ) ; |
||||
$gDialog.find( ".disclaimer a.asa-scenario" ).attr( "href", url ) ; |
||||
// update the UI
|
||||
fixup_external_links( $gDialog ) ; |
||||
addAsaCreditPanel( $(".ui-dialog.scenario-upload"), asaScenarioId ) ; |
||||
var $btnPane = $( ".ui-dialog.scenario-upload .ui-dialog-buttonpane" ) ; |
||||
var $btn = $btnPane.find( "button.upload" ) ; |
||||
$btn.prepend( |
||||
$( "<img src='" + gImagesBaseUrl+"/upload.png" + "' style='height:0.8em;margin:0 0.35em -1px 0;'>" ) |
||||
) ; |
||||
onResize() ; |
||||
updateUi() ; |
||||
}, |
||||
resize: onResize, |
||||
buttons: { |
||||
Upload: { text: "Upload", class: "upload", click: function() { |
||||
uploadFiles( asaScenarioId ) ; |
||||
} }, |
||||
Cancel: function() { |
||||
$gDialog.dialog( "close" ) ; |
||||
}, |
||||
}, |
||||
} ) ; |
||||
} ; |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function uploadFiles( asaScenarioId ) |
||||
{ |
||||
// check if a full set of files is being uploaded
|
||||
var warningMsg, width ; |
||||
if ( ! gVsavData && ! gScreenshotData ) { |
||||
warningMsg = "<p> Only the <em>" + gAppConfig.APP_NAME + "</em> setup will be uploaded." + |
||||
"<p> Do you want to skip uploading a VASL setup and screenshot?" ; |
||||
width = 480 ; |
||||
} else if ( ! gVsavData ) |
||||
warningMsg = "Do you want to skip uploading a VASL setup?" ; |
||||
else if ( ! gScreenshotData ) |
||||
warningMsg = "Do you want to skip uploading a screenshot of the VASL setup?" ; |
||||
if ( ! warningMsg ) { |
||||
// yup - just do it
|
||||
doUploadFiles() ; |
||||
} else { |
||||
// nope - confirm with the user first
|
||||
ask( "Incomplete upload", warningMsg, { |
||||
width: width, |
||||
ok: doUploadFiles, |
||||
} ) ; |
||||
} |
||||
|
||||
function doUploadFiles() { |
||||
|
||||
// unload the vasl-templates setup
|
||||
var vtSetup = unload_params_for_save( true ) ; |
||||
delete vtSetup.VICTORY_CONDITIONS ; |
||||
delete vtSetup.SSR ; |
||||
|
||||
// unload the authentication details
|
||||
var userName = $gDialog.find( ".auth .user" ).val().trim() ; |
||||
var apiToken = $gDialog.find( ".auth .token" ).val().trim() ; |
||||
gUserSettings["asa-user-name"] = userName ; |
||||
gUserSettings["asa-api-token"] = btoa( apiToken ) ; |
||||
save_user_settings() ; |
||||
|
||||
// generate a unique prefix for the filenames
|
||||
// NOTE: We want to upload the files as a group, so that when we get them back, we can tell
|
||||
// which screenshots go with which VASL/vasl-templates setups. The ASL Scenario Archive doesn't
|
||||
// provide a mechanism for doing this, so we do it by adding a prefix to the filenames.
|
||||
// This is separated from the real filename with a pipe character, so that we (and the website)
|
||||
// can figure out what the real filename is.
|
||||
// NOTE: This is not actually necssary any more, since the ASL Scenario Archive only maintains
|
||||
// the most recently uploaded group of files for a given user+scenario, but it's not a bad idea
|
||||
// for us to keep doing this.
|
||||
var prefix = userName + ":" + Math.floor(Date.now()/1000) ; |
||||
|
||||
// prepare the upload
|
||||
var formData = new FormData() ; |
||||
formData.append( "vt_setup", |
||||
makeBlob( JSON.stringify( vtSetup, null, 4 ), "application/json" ), |
||||
prefix + "|" + "scenario.json" |
||||
) ; |
||||
if ( gVsavData ) { |
||||
formData.append( "vasl_setup", |
||||
makeBlob( gVsavData[1] ), |
||||
prefix + "|" + gVsavData[0] |
||||
) ; |
||||
} |
||||
if ( gScreenshotData ) { |
||||
formData.append( "screenshot", |
||||
makeBlob( gScreenshotData[1] ), |
||||
prefix + "|" + gScreenshotData[0] |
||||
) ; |
||||
} |
||||
|
||||
// upload the files
|
||||
var url = gAppConfig.ASA_UPLOAD_URL ; |
||||
if ( getUrlParam( "vsav_persistence" ) ) { |
||||
// NOTE: We are in test mode - always upload to our own test endpoint.
|
||||
url = "/test-asa-upload/{ID}?user={USER}&token={TOKEN}" ; |
||||
} |
||||
url = url.replace( "{ID}", asaScenarioId ).replace( "{USER}", userName ).replace( "{TOKEN}", apiToken ) ; |
||||
var $dlg = _show_vassal_shim_progress_dlg( "Uploading your scenario..." ) ; |
||||
$.ajax( { |
||||
url: url, |
||||
method: "POST", |
||||
data: formData, |
||||
dataType: "json", |
||||
contentType: false, |
||||
processData: false, |
||||
} ).done( function( resp ) { |
||||
|
||||
// check the response
|
||||
$dlg.dialog( "close" ) ; |
||||
if ( resp.result.status == "ok" ) { |
||||
var msg = resp.result.message ? resp.result.message.replace("1 file(s)","1 file") : "The scenario was uploaded OK." ; |
||||
showInfoMsg( msg ) ; |
||||
// all done - we can close the dialog now
|
||||
// NOTE: While the uploaded files are available on the website immediately, we normally wouldn't
|
||||
// see them here for quite a while (since we need to wait until we download a new copy of the scenarios).
|
||||
// This is a little unsatisfactory - the user would like to see their uploads here immediately - so we
|
||||
// notify the back-end, and it will get a fresh copy of just this scenario. Other users still won't see
|
||||
// the new files until they download a new copy of the scenario index, but there's not much we can do
|
||||
// about that. Since this is all happening in the background, we can still close the dialog and return
|
||||
// to the user, and only show a notification if something goes wrong.
|
||||
onSuccessfulUpload() ; |
||||
$gDialog.dialog( "close" ) ; |
||||
} else if ( resp.result.status == "warning" ) |
||||
showWarningMsg( resp.result.message ) ; |
||||
else if ( resp.result.status == "error" ) |
||||
showErrorMsg( resp.result.message ) ; |
||||
else |
||||
showErrorMsg( "Unknown response status: " + resp.result.status ) ; |
||||
|
||||
} ).fail( function( xhr, status, errorMsg ) { |
||||
|
||||
// the upload failed - report the error
|
||||
$dlg.dialog( "close" ) ; |
||||
showErrorMsg( "Can't upload the scenario:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ; |
||||
|
||||
} ) ; |
||||
|
||||
} |
||||
|
||||
function onSuccessfulUpload() { |
||||
// notify the backend that the files were uploaded successfully
|
||||
$.ajax( { |
||||
url: gOnSuccessfulAsaUploadUrl.replace( "ID", asaScenarioId ), |
||||
} ).done( function( resp ) { |
||||
if ( resp.status !== "ok" ) { |
||||
showWarningMsg( "Couldn't update the local scenario index:" + |
||||
"<div class='pre'>" + escapeHTML(resp.message) + "</div>" |
||||
) ; |
||||
} |
||||
} ).fail( function( xhr, status, errorMsg ) { |
||||
showErrorMsg( "Couldn't update the local scenario index:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ; |
||||
} ) ; |
||||
} |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function prepareUploadFiles( vsavFilename, vsavData ) |
||||
{ |
||||
function removeLoadingSpinner() { |
||||
$gScreenshotContainer.find( ".preview" ).hide() ; |
||||
$gScreenshotContainer.find( ".hint" ).show() ; |
||||
// NOTE: This function is called if the prepare failed, so we want to show
|
||||
// the "remove" button, so the user can remove the (possibly) invalid VSAV file.
|
||||
$gVsavContainer.find( ".remove" ).show() ; |
||||
} |
||||
|
||||
// send a request to the backend to prepare the files
|
||||
setScreenshotPreview( gImagesBaseUrl + "/loader.gif", false ) ; |
||||
var data = { |
||||
filename: vsavFilename, |
||||
vsav_data: vsavData, |
||||
} ; |
||||
$.ajax( { |
||||
url: gPrepareAsaUploadUrl, |
||||
type: "POST", |
||||
data: JSON.stringify( data ), |
||||
contentType: "application/json", |
||||
} ).done( function( resp ) { |
||||
|
||||
// check the response
|
||||
data = _check_vassal_shim_response( resp, "Can't prepare the VASL scenario." ) ; |
||||
if ( ! data ) { |
||||
removeLoadingSpinner() ; |
||||
return ; |
||||
} |
||||
|
||||
// save the prepared files
|
||||
gVsavData = [ resp.filename, atob(resp.stripped_vsav) ] ; |
||||
$gVsavContainer.find( ".remove" ).show() ; |
||||
if ( resp.screenshot ) { |
||||
gScreenshotData = [ "auto-generated.jpg", atob(resp.screenshot) ] ; |
||||
setScreenshotPreview( "data:image/png;base64,"+resp.screenshot, true ) ; |
||||
} else { |
||||
showMsgDialog( "Screenshot error", |
||||
"<p> <img src='" + gImagesBaseUrl+"/vassal-screenshot-hint.png" + "' style='height:12em;float:left;margin-right:1em;'>" + |
||||
"Couldn't automatically generate a screenshot for the scenario." + |
||||
"<p> Load the scenario into VASSAL and create one manually, then add it here.", |
||||
550 |
||||
) ; |
||||
removeLoadingSpinner() ; |
||||
} |
||||
|
||||
} ).fail( function( xhr, status, errorMsg ) { |
||||
|
||||
// the prepare failed - report the error
|
||||
removeLoadingSpinner() ; |
||||
showErrorMsg( "Can't prepare the VASL scenario:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ; |
||||
|
||||
} ) ; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function setScreenshotPreview( imageData, isPreviewImage ) |
||||
{ |
||||
// check if we should clear the current image preview
|
||||
if ( ! imageData ) { |
||||
// yup - make it so
|
||||
gScreenshotData = null ; |
||||
$gScreenshotContainer.find( ".preview" ).hide() ; |
||||
$gScreenshotContainer.find( ".remove" ).hide() ; |
||||
$gScreenshotContainer.find( ".hint" ).show() ; |
||||
return ; |
||||
} |
||||
|
||||
// load the screenshot preview image
|
||||
$gScreenshotContainer.find( ".hint" ).hide() ; |
||||
var $preview = $gScreenshotContainer.find( ".preview" ).hide() ; |
||||
var $img = $preview.find( "img" ) ; |
||||
$img.css( "border-width", isPreviewImage ? "1px" : 0 ) ; |
||||
$img.attr( "src", imageData ).on( "load", function() { |
||||
onResize() ; |
||||
$preview.show() ; |
||||
} ) ; |
||||
|
||||
// update the "remove" button
|
||||
var $btn = $gScreenshotContainer.find( ".remove" ) ; |
||||
if ( isPreviewImage ) |
||||
$btn.show() ; |
||||
else |
||||
$btn.hide() ; |
||||
} |
||||
|
||||
function onResize() |
||||
{ |
||||
// FUDGE! The screenshot container and image are set to have a height of 100%,
|
||||
// but at some point, a parent element needs to have an actual height set.
|
||||
$gScreenshotContainer.css( "height", |
||||
$gDialog.innerHeight() - $gScreenshotContainer.position().top - 15 |
||||
) ; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
} )() ; // end local namespace
|
@ -0,0 +1,5 @@ |
||||
<div id="scenario-downloads-dialog" style="display:none;"> |
||||
|
||||
<ul class="fgroups"> </ul> |
||||
|
||||
</div> |
@ -0,0 +1,66 @@ |
||||
<div id="scenario-upload-dialog" style="display:none;"> |
||||
|
||||
<div class="row"> |
||||
<label for="scenario"> Scenario: </label> <span class="scenario-name"></span> |
||||
<span class="scenario-id"></span> |
||||
<span class="asa-id"></span> |
||||
</div> |
||||
|
||||
<div class="auth"> |
||||
<div class="row"> |
||||
<label for="user"> User name: </label> <input class="user" type="text" size="20"> |
||||
<span class="hint"> (register for an account <a href="https://www.aslscenarioarchive.com/register.php">here</a>) <span> |
||||
</div> |
||||
<div class="row"> |
||||
<label for="token"> API token: </label> <input class="token" type="text" size="20"> |
||||
<span class="hint"> (get this from your account's <em>My Page</em>) </span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="grid"> |
||||
|
||||
<div class="left"> |
||||
|
||||
<div class="vsav-container"> |
||||
<div class="hint"> |
||||
<img src="{{url_for('static',filename='images/lfa/file.png')}}"> |
||||
Click here to select your VASL save file, or drag it in. |
||||
</div> |
||||
<div class="file-info" style="display:none;"> |
||||
<img src="{{url_for('static',filename='images/lfa/file.png')}}"> |
||||
<div> |
||||
<span class="name"></span> |
||||
</div> |
||||
</div> |
||||
<img src="{{url_for('static',filename='images/cross.png')}}" class="remove" title="Remove this file" style="display:none;"> |
||||
</div> |
||||
|
||||
<div class="disclaimer"> |
||||
Your <em>vasl-templates</em> setup will also be uploaded. |
||||
<div style="margin-top:0.5em;display:flex;"> |
||||
<img src="{{url_for('static',filename='images/warning.gif')}}" style="height:1.5em;margin:0.1em 0.5em 0 0;"> |
||||
<div> For copyright reasons, some information will be removed from the uploads (e.g. Victory Conditions, SSR's). If you are sure that the scenario is <em>not</em> copyrighted, you can upload the complete files at the <a href="" class="asa-scenario">ASL Scenario Archive</a>. </div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> <!-- end left --> |
||||
|
||||
<div class="right"> |
||||
|
||||
<div class="screenshot-container"> |
||||
<div class="hint"> |
||||
<img src="{{url_for('static',filename='images/screenshot.png')}}"> |
||||
Click here to select a screenshot file, <br> or drag one in. |
||||
</div> |
||||
<div class="preview" style="display:none;"> |
||||
<img src=""> |
||||
</div> |
||||
<img src="{{url_for('static',filename='images/cross.png')}}" class="remove" title="Remove this file" style="display:none;"> |
||||
</div> |
||||
|
||||
</div> <!-- end right --> |
||||
|
||||
</div> <!-- end grid --> |
||||
|
||||
</div> |
||||
|
@ -0,0 +1,6 @@ |
||||
{ "result": { |
||||
|
||||
"status": "error", |
||||
"message": "Invalid user token" |
||||
|
||||
} } |
@ -0,0 +1,6 @@ |
||||
{ "result": { |
||||
|
||||
"status": "error", |
||||
"message": "Invalid user token" |
||||
|
||||
} } |
@ -0,0 +1,6 @@ |
||||
{ "result": { |
||||
|
||||
"status": "error", |
||||
"message": "Invalid user token" |
||||
|
||||
} } |
@ -0,0 +1,6 @@ |
||||
{ "result": { |
||||
|
||||
"status": "error", |
||||
"message": "No screenshot|vt_setup|vasl_setup uploaded" |
||||
|
||||
} } |
@ -0,0 +1,6 @@ |
||||
{ "result": { |
||||
|
||||
"status": "ok", |
||||
"message": "Uploaded files successfully" |
||||
|
||||
} } |
@ -0,0 +1,56 @@ |
||||
package vassal_shim ; |
||||
|
||||
import java.io.File ; |
||||
|
||||
import VASSAL.build.module.map.ImageSaver ; |
||||
import VASSAL.build.module.Map ; |
||||
import VASSAL.tools.swing.ProgressDialog ; |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
public class AppImageSaver extends ImageSaver |
||||
{ |
||||
// FUDGE! We implement our own version of ImageSaver so that we can get access
|
||||
// to its protected member variables :-/
|
||||
|
||||
// FUDGE! VASSAL's ImageSaver shows a progress dialog as the screenshot is generated,
|
||||
// so we need to provide one of these to stop it from crashing :-/
|
||||
// We detect when the process has finished when VASSAL "closes" the dialog.
|
||||
class DummyProgressDialog extends ProgressDialog |
||||
{ |
||||
private boolean isDone ; |
||||
public DummyProgressDialog() { |
||||
super( null, "", "" ) ; |
||||
isDone = false ; |
||||
} |
||||
public void dispose() { |
||||
isDone = true ; |
||||
super.dispose() ; |
||||
} |
||||
public boolean isDone() { return isDone ; } |
||||
} |
||||
|
||||
public AppImageSaver( Map map ) |
||||
{ |
||||
// initialize
|
||||
super( map ) ; |
||||
} |
||||
|
||||
public void generateScreenshot( File outputFile, int width, int height, int timeout ) |
||||
throws InterruptedException |
||||
{ |
||||
// install our dummy progress dialog into ImageSaver
|
||||
dialog = new DummyProgressDialog() ; |
||||
|
||||
// call into VASSAL to generate the screenshot
|
||||
super.writeMapRectAsImage( outputFile, 0, 0, width, height ) ; |
||||
|
||||
// wait for the task to finish
|
||||
for ( int i=0 ; i < timeout ; ++i ) { |
||||
if ( ((DummyProgressDialog)dialog).isDone() ) |
||||
return ; |
||||
Thread.sleep( 1*1000 ) ; |
||||
} |
||||
throw new RuntimeException( "Screenshot timeout." ) ; |
||||
} |
||||
} |