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.
467 lines
20 KiB
467 lines
20 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 ;
|
|
var gScenarioUploadDlgState = null ;
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
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,
|
|
position: gScenarioUploadDlgState ? gScenarioUploadDlgState.position : { my: "center", at: "center", of: window },
|
|
width: gScenarioUploadDlgState ? gScenarioUploadDlgState.width: $(window).width() * 0.8,
|
|
height: gScenarioUploadDlgState ? gScenarioUploadDlgState.height: $(window).height() * 0.8,
|
|
minWidth: 800, 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,
|
|
beforeClose: function() {
|
|
gScenarioUploadDlgState = getDialogState( $(this) ) ;
|
|
},
|
|
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
|
|
|