2018-05-15 19:36:45 +02:00
/ * *
* @ author n1474335 [ n1474335 @ gmail . com ]
2019-04-02 17:58:36 +02:00
* @ author j433866 [ j433866 @ gmail . com ]
2018-05-15 19:36:45 +02:00
* @ copyright Crown Copyright 2016
* @ license Apache - 2.0
* /
import Utils from "../core/Utils" ;
import FileSaver from "file-saver" ;
2019-05-07 10:26:55 +02:00
import ZipWorker from "worker-loader?inline&fallback=false!./ZipWorker" ;
2019-04-03 13:00:47 +02:00
2018-05-15 19:36:45 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Waiter to handle events related to the output
* /
2018-05-15 19:36:45 +02:00
class OutputWaiter {
/ * *
* OutputWaiter constructor .
*
* @ param { App } app - The main view object for CyberChef .
2019-04-02 17:58:36 +02:00
* @ param { Manager } manager - The CyberChef event manager
2018-05-15 19:36:45 +02:00
* /
constructor ( app , manager ) {
this . app = app ;
this . manager = manager ;
2019-04-25 17:32:48 +02:00
this . outputs = { } ;
2019-04-26 16:15:44 +02:00
this . activeTab = - 1 ;
2019-04-04 11:15:13 +02:00
2019-05-07 10:26:55 +02:00
this . zipWorker = null ;
2019-04-02 17:58:36 +02:00
this . maxTabs = 4 ; // Calculate this
2018-05-15 19:36:45 +02:00
}
2019-04-04 11:15:13 +02:00
/ * *
* Calculates the maximum number of tabs to display
* /
calcMaxTabs ( ) {
const numTabs = Math . floor ( ( document . getElementById ( "IO" ) . offsetWidth - 75 ) / 120 ) ;
2019-05-29 14:25:12 +02:00
if ( numTabs !== this . maxTabs ) {
this . maxTabs = numTabs ;
this . refreshTabs ( this . getActiveTab ( ) ) ;
}
2019-04-04 11:15:13 +02:00
}
2018-05-15 19:36:45 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Gets the output for the specified input number
2018-05-15 19:36:45 +02:00
*
2019-05-14 17:13:36 +02: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 13:00:47 +02:00
* @ returns { string | ArrayBuffer }
2018-05-15 19:36:45 +02:00
* /
2019-05-07 15:20:18 +02:00
getOutput ( inputNum , raw = true ) {
2019-04-25 17:32:48 +02:00
if ( this . outputs [ inputNum ] === undefined || this . outputs [ inputNum ] === null ) return - 1 ;
2018-05-15 19:36:45 +02:00
2019-04-25 17:32:48 +02:00
if ( this . outputs [ inputNum ] . data === null ) return "" ;
2018-05-15 19:36:45 +02:00
2019-05-07 15:20:18 +02: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 14:46:29 +02:00
} else if ( typeof data !== "object" && typeof data !== "string" ) {
data = String ( data ) ;
2019-05-07 15:20:18 +02:00
}
return data ;
} else if ( typeof this . outputs [ inputNum ] . data . result === "string" ) {
2019-05-03 12:49:14 +02:00
return this . outputs [ inputNum ] . data . result ;
2019-04-25 17:32:48 +02:00
} else {
2019-05-03 12:49:14 +02:00
return this . outputs [ inputNum ] . data . result || "" ;
2019-03-27 10:05:10 +01:00
}
}
2019-05-08 15:47:05 +02:00
/ * *
* Checks if an output exists in the output dictionary
*
2019-05-14 17:13:36 +02:00
* @ param { number } inputNum - The number of the output we ' re looking for
2019-05-08 15:47:05 +02:00
* @ returns { boolean }
* /
outputExists ( inputNum ) {
if ( this . outputs [ inputNum ] === undefined ||
this . outputs [ inputNum ] === null ) {
return false ;
}
return true ;
}
2018-05-15 19:36:45 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Gets the output string or FileBuffer for the active input
2018-05-15 19:36:45 +02:00
*
2019-05-14 17:13:36 +02:00
* @ param { boolean } [ raw = true ] - If true , returns the raw data instead of the presented result .
2019-04-02 17:58:36 +02:00
* @ returns { string | ArrayBuffer }
2018-05-15 19:36:45 +02:00
* /
2019-05-07 15:20:18 +02:00
getActive ( raw = true ) {
return this . getOutput ( this . getActiveTab ( ) , raw ) ;
2018-05-15 19:36:45 +02:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Adds a new output to the output array .
* Creates a new tab if we have less than maxtabs tabs open
2018-05-15 19:36:45 +02:00
*
2019-05-14 17:13:36 +02:00
* @ param { number } inputNum - The inputNum of the new output
* @ param { boolean } [ changeTab = true ] - If true , change to the new output
2018-05-15 19:36:45 +02:00
* /
2019-04-02 17:58:36 +02:00
addOutput ( inputNum , changeTab = true ) {
2019-05-08 15:47:05 +02:00
// Remove the output (will only get removed if it already exists)
this . removeOutput ( inputNum ) ;
2019-04-02 17:58:36 +02:00
const newOutput = {
data : null ,
inputNum : inputNum ,
2019-05-01 16:19:01 +02:00
statusMessage : ` Input ${ inputNum } has not been baked yet. ` ,
2019-04-02 17:58:36 +02:00
error : null ,
2019-05-07 10:26:55 +02:00
status : "inactive" ,
2019-05-16 11:42:07 +02:00
bakeId : - 1 ,
2019-05-28 12:59:57 +02:00
progress : false
2019-04-02 17:58:36 +02:00
} ;
2019-04-25 17:32:48 +02:00
this . outputs [ inputNum ] = newOutput ;
2019-04-02 17:58:36 +02:00
this . addTab ( inputNum , changeTab ) ;
2018-05-15 19:36:45 +02:00
}
/ * *
2019-04-02 17:58:36 +02: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 12:59:57 +02:00
* @ param { ArrayBuffer | String } data
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum
2019-05-10 14:47:48 +02:00
* @ param { boolean } set
2018-05-15 19:36:45 +02:00
* /
2019-05-10 14:47:48 +02:00
updateOutputValue ( data , inputNum , set = true ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) {
2019-04-25 17:32:48 +02:00
this . addOutput ( inputNum ) ;
2019-04-02 17:58:36 +02:00
}
2018-05-15 19:36:45 +02:00
2019-04-25 17:32:48 +02:00
this . outputs [ inputNum ] . data = data ;
2018-05-15 19:36:45 +02:00
2019-05-10 14:47:48 +02:00
if ( set ) this . set ( inputNum ) ;
2018-05-15 19:36:45 +02:00
}
/ * *
2019-04-02 17:58:36 +02: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 14:47:48 +02:00
* @ param { boolean } [ set = true ]
2018-05-15 19:36:45 +02:00
* /
2019-05-10 14:47:48 +02:00
updateOutputMessage ( statusMessage , inputNum , set = true ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2019-04-25 17:32:48 +02:00
this . outputs [ inputNum ] . statusMessage = statusMessage ;
2019-05-10 14:47:48 +02:00
if ( set ) this . set ( inputNum ) ;
2018-05-15 19:36:45 +02:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Updates the error value for the output in the output array .
2019-05-08 15:47:05 +02:00
* If this is the active output tab , calls app . handleError .
2019-04-02 17:58:36 +02:00
* Otherwise , the error will be handled when the output is switched to
2018-05-15 19:36:45 +02:00
*
2019-04-02 17:58:36 +02:00
* @ param { Error } error
* @ param { number } inputNum
2019-05-01 18:08:36 +02:00
* @ param { number } [ progress = 0 ]
2018-05-15 19:36:45 +02:00
* /
2019-05-01 18:08:36 +02:00
updateOutputError ( error , inputNum , progress = 0 ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2018-05-15 19:36:45 +02:00
2019-05-10 14:47:48 +02:00
const errorString = error . displayStr || error . toString ( ) ;
this . outputs [ inputNum ] . error = errorString ;
2019-05-01 18:08:36 +02:00
this . outputs [ inputNum ] . progress = progress ;
this . updateOutputStatus ( "error" , inputNum ) ;
2019-04-02 17:58:36 +02:00
}
2018-05-15 19:36:45 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Updates the status value for the output in the output array
2018-05-15 19:36:45 +02:00
*
2019-04-02 17:58:36 +02:00
* @ param { string } status
* @ param { number } inputNum
2018-05-15 19:36:45 +02:00
* /
2019-04-02 17:58:36 +02:00
updateOutputStatus ( status , inputNum ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2019-04-25 17:32:48 +02:00
this . outputs [ inputNum ] . status = status ;
2018-05-15 19:36:45 +02:00
2019-05-01 18:08:36 +02:00
if ( status !== "error" ) {
delete this . outputs [ inputNum ] . error ;
}
2019-04-02 17:58:36 +02:00
this . set ( inputNum ) ;
2018-05-15 19:36:45 +02:00
}
2019-05-07 10:26:55 +02: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 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2019-05-07 10:26:55 +02:00
this . outputs [ inputNum ] . bakeId = bakeId ;
}
2019-05-08 12:57:22 +02:00
/ * *
* Updates the stored progress value for the output in the output array
*
* @ param { number } progress
* @ param { number } inputNum
* /
updateOutputProgress ( progress , inputNum ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2019-05-08 12:57:22 +02:00
this . outputs [ inputNum ] . progress = progress ;
}
2018-05-15 19:36:45 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Removes an output from the output array .
*
* @ param { number } inputNum
2018-05-15 19:36:45 +02:00
* /
2019-04-02 17:58:36 +02:00
removeOutput ( inputNum ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2018-05-15 19:36:45 +02:00
2019-04-25 17:32:48 +02:00
delete ( this . outputs [ inputNum ] ) ;
2019-04-02 17:58:36 +02:00
}
2018-05-15 19:36:45 +02:00
2019-04-30 14:18:22 +02: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 19:36:45 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Sets the output in the output textarea .
*
* @ param { number } inputNum
2018-05-15 19:36:45 +02:00
* /
2019-05-08 14:46:29 +02:00
async set ( inputNum ) {
2019-05-23 16:29:58 +02:00
return new Promise ( async function ( resolve , reject ) {
2019-05-08 14:46:29 +02:00
const output = this . outputs [ inputNum ] ;
if ( output === undefined || output === null ) return ;
if ( typeof inputNum !== "number" ) inputNum = parseInt ( inputNum , 10 ) ;
if ( inputNum !== this . getActiveTab ( ) ) return ;
2019-05-23 16:29:58 +02:00
this . toggleLoader ( true ) ;
2019-05-08 14:46:29 +02:00
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 17:58:36 +02:00
2019-05-08 14:46:29 +02:00
if ( output . progress !== undefined ) {
this . manager . recipe . updateBreakpointIndicator ( output . progress ) ;
} else {
this . manager . recipe . updateBreakpointIndicator ( false ) ;
}
document . getElementById ( "show-file-overlay" ) . style . display = "none" ;
2019-05-01 18:08:36 +02:00
2019-05-08 14:46:29 +02: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 17:58:36 +02:00
outputText . style . display = "block" ;
2019-05-08 14:46:29 +02:00
outputText . classList . remove ( "blur" ) ;
2019-04-02 17:58:36 +02:00
outputHtml . style . display = "none" ;
outputFile . style . display = "none" ;
2019-05-08 14:46:29 +02:00
outputHighlighter . display = "none" ;
inputHighlighter . display = "none" ;
2018-05-15 19:36:45 +02:00
2019-05-08 14:46:29 +02:00
outputText . value = output . error ;
2019-04-02 17:58:36 +02:00
outputHtml . innerHTML = "" ;
2019-05-08 14:46:29 +02:00
} else if ( output . status === "baked" || output . status === "inactive" ) {
2019-05-23 16:29:58 +02:00
document . querySelector ( "#output-loader .loading-msg" ) . textContent = ` Loading output ${ inputNum } ` ;
2019-05-08 14:46:29 +02:00
this . displayTabInfo ( inputNum ) ;
this . closeFile ( ) ;
let scriptElements , lines , length ;
2018-05-15 19:36:45 +02:00
2019-05-08 14:46:29 +02:00
if ( output . data === null ) {
2019-05-01 18:08:36 +02:00
outputText . style . display = "block" ;
outputHtml . style . display = "none" ;
outputFile . style . display = "none" ;
outputHighlighter . display = "block" ;
inputHighlighter . display = "block" ;
2019-05-08 14:46:29 +02:00
outputText . value = "" ;
2019-05-01 18:08:36 +02:00
outputHtml . innerHTML = "" ;
2019-05-08 14:46:29 +02:00
lines = 0 ;
length = 0 ;
2019-05-23 16:29:58 +02:00
this . toggleLoader ( false ) ;
2019-05-08 14:46:29 +02: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 16:29:58 +02:00
length = output . data . result . byteLength ;
this . setFile ( await this . getDishBuffer ( output . data . dish ) ) ;
2019-05-08 14:46:29 +02: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 16:29:58 +02: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 14:46:29 +02:00
this . setOutputInfo ( length , lines , output . data . duration ) ;
this . backgroundMagic ( ) ;
2019-04-02 17:58:36 +02:00
}
2019-05-08 14:46:29 +02:00
} . bind ( this ) ) ;
2018-05-15 19:36:45 +02:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Shows file details
*
* @ param { ArrayBuffer } buf
2018-05-15 19:36:45 +02:00
* /
2019-04-02 17:58:36 +02: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 19:36:45 +02:00
2019-04-02 17:58:36 +02:00
fileOverlay . style . display = "block" ;
2019-05-08 14:46:29 +02:00
fileSize . textContent = buf . byteLength . toLocaleString ( ) + " bytes" ;
2018-05-15 19:36:45 +02:00
2019-04-02 17:58:36 +02:00
outputText . classList . add ( "blur" ) ;
outputText . value = Utils . printable ( Utils . arrayBufferToStr ( fileSlice ) ) ;
2018-05-15 19:36:45 +02:00
}
2019-04-02 17:58:36 +02:00
/ * *
* Clears output file details
* /
closeFile ( ) {
document . getElementById ( "output-file" ) . style . display = "none" ;
document . getElementById ( "output-text" ) . classList . remove ( "blur" ) ;
}
2018-05-15 19:36:45 +02:00
2019-05-23 16:29:58 +02: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 19:36:45 +02:00
/ * *
2019-01-16 13:29:34 +01:00
* Save bombe object then remove it from the DOM so that it does not cause performance issues .
2019-01-15 20:03:17 +01:00
* /
saveBombe ( ) {
2019-01-16 13:29:34 +01:00
this . bombeEl = document . getElementById ( "bombe" ) ;
this . bombeEl . parentNode . removeChild ( this . bombeEl ) ;
2019-01-15 20:03:17 +01: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 200 ms . We add it to the DOM just before that so that
* it is ready to fade in without stuttering .
2018-05-15 19:36:45 +02:00
*
2019-05-23 16:29:58 +02:00
* @ param { boolean } value - If true , show the loader
2018-05-15 19:36:45 +02:00
* /
toggleLoader ( value ) {
2019-01-15 20:03:17 +01:00
clearTimeout ( this . appendBombeTimeout ) ;
clearTimeout ( this . outputLoaderTimeout ) ;
2018-05-15 19:36:45 +02:00
const outputLoader = document . getElementById ( "output-loader" ) ,
2019-01-15 20:03:17 +01:00
outputElement = document . getElementById ( "output-text" ) ,
2019-01-16 13:29:34 +01:00
animation = document . getElementById ( "output-loader-animation" ) ;
2018-05-15 19:36:45 +02:00
if ( value ) {
this . manager . controls . hideStaleIndicator ( ) ;
2019-01-15 20:03:17 +01:00
2019-05-01 16:19:01 +02:00
// Don't add the bombe if it's already there!
if ( animation . children . length > 0 ) return ;
2019-01-15 20:03:17 +01: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 13:29:34 +01:00
animation . appendChild ( this . bombeEl ) ;
2019-01-15 20:03:17 +01:00
} . bind ( this ) , 150 ) ;
// Show the loading screen
this . outputLoaderTimeout = setTimeout ( function ( ) {
2018-05-15 19:36:45 +02:00
outputElement . disabled = true ;
outputLoader . style . visibility = "visible" ;
outputLoader . style . opacity = 1 ;
2019-04-25 17:32:48 +02:00
} , 200 ) ;
2018-05-15 19:36:45 +02:00
} else {
2019-01-15 20:03:17 +01:00
// Remove the Bombe from the DOM to save resources
this . outputLoaderTimeout = setTimeout ( function ( ) {
try {
2019-01-16 13:29:34 +01:00
animation . removeChild ( this . bombeEl ) ;
2019-01-15 20:03:17 +01:00
} catch ( err ) { }
} . bind ( this ) , 500 ) ;
2018-05-15 19:36:45 +02:00
outputElement . disabled = false ;
outputLoader . style . opacity = 0 ;
outputLoader . style . visibility = "hidden" ;
}
}
2019-04-03 13:00:47 +02:00
/ * *
* Handler for save click events .
* Saves the current output to a file .
* /
saveClick ( ) {
2019-04-25 17:32:48 +02:00
this . downloadFile ( ) ;
2019-04-03 13:00:47 +02:00
}
/ * *
* Handler for file download events .
* /
async downloadFile ( ) {
2019-05-08 11:10:14 +02:00
let fileName = window . prompt ( "Please enter a filename: " , "download.dat" ) ;
if ( fileName === null ) fileName = "download.dat" ;
2019-05-07 15:20:18 +02:00
const file = new File ( [ this . getActive ( true ) ] , fileName ) ;
2019-04-03 13:00:47 +02:00
FileSaver . saveAs ( file , fileName , false ) ;
}
/ * *
* Handler for save all click event
* Saves all outputs to a single archvie file
* /
saveAllClick ( ) {
2019-05-07 13:00:53 +02: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 13:00:47 +02:00
}
2019-05-07 10:26:55 +02:00
/ * *
* Spawns a new ZipWorker and sends it the outputs so that they can
* be zipped for download
* /
2019-05-23 16:29:58 +02: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 10:26:55 +02:00
}
}
2019-05-23 16:29:58 +02:00
let fileName = window . prompt ( "Please enter a filename: " , "download.zip" ) ;
2019-05-07 10:26:55 +02:00
2019-05-23 16:29:58 +02: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 10:26:55 +02:00
2019-05-23 16:29:58 +02:00
if ( ! fileName . match ( /.zip$/ ) ) {
fileName += ".zip" ;
}
2019-05-07 13:00:53 +02:00
2019-05-23 16:29:58 +02:00
let fileExt = window . prompt ( "Please enter a file extension for the files, or leave blank to detect automatically." , "" ) ;
2019-05-07 10:26:55 +02:00
2019-05-23 16:29:58 +02:00
if ( fileExt === null ) fileExt = "" ;
2019-05-07 10:26:55 +02:00
2019-05-23 16:29:58 +02:00
if ( this . zipWorker !== null ) {
this . terminateZipWorker ( ) ;
}
2019-05-07 10:26:55 +02:00
2019-05-23 16:29:58 +02:00
const downloadButton = document . getElementById ( "save-all-to-file" ) ;
2019-05-07 10:26:55 +02:00
2019-05-23 16:29:58 +02:00
downloadButton . classList . add ( "spin" ) ;
downloadButton . title = ` Zipping ${ inputNums . length } files... ` ;
downloadButton . setAttribute ( "data-original-title" , ` Zipping ${ inputNums . length } files... ` ) ;
2019-05-07 10:26:55 +02:00
2019-05-23 16:29:58 +02:00
downloadButton . firstElementChild . innerHTML = "autorenew" ;
2019-05-07 10:26:55 +02:00
2019-05-23 16:29:58 +02: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 10:26:55 +02: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 13:00:53 +02: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 10:26:55 +02: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 19:36:45 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Adds a new output tab .
2018-05-15 19:36:45 +02:00
*
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum
* @ param { boolean } [ changeTab = true ]
2018-05-15 19:36:45 +02:00
* /
2019-04-02 17:58:36 +02:00
addTab ( inputNum , changeTab = true ) {
const tabsWrapper = document . getElementById ( "output-tabs" ) ;
const numTabs = tabsWrapper . children . length ;
2018-05-15 19:36:45 +02:00
2019-04-30 14:18:22 +02:00
if ( this . getTabItem ( inputNum ) === undefined && numTabs < this . maxTabs ) {
2019-04-02 17:58:36 +02:00
// Create a new tab element
const newTab = this . createTabElement ( inputNum ) ;
2018-05-15 19:36:45 +02:00
2019-04-02 17:58:36 +02:00
tabsWrapper . appendChild ( newTab ) ;
2018-05-15 19:36:45 +02:00
2019-04-02 17:58:36 +02:00
if ( numTabs > 0 ) {
tabsWrapper . parentElement . style . display = "block" ;
2019-04-03 13:00:47 +02:00
2019-04-02 17:58:36 +02:00
document . getElementById ( "output-wrapper" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
document . getElementById ( "output-highlighter" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
document . getElementById ( "output-file" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
document . getElementById ( "output-loader" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
2019-05-08 11:10:14 +02:00
document . getElementById ( "show-file-overlay" ) . style . top = "calc(var(--tab-height) + var(--title-height) + 10px)" ;
2019-04-25 17:32:48 +02:00
document . getElementById ( "save-all-to-file" ) . style . display = "inline-block" ;
2019-04-30 14:18:22 +02:00
} else {
tabsWrapper . parentElement . style . display = "none" ;
document . getElementById ( "output-wrapper" ) . style . height = "calc(100% - var(--title-height))" ;
document . getElementById ( "output-highlighter" ) . style . height = "calc(100% - var(--title-height))" ;
document . getElementById ( "output-file" ) . style . height = "calc(100% - var(--title-height))" ;
document . getElementById ( "output-loader" ) . style . height = "calc(100% - var(--title-height))" ;
2019-05-08 11:10:14 +02:00
document . getElementById ( "show-file-overlay" ) . style . top = "calc(var(--title-height) + 10px)" ;
2019-04-30 14:18:22 +02:00
document . getElementById ( "save-all-to-file" ) . style . display = "none" ;
2019-04-02 17:58:36 +02:00
}
2019-05-30 15:32:05 +02: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 17:58:36 +02:00
}
2018-05-15 19:36:45 +02:00
2019-04-02 17:58:36 +02:00
if ( changeTab ) {
2019-04-03 13:00:47 +02:00
this . changeTab ( inputNum , false ) ;
2019-04-02 17:58:36 +02:00
}
2018-05-15 19:36:45 +02:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Changes the active tab
2018-05-15 19:36:45 +02:00
*
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum
2019-04-03 13:00:47 +02:00
* @ param { boolean } [ changeInput = false ]
2018-05-15 19:36:45 +02:00
* /
2019-04-03 13:00:47 +02:00
changeTab ( inputNum , changeInput = false ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2019-04-02 17:58:36 +02:00
const currentNum = this . getActiveTab ( ) ;
2019-05-07 15:20:18 +02:00
2019-05-01 15:46:05 +02:00
this . hideMagicButton ( ) ;
2019-04-02 17:58:36 +02:00
2019-05-07 15:20:18 +02:00
this . manager . highlighter . removeHighlights ( ) ;
getSelection ( ) . removeAllRanges ( ) ;
2019-04-02 17:58:36 +02:00
const tabsWrapper = document . getElementById ( "output-tabs" ) ;
const tabs = tabsWrapper . children ;
let found = false ;
for ( let i = 0 ; i < tabs . length ; i ++ ) {
if ( tabs . item ( i ) . getAttribute ( "inputNum" ) === inputNum . toString ( ) ) {
tabs . item ( i ) . classList . add ( "active-output-tab" ) ;
2019-04-26 16:15:44 +02:00
this . activeTab = inputNum ;
2019-04-02 17:58:36 +02:00
found = true ;
} else {
tabs . item ( i ) . classList . remove ( "active-output-tab" ) ;
}
}
if ( ! found ) {
let direction = "right" ;
if ( currentNum > inputNum ) {
direction = "left" ;
}
2018-05-15 19:36:45 +02:00
2019-04-02 17:58:36 +02:00
const newOutputs = this . getNearbyNums ( inputNum , direction ) ;
2019-05-30 14:28:45 +02:00
const tabsLeft = ( newOutputs [ 0 ] !== this . getSmallestInputNum ( ) ) ;
const tabsRight = ( newOutputs [ newOutputs . length - 1 ] !== this . getLargestInputNum ( ) ) ;
const firstTabElement = document . getElementById ( "output-tabs" ) . firstElementChild ;
const lastTabElement = document . getElementById ( "output-tabs" ) . lastElementChild ;
if ( firstTabElement ) {
if ( tabsLeft ) {
firstTabElement . style . boxShadow = "15px 0px 15px -15px var(--primary-border-colour) inset" ;
} else {
firstTabElement . style . boxShadow = "" ;
}
}
if ( lastTabElement ) {
if ( tabsRight ) {
lastTabElement . style . boxShadow = "-15px 0px 15px -15px var(--primary-border-colour) inset" ;
} else {
lastTabElement . style . boxShadow = "" ;
}
}
2019-04-02 17:58:36 +02:00
for ( let i = 0 ; i < newOutputs . length ; i ++ ) {
tabs . item ( i ) . setAttribute ( "inputNum" , newOutputs [ i ] . toString ( ) ) ;
this . displayTabInfo ( newOutputs [ i ] ) ;
if ( newOutputs [ i ] === inputNum ) {
2019-04-26 16:15:44 +02:00
this . activeTab = inputNum ;
2019-04-03 13:00:47 +02:00
tabs . item ( i ) . classList . add ( "active-output-tab" ) ;
2019-04-02 17:58:36 +02:00
}
}
}
2018-05-15 19:36:45 +02:00
2019-04-02 17:58:36 +02:00
this . set ( inputNum ) ;
2019-04-03 13:00:47 +02:00
if ( changeInput ) {
this . manager . input . changeTab ( inputNum , false ) ;
}
2019-04-02 17:58:36 +02:00
}
2018-06-03 18:33:13 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Handler for changing tabs event
*
* @ param { event } mouseEvent
2018-06-03 18:33:13 +02:00
* /
2019-04-02 17:58:36 +02:00
changeTabClick ( mouseEvent ) {
2019-04-04 14:13:21 +02:00
if ( ! mouseEvent . target ) return ;
const tabNum = mouseEvent . target . parentElement . getAttribute ( "inputNum" ) ;
2019-04-02 17:58:36 +02:00
if ( tabNum ) {
2019-04-03 13:00:47 +02:00
this . changeTab ( parseInt ( tabNum , 10 ) , this . app . options . syncTabs ) ;
}
}
/ * *
* Handler for changing to the left tab
* /
changeTabLeft ( ) {
const currentTab = this . getActiveTab ( ) ;
2019-04-25 17:32:48 +02:00
this . changeTab ( this . getPreviousInputNum ( currentTab ) , this . app . options . syncTabs ) ;
2019-04-03 13:00:47 +02:00
}
/ * *
* Handler for changing to the right tab
* /
changeTabRight ( ) {
const currentTab = this . getActiveTab ( ) ;
this . changeTab ( this . getNextInputNum ( currentTab ) , this . app . options . syncTabs ) ;
}
/ * *
* Handler for go to tab button clicked
* /
goToTab ( ) {
const tabNum = parseInt ( window . prompt ( "Enter tab number:" , this . getActiveTab ( ) . toString ( ) ) , 10 ) ;
2019-05-08 15:47:05 +02:00
if ( this . outputExists ( tabNum ) ) {
2019-04-03 13:00:47 +02:00
this . changeTab ( tabNum , this . app . options . syncTabs ) ;
2018-06-03 18:33:13 +02:00
}
}
/ * *
2019-04-02 17:58:36 +02:00
* Generates a list of the nearby inputNums
2019-04-04 11:15:13 +02:00
* @ param inputNum
* @ param direction
2018-06-03 18:33:13 +02:00
* /
2019-04-02 17:58:36 +02:00
getNearbyNums ( inputNum , direction ) {
const nums = [ ] ;
2019-04-04 11:15:13 +02:00
for ( let i = 0 ; i < this . maxTabs ; i ++ ) {
let newNum ;
if ( i === 0 ) {
newNum = inputNum ;
} else {
switch ( direction ) {
case "left" :
newNum = this . getNextInputNum ( nums [ i - 1 ] ) ;
if ( newNum === nums [ i - 1 ] ) {
direction = "right" ;
2019-05-14 17:13:36 +02:00
newNum = this . getPreviousInputNum ( nums [ 0 ] ) ;
2019-04-04 11:15:13 +02:00
}
2019-04-03 17:05:10 +02:00
break ;
2019-04-04 11:15:13 +02:00
case "right" :
newNum = this . getPreviousInputNum ( nums [ i - 1 ] ) ;
if ( newNum === nums [ i - 1 ] ) {
direction = "left" ;
2019-05-14 17:13:36 +02:00
newNum = this . getNextInputNum ( nums [ 0 ] ) ;
2019-04-04 11:15:13 +02:00
}
2019-04-02 17:58:36 +02:00
}
}
2019-04-04 11:15:13 +02:00
if ( ! nums . includes ( newNum ) && ( newNum > 0 ) ) {
nums . push ( newNum ) ;
}
2019-04-02 17:58:36 +02:00
}
nums . sort ( function ( a , b ) {
return a - b ;
} ) ;
return nums ;
2018-07-27 15:37:38 +02:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Gets the largest inputNum
2018-07-27 15:37:38 +02:00
*
2019-04-02 17:58:36 +02:00
* @ returns { number }
2018-07-27 15:37:38 +02:00
* /
2019-04-02 17:58:36 +02:00
getLargestInputNum ( ) {
2019-04-25 17:32:48 +02:00
const inputNums = Object . keys ( this . outputs ) ;
2019-05-29 14:25:12 +02:00
if ( inputNums . length === 0 ) return - 1 ;
return Math . max ( ... inputNums ) ;
2018-07-27 15:37:38 +02:00
}
2019-04-03 13:00:47 +02:00
/ * *
* Gets the smallest inputNum
*
* @ returns { number }
* /
getSmallestInputNum ( ) {
2019-04-25 17:32:48 +02:00
const inputNums = Object . keys ( this . outputs ) ;
2019-05-29 14:25:12 +02:00
if ( inputNums . length === 0 ) return - 1 ;
return Math . min ( ... inputNums ) ;
2019-04-03 13:00:47 +02:00
}
2018-07-27 15:37:38 +02:00
/ * *
2019-04-02 17:58:36 +02:00
* Gets the previous inputNum
2018-07-27 15:37:38 +02:00
*
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum - The current input number
* @ returns { number }
2018-07-27 15:37:38 +02:00
* /
2019-04-02 17:58:36 +02:00
getPreviousInputNum ( inputNum ) {
2019-04-25 17:32:48 +02:00
const inputNums = Object . keys ( this . outputs ) ;
2019-05-29 14:25:12 +02:00
if ( inputNums . length === 0 ) return - 1 ;
let num = Math . min ( ... inputNums ) ;
2019-04-25 17:32:48 +02: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 17:58:36 +02:00
}
}
}
return num ;
2018-07-27 15:37:38 +02:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Gets the next inputNum
*
* @ param { number } inputNum - The current input number
* @ returns { number }
2018-07-27 15:37:38 +02:00
* /
2019-04-02 17:58:36 +02:00
getNextInputNum ( inputNum ) {
2019-04-25 17:32:48 +02:00
const inputNums = Object . keys ( this . outputs ) ;
2019-05-29 14:25:12 +02:00
if ( inputNums . length === 0 ) return - 1 ;
let num = Math . max ( ... inputNums ) ;
2019-04-25 17:32:48 +02: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 17:58:36 +02:00
}
}
}
return num ;
2018-06-03 18:33:13 +02:00
}
2019-03-09 07:25:27 +01:00
/ * *
2019-04-02 17:58:36 +02:00
* Removes a tab and it ' s corresponding output
2019-03-09 07:25:27 +01:00
*
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum
2019-03-09 07:25:27 +01:00
* /
2019-04-02 17:58:36 +02:00
removeTab ( inputNum ) {
2019-05-08 15:47:05 +02:00
if ( ! this . outputExists ( inputNum ) ) return ;
2019-04-03 13:00:47 +02:00
let activeTab = this . getActiveTab ( ) ;
2019-03-09 07:25:27 +01:00
2019-04-02 17:58:36 +02:00
const tabElement = this . getTabItem ( inputNum ) ;
2019-03-09 07:25:27 +01:00
2019-04-02 17:58:36 +02:00
this . removeOutput ( inputNum ) ;
if ( tabElement !== null ) {
// find new tab number?
2019-04-03 13:00:47 +02:00
if ( inputNum === activeTab ) {
activeTab = this . getPreviousInputNum ( activeTab ) ;
if ( activeTab === this . getActiveTab ( ) ) {
activeTab = this . getNextInputNum ( activeTab ) ;
}
}
this . refreshTabs ( activeTab ) ;
}
}
/ * *
* Redraw the entire tab bar to remove any outdated tabs
* @ param { number } activeTab
* /
refreshTabs ( activeTab ) {
const tabsList = document . getElementById ( "output-tabs" ) ;
let newInputs = this . getNearbyNums ( activeTab , "right" ) ;
if ( newInputs . length < this . maxTabs ) {
newInputs = this . getNearbyNums ( activeTab , "left" ) ;
}
for ( let i = tabsList . children . length - 1 ; i >= 0 ; i -- ) {
tabsList . children . item ( i ) . remove ( ) ;
}
for ( let i = 0 ; i < newInputs . length ; i ++ ) {
tabsList . appendChild ( this . createTabElement ( newInputs [ i ] ) ) ;
this . displayTabInfo ( newInputs [ i ] ) ;
}
2019-05-30 14:28:45 +02:00
const tabsLeft = ( newInputs [ 0 ] !== this . getSmallestInputNum ( ) ) ;
const tabsRight = ( newInputs [ newInputs . length - 1 ] !== this . getLargestInputNum ( ) ) ;
const firstTabElement = document . getElementById ( "output-tabs" ) . firstElementChild ;
const lastTabElement = document . getElementById ( "output-tabs" ) . lastElementChild ;
if ( firstTabElement ) {
if ( tabsLeft ) {
firstTabElement . style . boxShadow = "15px 0px 15px -15px var(--primary-border-colour) inset" ;
} else {
firstTabElement . style . boxShadow = "" ;
}
}
if ( lastTabElement ) {
if ( tabsRight ) {
lastTabElement . style . boxShadow = "-15px 0px 15px -15px var(--primary-border-colour) inset" ;
} else {
lastTabElement . style . boxShadow = "" ;
}
}
2019-04-03 13:00:47 +02:00
if ( newInputs . length > 1 ) {
tabsList . parentElement . style . display = "block" ;
document . getElementById ( "output-wrapper" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
document . getElementById ( "output-highlighter" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
document . getElementById ( "output-file" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
document . getElementById ( "output-loader" ) . style . height = "calc(100% - var(--tab-height) - var(--title-height))" ;
2019-05-08 11:10:14 +02:00
document . getElementById ( "show-file-overlay" ) . style . top = "calc(var(--tab-height) + var(--title-height) + 10px)" ;
2019-04-03 13:00:47 +02:00
2019-04-25 17:32:48 +02:00
document . getElementById ( "save-all-to-file" ) . style . display = "inline-block" ;
2019-04-03 13:00:47 +02:00
} else {
tabsList . parentElement . style . display = "none" ;
document . getElementById ( "output-wrapper" ) . style . height = "calc(100% - var(--title-height))" ;
document . getElementById ( "output-highlighter" ) . style . height = "calc(100% - var(--title-height))" ;
document . getElementById ( "output-file" ) . style . height = "calc(100% - var(--title-height))" ;
document . getElementById ( "output-loader" ) . style . height = "calc(100% - var(--title-height))" ;
2019-05-08 11:10:14 +02:00
document . getElementById ( "show-file-overlay" ) . style . top = "calc(var(--title-height) + 10px)" ;
2019-04-25 17:32:48 +02:00
document . getElementById ( "save-all-to-file" ) . style . display = "none" ;
2019-04-02 17:58:36 +02:00
}
2019-04-03 13:00:47 +02:00
this . changeTab ( activeTab ) ;
2019-03-09 07:25:27 +01:00
}
2019-03-27 10:05:10 +01:00
/ * *
2019-04-02 17:58:36 +02:00
* Creates a new tab element to be added to the tab bar
2019-03-27 10:05:10 +01:00
*
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum
2019-03-27 10:05:10 +01:00
* /
2019-04-02 17:58:36 +02:00
createTabElement ( inputNum ) {
2019-03-27 10:05:10 +01:00
const newTab = document . createElement ( "li" ) ;
2019-04-02 17:58:36 +02:00
newTab . setAttribute ( "inputNum" , inputNum . toString ( ) ) ;
2019-03-27 10:05:10 +01:00
const newTabContent = document . createElement ( "div" ) ;
newTabContent . classList . add ( "output-tab-content" ) ;
2019-04-02 17:58:36 +02:00
newTabContent . innerText = ` Tab ${ inputNum . toString ( ) } ` ;
2019-03-27 10:05:10 +01:00
2019-04-02 17:58:36 +02:00
// Do we want remove tab button on output?
2019-03-27 10:05:10 +01:00
newTab . appendChild ( newTabContent ) ;
2019-04-02 17:58:36 +02:00
return newTab ;
2019-03-27 10:05:10 +01:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Gets the number of the current active tab
2019-03-27 10:05:10 +01:00
*
2019-04-02 17:58:36 +02:00
* @ returns { number }
2019-03-27 10:05:10 +01:00
* /
2019-04-02 17:58:36 +02:00
getActiveTab ( ) {
2019-04-26 16:15:44 +02:00
return this . activeTab ;
2019-03-27 14:48:54 +01:00
}
/ * *
2019-04-02 17:58:36 +02:00
* Gets the li element for a tab
2019-03-27 14:48:54 +01:00
*
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum
2019-03-27 14:48:54 +01:00
* /
2019-04-02 17:58:36 +02:00
getTabItem ( inputNum ) {
const tabs = document . getElementById ( "output-tabs" ) . children ;
for ( let i = 0 ; i < tabs . length ; i ++ ) {
if ( parseInt ( tabs . item ( i ) . getAttribute ( "inputNum" ) , 10 ) === inputNum ) {
return tabs . item ( i ) ;
}
2019-03-27 14:48:54 +01:00
}
}
/ * *
2019-04-02 17:58:36 +02:00
* Display output information in the tab header
2019-03-27 14:48:54 +01:00
*
2019-04-02 17:58:36 +02:00
* @ param { number } inputNum
2019-03-27 14:48:54 +01:00
* /
2019-04-02 17:58:36 +02:00
displayTabInfo ( inputNum ) {
const tabItem = this . getTabItem ( inputNum ) ;
2019-03-27 14:48:54 +01:00
2019-04-02 17:58:36 +02:00
if ( ! tabItem ) return ;
2019-03-27 14:48:54 +01:00
2019-04-02 17:58:36 +02:00
const tabContent = tabItem . firstElementChild ;
2019-03-27 14:48:54 +01:00
2019-04-02 17:58:36 +02:00
tabContent . innerText = ` Tab ${ inputNum } ` ;
2019-03-27 14:48:54 +01:00
2019-03-27 10:05:10 +01:00
}
2019-04-25 17:32:48 +02: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 16:29:58 +02:00
async backgroundMagic ( ) {
2019-04-25 17:32:48 +02:00
this . hideMagicButton ( ) ;
2019-05-07 15:20:18 +02:00
if ( ! this . app . options . autoMagic || ! this . getActive ( true ) ) return ;
2019-05-23 16:29:58 +02:00
const dish = this . outputs [ this . getActiveTab ( ) ] . data . dish ;
const buffer = await this . getDishBuffer ( dish ) ;
const sample = buffer . slice ( 0 , 1000 ) || "" ;
2019-05-07 15:20:18 +02:00
2019-05-01 15:46:05 +02:00
if ( sample . length || sample . byteLength ) {
2019-04-25 17:32:48 +02: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 16:29:58 +02:00
async displayFileSlice ( ) {
document . querySelector ( "#output-loader .loading-msg" ) . textContent = "Loading file slice..." ;
this . toggleLoader ( true ) ;
2019-05-08 11:10:14 +02: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 17:32:48 +02: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-05-28 16:01:49 +02:00
output = this . outputs [ this . getActiveTab ( ) ] . data ;
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 17:32:48 +02:00
2019-05-08 11:10:14 +02:00
outputText . classList . remove ( "blur" ) ;
2019-04-25 17:32:48 +02:00
showFileOverlay . style . display = "block" ;
2019-05-28 16:01:49 +02:00
outputText . value = Utils . printable ( str , true ) ;
2019-05-08 11:10:14 +02:00
outputText . style . display = "block" ;
outputHtml . style . display = "none" ;
outputFile . style . display = "none" ;
outputHighlighter . display = "block" ;
inputHighlighter . display = "block" ;
2019-04-25 17:32:48 +02:00
2019-05-23 16:29:58 +02:00
this . toggleLoader ( false ) ;
2019-04-25 17:32:48 +02:00
}
2019-05-08 11:10:14 +02: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" ;
this . set ( this . getActiveTab ( ) ) ;
}
/ * *
* 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 17:32:48 +02:00
/ * *
* Handler for copy click events .
* Copies the output to the clipboard
* /
copyClick ( ) {
2019-05-07 15:20:18 +02:00
let output = this . getActive ( true ) ;
2019-05-07 10:26:55 +02:00
if ( typeof output !== "string" ) {
output = Utils . arrayBufferToStr ( output ) ;
}
2019-04-25 17:32:48 +02: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 15:20:18 +02:00
/ * *
* Returns true if the output contains carriage returns
*
* @ returns { boolean }
* /
containsCR ( ) {
2019-05-15 10:37:07 +02:00
return this . getActive ( false ) . indexOf ( "\r" ) >= 0 ;
2019-05-07 15:20:18 +02:00
}
2019-05-07 16:34:36 +02:00
/ * *
* Handler for switch click events .
* Moves the current output into the input textarea .
* /
switchClick ( ) {
const active = this . getActive ( true ) ;
if ( typeof active === "string" ) {
this . manager . input . inputWorker . postMessage ( {
action : "inputSwitch" ,
data : {
inputNum : this . manager . input . getActiveTab ( ) ,
outputData : active
}
} ) ;
} else {
this . manager . input . inputWorker . postMessage ( {
action : "inputSwitch" ,
data : {
inputNum : this . manager . input . getActiveTab ( ) ,
outputData : active
}
} , [ active ] ) ;
}
}
/ * *
* 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 16:36:42 +02: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 10:37:07 +02: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 14:28:45 +02:00
let contentFilterExp ;
try {
contentFilterExp = new RegExp ( contentFilter , "i" ) ;
} catch ( error ) {
this . app . handleError ( error ) ;
return ;
}
2019-05-15 10:37:07 +02: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 17:24:49 +02:00
output . status === "error" && showErrored ||
2019-05-15 10:37:07 +02:00
output . status === "stale" && showStale ||
output . status === "inactive" && showStale ) {
const outDisplay = {
"pending" : "Not baked yet" ,
"baking" : "Baking" ,
2019-05-15 17:24:49 +02:00
"error" : output . error || "Errored" ,
2019-05-15 10:37:07 +02:00
"stale" : "Stale (output is out of date)" ,
"inactive" : "Not baked yet"
} ;
results . push ( {
inputNum : iNum ,
textDisplay : outDisplay [ output . status ]
} ) ;
2019-05-28 16:01:49 +02: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 14:28:45 +02:00
if ( contentFilterExp . test ( data ) ) {
2019-05-28 16:01:49 +02:00
results . push ( {
inputNum : iNum ,
textDisplay : data . slice ( 0 , 100 )
} ) ;
}
} else if ( output . progress !== false && showErrored ) {
2019-05-15 10:37:07 +02: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 14:28:45 +02:00
if ( contentFilterExp . test ( data ) ) {
2019-05-15 10:37:07 +02: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 17:03:18 +02: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 19:36:45 +02:00
}
export default OutputWaiter ;