@ -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." ) ; |
||||||
|
} |
||||||
|
} |