Create attractive VASL scenarios, with loads of useful information embedded to assist with game play. https://vasl-templates.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
vasl-templates/vasl_templates/webapp/static/scenario-upload.js

461 lines
19 KiB

/* 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( "ASL Scenario Archive upload",
"VASL scenario files should be less than " + maxBytes + " KB.", {
ok: function() { doSelectVsavData( fname, vsavData ) ; },
ok_caption: "Continue",
} ) ;
}
}
} ;
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( "ASL Scenario Archive upload",
"Screenshots should be less than " + maxBytes + " KB.", {
ok: doSelectScreenshotFile,
ok_caption: "Continue",
} ) ;
}
}
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"
) ;
}
// show 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( "ASL Scenario Archive upload", warningMsg, {
width: width,
ok: doUploadFiles,
ok_caption: "Continue",
} ) ;
}
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() ;
var vtSetupJson = JSON.stringify( vtSetup, null, 4 ) ;
var vtSetupJsonUTF8 = toUTF8( vtSetupJson ) ;
formData.append( "vt_setup",
makeBlob( vtSetupJsonUTF8, "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 $pleaseWait = showPleaseWaitDialog( "Uploading your scenario...", { width: 260 } ) ;
$.ajax( {
url: url,
method: "POST",
data: formData,
dataType: "json",
contentType: false,
processData: false,
} ).done( function( resp ) {
// check the response
$pleaseWait.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
$pleaseWait.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