2018-05-15 17:36:45 +00:00
/ * *
* @ author n1474335 [ n1474335 @ gmail . com ]
2019-03-29 13:29:24 +00:00
* @ author j433866 [ j433866 @ gmail . com ]
2019-06-03 11:20:06 +01:00
* @ copyright Crown Copyright 2016
2018-05-15 17:36:45 +00:00
* @ license Apache - 2.0
* /
2019-06-06 09:09:48 +01:00
import LoaderWorker from "worker-loader?inline&fallback=false!../workers/LoaderWorker" ;
import InputWorker from "worker-loader?inline&fallback=false!../workers/InputWorker" ;
2019-07-09 12:23:59 +01:00
import Utils from "../../core/Utils.mjs" ;
import { toBase64 } from "../../core/lib/Base64.mjs" ;
import { isImage } from "../../core/lib/FileType.mjs" ;
2018-05-15 17:36:45 +00:00
/ * *
* Waiter to handle events related to the input .
* /
class InputWaiter {
/ * *
* InputWaiter constructor .
*
2019-05-01 17:08:36 +01:00
* @ param { App } app - The main view object for CyberChef .
* @ param { Manager } manager - The CyberChef event manager .
2018-05-15 17:36:45 +00:00
* /
constructor ( app , manager ) {
this . app = app ;
this . manager = manager ;
// Define keys that don't change the input so we don't have to autobake when they are pressed
this . badKeys = [
16 , //Shift
17 , //Ctrl
18 , //Alt
19 , //Pause
20 , //Caps
27 , //Esc
33 , 34 , 35 , 36 , //PgUp, PgDn, End, Home
37 , 38 , 39 , 40 , //Directional
44 , //PrntScrn
91 , 92 , //Win
93 , //Context
112 , 113 , 114 , 115 , 116 , 117 , 118 , 119 , 120 , 121 , 122 , 123 , //F1-12
144 , //Num
145 , //Scroll
] ;
2019-04-25 16:32:48 +01:00
this . inputWorker = null ;
2019-03-29 13:29:24 +00:00
this . loaderWorkers = [ ] ;
2019-04-25 16:32:48 +01:00
this . workerId = 0 ;
2019-06-07 13:52:04 +01:00
this . maxTabs = this . manager . tabs . calcMaxTabs ( ) ;
2019-05-28 11:59:57 +01:00
this . callbacks = { } ;
this . callbackID = 0 ;
2019-06-13 14:48:28 +01:00
this . maxWorkers = 1 ;
if ( navigator . hardwareConcurrency !== undefined &&
navigator . hardwareConcurrency > 1 ) {
2019-07-02 12:23:46 +01:00
// Subtract 1 from hardwareConcurrency value to avoid using
// the entire available resources
2019-06-13 14:48:28 +01:00
this . maxWorkers = navigator . hardwareConcurrency - 1 ;
}
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 ( ) ;
if ( this . inputWorker && this . maxTabs !== numTabs ) {
this . maxTabs = numTabs ;
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
action : "updateMaxTabs" ,
2019-05-29 13:25:12 +01:00
data : {
2019-06-06 16:33:35 +01:00
maxTabs : numTabs ,
activeTab : this . manager . tabs . getActiveInputTab ( )
2019-05-29 13:25:12 +01:00
}
2019-04-25 16:32:48 +01:00
} ) ;
}
2019-04-04 10:15:13 +01:00
}
2019-03-29 13:29:24 +00:00
/ * *
2019-04-25 16:32:48 +01:00
* Terminates any existing workers and sets up a new InputWorker and LoaderWorker
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
setupInputWorker ( ) {
2019-04-30 13:18:22 +01:00
if ( this . inputWorker !== null ) {
this . inputWorker . terminate ( ) ;
this . inputWorker = null ;
}
2019-04-25 16:32:48 +01:00
for ( let i = this . loaderWorkers . length - 1 ; i >= 0 ; i -- ) {
this . removeLoaderWorker ( this . loaderWorkers [ i ] ) ;
}
log . debug ( "Adding new InputWorker" ) ;
this . inputWorker = new InputWorker ( ) ;
this . inputWorker . postMessage ( {
action : "updateMaxWorkers" ,
data : this . maxWorkers
} ) ;
this . inputWorker . postMessage ( {
action : "updateMaxTabs" ,
2019-05-29 14:08:37 +01:00
data : {
maxTabs : this . maxTabs ,
2019-06-06 16:33:35 +01:00
activeTab : this . manager . tabs . getActiveInputTab ( )
2019-05-29 14:08:37 +01:00
}
2019-04-25 16:32:48 +01:00
} ) ;
2019-05-02 11:29:54 +01:00
this . inputWorker . postMessage ( {
action : "setLogLevel" ,
data : log . getLevel ( )
} ) ;
2019-04-25 16:32:48 +01:00
this . inputWorker . addEventListener ( "message" , this . handleInputWorkerMessage . bind ( this ) ) ;
}
/ * *
* Activates a loaderWorker and sends it to the InputWorker
* /
activateLoaderWorker ( ) {
2019-04-30 15:23:41 +01:00
const workerIdx = this . addLoaderWorker ( ) ;
2019-04-25 16:32:48 +01:00
if ( workerIdx === - 1 ) return ;
const workerObj = this . loaderWorkers [ workerIdx ] ;
this . inputWorker . postMessage ( {
action : "loaderWorkerReady" ,
data : {
2019-05-01 14:12:36 +01:00
id : workerObj . id
2019-04-25 16:32:48 +01:00
}
2019-05-01 14:12:36 +01:00
} ) ;
2019-03-29 13:29:24 +00:00
}
2018-05-15 17:36:45 +00:00
/ * *
2019-03-29 13:29:24 +00:00
* Adds a new loaderWorker
2018-05-15 17:36:45 +00:00
*
2019-05-14 11:45:13 +01:00
* @ returns { number } - The index of the created worker
2018-05-15 17:36:45 +00:00
* /
2019-03-29 13:29:24 +00:00
addLoaderWorker ( ) {
if ( this . loaderWorkers . length === this . maxWorkers ) {
return - 1 ;
2019-03-22 15:10:19 +00:00
}
2019-03-29 13:29:24 +00:00
log . debug ( "Adding new LoaderWorker." ) ;
const newWorker = new LoaderWorker ( ) ;
2019-04-25 16:32:48 +01:00
const workerId = this . workerId ++ ;
2019-05-01 14:12:36 +01:00
newWorker . addEventListener ( "message" , this . handleLoaderMessage . bind ( this ) ) ;
newWorker . postMessage ( { id : workerId } ) ;
2019-03-29 13:29:24 +00:00
const newWorkerObj = {
worker : newWorker ,
2019-05-01 14:12:36 +01:00
id : workerId
2019-03-29 13:29:24 +00:00
} ;
this . loaderWorkers . push ( newWorkerObj ) ;
return this . loaderWorkers . indexOf ( newWorkerObj ) ;
2018-05-15 17:36:45 +00:00
}
2019-03-27 09:05:10 +00:00
/ * *
2019-04-02 16:58:36 +01:00
* Removes a loaderworker
2019-03-27 09:05:10 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { Object } workerObj - Object containing the loaderWorker and its id
* @ param { LoaderWorker } workerObj . worker - The actual loaderWorker
* @ param { number } workerObj . id - The ID of the loaderWorker
2019-03-27 09:05:10 +00:00
* /
2019-03-29 13:29:24 +00:00
removeLoaderWorker ( workerObj ) {
const idx = this . loaderWorkers . indexOf ( workerObj ) ;
if ( idx === - 1 ) {
return ;
}
2019-04-25 16:32:48 +01:00
log . debug ( ` Terminating worker ${ this . loaderWorkers [ idx ] . id } ` ) ;
2019-03-29 13:29:24 +00:00
this . loaderWorkers [ idx ] . worker . terminate ( ) ;
this . loaderWorkers . splice ( idx , 1 ) ;
}
2019-03-27 09:05:10 +00:00
2019-03-29 13:29:24 +00:00
/ * *
2019-04-25 16:32:48 +01:00
* Finds and returns the object for the loaderWorker of a given id
2019-03-29 13:29:24 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { number } id - The ID of the loaderWorker to find
* @ returns { object }
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
getLoaderWorker ( id ) {
const idx = this . getLoaderWorkerIndex ( id ) ;
if ( idx === - 1 ) return ;
return this . loaderWorkers [ idx ] ;
}
/ * *
* Gets the index for the loaderWorker of a given id
*
2019-05-14 11:45:13 +01:00
* @ param { number } id - The ID of hte loaderWorker to find
* @ returns { number } The current index of the loaderWorker in the array
2019-04-25 16:32:48 +01:00
* /
getLoaderWorkerIndex ( id ) {
2019-03-29 13:29:24 +00:00
for ( let i = 0 ; i < this . loaderWorkers . length ; i ++ ) {
2019-04-25 16:32:48 +01:00
if ( this . loaderWorkers [ i ] . id === id ) {
return i ;
2019-03-29 13:29:24 +00:00
}
}
2019-04-25 16:32:48 +01:00
return - 1 ;
2019-03-29 13:29:24 +00:00
}
2019-05-01 14:12:36 +01:00
/ * *
* Sends an input to be loaded to the loaderWorker
*
2019-05-14 11:45:13 +01:00
* @ param { object } inputData - Object containing the input to be loaded
* @ param { File } inputData . file - The actual file object to load
* @ param { number } inputData . inputNum - The inputNum for the file object
* @ param { number } inputData . workerId - The ID of the loaderWorker that will load it
2019-05-01 14:12:36 +01:00
* /
loadInput ( inputData ) {
const idx = this . getLoaderWorkerIndex ( inputData . workerId ) ;
if ( idx === - 1 ) return ;
this . loaderWorkers [ idx ] . worker . postMessage ( {
file : inputData . file ,
inputNum : inputData . inputNum
} ) ;
}
/ * *
* Handler for messages sent back by the loaderWorker
2019-05-14 11:45:13 +01:00
* Sends the message straight to the inputWorker to be handled there .
2019-05-01 14:12:36 +01:00
*
* @ param { MessageEvent } e
* /
handleLoaderMessage ( e ) {
const r = e . data ;
2019-07-05 12:36:15 +01:00
if ( Object . prototype . hasOwnProperty . call ( r , "progress" ) &&
Object . prototype . hasOwnProperty . call ( r , "inputNum" ) ) {
2019-06-10 16:07:01 +01:00
this . manager . tabs . updateInputTabProgress ( r . inputNum , r . progress , 100 ) ;
}
2019-07-05 12:36:15 +01:00
const transferable = Object . prototype . hasOwnProperty . call ( r , "fileBuffer" ) ? [ r . fileBuffer ] : undefined ;
2019-07-02 12:23:46 +01:00
this . inputWorker . postMessage ( {
action : "loaderWorkerMessage" ,
data : r
} , transferable ) ;
2019-05-01 14:12:36 +01:00
}
2019-04-25 16:32:48 +01:00
2019-03-29 13:29:24 +00:00
/ * *
2019-04-25 16:32:48 +01:00
* Handler for messages sent back by the inputWorker
2019-03-29 13:29:24 +00:00
*
2019-04-25 16:32:48 +01:00
* @ param { MessageEvent } e
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
handleInputWorkerMessage ( e ) {
const r = e . data ;
2019-07-05 12:36:15 +01:00
if ( ! ( "action" in r ) ) {
2019-06-03 11:20:06 +01:00
log . error ( "A message was received from the InputWorker with no action property. Ignoring message." ) ;
2019-04-25 16:32:48 +01:00
return ;
}
log . debug ( ` Receiving ${ r . action } from InputWorker. ` ) ;
switch ( r . action ) {
case "activateLoaderWorker" :
this . activateLoaderWorker ( ) ;
break ;
case "loadInput" :
2019-05-01 14:12:36 +01:00
this . loadInput ( r . data ) ;
2019-04-25 16:32:48 +01:00
break ;
case "terminateLoaderWorker" :
this . removeLoaderWorker ( this . getLoaderWorker ( r . data ) ) ;
break ;
case "refreshTabs" :
2019-05-29 16:29:34 +01:00
this . refreshTabs ( r . data . nums , r . data . activeTab , r . data . tabsLeft , r . data . tabsRight ) ;
2019-04-25 16:32:48 +01:00
break ;
case "changeTab" :
this . changeTab ( r . data , this . app . options . syncTabs ) ;
break ;
case "updateTabHeader" :
2019-06-06 16:33:35 +01:00
this . manager . tabs . updateInputTabHeader ( r . data . inputNum , r . data . input ) ;
2019-04-25 16:32:48 +01:00
break ;
case "loadingInfo" :
2019-04-30 13:18:22 +01:00
this . showLoadingInfo ( r . data , true ) ;
2019-04-25 16:32:48 +01:00
break ;
case "setInput" :
2019-06-07 13:52:04 +01:00
this . app . debounce ( this . set , 50 , "setInput" , this , [ r . data . inputObj , r . data . silent ] ) ( ) ;
2019-04-25 16:32:48 +01:00
break ;
case "inputAdded" :
this . inputAdded ( r . data . changeTab , r . data . inputNum ) ;
break ;
2019-04-26 15:15:44 +01:00
case "queueInput" :
this . manager . worker . queueInput ( r . data ) ;
break ;
2019-05-29 14:08:37 +01:00
case "queueInputError" :
this . manager . worker . queueInputError ( r . data ) ;
break ;
2019-05-20 16:53:56 +01:00
case "bakeAllInputs" :
this . manager . worker . bakeAllInputs ( r . data ) ;
2019-04-26 15:15:44 +01:00
break ;
2019-04-30 14:15:05 +01:00
case "displayTabSearchResults" :
this . displayTabSearchResults ( r . data ) ;
break ;
2019-05-30 13:28:45 +01:00
case "filterTabError" :
this . app . handleError ( r . data ) ;
break ;
2019-05-02 11:29:54 +01:00
case "setUrl" :
this . setUrl ( r . data ) ;
break ;
2019-05-07 15:34:36 +01:00
case "inputSwitch" :
this . manager . output . inputSwitch ( r . data ) ;
break ;
2019-05-28 11:59:57 +01:00
case "getInput" :
2019-06-04 09:36:50 +01:00
case "getInputNums" :
2019-05-28 11:59:57 +01:00
this . callbacks [ r . data . id ] ( r . data ) ;
break ;
2019-05-28 14:11:05 +01:00
case "removeChefWorker" :
this . removeChefWorker ( ) ;
break ;
2019-08-22 11:53:41 +01:00
case "fileLoaded" :
this . fileLoaded ( r . data . inputNum ) ;
break ;
2019-04-25 16:32:48 +01:00
default :
log . error ( ` Unknown action ${ r . action } . ` ) ;
}
}
2019-03-29 13:29:24 +00:00
/ * *
2019-05-14 16:13:36 +01:00
* Sends a message to the inputWorker to bake all inputs
2019-04-25 16:32:48 +01:00
* /
2019-05-14 16:13:36 +01:00
bakeAll ( ) {
2019-05-28 11:59:57 +01:00
this . app . progress = 0 ;
2019-07-03 13:52:56 +01:00
this . app . debounce ( this . manager . controls . toggleBakeButtonFunction , 20 , "toggleBakeButton" , this , [ "loading" ] ) ;
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
2019-05-14 16:13:36 +01:00
action : "bakeAll"
2019-04-25 16:32:48 +01:00
} ) ;
}
/ * *
* Sets the input in the input area
2019-03-29 13:29:24 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { object } inputData - Object containing the input and its metadata
* @ param { number } inputData . inputNum - The unique inputNum for the selected input
* @ param { string | object } inputData . input - The actual input data
* @ param { string } inputData . name - The name of the input file
* @ param { number } inputData . size - The size in bytes of the input file
* @ param { string } inputData . type - The MIME type of the input file
* @ param { number } inputData . progress - The load progress of the input file
2019-08-22 11:53:41 +01:00
* @ param { boolean } [ silent = false ] - If false , fires the manager statechange event
2019-03-29 13:29:24 +00:00
* /
2019-05-08 13:46:29 +01:00
async set ( inputData , silent = false ) {
return new Promise ( function ( resolve , reject ) {
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
2019-05-08 13:46:29 +01:00
if ( inputData . inputNum !== activeTab ) return ;
const inputText = document . getElementById ( "input-text" ) ;
if ( typeof inputData . input === "string" ) {
inputText . value = inputData . input ;
const fileOverlay = document . getElementById ( "input-file" ) ,
fileName = document . getElementById ( "input-file-name" ) ,
fileSize = document . getElementById ( "input-file-size" ) ,
fileType = document . getElementById ( "input-file-type" ) ,
fileLoaded = document . getElementById ( "input-file-loaded" ) ;
fileOverlay . style . display = "none" ;
fileName . textContent = "" ;
fileSize . textContent = "" ;
fileType . textContent = "" ;
fileLoaded . textContent = "" ;
inputText . style . overflow = "auto" ;
2019-05-15 09:37:07 +01:00
inputText . classList . remove ( "blur" ) ;
2019-06-06 09:33:51 +01:00
inputText . scroll ( 0 , 0 ) ;
2019-05-08 13:46:29 +01:00
const lines = inputData . input . length < ( this . app . options . ioDisplayThreshold * 1024 ) ?
inputData . input . count ( "\n" ) + 1 : null ;
this . setInputInfo ( inputData . input . length , lines ) ;
2019-06-04 11:59:44 +01:00
// Set URL to current input
const inputStr = toBase64 ( inputData . input , "A-Za-z0-9+/" ) ;
if ( inputStr . length > 0 && inputStr . length <= 68267 ) {
this . setUrl ( {
includeInput : true ,
input : inputStr
} ) ;
}
2019-05-20 16:53:56 +01:00
if ( ! silent ) window . dispatchEvent ( this . manager . statechange ) ;
2019-05-08 13:46:29 +01:00
} else {
2019-08-22 11:53:41 +01:00
this . setFile ( inputData , silent ) ;
2019-05-08 13:46:29 +01:00
}
2019-04-25 16:32:48 +01:00
2019-05-08 13:46:29 +01:00
} . bind ( this ) ) ;
2018-05-15 17:36:45 +00:00
}
2019-03-29 13:29:24 +00:00
/ * *
2019-05-14 11:45:13 +01:00
* Displays file details
2019-03-29 13:29:24 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { object } inputData - Object containing the input and its metadata
* @ param { number } inputData . inputNum - The unique inputNum for the selected input
* @ param { string | object } inputData . input - The actual input data
* @ param { string } inputData . name - The name of the input file
* @ param { number } inputData . size - The size in bytes of the input file
* @ param { string } inputData . type - The MIME type of the input file
* @ param { number } inputData . progress - The load progress of the input file
2019-08-22 11:53:41 +01:00
* @ param { boolean } [ silent = true ] - If false , fires the manager statechange event
2019-03-29 13:29:24 +00:00
* /
2019-08-22 11:53:41 +01:00
setFile ( inputData , silent = true ) {
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
2019-04-25 16:32:48 +01:00
if ( inputData . inputNum !== activeTab ) return ;
2019-03-29 13:29:24 +00:00
2019-04-25 16:32:48 +01:00
const fileOverlay = document . getElementById ( "input-file" ) ,
fileName = document . getElementById ( "input-file-name" ) ,
fileSize = document . getElementById ( "input-file-size" ) ,
fileType = document . getElementById ( "input-file-type" ) ,
fileLoaded = document . getElementById ( "input-file-loaded" ) ;
2019-03-29 13:29:24 +00:00
2019-04-25 16:32:48 +01:00
fileOverlay . style . display = "block" ;
fileName . textContent = inputData . name ;
fileSize . textContent = inputData . size + " bytes" ;
fileType . textContent = inputData . type ;
2019-05-08 16:44:11 +01:00
if ( inputData . status === "error" ) {
fileLoaded . textContent = "Error" ;
fileLoaded . style . color = "#FF0000" ;
} else {
fileLoaded . style . color = "" ;
fileLoaded . textContent = inputData . progress + "%" ;
}
2019-03-29 13:29:24 +00:00
2019-05-29 11:46:21 +01:00
this . setInputInfo ( inputData . size , null ) ;
2019-04-25 16:32:48 +01:00
this . displayFilePreview ( inputData ) ;
2019-08-22 11:53:41 +01:00
if ( ! silent ) window . dispatchEvent ( this . manager . statechange ) ;
}
/ * *
* Update file details when a file completes loading
*
* @ param { number } inputNum - The inputNum of the input which has finished loading
* /
fileLoaded ( inputNum ) {
this . manager . tabs . updateInputTabProgress ( inputNum , 100 , 100 ) ;
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
if ( activeTab !== inputNum ) return ;
this . inputWorker . postMessage ( {
action : "setInput" ,
data : {
inputNum : inputNum ,
silent : false
}
} ) ;
this . updateFileProgress ( inputNum , 100 ) ;
2019-03-29 13:29:24 +00:00
}
2018-05-15 17:36:45 +00:00
2019-07-03 11:41:22 +01:00
/ * *
* Render the input thumbnail
* /
async renderFileThumb ( ) {
const activeTab = this . manager . tabs . getActiveInputTab ( ) ,
input = await this . getInputValue ( activeTab ) ,
fileThumb = document . getElementById ( "input-file-thumbnail" ) ;
if ( typeof input === "string" ||
! this . app . options . imagePreview ) {
this . resetFileThumb ( ) ;
return ;
}
const inputArr = new Uint8Array ( input ) ,
type = isImage ( inputArr ) ;
if ( type && type !== "image/tiff" && inputArr . byteLength <= 512000 ) {
// Most browsers don't support displaying TIFFs, so ignore them
// Don't render images over 512000 bytes
const blob = new Blob ( [ inputArr ] , { type : type } ) ,
url = URL . createObjectURL ( blob ) ;
fileThumb . src = url ;
} else {
this . resetFileThumb ( ) ;
}
}
2019-05-01 17:08:36 +01:00
/ * *
* Reset the input thumbnail to the default icon
* /
resetFileThumb ( ) {
const fileThumb = document . getElementById ( "input-file-thumbnail" ) ;
2019-06-06 09:09:48 +01:00
fileThumb . src = require ( "../static/images/file-128x128.png" ) ;
2019-05-01 17:08:36 +01:00
}
2018-05-15 17:36:45 +00:00
/ * *
2019-04-25 16:32:48 +01:00
* Shows a chunk of the file in the input behind the file overlay
2018-05-15 17:36:45 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { Object } inputData - Object containing the input data
* @ param { number } inputData . inputNum - The inputNum of the file being displayed
* @ param { ArrayBuffer } inputData . input - The actual input to display
2018-05-15 17:36:45 +00:00
* /
2019-04-25 16:32:48 +01:00
displayFilePreview ( inputData ) {
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ,
2019-04-25 16:32:48 +01:00
input = inputData . input ,
2019-07-03 11:41:22 +01:00
inputText = document . getElementById ( "input-text" ) ;
2019-04-25 16:32:48 +01:00
if ( inputData . inputNum !== activeTab ) return ;
inputText . style . overflow = "hidden" ;
inputText . classList . add ( "blur" ) ;
2019-05-01 17:08:36 +01:00
inputText . value = Utils . printable ( Utils . arrayBufferToStr ( input . slice ( 0 , 4096 ) ) ) ;
2019-07-03 11:41:22 +01:00
this . renderFileThumb ( ) ;
2019-05-01 17:08:36 +01:00
2018-05-15 17:36:45 +00:00
}
2019-03-29 13:29:24 +00:00
/ * *
2019-05-14 11:45:13 +01:00
* Updates the displayed load progress for a file
2019-03-29 13:29:24 +00:00
*
* @ param { number } inputNum
2019-05-14 11:45:13 +01:00
* @ param { number | string } progress - Either a number or "error"
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
updateFileProgress ( inputNum , progress ) {
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
2019-04-25 16:32:48 +01:00
if ( inputNum !== activeTab ) return ;
const fileLoaded = document . getElementById ( "input-file-loaded" ) ;
2019-05-10 13:45:27 +01:00
let oldProgress = fileLoaded . textContent ;
if ( oldProgress !== "Error" ) {
oldProgress = parseInt ( oldProgress . replace ( "%" , "" ) , 10 ) ;
}
2019-05-08 16:44:11 +01:00
if ( progress === "error" ) {
fileLoaded . textContent = "Error" ;
fileLoaded . style . color = "#FF0000" ;
} else {
fileLoaded . textContent = progress + "%" ;
fileLoaded . style . color = "" ;
}
2019-03-29 13:29:24 +00:00
}
2018-05-15 17:36:45 +00:00
/ * *
2019-05-14 11:45:13 +01:00
* Updates the stored value for the specified inputNum
2018-05-15 17:36:45 +00:00
*
2019-03-29 13:29:24 +00:00
* @ param { number } inputNum
2019-04-25 16:32:48 +01:00
* @ param { string | ArrayBuffer } value
2019-07-25 15:16:07 +01:00
* @ param { boolean } [ force = false ] - If true , forces the value to be updated even if the type is different to the currently stored type
2018-05-15 17:36:45 +00:00
* /
2019-07-25 15:16:07 +01:00
updateInputValue ( inputNum , value , force = false ) {
2019-05-02 13:54:15 +01:00
let includeInput = false ;
const recipeStr = toBase64 ( value , "A-Za-z0-9+/" ) ; // B64 alphabet with no padding
if ( recipeStr . length > 0 && recipeStr . length <= 68267 ) {
includeInput = true ;
}
this . setUrl ( {
includeInput : includeInput ,
input : recipeStr
} ) ;
2019-05-07 15:34:36 +01:00
2019-06-03 11:20:06 +01:00
// Value is either a string set by the input or an ArrayBuffer from a LoaderWorker,
// so is safe to use typeof === "string"
const transferable = ( typeof value !== "string" ) ? [ value ] : undefined ;
this . inputWorker . postMessage ( {
action : "updateInputValue" ,
data : {
inputNum : inputNum ,
2019-07-25 15:16:07 +01:00
value : value ,
force : force
2019-06-03 11:20:06 +01:00
}
} , transferable ) ;
2019-05-07 15:34:36 +01:00
}
/ * *
2019-05-14 11:45:13 +01:00
* Updates the . data property for the input of the specified inputNum .
* Used for switching the output into the input
2019-05-07 15:34:36 +01:00
*
2019-05-14 11:45:13 +01:00
* @ param { number } inputNum - The inputNum of the input we ' re changing
* @ param { object } inputData - The new data object
2019-05-07 15:34:36 +01:00
* /
updateInputObj ( inputNum , inputData ) {
2019-06-03 11:20:06 +01:00
const transferable = ( typeof inputData !== "string" ) ? [ inputData . fileBuffer ] : undefined ;
this . inputWorker . postMessage ( {
action : "updateInputObj" ,
data : {
inputNum : inputNum ,
data : inputData
}
} , transferable ) ;
2019-03-29 13:29:24 +00:00
}
2018-05-15 17:36:45 +00:00
2019-05-28 11:59:57 +01:00
/ * *
* Get the input value for the specified input
*
* @ param { number } inputNum - The inputNum of the input to retrieve from the inputWorker
* @ returns { ArrayBuffer | string }
* /
async getInputValue ( inputNum ) {
return await new Promise ( resolve => {
this . getInput ( inputNum , false , r => {
resolve ( r . data ) ;
} ) ;
} ) ;
}
/ * *
* Get the input object for the specified input
*
* @ param { number } inputNum - The inputNum of the input to retrieve from the inputWorker
* @ returns { object }
* /
async getInputObj ( inputNum ) {
return await new Promise ( resolve => {
this . getInput ( inputNum , true , r => {
resolve ( r . data ) ;
} ) ;
} ) ;
}
/ * *
* Gets the specified input from the inputWorker
*
* @ param { number } inputNum - The inputNum of the data to get
* @ param { boolean } getObj - If true , get the actual data object of the input instead of just the value
* @ param { Function } callback - The callback to execute when the input is returned
* @ returns { ArrayBuffer | string | object }
* /
getInput ( inputNum , getObj , callback ) {
const id = this . callbackID ++ ;
this . callbacks [ id ] = callback ;
this . inputWorker . postMessage ( {
action : "getInput" ,
data : {
inputNum : inputNum ,
getObj : getObj ,
id : id
}
} ) ;
}
2019-06-04 09:36:50 +01:00
/ * *
* Gets the number of inputs from the inputWorker
*
* @ returns { object }
* /
async getInputNums ( ) {
return await new Promise ( resolve => {
this . getNums ( r => {
2019-06-06 16:33:35 +01:00
resolve ( r ) ;
2019-06-04 09:36:50 +01:00
} ) ;
} ) ;
}
/ * *
* Gets a list of inputNums from the inputWorker , and sends
* them back to the specified callback
* /
getNums ( callback ) {
const id = this . callbackID ++ ;
this . callbacks [ id ] = callback ;
this . inputWorker . postMessage ( {
action : "getInputNums" ,
data : id
} ) ;
}
2019-03-29 13:29:24 +00:00
/ * *
2019-04-25 16:32:48 +01:00
* Displays information about the input .
*
* @ param { number } length - The length of the current input string
* @ param { number } lines - The number of the lines in the current input string
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
setInputInfo ( length , lines ) {
let width = length . toString ( ) . length . toLocaleString ( ) ;
width = width < 2 ? 2 : width ;
2019-03-22 15:10:19 +00:00
2019-04-25 16:32:48 +01:00
const lengthStr = length . toString ( ) . padStart ( width , " " ) . replace ( / /g , " " ) ;
let msg = "length: " + lengthStr ;
2019-03-22 15:10:19 +00:00
2019-04-25 16:32:48 +01:00
if ( typeof lines === "number" ) {
const linesStr = lines . toString ( ) . padStart ( width , " " ) . replace ( / /g , " " ) ;
msg += "<br>lines: " + linesStr ;
2019-03-29 13:29:24 +00:00
}
2019-04-25 16:32:48 +01:00
document . getElementById ( "input-info" ) . innerHTML = msg ;
2018-05-15 17:36:45 +00:00
2019-04-25 16:32:48 +01:00
}
2018-05-15 17:36:45 +00:00
2019-05-20 16:53:56 +01:00
/ * *
* Handler for input change events .
* Debounces the input so we don ' t call autobake too often .
*
* @ param { event } e
* /
debounceInputChange ( e ) {
2019-06-03 14:59:41 +01:00
this . app . debounce ( this . inputChange , 50 , "inputChange" , this , [ e ] ) ( ) ;
2019-05-20 16:53:56 +01:00
}
2018-05-15 17:36:45 +00:00
/ * *
2019-05-14 11:45:13 +01:00
* Handler for input change events .
* Updates the value stored in the inputWorker
2018-05-15 17:36:45 +00:00
*
* @ param { event } e
*
* @ fires Manager # statechange
* /
inputChange ( e ) {
2019-03-29 13:29:24 +00:00
// Ignore this function if the input is a file
2019-04-25 16:32:48 +01:00
const fileOverlay = document . getElementById ( "input-file" ) ;
if ( fileOverlay . style . display === "block" ) return ;
2018-05-15 17:36:45 +00:00
2019-05-07 14:20:18 +01:00
// Remove highlighting from input and output panes as the offsets might be different now
this . manager . highlighter . removeHighlights ( ) ;
2019-04-25 16:32:48 +01:00
const textArea = document . getElementById ( "input-text" ) ;
const value = ( textArea . value !== undefined ) ? textArea . value : "" ;
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
2018-05-15 17:36:45 +00:00
this . app . progress = 0 ;
2019-04-25 16:32:48 +01:00
const lines = value . length < ( this . app . options . ioDisplayThreshold * 1024 ) ?
( value . count ( "\n" ) + 1 ) : null ;
this . setInputInfo ( value . length , lines ) ;
this . updateInputValue ( activeTab , value ) ;
2019-06-10 13:08:03 +01:00
this . manager . tabs . updateInputTabHeader ( activeTab , value . replace ( /[\n\r]/g , "" ) . slice ( 0 , 100 ) ) ;
2018-05-15 17:36:45 +00:00
if ( e && this . badKeys . indexOf ( e . keyCode ) < 0 ) {
// Fire the statechange event as the input has been modified
window . dispatchEvent ( this . manager . statechange ) ;
}
}
2019-04-30 11:48:01 +01:00
/ * *
* Handler for input paste events
* Checks that the size of the input is below the display limit , otherwise treats it as a file / blob
*
* @ param { event } e
* /
2019-08-22 11:53:41 +01:00
async inputPaste ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2019-04-30 11:48:01 +01:00
const pastedData = e . clipboardData . getData ( "Text" ) ;
2019-08-22 11:53:41 +01:00
const preserve = await this . preserveCarriageReturns ( pastedData ) ;
if ( pastedData . length < ( this . app . options . ioDisplayThreshold * 1024 ) && ! preserve ) {
2019-05-14 11:45:13 +01:00
// Pasting normally fires the inputChange() event before
// changing the value, so instead change it here ourselves
// and manually fire inputChange()
2019-06-06 10:26:16 +01:00
const inputText = document . getElementById ( "input-text" ) ;
const selStart = inputText . selectionStart ;
const selEnd = inputText . selectionEnd ;
const startVal = inputText . value . slice ( 0 , selStart ) ;
const endVal = inputText . value . slice ( selEnd ) ;
inputText . value = startVal + pastedData + endVal ;
inputText . setSelectionRange ( selStart + pastedData . length , selStart + pastedData . length ) ;
2019-05-20 16:53:56 +01:00
this . debounceInputChange ( e ) ;
2019-04-30 11:48:01 +01:00
} else {
const file = new File ( [ pastedData ] , "PastedData" , {
type : "text/plain" ,
lastModified : Date . now ( )
} ) ;
this . loadUIFiles ( [ file ] ) ;
return false ;
}
}
2018-05-15 17:36:45 +00:00
/ * *
* Handler for input dragover events .
* Gives the user a visual cue to show that items can be dropped here .
*
* @ param { event } e
* /
inputDragover ( e ) {
// This will be set if we're dragging an operation
if ( e . dataTransfer . effectAllowed === "move" )
return false ;
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
e . target . closest ( "#input-text,#input-file" ) . classList . add ( "dropping-file" ) ;
}
/ * *
* Handler for input dragleave events .
* Removes the visual cue .
*
* @ param { event } e
* /
inputDragleave ( e ) {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
2019-03-22 15:10:19 +00:00
e . target . closest ( "#input-text,#input-file" ) . classList . remove ( "dropping-file" ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Handler for input drop events .
2019-04-25 16:32:48 +01:00
* Loads the dragged data .
2018-05-15 17:36:45 +00:00
*
* @ param { event } e
* /
inputDrop ( e ) {
// This will be set if we're dragging an operation
if ( e . dataTransfer . effectAllowed === "move" )
return false ;
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
const text = e . dataTransfer . getData ( "Text" ) ;
2019-03-22 15:10:19 +00:00
e . target . closest ( "#input-text,#input-file" ) . classList . remove ( "dropping-file" ) ;
2018-05-15 17:36:45 +00:00
if ( text ) {
2019-05-14 11:45:13 +01:00
// Append the text to the current input and fire inputChange()
document . getElementById ( "input-text" ) . value += text ;
this . inputChange ( e ) ;
2018-05-15 17:36:45 +00:00
return ;
}
2019-03-22 15:10:19 +00:00
if ( e . dataTransfer . files && e . dataTransfer . files . length > 0 ) {
2019-03-29 13:29:24 +00:00
this . loadUIFiles ( e . dataTransfer . files ) ;
2018-05-15 17:36:45 +00:00
}
}
2019-01-18 15:07:19 +00:00
/ * *
* Handler for open input button events
* Loads the opened data into the input textarea
*
* @ param { event } e
* /
inputOpen ( e ) {
e . preventDefault ( ) ;
2019-03-22 15:10:19 +00:00
2019-04-04 13:13:21 +01:00
if ( e . target . files . length > 0 ) {
this . loadUIFiles ( e . target . files ) ;
e . target . value = "" ;
2019-03-22 15:10:19 +00:00
}
2019-01-18 15:07:19 +00:00
}
2019-08-22 11:53:41 +01:00
/ * *
* Checks if an input contains carriage returns .
* If a CR is detected , checks if the preserve CR option has been set ,
* and if not , asks the user for their preference .
*
* @ param { string } input - The input to be checked
* @ returns { boolean } - If true , the input contains a CR which should be
* preserved , so display an overlay so it can ' t be edited
* /
async preserveCarriageReturns ( input ) {
if ( input . indexOf ( "\r" ) >= 0 ) {
const optionsStr = "This behaviour can be changed in the <a href='#' onclick='document.getElementById(\"options\").click()'>options</a>" ;
if ( ! this . app . options . userSetCR ) {
let preserve = await new Promise ( function ( resolve , reject ) {
this . app . confirm (
"Carriage Return Detected" ,
"A carriage return was detected in your input. As HTML textareas can't display carriage returns, editing must be turned off to preserve them. <br>Alternatively, you can enable editing but your carriage returns will not be preserved.<br><br>This preference will be saved, and can be toggled in the options." ,
"Preserve Carriage Returns" ,
"Enable Editing" , resolve , this ) ;
} . bind ( this ) ) ;
if ( preserve === undefined ) {
this . app . alert ( ` Not preserving carriage returns. ${ optionsStr } ` , 4000 ) ;
preserve = false ;
}
this . manager . options . updateOption ( "preserveCR" , preserve ) ;
this . manager . options . updateOption ( "userSetCR" , true ) ;
} else {
if ( this . app . options . preserveCR ) {
2019-08-22 11:56:14 +01:00
this . app . alert ( ` A carriage return was detected in your input, so editing has been disabled to preserve it. ${ optionsStr } ` , 6000 ) ;
2019-08-22 11:53:41 +01:00
} else {
2019-08-22 11:56:14 +01:00
this . app . alert ( ` A carriage return was detected in your input. Editing is remaining enabled, but any carriage returns will be removed. ${ optionsStr } ` , 6000 ) ;
2019-08-22 11:53:41 +01:00
}
}
return this . app . options . preserveCR ;
} else {
return false ;
}
}
2018-05-15 17:36:45 +00:00
/ * *
2019-04-26 15:15:44 +01:00
* Load files from the UI into the inputWorker
2018-05-15 17:36:45 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { FileList } files - The list of files to be loaded
2018-05-15 17:36:45 +00:00
* /
2019-03-29 13:29:24 +00:00
loadUIFiles ( files ) {
2019-04-25 16:32:48 +01:00
const numFiles = files . length ;
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
2019-04-25 16:32:48 +01:00
log . debug ( ` Loading ${ numFiles } files. ` ) ;
2018-05-15 17:36:45 +00:00
2019-05-14 11:45:13 +01:00
// Display the number of files as pending so the user
// knows that we've received the files.
2019-04-26 15:15:44 +01:00
this . showLoadingInfo ( {
pending : numFiles ,
loading : 0 ,
loaded : 0 ,
total : numFiles ,
activeProgress : {
inputNum : activeTab ,
progress : 0
}
2019-04-30 13:18:22 +01:00
} , false ) ;
2019-04-26 15:15:44 +01:00
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
action : "loadUIFiles" ,
2019-04-26 15:15:44 +01:00
data : {
files : files ,
activeTab : activeTab
}
2019-04-25 16:32:48 +01:00
} ) ;
2018-05-15 17:36:45 +00:00
}
2019-04-25 16:32:48 +01:00
/ * *
* Handler for open input button click .
* Opens the open file dialog .
* /
inputOpenClick ( ) {
document . getElementById ( "open-file" ) . click ( ) ;
}
2019-03-29 13:29:24 +00:00
2019-04-25 16:32:48 +01:00
/ * *
* Handler for open folder button click
* Opens the open folder dialog .
* /
folderOpenClick ( ) {
document . getElementById ( "open-folder" ) . click ( ) ;
2019-02-11 14:48:25 +00:00
}
2018-05-15 17:36:45 +00:00
/ * *
2019-05-14 11:45:13 +01:00
* Display the loaded files information in the input header .
* Also , sets the background of the Input header to be a progress bar
* @ param { object } loadedData - Object containing the loading information
* @ param { number } loadedData . pending - How many files are pending ( not loading / loaded )
* @ param { number } loadedData . loading - How many files are being loaded
* @ param { number } loadedData . loaded - How many files have been loaded
* @ param { number } loadedData . total - The total number of files
* @ param { object } loadedData . activeProgress - Object containing data about the active tab
* @ param { number } loadedData . activeProgress . inputNum - The inputNum of the input the progress is for
* @ param { number } loadedData . activeProgress . progress - The loading progress of the active input
* @ param { boolean } autoRefresh - If true , automatically refreshes the loading info by sending a message to the inputWorker after 100 ms
2018-05-15 17:36:45 +00:00
* /
2019-04-30 13:18:22 +01:00
showLoadingInfo ( loadedData , autoRefresh ) {
2019-04-25 16:32:48 +01:00
const pending = loadedData . pending ;
const loading = loadedData . loading ;
const loaded = loadedData . loaded ;
const total = loadedData . total ;
2019-05-30 13:28:45 +01:00
let width = total . toLocaleString ( ) . length ;
2019-03-29 13:29:24 +00:00
width = width < 2 ? 2 : width ;
2019-05-30 13:28:45 +01:00
const totalStr = total . toLocaleString ( ) . padStart ( width , " " ) . replace ( / /g , " " ) ;
2019-06-03 11:20:06 +01:00
let msg = "total: " + totalStr ;
2019-03-29 13:29:24 +00:00
2019-05-30 13:28:45 +01:00
const loadedStr = loaded . toLocaleString ( ) . padStart ( width , " " ) . replace ( / /g , " " ) ;
2019-06-03 11:20:06 +01:00
msg += "<br>loaded: " + loadedStr ;
2019-03-29 13:29:24 +00:00
2019-04-25 16:32:48 +01:00
if ( pending > 0 ) {
2019-05-30 13:28:45 +01:00
const pendingStr = pending . toLocaleString ( ) . padStart ( width , " " ) . replace ( / /g , " " ) ;
2019-06-03 11:20:06 +01:00
msg += "<br>pending: " + pendingStr ;
2019-04-25 16:32:48 +01:00
} else if ( loading > 0 ) {
2019-05-30 13:28:45 +01:00
const loadingStr = loading . toLocaleString ( ) . padStart ( width , " " ) . replace ( / /g , " " ) ;
2019-06-03 11:20:06 +01:00
msg += "<br>loading: " + loadingStr ;
2019-03-29 13:29:24 +00:00
}
2019-06-04 11:42:27 +01:00
const inFiles = document . getElementById ( "input-files-info" ) ;
if ( total > 1 ) {
inFiles . innerHTML = msg ;
inFiles . style . display = "" ;
} else {
inFiles . style . display = "none" ;
}
2018-05-15 17:36:45 +00:00
2019-04-25 16:32:48 +01:00
this . updateFileProgress ( loadedData . activeProgress . inputNum , loadedData . activeProgress . progress ) ;
2019-04-26 15:15:44 +01:00
2019-05-02 15:44:31 +01:00
const inputTitle = document . getElementById ( "input" ) . firstElementChild ;
if ( loaded < total ) {
2019-05-07 12:00:37 +01:00
const percentComplete = loaded / total * 100 ;
2019-05-02 15:44:31 +01:00
inputTitle . style . background = ` linear-gradient(to right, var(--title-background-colour) ${ percentComplete } %, var(--primary-background-colour) ${ percentComplete } %) ` ;
} else {
inputTitle . style . background = "" ;
}
2019-04-30 13:18:22 +01:00
if ( loaded < total && autoRefresh ) {
2019-04-26 15:15:44 +01:00
setTimeout ( function ( ) {
this . inputWorker . postMessage ( {
action : "getLoadProgress" ,
2019-06-06 16:33:35 +01:00
data : this . manager . tabs . getActiveInputTab ( )
2019-04-26 15:15:44 +01:00
} ) ;
} . bind ( this ) , 100 ) ;
}
2019-03-22 15:10:19 +00:00
}
2019-03-21 12:31:01 +00:00
2019-03-22 15:10:19 +00:00
/ * *
2019-05-14 11:45:13 +01:00
* Change to a different tab .
2019-03-22 15:10:19 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { number } inputNum - The inputNum of the tab to change to
* @ param { boolean } [ changeOutput = false ] - If true , also changes the output
2019-03-22 15:10:19 +00:00
* /
2019-04-25 16:32:48 +01:00
changeTab ( inputNum , changeOutput ) {
2019-06-06 16:53:58 +01:00
if ( this . manager . tabs . getInputTabItem ( inputNum ) !== null ) {
this . manager . tabs . changeInputTab ( inputNum ) ;
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
2019-06-06 16:33:35 +01:00
action : "setInput" ,
2019-04-25 16:32:48 +01:00
data : {
inputNum : inputNum ,
2019-06-06 16:33:35 +01:00
silent : true
2019-03-29 13:29:24 +00:00
}
2019-04-25 16:32:48 +01:00
} ) ;
2019-03-29 13:29:24 +00:00
} else {
2019-06-06 16:33:35 +01:00
const minNum = Math . min ( ... this . manager . tabs . getInputTabList ( ) ) ;
let direction = "right" ;
if ( inputNum < minNum ) {
direction = "left" ;
}
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
2019-06-06 16:33:35 +01:00
action : "refreshTabs" ,
2019-04-26 15:15:44 +01:00
data : {
inputNum : inputNum ,
2019-06-06 16:33:35 +01:00
direction : direction
2019-04-26 15:15:44 +01:00
}
2019-04-25 16:32:48 +01:00
} ) ;
2019-03-27 13:48:54 +00:00
}
2019-04-03 12:00:47 +01:00
if ( changeOutput ) {
this . manager . output . changeTab ( inputNum , false ) ;
}
2019-03-21 12:31:01 +00:00
}
/ * *
2019-04-25 16:32:48 +01:00
* Handler for clicking on a tab
2019-03-21 12:31:01 +00:00
*
* @ param { event } mouseEvent
* /
2019-03-22 15:10:19 +00:00
changeTabClick ( mouseEvent ) {
2019-04-25 16:32:48 +01:00
if ( ! mouseEvent . target ) return ;
2019-04-04 13:13:21 +01:00
const tabNum = mouseEvent . target . parentElement . getAttribute ( "inputNum" ) ;
2019-04-25 16:32:48 +01:00
if ( tabNum >= 0 ) {
2019-04-03 12:00:47 +01:00
this . changeTab ( parseInt ( tabNum , 10 ) , this . app . options . syncTabs ) ;
2019-03-29 13:29:24 +00:00
}
}
2019-03-21 12:31:01 +00:00
2019-04-30 13:18:22 +01:00
/ * *
* Handler for clear all IO events .
2019-05-14 11:45:13 +01:00
* Resets the input , output and info areas , and creates a new inputWorker
2019-04-30 13:18:22 +01:00
* /
clearAllIoClick ( ) {
2019-05-20 16:53:56 +01:00
this . manager . worker . cancelBake ( true , true ) ;
this . manager . worker . loaded = false ;
2019-04-30 13:18:22 +01:00
this . manager . output . removeAllOutputs ( ) ;
2019-05-16 13:04:04 +01:00
this . manager . output . terminateZipWorker ( ) ;
2019-04-30 13:18:22 +01:00
2019-05-07 14:20:18 +01:00
this . manager . highlighter . removeHighlights ( ) ;
getSelection ( ) . removeAllRanges ( ) ;
2019-07-24 14:22:56 +01:00
const tabsList = document . getElementById ( "input-tabs" ) ;
const tabsListChildren = tabsList . children ;
tabsList . classList . remove ( "tabs-left" ) ;
tabsList . classList . remove ( "tabs-right" ) ;
for ( let i = tabsListChildren . length - 1 ; i >= 0 ; i -- ) {
tabsListChildren . item ( i ) . remove ( ) ;
2019-04-30 13:18:22 +01:00
}
2019-05-31 08:54:01 +01:00
this . showLoadingInfo ( {
pending : 0 ,
loading : 0 ,
loaded : 1 ,
total : 1 ,
activeProgress : {
inputNum : 1 ,
progress : 100
}
} ) ;
2019-04-30 13:18:22 +01:00
this . setupInputWorker ( ) ;
2019-05-23 15:29:58 +01:00
this . manager . worker . setupChefWorker ( ) ;
2019-04-30 13:18:22 +01:00
this . addInput ( true ) ;
2019-05-21 11:24:44 +01:00
this . bakeAll ( ) ;
2019-04-30 13:18:22 +01:00
}
2019-04-25 16:32:48 +01:00
2019-04-30 13:34:00 +01:00
/ * *
* Handler for clear IO click event .
* Resets the input for the current tab
* /
clearIoClick ( ) {
2019-06-06 16:33:35 +01:00
const inputNum = this . manager . tabs . getActiveInputTab ( ) ;
2019-04-30 13:34:00 +01:00
if ( inputNum === - 1 ) return ;
2019-05-07 14:20:18 +01:00
this . manager . highlighter . removeHighlights ( ) ;
getSelection ( ) . removeAllRanges ( ) ;
2019-07-25 15:16:07 +01:00
this . updateInputValue ( inputNum , "" , true ) ;
2019-04-30 13:34:00 +01:00
this . set ( {
inputNum : inputNum ,
input : ""
} ) ;
2019-06-06 16:33:35 +01:00
this . manager . tabs . updateInputTabHeader ( inputNum , "" ) ;
2019-04-30 13:34:00 +01:00
}
2019-04-25 16:32:48 +01:00
2019-03-29 13:29:24 +00:00
/ * *
2019-04-25 16:32:48 +01:00
* Sets the console log level in the worker .
2019-03-29 13:29:24 +00:00
*
2019-04-25 16:32:48 +01:00
* @ param { string } level
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
setLogLevel ( level ) {
if ( ! this . inputWorker ) return ;
this . inputWorker . postMessage ( {
action : "setLogLevel" ,
data : log . getLevel ( )
} ) ;
2019-03-29 13:29:24 +00:00
}
/ * *
2019-04-25 16:32:48 +01:00
* Sends a message to the inputWorker to add a new input .
2019-05-14 11:45:13 +01:00
* @ param { boolean } [ changeTab = false ] - If true , changes the tab to the new input
2019-04-25 16:32:48 +01:00
* /
2019-04-30 13:18:22 +01:00
addInput ( changeTab = false ) {
2019-04-25 16:32:48 +01:00
if ( ! this . inputWorker ) return ;
this . inputWorker . postMessage ( {
2019-04-30 13:18:22 +01:00
action : "addInput" ,
data : changeTab
2019-04-25 16:32:48 +01:00
} ) ;
}
2019-04-30 13:18:22 +01:00
/ * *
2019-05-14 11:45:13 +01:00
* Handler for add input button clicked .
2019-04-30 13:18:22 +01:00
* /
addInputClick ( ) {
this . addInput ( true ) ;
}
2019-04-25 16:32:48 +01:00
/ * *
* Handler for when the inputWorker adds a new input
2019-03-29 13:29:24 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { boolean } changeTab - Whether or not to change to the new input tab
* @ param { number } inputNum - The new inputNum
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
inputAdded ( changeTab , inputNum ) {
2019-05-08 14:47:05 +01:00
this . addTab ( inputNum , changeTab ) ;
2019-05-28 14:11:05 +01:00
2019-06-04 11:42:27 +01:00
this . manager . output . addOutput ( inputNum , changeTab ) ;
2019-05-28 14:11:05 +01:00
this . manager . worker . addChefWorker ( ) ;
}
/ * *
* Remove a chefWorker from the workerWaiter if we remove an input
* /
removeChefWorker ( ) {
const workerIdx = this . manager . worker . getInactiveChefWorker ( true ) ;
const worker = this . manager . worker . chefWorkers [ workerIdx ] ;
this . manager . worker . removeChefWorker ( worker ) ;
2019-03-22 15:10:19 +00:00
}
2019-03-21 12:31:01 +00:00
2019-03-22 15:10:19 +00:00
/ * *
2019-05-08 14:47:05 +01:00
* Adds a new input tab .
2019-03-22 15:10:19 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { number } inputNum - The inputNum of the new tab
* @ param { boolean } [ changeTab = true ] - If true , changes to the new tab once it ' s been added
2019-03-22 15:10:19 +00:00
* /
2019-05-08 14:47:05 +01:00
addTab ( inputNum , changeTab = true ) {
2019-06-06 16:33:35 +01:00
const tabsWrapper = document . getElementById ( "input-tabs" ) ,
numTabs = tabsWrapper . children . length ;
2019-05-08 14:47:05 +01:00
2019-06-06 16:33:35 +01:00
if ( ! this . manager . tabs . getInputTabItem ( inputNum ) && numTabs < this . maxTabs ) {
const newTab = this . manager . tabs . createInputTabElement ( inputNum , changeTab ) ;
2019-05-08 14:47:05 +01:00
tabsWrapper . appendChild ( newTab ) ;
if ( numTabs > 0 ) {
2019-06-06 16:33:35 +01:00
this . manager . tabs . showTabBar ( ) ;
2019-05-08 14:47:05 +01:00
} else {
2019-06-06 16:33:35 +01:00
this . manager . tabs . hideTabBar ( ) ;
2019-05-08 14:47:05 +01:00
}
2019-05-08 16:44:11 +01:00
this . inputWorker . postMessage ( {
action : "updateTabHeader" ,
data : inputNum
} ) ;
2019-05-30 14:32:05 +01:00
} else if ( numTabs === this . maxTabs ) {
// Can't create a new tab
2019-07-10 14:58:07 +01:00
document . getElementById ( "input-tabs" ) . lastElementChild . classList . add ( "tabs-right" ) ;
2019-05-08 14:47:05 +01:00
}
2019-06-06 16:33:35 +01:00
if ( changeTab ) this . changeTab ( inputNum , false ) ;
}
/ * *
* Refreshes the input tabs , and changes to activeTab
*
* @ param { number [ ] } nums - The inputNums to be displayed as tabs
* @ param { number } activeTab - The tab to change to
* @ param { boolean } tabsLeft - True if there are input tabs to the left of the displayed tabs
* @ param { boolean } tabsRight - True if there are input tabs to the right of the displayed tabs
* /
refreshTabs ( nums , activeTab , tabsLeft , tabsRight ) {
this . manager . tabs . refreshInputTabs ( nums , activeTab , tabsLeft , tabsRight ) ;
2019-06-07 13:52:04 +01:00
this . inputWorker . postMessage ( {
action : "setInput" ,
data : {
inputNum : activeTab ,
silent : true
}
} ) ;
2019-05-08 14:47:05 +01:00
}
2019-03-29 13:29:24 +00:00
/ * *
2019-04-25 16:32:48 +01:00
* Sends a message to the inputWorker to remove an input .
* If the input tab is on the screen , refreshes the tabs
2019-03-29 13:29:24 +00:00
*
2019-05-14 11:45:13 +01:00
* @ param { number } inputNum - The inputNum of the tab to be removed
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
removeInput ( inputNum ) {
let refresh = false ;
2019-06-06 16:33:35 +01:00
if ( this . manager . tabs . getInputTabItem ( inputNum ) !== null ) {
2019-04-25 16:32:48 +01:00
refresh = true ;
2019-03-29 13:29:24 +00:00
}
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
action : "removeInput" ,
data : {
inputNum : inputNum ,
2019-05-28 14:11:05 +01:00
refreshTabs : refresh ,
removeChefWorker : true
2019-04-25 16:32:48 +01:00
}
} ) ;
2019-04-30 13:34:00 +01:00
this . manager . output . removeTab ( inputNum ) ;
2019-03-29 13:29:24 +00:00
}
/ * *
2019-04-25 16:32:48 +01:00
* Handler for clicking on a remove tab button
2019-05-14 11:45:13 +01:00
*
2019-04-25 16:32:48 +01:00
* @ param { event } mouseEvent
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
removeTabClick ( mouseEvent ) {
if ( ! mouseEvent . target ) {
return ;
}
2019-05-14 11:45:13 +01:00
const tabNum = mouseEvent . target . closest ( "button" ) . parentElement . getAttribute ( "inputNum" ) ;
2019-04-25 16:32:48 +01:00
if ( tabNum ) {
this . removeInput ( parseInt ( tabNum , 10 ) ) ;
2019-03-21 12:31:01 +00:00
}
}
2019-06-03 16:10:05 +01:00
/ * *
* Handler for scrolling on the input 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
}
2019-06-03 16:10:05 +01:00
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
}
/ * *
* 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-03-29 13:29:24 +00:00
/ * *
2019-06-04 11:42:27 +01:00
* Changes to the next ( right ) tab
2019-03-29 13:29:24 +00:00
* /
2019-04-25 16:32:48 +01:00
changeTabRight ( ) {
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
2019-06-06 16:53:58 +01:00
if ( activeTab === - 1 ) return ;
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
action : "changeTabRight" ,
data : {
2019-06-06 16:33:35 +01:00
activeTab : activeTab
2019-04-25 16:32:48 +01:00
}
2019-03-29 13:29:24 +00:00
} ) ;
2019-04-25 16:32:48 +01:00
}
2019-03-29 13:29:24 +00:00
2019-04-25 16:32:48 +01:00
/ * *
2019-06-04 11:42:27 +01:00
* Changes to the previous ( left ) tab
2019-04-25 16:32:48 +01:00
* /
changeTabLeft ( ) {
2019-06-06 16:33:35 +01:00
const activeTab = this . manager . tabs . getActiveInputTab ( ) ;
2019-06-06 16:53:58 +01:00
if ( activeTab === - 1 ) return ;
2019-04-25 16:32:48 +01:00
this . inputWorker . postMessage ( {
action : "changeTabLeft" ,
data : {
2019-06-06 16:33:35 +01:00
activeTab : activeTab
2019-04-25 16:32:48 +01:00
}
} ) ;
2019-03-29 13:29:24 +00:00
}
2019-04-26 15:15:44 +01:00
/ * *
* Handler for go to tab button clicked
* /
2019-06-04 09:36:50 +01:00
async goToTab ( ) {
const inputNums = await this . getInputNums ( ) ;
2019-06-06 16:33:35 +01:00
let tabNum = window . prompt ( ` Enter tab number ( ${ inputNums . min } - ${ inputNums . max } ): ` , this . manager . tabs . getActiveInputTab ( ) . toString ( ) ) ;
2019-06-04 09:41:47 +01:00
if ( tabNum === null ) return ;
tabNum = parseInt ( tabNum , 10 ) ;
2019-04-26 15:15:44 +01:00
this . changeTab ( tabNum , this . app . options . syncTabs ) ;
}
2019-04-30 14:15:05 +01:00
/ * *
* Handler for find tab button clicked
* /
findTab ( ) {
this . filterTabSearch ( ) ;
$ ( "#input-tab-modal" ) . modal ( ) ;
}
/ * *
* Sends a message to the inputWorker to search the inputs
* /
filterTabSearch ( ) {
const showPending = document . getElementById ( "input-show-pending" ) . checked ;
const showLoading = document . getElementById ( "input-show-loading" ) . checked ;
const showLoaded = document . getElementById ( "input-show-loaded" ) . checked ;
2019-05-30 13:28:45 +01:00
const filter = document . getElementById ( "input-filter" ) . value ;
const filterType = document . getElementById ( "input-filter-button" ) . innerText ;
2019-04-30 14:15:05 +01:00
const numResults = parseInt ( document . getElementById ( "input-num-results" ) . value , 10 ) ;
this . inputWorker . postMessage ( {
action : "filterTabs" ,
data : {
showPending : showPending ,
showLoading : showLoading ,
showLoaded : showLoaded ,
2019-05-30 13:28:45 +01:00
filter : filter ,
filterType : filterType ,
2019-04-30 14:15:05 +01:00
numResults : numResults
}
} ) ;
}
2019-05-30 13:28:45 +01:00
/ * *
* Handle when an option in the filter drop down box is clicked
*
* @ param { event } mouseEvent
* /
filterOptionClick ( mouseEvent ) {
document . getElementById ( "input-filter-button" ) . innerText = mouseEvent . target . innerText ;
this . filterTabSearch ( ) ;
}
2019-04-30 14:15:05 +01:00
/ * *
* Displays the results of a tab search in the find tab box
*
2019-05-14 11:45:13 +01:00
* @ param { object [ ] } results - List of results objects
2019-04-30 14:15:05 +01:00
*
* /
displayTabSearchResults ( results ) {
const resultsList = document . getElementById ( "input-search-results" ) ;
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 ( "input-filter-result" ) ;
newListItem . setAttribute ( "inputNum" , results [ i ] . inputNum ) ;
newListItem . innerText = ` ${ results [ i ] . inputNum } : ${ results [ i ] . textDisplay } ` ;
resultsList . appendChild ( newListItem ) ;
}
}
/ * *
* Handler for clicking on a filter result
*
* @ param { event } e
* /
filterItemClick ( e ) {
if ( ! e . target ) return ;
const inputNum = parseInt ( e . target . getAttribute ( "inputNum" ) , 10 ) ;
if ( inputNum <= 0 ) return ;
$ ( "#input-tab-modal" ) . modal ( "hide" ) ;
this . changeTab ( inputNum , this . app . options . syncTabs ) ;
}
2019-05-02 11:29:54 +01:00
/ * *
* Update the input URL to the new value
*
2019-05-14 11:45:13 +01:00
* @ param { object } urlData - Object containing the URL data
* @ param { boolean } urlData . includeInput - If true , the input is included in the title
* @ param { string } urlData . input - The input data to be included
2019-05-02 11:29:54 +01:00
* /
setUrl ( urlData ) {
2019-05-02 13:54:15 +01:00
this . app . updateTitle ( urlData . includeInput , urlData . input , true ) ;
2019-05-02 11:29:54 +01:00
}
2019-04-30 14:15:05 +01:00
2018-05-15 17:36:45 +00:00
}
export default InputWaiter ;