2018-05-15 17:36:45 +00:00
/**
* @author n1474335 [n1474335@gmail.com]
2019-04-02 16:58:36 +01:00
* @author j433866 [j433866@gmail.com]
2018-05-15 17:36:45 +00:00
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
2019-06-06 09:09:48 +01:00
import Utils from "../../core/Utils" ;
2018-05-15 17:36:45 +00:00
import FileSaver from "file-saver" ;
2019-06-06 09:09:48 +01:00
import ZipWorker from "worker-loader?inline&fallback=false!../workers/ZipWorker" ;
2019-04-03 12:00:47 +01:00
2018-05-15 17:36:45 +00:00
/**
2019-04-02 16:58:36 +01:00
* Waiter to handle events related to the output
*/
2018-05-15 17:36:45 +00:00
class OutputWaiter {
/**
* OutputWaiter constructor.
*
* @param {App} app - The main view object for CyberChef.
2019-04-02 16:58:36 +01:00
* @param {Manager} manager - The CyberChef event manager
2018-05-15 17:36:45 +00:00
*/
constructor ( app , manager ) {
this . app = app ;
this . manager = manager ;
2019-04-25 16:32:48 +01:00
this . outputs = {};
2019-05-07 09:26:55 +01:00
this . zipWorker = null ;
2019-06-07 13:52:04 +01:00
this . maxTabs = this . manager . tabs . calcMaxTabs ();
2019-06-06 16:33:35 +01:00
this . tabTimeout = null ;
2018-05-15 17:36:45 +00:00
}
2019-04-04 10:15:13 +01:00
/**
* Calculates the maximum number of tabs to display
*/
calcMaxTabs () {
2019-06-06 16:33:35 +01:00
const numTabs = this . manager . tabs . calcMaxTabs ();
2019-05-29 13:25:12 +01:00
if ( numTabs !== this . maxTabs ) {
this . maxTabs = numTabs ;
2019-06-07 13:52:04 +01:00
this . refreshTabs ( this . manager . tabs . getActiveOutputTab (), "right" );
2019-05-29 13:25:12 +01:00
}
2019-04-04 10:15:13 +01:00
}
2018-05-15 17:36:45 +00:00
/**
2019-04-02 16:58:36 +01:00
* Gets the output for the specified input number
2018-05-15 17:36:45 +00:00
*
2019-05-14 16:13:36 +01:00
* @param {number} inputNum - The input to get the output for
* @param {boolean} [raw=true] - If true, returns the raw data instead of the presented result.
2019-04-03 12:00:47 +01:00
* @returns {string | ArrayBuffer}
2018-05-15 17:36:45 +00:00
*/
2019-05-07 14:20:18 +01:00
getOutput ( inputNum , raw = true ) {
2019-06-03 16:10:05 +01:00
if ( ! this . outputExists ( inputNum )) return - 1 ;
2018-05-15 17:36:45 +00:00
2019-04-25 16:32:48 +01:00
if ( this . outputs [ inputNum ]. data === null ) return "" ;
2018-05-15 17:36:45 +00:00
2019-05-07 14:20:18 +01:00
if ( raw ) {
let data = this . outputs [ inputNum ]. data . dish . value ;
if ( Array . isArray ( data )) {
data = new Uint8Array ( data . length );
for ( let i = 0 ; i < data . length ; i ++ ) {
data [ i ] = this . outputs [ inputNum ]. data . dish . value [ i ];
}
data = data . buffer ;
2019-05-08 13:46:29 +01:00
} else if ( typeof data !== "object" && typeof data !== "string" ) {
data = String ( data );
2019-05-07 14:20:18 +01:00
}
return data ;
} else if ( typeof this . outputs [ inputNum ]. data . result === "string" ) {
2019-05-03 11:49:14 +01:00
return this . outputs [ inputNum ]. data . result ;
2019-04-25 16:32:48 +01:00
} else {
2019-05-03 11:49:14 +01:00
return this . outputs [ inputNum ]. data . result || "" ;
2019-03-27 09:05:10 +00:00
}
}
2019-06-10 12:47:27 +01:00
/**
* Gets the dish object for an output.
*
* @param inputNum - The inputNum of the output to get the dish of
* @returns {Dish}
*/
getOutputDish ( inputNum ) {
if ( this . outputExists ( inputNum ) &&
this . outputs [ inputNum ]. data &&
this . outputs [ inputNum ]. data . dish ) {
return this . outputs [ inputNum ]. data . dish ;
}
return null ;
}
2019-05-08 14:47:05 +01:00
/**
* Checks if an output exists in the output dictionary
*
2019-05-14 16:13:36 +01:00
* @param {number} inputNum - The number of the output we're looking for
2019-05-08 14:47:05 +01:00
* @returns {boolean}
*/
outputExists ( inputNum ) {
if ( this . outputs [ inputNum ] === undefined ||
this . outputs [ inputNum ] === null ) {
return false ;
}
return true ;
}
2018-05-15 17:36:45 +00:00
/**
2019-04-02 16:58:36 +01:00
* Gets the output string or FileBuffer for the active input
2018-05-15 17:36:45 +00:00
*
2019-05-14 16:13:36 +01:00
* @param {boolean} [raw=true] - If true, returns the raw data instead of the presented result.
2019-04-02 16:58:36 +01:00
* @returns {string | ArrayBuffer}
2018-05-15 17:36:45 +00:00
*/
2019-05-07 14:20:18 +01:00
getActive ( raw = true ) {
2019-06-06 16:33:35 +01:00
return this . getOutput ( this . manager . tabs . getActiveOutputTab (), raw );
2018-05-15 17:36:45 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Adds a new output to the output array.
* Creates a new tab if we have less than maxtabs tabs open
2018-05-15 17:36:45 +00:00
*
2019-05-14 16:13:36 +01:00
* @param {number} inputNum - The inputNum of the new output
* @param {boolean} [changeTab=true] - If true, change to the new output
2018-05-15 17:36:45 +00:00
*/
2019-04-02 16:58:36 +01:00
addOutput ( inputNum , changeTab = true ) {
2019-05-08 14:47:05 +01:00
// Remove the output (will only get removed if it already exists)
this . removeOutput ( inputNum );
2019-04-02 16:58:36 +01:00
const newOutput = {
data : null ,
inputNum : inputNum ,
2019-05-01 15:19:01 +01:00
statusMessage : `Input ${ inputNum } has not been baked yet.` ,
2019-04-02 16:58:36 +01:00
error : null ,
2019-05-07 09:26:55 +01:00
status : "inactive" ,
2019-05-16 10:42:07 +01:00
bakeId : - 1 ,
2019-05-28 11:59:57 +01:00
progress : false
2019-04-02 16:58:36 +01:00
};
2019-04-25 16:32:48 +01:00
this . outputs [ inputNum ] = newOutput ;
2019-04-02 16:58:36 +01:00
this . addTab ( inputNum , changeTab );
2018-05-15 17:36:45 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Updates the value for the output in the output array.
* If this is the active output tab, updates the output textarea
*
2019-05-28 11:59:57 +01:00
* @param {ArrayBuffer | String} data
2019-04-02 16:58:36 +01:00
* @param {number} inputNum
2019-05-10 13:47:48 +01:00
* @param {boolean} set
2018-05-15 17:36:45 +00:00
*/
2019-05-10 13:47:48 +01:00
updateOutputValue ( data , inputNum , set = true ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) {
2019-04-25 16:32:48 +01:00
this . addOutput ( inputNum );
2019-04-02 16:58:36 +01:00
}
2018-05-15 17:36:45 +00:00
2019-04-25 16:32:48 +01:00
this . outputs [ inputNum ]. data = data ;
2018-05-15 17:36:45 +00:00
2019-06-10 15:39:21 +01:00
const tabItem = this . manager . tabs . getOutputTabItem ( inputNum );
if ( tabItem ) tabItem . style . background = "" ;
2019-05-10 13:47:48 +01:00
if ( set ) this . set ( inputNum );
2018-05-15 17:36:45 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Updates the status message for the output in the output array.
* If this is the active output tab, updates the output textarea
*
* @param {string} statusMessage
* @param {number} inputNum
2019-05-10 13:47:48 +01:00
* @param {boolean} [set=true]
2018-05-15 17:36:45 +00:00
*/
2019-05-10 13:47:48 +01:00
updateOutputMessage ( statusMessage , inputNum , set = true ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2019-04-25 16:32:48 +01:00
this . outputs [ inputNum ]. statusMessage = statusMessage ;
2019-05-10 13:47:48 +01:00
if ( set ) this . set ( inputNum );
2018-05-15 17:36:45 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Updates the error value for the output in the output array.
2019-05-08 14:47:05 +01:00
* If this is the active output tab, calls app.handleError.
2019-04-02 16:58:36 +01:00
* Otherwise, the error will be handled when the output is switched to
2018-05-15 17:36:45 +00:00
*
2019-04-02 16:58:36 +01:00
* @param {Error} error
* @param {number} inputNum
2019-05-01 17:08:36 +01:00
* @param {number} [progress=0]
2018-05-15 17:36:45 +00:00
*/
2019-05-01 17:08:36 +01:00
updateOutputError ( error , inputNum , progress = 0 ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2018-05-15 17:36:45 +00:00
2019-05-10 13:47:48 +01:00
const errorString = error . displayStr || error . toString ();
this . outputs [ inputNum ]. error = errorString ;
2019-05-01 17:08:36 +01:00
this . outputs [ inputNum ]. progress = progress ;
this . updateOutputStatus ( "error" , inputNum );
2019-04-02 16:58:36 +01:00
}
2018-05-15 17:36:45 +00:00
/**
2019-04-02 16:58:36 +01:00
* Updates the status value for the output in the output array
2018-05-15 17:36:45 +00:00
*
2019-04-02 16:58:36 +01:00
* @param {string} status
* @param {number} inputNum
2018-05-15 17:36:45 +00:00
*/
2019-04-02 16:58:36 +01:00
updateOutputStatus ( status , inputNum ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2019-04-25 16:32:48 +01:00
this . outputs [ inputNum ]. status = status ;
2018-05-15 17:36:45 +00:00
2019-05-01 17:08:36 +01:00
if ( status !== "error" ) {
delete this . outputs [ inputNum ]. error ;
}
2019-06-10 12:47:27 +01:00
this . displayTabInfo ( inputNum );
2019-04-02 16:58:36 +01:00
this . set ( inputNum );
2018-05-15 17:36:45 +00:00
}
2019-05-07 09:26:55 +01:00
/**
* Updates the stored bake ID for the output in the ouptut array
*
* @param {number} bakeId
* @param {number} inputNum
*/
updateOutputBakeId ( bakeId , inputNum ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2019-05-07 09:26:55 +01:00
this . outputs [ inputNum ]. bakeId = bakeId ;
}
2019-05-08 11:57:22 +01:00
/**
* Updates the stored progress value for the output in the output array
*
* @param {number} progress
2019-06-10 15:39:21 +01:00
* @param {number} total
2019-05-08 11:57:22 +01:00
* @param {number} inputNum
*/
2019-06-10 15:39:21 +01:00
updateOutputProgress ( progress , total , inputNum ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2019-05-08 11:57:22 +01:00
this . outputs [ inputNum ]. progress = progress ;
2019-06-10 15:39:21 +01:00
this . manager . tabs . updateOutputTabProgress ( inputNum , progress , total );
2019-05-08 11:57:22 +01:00
}
2018-05-15 17:36:45 +00:00
/**
2019-04-02 16:58:36 +01:00
* Removes an output from the output array.
*
* @param {number} inputNum
2018-05-15 17:36:45 +00:00
*/
2019-04-02 16:58:36 +01:00
removeOutput ( inputNum ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2018-05-15 17:36:45 +00:00
2019-04-25 16:32:48 +01:00
delete ( this . outputs [ inputNum ]);
2019-04-02 16:58:36 +01:00
}
2018-05-15 17:36:45 +00:00
2019-04-30 13:18:22 +01:00
/**
* Removes all output tabs
*/
removeAllOutputs () {
this . outputs = {};
const tabs = document . getElementById ( "output-tabs" ). children ;
for ( let i = tabs . length - 1 ; i >= 0 ; i -- ) {
tabs . item ( i ). remove ();
}
}
2018-05-15 17:36:45 +00:00
/**
2019-04-02 16:58:36 +01:00
* Sets the output in the output textarea.
*
* @param {number} inputNum
2018-05-15 17:36:45 +00:00
*/
2019-05-08 13:46:29 +01:00
async set ( inputNum ) {
2019-06-07 13:52:04 +01:00
if ( inputNum !== this . manager . tabs . getActiveOutputTab ()) return ;
this . toggleLoader ( true );
2019-05-23 15:29:58 +01:00
return new Promise ( async function ( resolve , reject ) {
2019-05-08 13:46:29 +01:00
const output = this . outputs [ inputNum ];
if ( output === undefined || output === null ) return ;
if ( typeof inputNum !== "number" ) inputNum = parseInt ( inputNum , 10 );
const outputText = document . getElementById ( "output-text" );
const outputHtml = document . getElementById ( "output-html" );
const outputFile = document . getElementById ( "output-file" );
const outputHighlighter = document . getElementById ( "output-highlighter" );
const inputHighlighter = document . getElementById ( "input-highlighter" );
// If pending or baking, show loader and status message
// If error, style the tab and handle the error
// If done, display the output if it's the active tab
// If inactive, show the last bake value (or blank)
if ( output . status === "inactive" ||
output . status === "stale" ||
( output . status === "baked" && output . bakeId < this . manager . worker . bakeId )) {
this . manager . controls . showStaleIndicator ();
} else {
this . manager . controls . hideStaleIndicator ();
}
2019-04-02 16:58:36 +01:00
2019-06-10 15:39:21 +01:00
if ( output . progress !== undefined && ! this . app . baking ) {
2019-05-08 13:46:29 +01:00
this . manager . recipe . updateBreakpointIndicator ( output . progress );
} else {
this . manager . recipe . updateBreakpointIndicator ( false );
}
document . getElementById ( "show-file-overlay" ). style . display = "none" ;
2019-05-01 17:08:36 +01:00
2019-05-08 13:46:29 +01:00
if ( output . status === "pending" || output . status === "baking" ) {
// show the loader and the status message if it's being shown
// otherwise don't do anything
document . querySelector ( "#output-loader .loading-msg" ). textContent = output . statusMessage ;
} else if ( output . status === "error" ) {
// style the tab if it's being shown
this . toggleLoader ( false );
2019-04-02 16:58:36 +01:00
outputText . style . display = "block" ;
2019-05-08 13:46:29 +01:00
outputText . classList . remove ( "blur" );
2019-04-02 16:58:36 +01:00
outputHtml . style . display = "none" ;
outputFile . style . display = "none" ;
2019-05-08 13:46:29 +01:00
outputHighlighter . display = "none" ;
inputHighlighter . display = "none" ;
2018-05-15 17:36:45 +00:00
2019-06-10 15:39:21 +01:00
if ( output . error ) {
outputText . value = output . error ;
} else {
outputText . value = output . data . result ;
}
2019-04-02 16:58:36 +01:00
outputHtml . innerHTML = "" ;
2019-05-08 13:46:29 +01:00
} else if ( output . status === "baked" || output . status === "inactive" ) {
2019-05-23 15:29:58 +01:00
document . querySelector ( "#output-loader .loading-msg" ). textContent = `Loading output ${ inputNum } ` ;
2019-05-08 13:46:29 +01:00
this . closeFile ();
let scriptElements , lines , length ;
2018-05-15 17:36:45 +00:00
2019-05-08 13:46:29 +01:00
if ( output . data === null ) {
2019-05-01 17:08:36 +01:00
outputText . style . display = "block" ;
outputHtml . style . display = "none" ;
outputFile . style . display = "none" ;
outputHighlighter . display = "block" ;
inputHighlighter . display = "block" ;
2019-05-08 13:46:29 +01:00
outputText . value = "" ;
2019-05-01 17:08:36 +01:00
outputHtml . innerHTML = "" ;
2019-05-08 13:46:29 +01:00
lines = 0 ;
length = 0 ;
2019-05-23 15:29:58 +01:00
this . toggleLoader ( false );
2019-05-08 13:46:29 +01:00
return ;
}
switch ( output . data . type ) {
case "html" :
outputText . style . display = "none" ;
outputHtml . style . display = "block" ;
outputFile . style . display = "none" ;
outputHighlighter . style . display = "none" ;
inputHighlighter . style . display = "none" ;
outputText . value = "" ;
outputHtml . innerHTML = output . data . result ;
// Execute script sections
scriptElements = outputHtml . querySelectorAll ( "script" );
for ( let i = 0 ; i < scriptElements . length ; i ++ ) {
try {
eval ( scriptElements [ i ]. innerHTML ); // eslint-disable-line no-eval
} catch ( err ) {
log . error ( err );
}
}
break ;
case "ArrayBuffer" :
outputText . style . display = "block" ;
outputHtml . style . display = "none" ;
outputHighlighter . display = "none" ;
inputHighlighter . display = "none" ;
outputText . value = "" ;
outputHtml . innerHTML = "" ;
2019-05-23 15:29:58 +01:00
length = output . data . result . byteLength ;
this . setFile ( await this . getDishBuffer ( output . data . dish ));
2019-05-08 13:46:29 +01:00
break ;
case "string" :
default :
outputText . style . display = "block" ;
outputHtml . style . display = "none" ;
outputFile . style . display = "none" ;
outputHighlighter . display = "block" ;
inputHighlighter . display = "block" ;
outputText . value = Utils . printable ( output . data . result , true );
outputHtml . innerHTML = "" ;
lines = output . data . result . count ( "\n" ) + 1 ;
length = output . data . result . length ;
break ;
}
2019-05-23 15:29:58 +01:00
this . toggleLoader ( false );
if ( output . data . type === "html" ) {
const dishStr = await this . getDishStr ( output . data . dish );
length = dishStr . length ;
lines = dishStr . count ( "\n" ) + 1 ;
}
2019-05-08 13:46:29 +01:00
this . setOutputInfo ( length , lines , output . data . duration );
this . backgroundMagic ();
2019-04-02 16:58:36 +01:00
}
2019-05-08 13:46:29 +01:00
}. bind ( this ));
2018-05-15 17:36:45 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Shows file details
*
* @param {ArrayBuffer} buf
2018-05-15 17:36:45 +00:00
*/
2019-04-02 16:58:36 +01:00
setFile ( buf ) {
// Display file overlay in output area with details
const fileOverlay = document . getElementById ( "output-file" ),
fileSize = document . getElementById ( "output-file-size" ),
outputText = document . getElementById ( "output-text" ),
fileSlice = buf . slice ( 0 , 4096 );
2018-05-15 17:36:45 +00:00
2019-04-02 16:58:36 +01:00
fileOverlay . style . display = "block" ;
2019-05-08 13:46:29 +01:00
fileSize . textContent = buf . byteLength . toLocaleString () + " bytes" ;
2018-05-15 17:36:45 +00:00
2019-04-02 16:58:36 +01:00
outputText . classList . add ( "blur" );
outputText . value = Utils . printable ( Utils . arrayBufferToStr ( fileSlice ));
2018-05-15 17:36:45 +00:00
}
2019-04-02 16:58:36 +01:00
/**
* Clears output file details
*/
closeFile () {
document . getElementById ( "output-file" ). style . display = "none" ;
document . getElementById ( "output-text" ). classList . remove ( "blur" );
}
2018-05-15 17:36:45 +00:00
2019-05-23 15:29:58 +01:00
/**
* Retrieves the dish as a string, returning the cached version if possible.
*
* @param {Dish} dish
* @returns {string}
*/
async getDishStr ( dish ) {
return await new Promise ( resolve => {
this . manager . worker . getDishAs ( dish , "string" , r => {
resolve ( r . value );
});
});
}
/**
* Retrieves the dish as an ArrayBuffer, returning the cached version if possible.
*
* @param {Dish} dish
* @returns {ArrayBuffer}
*/
async getDishBuffer ( dish ) {
return await new Promise ( resolve => {
this . manager . worker . getDishAs ( dish , "ArrayBuffer" , r => {
resolve ( r . value );
});
});
}
2018-05-15 17:36:45 +00:00
/**
2019-01-16 12:29:34 +00:00
* Save bombe object then remove it from the DOM so that it does not cause performance issues.
2019-01-15 19:03:17 +00:00
*/
saveBombe () {
2019-01-16 12:29:34 +00:00
this . bombeEl = document . getElementById ( "bombe" );
this . bombeEl . parentNode . removeChild ( this . bombeEl );
2019-01-15 19:03:17 +00:00
}
/**
* Shows or hides the output loading screen.
* The animated Bombe SVG, whilst quite aesthetically pleasing, is reasonably CPU
* intensive, so we remove it from the DOM when not in use. We only show it if the
* recipe is taking longer than 200ms. We add it to the DOM just before that so that
* it is ready to fade in without stuttering.
2018-05-15 17:36:45 +00:00
*
2019-05-23 15:29:58 +01:00
* @param {boolean} value - If true, show the loader
2018-05-15 17:36:45 +00:00
*/
toggleLoader ( value ) {
2019-01-15 19:03:17 +00:00
clearTimeout ( this . appendBombeTimeout );
clearTimeout ( this . outputLoaderTimeout );
2018-05-15 17:36:45 +00:00
const outputLoader = document . getElementById ( "output-loader" ),
2019-01-15 19:03:17 +00:00
outputElement = document . getElementById ( "output-text" ),
2019-01-16 12:29:34 +00:00
animation = document . getElementById ( "output-loader-animation" );
2018-05-15 17:36:45 +00:00
if ( value ) {
this . manager . controls . hideStaleIndicator ();
2019-01-15 19:03:17 +00:00
2019-05-01 15:19:01 +01:00
// Don't add the bombe if it's already there!
if ( animation . children . length > 0 ) return ;
2019-01-15 19:03:17 +00:00
// Start a timer to add the Bombe to the DOM just before we make it
// visible so that there is no stuttering
this . appendBombeTimeout = setTimeout ( function () {
2019-01-16 12:29:34 +00:00
animation . appendChild ( this . bombeEl );
2019-01-15 19:03:17 +00:00
}. bind ( this ), 150 );
// Show the loading screen
this . outputLoaderTimeout = setTimeout ( function () {
2018-05-15 17:36:45 +00:00
outputElement . disabled = true ;
outputLoader . style . visibility = "visible" ;
outputLoader . style . opacity = 1 ;
2019-04-25 16:32:48 +01:00
}, 200 );
2018-05-15 17:36:45 +00:00
} else {
2019-01-15 19:03:17 +00:00
// Remove the Bombe from the DOM to save resources
this . outputLoaderTimeout = setTimeout ( function () {
try {
2019-01-16 12:29:34 +00:00
animation . removeChild ( this . bombeEl );
2019-01-15 19:03:17 +00:00
} catch ( err ) {}
}. bind ( this ), 500 );
2018-05-15 17:36:45 +00:00
outputElement . disabled = false ;
outputLoader . style . opacity = 0 ;
outputLoader . style . visibility = "hidden" ;
}
}
2019-04-03 12:00:47 +01:00
/**
* Handler for save click events.
* Saves the current output to a file.
*/
saveClick () {
2019-04-25 16:32:48 +01:00
this . downloadFile ();
2019-04-03 12:00:47 +01:00
}
/**
* Handler for file download events.
*/
async downloadFile () {
2019-05-08 10:10:14 +01:00
let fileName = window . prompt ( "Please enter a filename: " , "download.dat" );
if ( fileName === null ) fileName = "download.dat" ;
2019-05-07 14:20:18 +01:00
const file = new File ([ this . getActive ( true )], fileName );
2019-04-03 12:00:47 +01:00
FileSaver . saveAs ( file , fileName , false );
}
/**
* Handler for save all click event
* Saves all outputs to a single archvie file
*/
saveAllClick () {
2019-05-07 12:00:53 +01:00
const downloadButton = document . getElementById ( "save-all-to-file" );
if ( downloadButton . firstElementChild . innerHTML === "archive" ) {
this . downloadAllFiles ();
} else if ( window . confirm ( "Cancel zipping of outputs?" )) {
this . terminateZipWorker ();
}
2019-04-03 12:00:47 +01:00
}
2019-05-07 09:26:55 +01:00
/**
* Spawns a new ZipWorker and sends it the outputs so that they can
* be zipped for download
*/
2019-05-23 15:29:58 +01:00
async downloadAllFiles () {
return new Promise ( resolve => {
const inputNums = Object . keys ( this . outputs );
for ( let i = 0 ; i < inputNums . length ; i ++ ) {
const iNum = inputNums [ i ];
if ( this . outputs [ iNum ]. status !== "baked" ||
this . outputs [ iNum ]. bakeId !== this . manager . worker . bakeId ) {
if ( window . confirm ( "Not all outputs have been baked yet. Continue downloading outputs?" )) {
break ;
} else {
return ;
}
2019-05-07 09:26:55 +01:00
}
}
2019-05-23 15:29:58 +01:00
let fileName = window . prompt ( "Please enter a filename: " , "download.zip" );
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
if ( fileName === null || fileName === "" ) {
// Don't zip the files if there isn't a filename
this . app . alert ( "No filename was specified." , 3000 );
return ;
}
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
if ( ! fileName . match ( /.zip$/ )) {
fileName += ".zip" ;
}
2019-05-07 12:00:53 +01:00
2019-05-23 15:29:58 +01:00
let fileExt = window . prompt ( "Please enter a file extension for the files, or leave blank to detect automatically." , "" );
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
if ( fileExt === null ) fileExt = "" ;
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
if ( this . zipWorker !== null ) {
this . terminateZipWorker ();
}
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
const downloadButton = document . getElementById ( "save-all-to-file" );
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
downloadButton . classList . add ( "spin" );
downloadButton . title = `Zipping ${ inputNums . length } files...` ;
downloadButton . setAttribute ( "data-original-title" , `Zipping ${ inputNums . length } files...` );
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
downloadButton . firstElementChild . innerHTML = "autorenew" ;
2019-05-07 09:26:55 +01:00
2019-05-23 15:29:58 +01:00
log . debug ( "Creating ZipWorker" );
this . zipWorker = new ZipWorker ();
this . zipWorker . postMessage ({
outputs : this . outputs ,
filename : fileName ,
fileExtension : fileExt
});
this . zipWorker . addEventListener ( "message" , this . handleZipWorkerMessage . bind ( this ));
2019-05-07 09:26:55 +01:00
});
}
/**
* Terminate the ZipWorker
*/
terminateZipWorker () {
if ( this . zipWorker === null ) return ; // Already terminated
log . debug ( "Terminating ZipWorker." );
this . zipWorker . terminate ();
this . zipWorker = null ;
2019-05-07 12:00:53 +01:00
const downloadButton = document . getElementById ( "save-all-to-file" );
downloadButton . classList . remove ( "spin" );
downloadButton . title = "Save all outputs to a zip file" ;
downloadButton . setAttribute ( "data-original-title" , "Save all outputs to a zip file" );
downloadButton . firstElementChild . innerHTML = "archive" ;
2019-05-07 09:26:55 +01:00
}
/**
* Handle messages sent back by the ZipWorker
*/
handleZipWorkerMessage ( e ) {
const r = e . data ;
if ( ! r . hasOwnProperty ( "zippedFile" )) {
log . error ( "No zipped file was sent in the message." );
this . terminateZipWorker ();
return ;
}
if ( ! r . hasOwnProperty ( "filename" )) {
log . error ( "No filename was sent in the message." );
this . terminateZipWorker ();
return ;
}
const file = new File ([ r . zippedFile ], r . filename );
FileSaver . saveAs ( file , r . filename , false );
this . terminateZipWorker ();
}
2018-05-15 17:36:45 +00:00
/**
2019-04-02 16:58:36 +01:00
* Adds a new output tab.
2018-05-15 17:36:45 +00:00
*
2019-04-02 16:58:36 +01:00
* @param {number} inputNum
* @param {boolean} [changeTab=true]
2018-05-15 17:36:45 +00:00
*/
2019-04-02 16:58:36 +01:00
addTab ( inputNum , changeTab = true ) {
const tabsWrapper = document . getElementById ( "output-tabs" );
const numTabs = tabsWrapper . children . length ;
2018-05-15 17:36:45 +00:00
2019-06-06 16:33:35 +01:00
if ( ! this . manager . tabs . getOutputTabItem ( inputNum ) && numTabs < this . maxTabs ) {
2019-04-02 16:58:36 +01:00
// Create a new tab element
2019-06-06 16:33:35 +01:00
const newTab = this . manager . tabs . createOutputTabElement ( inputNum , changeTab );
2019-04-02 16:58:36 +01:00
tabsWrapper . appendChild ( newTab );
2019-05-30 14:32:05 +01:00
} else if ( numTabs === this . maxTabs ) {
// Can't create a new tab
document . getElementById ( "output-tabs" ). lastElementChild . style . boxShadow = "-15px 0px 15px -15px var(--primary-border-colour) inset" ;
2019-04-02 16:58:36 +01:00
}
2018-05-15 17:36:45 +00:00
2019-06-10 12:47:27 +01:00
this . displayTabInfo ( inputNum );
2019-04-02 16:58:36 +01:00
if ( changeTab ) {
2019-04-03 12:00:47 +01:00
this . changeTab ( inputNum , false );
2019-04-02 16:58:36 +01:00
}
2018-05-15 17:36:45 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Changes the active tab
2018-05-15 17:36:45 +00:00
*
2019-04-02 16:58:36 +01:00
* @param {number} inputNum
2019-04-03 12:00:47 +01:00
* @param {boolean} [changeInput = false]
2018-05-15 17:36:45 +00:00
*/
2019-04-03 12:00:47 +01:00
changeTab ( inputNum , changeInput = false ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2019-06-06 16:33:35 +01:00
const currentNum = this . manager . tabs . getActiveOutputTab ();
2019-05-07 14:20:18 +01:00
2019-05-01 14:46:05 +01:00
this . hideMagicButton ();
2019-04-02 16:58:36 +01:00
2019-05-07 14:20:18 +01:00
this . manager . highlighter . removeHighlights ();
getSelection (). removeAllRanges ();
2019-06-06 16:33:35 +01:00
if ( ! this . manager . tabs . changeOutputTab ( inputNum )) {
2019-04-02 16:58:36 +01:00
let direction = "right" ;
if ( currentNum > inputNum ) {
direction = "left" ;
}
const newOutputs = this . getNearbyNums ( inputNum , direction );
2019-05-30 13:28:45 +01:00
const tabsLeft = ( newOutputs [ 0 ] !== this . getSmallestInputNum ());
const tabsRight = ( newOutputs [ newOutputs . length - 1 ] !== this . getLargestInputNum ());
2019-06-06 16:33:35 +01:00
this . manager . tabs . refreshOutputTabs ( newOutputs , inputNum , tabsLeft , tabsRight );
2019-06-10 12:47:27 +01:00
for ( let i = 0 ; i < newOutputs . length ; i ++ ) {
this . displayTabInfo ( newOutputs [ i ]);
}
2019-04-02 16:58:36 +01:00
}
2018-05-15 17:36:45 +00:00
2019-06-03 16:10:05 +01:00
this . app . debounce ( this . set , 50 , "setOutput" , this , [ inputNum ])();
2019-04-03 12:00:47 +01:00
2019-06-06 10:26:16 +01:00
document . getElementById ( "output-html" ). scroll ( 0 , 0 );
document . getElementById ( "output-text" ). scroll ( 0 , 0 );
2019-04-03 12:00:47 +01:00
if ( changeInput ) {
this . manager . input . changeTab ( inputNum , false );
}
2019-04-02 16:58:36 +01:00
}
2018-06-03 17:33:13 +01:00
/**
2019-04-02 16:58:36 +01:00
* Handler for changing tabs event
*
* @param {event} mouseEvent
2018-06-03 17:33:13 +01:00
*/
2019-04-02 16:58:36 +01:00
changeTabClick ( mouseEvent ) {
2019-04-04 13:13:21 +01:00
if ( ! mouseEvent . target ) return ;
const tabNum = mouseEvent . target . parentElement . getAttribute ( "inputNum" );
2019-04-02 16:58:36 +01:00
if ( tabNum ) {
2019-04-03 12:00:47 +01:00
this . changeTab ( parseInt ( tabNum , 10 ), this . app . options . syncTabs );
}
}
2019-06-03 16:10:05 +01:00
/**
* Handler for scrolling on the output tabs area
*
* @param {event} wheelEvent
*/
scrollTab ( wheelEvent ) {
wheelEvent . preventDefault ();
if ( wheelEvent . deltaY > 0 ) {
this . changeTabLeft ();
} else if ( wheelEvent . deltaY < 0 ) {
this . changeTabRight ();
}
2019-06-04 11:42:27 +01:00
}
/**
* Handler for mouse down on the next tab button
*/
nextTabClick () {
this . mousedown = true ;
this . changeTabRight ();
const time = 200 ;
const func = function ( time ) {
if ( this . mousedown ) {
this . changeTabRight ();
const newTime = ( time > 50 ) ? time = time - 10 : 50 ;
setTimeout ( func . bind ( this , [ newTime ]), newTime );
}
};
2019-06-06 16:33:35 +01:00
this . tabTimeout = setTimeout ( func . bind ( this , [ time ]), time );
2019-06-04 11:42:27 +01:00
}
2019-06-03 16:10:05 +01:00
2019-06-04 11:42:27 +01:00
/**
* Handler for mouse down on the previous tab button
*/
previousTabClick () {
this . mousedown = true ;
this . changeTabLeft ();
const time = 200 ;
const func = function ( time ) {
if ( this . mousedown ) {
this . changeTabLeft ();
const newTime = ( time > 50 ) ? time = time - 10 : 50 ;
setTimeout ( func . bind ( this , [ newTime ]), newTime );
}
};
2019-06-06 16:33:35 +01:00
this . tabTimeout = setTimeout ( func . bind ( this , [ time ]), time );
2019-06-04 11:42:27 +01:00
}
/**
* Handler for mouse up event on the tab buttons
*/
tabMouseUp () {
this . mousedown = false ;
2019-06-06 16:33:35 +01:00
clearTimeout ( this . tabTimeout );
this . tabTimeout = null ;
2019-06-03 16:10:05 +01:00
}
2019-04-03 12:00:47 +01:00
/**
* Handler for changing to the left tab
*/
changeTabLeft () {
2019-06-06 16:33:35 +01:00
const currentTab = this . manager . tabs . getActiveOutputTab ();
2019-04-25 16:32:48 +01:00
this . changeTab ( this . getPreviousInputNum ( currentTab ), this . app . options . syncTabs );
2019-04-03 12:00:47 +01:00
}
/**
* Handler for changing to the right tab
*/
changeTabRight () {
2019-06-06 16:33:35 +01:00
const currentTab = this . manager . tabs . getActiveOutputTab ();
2019-04-03 12:00:47 +01:00
this . changeTab ( this . getNextInputNum ( currentTab ), this . app . options . syncTabs );
}
/**
* Handler for go to tab button clicked
*/
goToTab () {
2019-06-04 09:39:47 +01:00
const min = this . getSmallestInputNum (),
2019-06-04 09:41:47 +01:00
max = this . getLargestInputNum ();
2019-06-06 16:33:35 +01:00
let tabNum = window . prompt ( `Enter tab number ( ${ min } - ${ max } ):` , this . manager . tabs . getActiveOutputTab (). toString ());
2019-06-04 09:41:47 +01:00
if ( tabNum === null ) return ;
tabNum = parseInt ( tabNum , 10 );
2019-05-08 14:47:05 +01:00
if ( this . outputExists ( tabNum )) {
2019-04-03 12:00:47 +01:00
this . changeTab ( tabNum , this . app . options . syncTabs );
2018-06-03 17:33:13 +01:00
}
}
/**
2019-04-02 16:58:36 +01:00
* Generates a list of the nearby inputNums
2019-04-04 10:15:13 +01:00
* @param inputNum
* @param direction
2018-06-03 17:33:13 +01:00
*/
2019-04-02 16:58:36 +01:00
getNearbyNums ( inputNum , direction ) {
const nums = [];
2019-04-04 10:15:13 +01:00
for ( let i = 0 ; i < this . maxTabs ; i ++ ) {
let newNum ;
2019-06-07 13:52:04 +01:00
if ( i === 0 && this . outputs [ inputNum ] !== undefined ) {
2019-04-04 10:15:13 +01:00
newNum = inputNum ;
} else {
switch ( direction ) {
case "left" :
newNum = this . getNextInputNum ( nums [ i - 1 ]);
if ( newNum === nums [ i - 1 ]) {
direction = "right" ;
2019-05-14 16:13:36 +01:00
newNum = this . getPreviousInputNum ( nums [ 0 ]);
2019-04-04 10:15:13 +01:00
}
2019-04-03 16:05:10 +01:00
break ;
2019-04-04 10:15:13 +01:00
case "right" :
newNum = this . getPreviousInputNum ( nums [ i - 1 ]);
if ( newNum === nums [ i - 1 ]) {
direction = "left" ;
2019-05-14 16:13:36 +01:00
newNum = this . getNextInputNum ( nums [ 0 ]);
2019-04-04 10:15:13 +01:00
}
2019-04-02 16:58:36 +01:00
}
}
2019-04-04 10:15:13 +01:00
if ( ! nums . includes ( newNum ) && ( newNum > 0 )) {
nums . push ( newNum );
}
2019-04-02 16:58:36 +01:00
}
nums . sort ( function ( a , b ) {
return a - b ;
});
return nums ;
2018-07-27 13:37:38 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Gets the largest inputNum
2018-07-27 13:37:38 +00:00
*
2019-04-02 16:58:36 +01:00
* @returns {number}
2018-07-27 13:37:38 +00:00
*/
2019-04-02 16:58:36 +01:00
getLargestInputNum () {
2019-04-25 16:32:48 +01:00
const inputNums = Object . keys ( this . outputs );
2019-05-29 13:25:12 +01:00
if ( inputNums . length === 0 ) return - 1 ;
return Math . max (... inputNums );
2018-07-27 13:37:38 +00:00
}
2019-04-03 12:00:47 +01:00
/**
* Gets the smallest inputNum
*
* @returns {number}
*/
getSmallestInputNum () {
2019-04-25 16:32:48 +01:00
const inputNums = Object . keys ( this . outputs );
2019-05-29 13:25:12 +01:00
if ( inputNums . length === 0 ) return - 1 ;
return Math . min (... inputNums );
2019-04-03 12:00:47 +01:00
}
2018-07-27 13:37:38 +00:00
/**
2019-04-02 16:58:36 +01:00
* Gets the previous inputNum
2018-07-27 13:37:38 +00:00
*
2019-04-02 16:58:36 +01:00
* @param {number} inputNum - The current input number
* @returns {number}
2018-07-27 13:37:38 +00:00
*/
2019-04-02 16:58:36 +01:00
getPreviousInputNum ( inputNum ) {
2019-04-25 16:32:48 +01:00
const inputNums = Object . keys ( this . outputs );
2019-05-29 13:25:12 +01:00
if ( inputNums . length === 0 ) return - 1 ;
let num = Math . min (... inputNums );
2019-04-25 16:32:48 +01:00
for ( let i = 0 ; i < inputNums . length ; i ++ ) {
const iNum = parseInt ( inputNums [ i ], 10 );
if ( iNum < inputNum ) {
if ( iNum > num ) {
num = iNum ;
2019-04-02 16:58:36 +01:00
}
}
}
return num ;
2018-07-27 13:37:38 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Gets the next inputNum
*
* @param {number} inputNum - The current input number
* @returns {number}
2018-07-27 13:37:38 +00:00
*/
2019-04-02 16:58:36 +01:00
getNextInputNum ( inputNum ) {
2019-04-25 16:32:48 +01:00
const inputNums = Object . keys ( this . outputs );
2019-05-29 13:25:12 +01:00
if ( inputNums . length === 0 ) return - 1 ;
let num = Math . max (... inputNums );
2019-04-25 16:32:48 +01:00
for ( let i = 0 ; i < inputNums . length ; i ++ ) {
const iNum = parseInt ( inputNums [ i ], 10 );
if ( iNum > inputNum ) {
if ( iNum < num ) {
num = iNum ;
2019-04-02 16:58:36 +01:00
}
}
}
return num ;
2018-06-03 17:33:13 +01:00
}
2019-03-09 06:25:27 +00:00
/**
2019-04-02 16:58:36 +01:00
* Removes a tab and it's corresponding output
2019-03-09 06:25:27 +00:00
*
2019-04-02 16:58:36 +01:00
* @param {number} inputNum
2019-03-09 06:25:27 +00:00
*/
2019-04-02 16:58:36 +01:00
removeTab ( inputNum ) {
2019-05-08 14:47:05 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2019-03-09 06:25:27 +00:00
2019-06-06 16:33:35 +01:00
const tabElement = this . manager . tabs . getOutputTabItem ( inputNum );
2019-03-09 06:25:27 +00:00
2019-04-02 16:58:36 +01:00
this . removeOutput ( inputNum );
if ( tabElement !== null ) {
2019-06-07 13:52:04 +01:00
this . refreshTabs ( this . getPreviousInputNum ( inputNum ), "left" );
2019-04-03 12:00:47 +01:00
}
}
/**
* Redraw the entire tab bar to remove any outdated tabs
* @param {number} activeTab
2019-06-07 13:52:04 +01:00
* @param {string} direction - Either "left" or "right"
2019-04-03 12:00:47 +01:00
*/
2019-06-07 13:52:04 +01:00
refreshTabs ( activeTab , direction ) {
2019-06-06 16:33:35 +01:00
const newNums = this . getNearbyNums ( activeTab , direction ),
tabsLeft = ( newNums [ 0 ] !== this . getSmallestInputNum ()),
2019-06-07 13:52:04 +01:00
tabsRight = ( newNums [ newNums . length - 1 ] !== this . getLargestInputNum ());
2019-06-06 16:33:35 +01:00
this . manager . tabs . refreshOutputTabs ( newNums , activeTab , tabsLeft , tabsRight );
2019-06-10 12:47:27 +01:00
for ( let i = 0 ; i < newNums . length ; i ++ ) {
this . displayTabInfo ( newNums [ i ]);
}
2019-03-27 13:48:54 +00:00
}
/**
2019-04-02 16:58:36 +01:00
* Display output information in the tab header
2019-03-27 13:48:54 +00:00
*
2019-04-02 16:58:36 +01:00
* @param {number} inputNum
2019-03-27 13:48:54 +00:00
*/
2019-06-10 12:47:27 +01:00
async displayTabInfo ( inputNum ) {
2019-06-10 15:39:21 +01:00
if ( ! this . outputExists ( inputNum )) return ;
2019-06-10 13:03:07 +01:00
const dish = this . getOutputDish ( inputNum );
2019-06-10 12:47:27 +01:00
let tabStr = "" ;
2019-03-27 13:48:54 +00:00
2019-06-10 12:47:27 +01:00
if ( dish !== null ) {
tabStr = await this . getDishStr ( this . getOutputDish ( inputNum ));
tabStr = tabStr . replace ( /[\n\r]/g , "" );
}
tabStr = tabStr . slice ( 0 , 200 );
2019-06-10 13:03:07 +01:00
this . manager . tabs . updateOutputTabHeader ( inputNum , tabStr );
2019-06-11 10:01:40 +01:00
if ( this . manager . worker . recipeConfig !== undefined ) {
this . manager . tabs . updateOutputTabProgress ( inputNum , this . outputs [ inputNum ]. progress , this . manager . worker . recipeConfig . length );
}
2019-03-27 13:48:54 +00:00
2019-06-10 15:39:21 +01:00
const tabItem = this . manager . tabs . getOutputTabItem ( inputNum );
if ( tabItem ) {
if ( this . outputs [ inputNum ]. status === "error" ) {
tabItem . style . color = "#FF0000" ;
} else {
tabItem . style . color = "" ;
}
}
2019-03-27 09:05:10 +00:00
}
2019-04-25 16:32:48 +01:00
/**
* Displays information about the output.
*
* @param {number} length - The length of the current output string
* @param {number} lines - The number of the lines in the current output string
* @param {number} duration - The length of time (ms) it took to generate the output
*/
setOutputInfo ( length , lines , duration ) {
if ( ! length ) return ;
let width = length . toString (). length ;
width = width < 4 ? 4 : width ;
const lengthStr = length . toString (). padStart ( width , " " ). replace ( / /g , " " );
const timeStr = ( duration . toString () + "ms" ). padStart ( width , " " ). replace ( / /g , " " );
let msg = "time: " + timeStr + "<br>length: " + lengthStr ;
if ( typeof lines === "number" ) {
const linesStr = lines . toString (). padStart ( width , " " ). replace ( / /g , " " );
msg += "<br>lines: " + linesStr ;
}
document . getElementById ( "output-info" ). innerHTML = msg ;
document . getElementById ( "input-selection-info" ). innerHTML = "" ;
document . getElementById ( "output-selection-info" ). innerHTML = "" ;
}
/**
* Triggers the BackgroundWorker to attempt Magic on the current output.
*/
2019-05-23 15:29:58 +01:00
async backgroundMagic () {
2019-04-25 16:32:48 +01:00
this . hideMagicButton ();
2019-05-07 14:20:18 +01:00
if ( ! this . app . options . autoMagic || ! this . getActive ( true )) return ;
2019-06-06 16:33:35 +01:00
const dish = this . outputs [ this . manager . tabs . getActiveOutputTab ()]. data . dish ;
2019-05-23 15:29:58 +01:00
const buffer = await this . getDishBuffer ( dish );
const sample = buffer . slice ( 0 , 1000 ) || "" ;
2019-05-07 14:20:18 +01:00
2019-05-01 14:46:05 +01:00
if ( sample . length || sample . byteLength ) {
2019-04-25 16:32:48 +01:00
this . manager . background . magic ( sample );
}
}
/**
* Handles the results of a background Magic call.
*
* @param {Object[]} options
*/
backgroundMagicResult ( options ) {
if ( ! options . length ||
! options [ 0 ]. recipe . length )
return ;
const currentRecipeConfig = this . app . getRecipeConfig ();
const newRecipeConfig = currentRecipeConfig . concat ( options [ 0 ]. recipe );
const opSequence = options [ 0 ]. recipe . map ( o => o . op ). join ( ", " );
this . showMagicButton ( opSequence , options [ 0 ]. data , newRecipeConfig );
}
/**
* Handler for Magic click events.
*
* Loads the Magic recipe.
*
* @fires Manager#statechange
*/
magicClick () {
const magicButton = document . getElementById ( "magic" );
this . app . setRecipeConfig ( JSON . parse ( magicButton . getAttribute ( "data-recipe" )));
window . dispatchEvent ( this . manager . statechange );
this . hideMagicButton ();
}
/**
* Displays the Magic button with a title and adds a link to a complete recipe.
*
* @param {string} opSequence
* @param {string} result
* @param {Object[]} recipeConfig
*/
showMagicButton ( opSequence , result , recipeConfig ) {
const magicButton = document . getElementById ( "magic" );
magicButton . setAttribute ( "data-original-title" , `<i> ${ opSequence } </i> will produce <span class="data-text">" ${ Utils . escapeHtml ( Utils . truncate ( result ), 30 ) } "</span>` );
magicButton . setAttribute ( "data-recipe" , JSON . stringify ( recipeConfig ), null , "" );
magicButton . classList . remove ( "hidden" );
}
/**
* Hides the Magic button and resets its values.
*/
hideMagicButton () {
const magicButton = document . getElementById ( "magic" );
magicButton . classList . add ( "hidden" );
magicButton . setAttribute ( "data-recipe" , "" );
magicButton . setAttribute ( "data-original-title" , "Magic!" );
}
/**
* Handler for file slice display events.
*/
2019-05-23 15:29:58 +01:00
async displayFileSlice () {
document . querySelector ( "#output-loader .loading-msg" ). textContent = "Loading file slice..." ;
this . toggleLoader ( true );
2019-05-08 10:10:14 +01:00
const outputText = document . getElementById ( "output-text" ),
outputHtml = document . getElementById ( "output-html" ),
outputFile = document . getElementById ( "output-file" ),
outputHighlighter = document . getElementById ( "output-highlighter" ),
inputHighlighter = document . getElementById ( "input-highlighter" ),
2019-04-25 16:32:48 +01:00
showFileOverlay = document . getElementById ( "show-file-overlay" ),
sliceFromEl = document . getElementById ( "output-file-slice-from" ),
sliceToEl = document . getElementById ( "output-file-slice-to" ),
sliceFrom = parseInt ( sliceFromEl . value , 10 ),
sliceTo = parseInt ( sliceToEl . value , 10 ),
2019-06-06 16:33:35 +01:00
output = this . outputs [ this . manager . tabs . getActiveOutputTab ()]. data ;
2019-05-28 15:01:49 +01:00
let str ;
if ( output . type === "ArrayBuffer" ) {
str = Utils . arrayBufferToStr ( output . result . slice ( sliceFrom , sliceTo ));
} else {
str = Utils . arrayBufferToStr ( await this . getDishBuffer ( output . dish ). slice ( sliceFrom , sliceTo ));
}
2019-04-25 16:32:48 +01:00
2019-05-08 10:10:14 +01:00
outputText . classList . remove ( "blur" );
2019-04-25 16:32:48 +01:00
showFileOverlay . style . display = "block" ;
2019-05-28 15:01:49 +01:00
outputText . value = Utils . printable ( str , true );
2019-05-08 10:10:14 +01:00
outputText . style . display = "block" ;
outputHtml . style . display = "none" ;
outputFile . style . display = "none" ;
outputHighlighter . display = "block" ;
inputHighlighter . display = "block" ;
2019-04-25 16:32:48 +01:00
2019-05-23 15:29:58 +01:00
this . toggleLoader ( false );
2019-04-25 16:32:48 +01:00
}
2019-05-08 10:10:14 +01:00
/**
* Handler for show file overlay events
*
* @param {Event} e
*/
showFileOverlayClick ( e ) {
const showFileOverlay = e . target ;
document . getElementById ( "output-text" ). classList . add ( "blur" );
showFileOverlay . style . display = "none" ;
2019-06-06 16:33:35 +01:00
this . set ( this . manager . tabs . getActiveOutputTab ());
2019-05-08 10:10:14 +01:00
}
/**
* Handler for extract file events.
*
* @param {Event} e
*/
async extractFileClick ( e ) {
e . preventDefault ();
e . stopPropagation ();
const el = e . target . nodeName === "I" ? e . target . parentNode : e . target ;
const blobURL = el . getAttribute ( "blob-url" );
const fileName = el . getAttribute ( "file-name" );
const blob = await fetch ( blobURL ). then ( r => r . blob ());
this . manager . input . loadUIFiles ([ new File ([ blob ], fileName , { type : blob . type })]);
}
2019-04-25 16:32:48 +01:00
/**
* Handler for copy click events.
* Copies the output to the clipboard
*/
copyClick () {
2019-05-07 14:20:18 +01:00
let output = this . getActive ( true );
2019-05-07 09:26:55 +01:00
if ( typeof output !== "string" ) {
output = Utils . arrayBufferToStr ( output );
}
2019-04-25 16:32:48 +01:00
// Create invisible textarea to populate with the raw dish string (not the printable version that
// contains dots instead of the actual bytes)
const textarea = document . createElement ( "textarea" );
textarea . style . position = "fixed" ;
textarea . style . top = 0 ;
textarea . style . left = 0 ;
textarea . style . width = 0 ;
textarea . style . height = 0 ;
textarea . style . border = "none" ;
textarea . value = output ;
document . body . appendChild ( textarea );
let success = false ;
try {
textarea . select ();
success = output && document . execCommand ( "copy" );
} catch ( err ) {
success = false ;
}
if ( success ) {
this . app . alert ( "Copied raw output successfully." , 2000 );
} else {
this . app . alert ( "Sorry, the output could not be copied." , 3000 );
}
// Clean up
document . body . removeChild ( textarea );
}
2019-05-07 14:20:18 +01:00
/**
* Returns true if the output contains carriage returns
*
* @returns {boolean}
*/
containsCR () {
2019-05-15 09:37:07 +01:00
return this . getActive ( false ). indexOf ( "\r" ) >= 0 ;
2019-05-07 14:20:18 +01:00
}
2019-05-07 15:34:36 +01:00
/**
* Handler for switch click events.
* Moves the current output into the input textarea.
*/
switchClick () {
const active = this . getActive ( true );
2019-06-06 16:33:35 +01:00
const transferable = ( typeof active === "string" ? undefined : [ active ]);
2019-05-07 15:34:36 +01:00
if ( typeof active === "string" ) {
this . manager . input . inputWorker . postMessage ({
action : "inputSwitch" ,
data : {
2019-06-06 16:33:35 +01:00
inputNum : this . manager . tabs . getActiveInputTab (),
2019-05-07 15:34:36 +01:00
outputData : active
}
2019-06-06 16:33:35 +01:00
}, transferable );
2019-05-07 15:34:36 +01:00
}
}
/**
* Handler for when the inputWorker has switched the inputs.
* Stores the old input
*
* @param {object} switchData
* @param {number} switchData.inputNum
* @param {string | object} switchData.data
* @param {ArrayBuffer} switchData.data.fileBuffer
* @param {number} switchData.data.size
* @param {string} switchData.data.type
* @param {string} switchData.data.name
*/
inputSwitch ( switchData ) {
this . switchOrigData = switchData ;
document . getElementById ( "undo-switch" ). disabled = false ;
}
/**
* Handler for undo switch click events.
* Removes the output from the input and replaces the input that was removed.
*/
undoSwitchClick () {
this . manager . input . updateInputObj ( this . switchOrigData . inputNum , this . switchOrigData . data );
const undoSwitch = document . getElementById ( "undo-switch" );
undoSwitch . disabled = true ;
$ ( undoSwitch ). tooltip ( "hide" );
this . manager . input . inputWorker . postMessage ({
action : "setInput" ,
data : {
inputNum : this . switchOrigData . inputNum ,
silent : false
}
});
}
2019-05-07 15:36:42 +01:00
/**
* Handler for maximise output click events.
* Resizes the output frame to be as large as possible, or restores it to its original size.
*/
maximiseOutputClick ( e ) {
const el = e . target . id === "maximise-output" ? e . target : e . target . parentNode ;
if ( el . getAttribute ( "data-original-title" ). indexOf ( "Maximise" ) === 0 ) {
this . app . initialiseSplitter ( true );
this . app . columnSplitter . collapse ( 0 );
this . app . columnSplitter . collapse ( 1 );
this . app . ioSplitter . collapse ( 0 );
$ ( el ). attr ( "data-original-title" , "Restore output pane" );
el . querySelector ( "i" ). innerHTML = "fullscreen_exit" ;
} else {
$ ( el ). attr ( "data-original-title" , "Maximise output pane" );
el . querySelector ( "i" ). innerHTML = "fullscreen" ;
this . app . initialiseSplitter ( false );
this . app . resetLayout ();
}
}
2019-05-15 09:37:07 +01:00
/**
* Handler for find tab button clicked
*/
findTab () {
this . filterTabSearch ();
$ ( "#output-tab-modal" ). modal ();
}
/**
* Searches the outputs using the filter settings and displays the results
*/
filterTabSearch () {
const showPending = document . getElementById ( "output-show-pending" ). checked ,
showBaking = document . getElementById ( "output-show-baking" ). checked ,
showBaked = document . getElementById ( "output-show-baked" ). checked ,
showStale = document . getElementById ( "output-show-stale" ). checked ,
showErrored = document . getElementById ( "output-show-errored" ). checked ,
contentFilter = document . getElementById ( "output-content-filter" ). value ,
resultsList = document . getElementById ( "output-search-results" ),
numResults = parseInt ( document . getElementById ( "output-num-results" ). value , 10 ),
inputNums = Object . keys ( this . outputs ),
results = [];
2019-05-30 13:28:45 +01:00
let contentFilterExp ;
try {
contentFilterExp = new RegExp ( contentFilter , "i" );
} catch ( error ) {
this . app . handleError ( error );
return ;
}
2019-05-15 09:37:07 +01:00
// Search through the outputs for matching output results
for ( let i = 0 ; i < inputNums . length ; i ++ ) {
const iNum = inputNums [ i ],
output = this . outputs [ iNum ];
if ( output . status === "pending" && showPending ||
output . status === "baking" && showBaking ||
2019-05-15 16:24:49 +01:00
output . status === "error" && showErrored ||
2019-05-15 09:37:07 +01:00
output . status === "stale" && showStale ||
output . status === "inactive" && showStale ) {
const outDisplay = {
"pending" : "Not baked yet" ,
"baking" : "Baking" ,
2019-05-15 16:24:49 +01:00
"error" : output . error || "Errored" ,
2019-05-15 09:37:07 +01:00
"stale" : "Stale (output is out of date)" ,
"inactive" : "Not baked yet"
};
results . push ({
inputNum : iNum ,
textDisplay : outDisplay [ output . status ]
});
2019-05-28 15:01:49 +01:00
} else if ( output . status === "baked" && showBaked && output . progress === false ) {
let data = this . getOutput ( iNum , false ). slice ( 0 , 4096 );
if ( typeof data !== "string" ) {
data = Utils . arrayBufferToStr ( data );
}
data = data . replace ( /[\r\n]/g , "" );
2019-05-30 13:28:45 +01:00
if ( contentFilterExp . test ( data )) {
2019-05-28 15:01:49 +01:00
results . push ({
inputNum : iNum ,
textDisplay : data . slice ( 0 , 100 )
});
}
} else if ( output . progress !== false && showErrored ) {
2019-05-15 09:37:07 +01:00
let data = this . getOutput ( iNum , false ). slice ( 0 , 4096 );
if ( typeof data !== "string" ) {
data = Utils . arrayBufferToStr ( data );
}
data = data . replace ( /[\r\n]/g , "" );
2019-05-30 13:28:45 +01:00
if ( contentFilterExp . test ( data )) {
2019-05-15 09:37:07 +01:00
results . push ({
inputNum : iNum ,
textDisplay : data . slice ( 0 , 100 )
});
}
}
if ( results . length >= numResults ) {
break ;
}
}
for ( let i = resultsList . children . length - 1 ; i >= 0 ; i -- ) {
resultsList . children . item ( i ). remove ();
}
for ( let i = 0 ; i < results . length ; i ++ ) {
const newListItem = document . createElement ( "li" );
newListItem . classList . add ( "output-filter-result" );
newListItem . setAttribute ( "inputNum" , results [ i ]. inputNum );
newListItem . innerText = ` ${ results [ i ]. inputNum } : ${ results [ i ]. textDisplay } ` ;
resultsList . appendChild ( newListItem );
}
}
2019-05-15 16:03:18 +01:00
/**
* Handler for clicking on a filter result.
* Changes to the clicked output
*
* @param {event} e
*/
filterItemClick ( e ) {
if ( ! e . target ) return ;
const inputNum = parseInt ( e . target . getAttribute ( "inputNum" ), 10 );
if ( inputNum <= 0 ) return ;
$ ( "#output-tab-modal" ). modal ( "hide" );
this . changeTab ( inputNum , this . app . options . syncTabs );
}
2018-05-15 17:36:45 +00:00
}
export default OutputWaiter ;