/** * Web worker to handle the inputs. * Handles storage, modification and retrieval of the inputs. * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Utils from "../../core/Utils.mjs"; import {detectFileType} from "../../core/lib/FileType.mjs"; // Default max values // These will be correctly calculated automatically self.maxWorkers = 4; self.maxTabs = 1; self.pendingFiles = []; self.inputs = {}; self.loaderWorkers = []; self.currentInputNum = 1; self.numInputs = 0; self.pendingInputs = 0; self.loadingInputs = 0; /** * Respond to message from parent thread. * * @param {MessageEvent} e */ self.addEventListener("message", function(e) { const r = e.data; if (!("action" in r)) { log.error("No action"); return; } log.debug(`Receiving ${r.action} from InputWaiter.`); switch (r.action) { case "loadUIFiles": self.loadFiles(r.data); break; case "loaderWorkerReady": self.loaderWorkerReady(r.data); break; case "updateMaxWorkers": self.maxWorkers = r.data; break; case "updateMaxTabs": self.updateMaxTabs(r.data.maxTabs, r.data.activeTab); break; case "updateInputValue": self.updateInputValue(r.data); break; case "updateInputObj": self.updateInputObj(r.data); break; case "updateInputProgress": self.updateInputProgress(r.data); break; case "bakeAll": self.bakeAllInputs(); break; case "bakeNext": self.bakeInput(r.data.inputNum, r.data.bakeId); break; case "getLoadProgress": self.getLoadProgress(r.data); break; case "setInput": self.setInput(r.data); break; case "setLogLevel": log.setLevel(r.data, false); break; case "addInput": self.addInput(r.data, "string"); break; case "refreshTabs": self.refreshTabs(r.data.inputNum, r.data.direction); break; case "removeInput": self.removeInput(r.data); break; case "changeTabRight": self.changeTabRight(r.data.activeTab); break; case "changeTabLeft": self.changeTabLeft(r.data.activeTab); break; case "autobake": self.autoBake(r.data.activeTab, 0, false); break; case "filterTabs": self.filterTabs(r.data); break; case "loaderWorkerMessage": self.handleLoaderMessage(r.data); break; case "inputSwitch": self.inputSwitch(r.data); break; case "updateTabHeader": self.updateTabHeader(r.data); break; case "step": self.autoBake(r.data.activeTab, r.data.progress, true); break; case "getInput": self.getInput(r.data); break; case "getInputNums": self.getInputNums(r.data); break; default: log.error(`Unknown action '${r.action}'.`); } }); /** * Gets the load progress of the input files, and the * load progress for the input given in inputNum * * @param {number} inputNum - The input to get the file loading progress for */ self.getLoadProgress = function(inputNum) { const total = self.numInputs; const pending = self.pendingFiles.length; const loading = self.loadingInputs; const loaded = total - pending - loading; self.postMessage({ action: "loadingInfo", data: { pending: pending, loading: loading, loaded: loaded, total: total, activeProgress: { inputNum: inputNum, progress: self.getInputProgress(inputNum) } } }); }; /** * Fired when an autobake is initiated. * Queues the active input and sends a bake command. * * @param {number} inputNum - The input to be baked * @param {number} progress - The current progress of the bake through the recipe * @param {boolean} [step=false] - Set to true if we should only execute one operation instead of the * whole recipe */ self.autoBake = function(inputNum, progress, step=false) { const input = self.getInputObj(inputNum); if (input) { self.postMessage({ action: "bakeAllInputs", data: { nums: [parseInt(inputNum, 10)], step: step, progress: progress } }); } }; /** * Fired when we want to bake all inputs (bake button clicked) * Sends a list of inputNums to the workerwaiter */ self.bakeAllInputs = function() { const inputNums = Object.keys(self.inputs), nums = []; for (let i = 0; i < inputNums.length; i++) { if (self.inputs[inputNums[i]].status === "loaded") { nums.push(parseInt(inputNums[i], 10)); } } self.postMessage({ action: "bakeAllInputs", data: { nums: nums, step: false, progress: 0 } }); }; /** * Gets the data for the provided inputNum and sends it to the WorkerWaiter * * @param {number} inputNum * @param {number} bakeId */ self.bakeInput = function(inputNum, bakeId) { const inputObj = self.getInputObj(inputNum); if (inputObj === null || inputObj === undefined || inputObj.status !== "loaded") { self.postMessage({ action: "queueInputError", data: { inputNum: inputNum, bakeId: bakeId } }); return; } let inputData = inputObj.data; if (typeof inputData !== "string") inputData = inputData.fileBuffer; self.postMessage({ action: "queueInput", data: { input: inputData, inputNum: inputNum, bakeId: bakeId } }); }; /** * Gets the stored object for a specific inputNum * * @param {number} inputNum - The input we want to get the object for * @returns {object} */ self.getInputObj = function(inputNum) { return self.inputs[inputNum]; }; /** * Gets the stored value for a specific inputNum. * * @param {number} inputNum - The input we want to get the value of * @returns {string | ArrayBuffer} */ self.getInputValue = function(inputNum) { if (self.inputs[inputNum]) { if (typeof self.inputs[inputNum].data === "string") { return self.inputs[inputNum].data; } else { return self.inputs[inputNum].data.fileBuffer; } } return ""; }; /** * Gets the stored value or object for a specific inputNum and sends it to the inputWaiter. * * @param {object} inputData - Object containing data about the input to retrieve * @param {number} inputData.inputNum - The inputNum of the input to get * @param {boolean} inputData.getObj - If true, returns the entire input object instead of just the value * @param {number} inputData.id - The callback ID for the callback to run when returned to the inputWaiter */ self.getInput = function(inputData) { const inputNum = inputData.inputNum, data = (inputData.getObj) ? self.getInputObj(inputNum) : self.getInputValue(inputNum); self.postMessage({ action: "getInput", data: { data: data, id: inputData.id } }); }; /** * Gets a list of the stored inputNums, along with the minimum and maximum * * @param {number} id - The callback ID to be executed when returned to the inputWaiter */ self.getInputNums = function(id) { const inputNums = Object.keys(self.inputs), min = self.getSmallestInputNum(inputNums), max = self.getLargestInputNum(inputNums); self.postMessage({ action: "getInputNums", data: { inputNums: inputNums, min: min, max: max, id: id } }); }; /** * Gets the load progress for a specific inputNum * * @param {number} inputNum - The input we want to get the progress of * @returns {number | string} - Returns "error" if there was a load error */ self.getInputProgress = function(inputNum) { const inputObj = self.getInputObj(inputNum); if (inputObj === undefined || inputObj === null) return; if (inputObj.status === "error") { return "error"; } return inputObj.progress; }; /** * Gets the largest inputNum of all the inputs * * @param {string[]} inputNums - The numbers to find the largest of * @returns {number} */ self.getLargestInputNum = function(inputNums) { return inputNums.reduce((acc, val) => { val = parseInt(val, 10); return val > acc ? val : acc; }, -1); }; /** * Gets the smallest inputNum of all the inputs * * @param {string[]} inputNums - The numbers to find the smallest of * @returns {number} */ self.getSmallestInputNum = function(inputNums) { const min = inputNums.reduce((acc, val) => { val = parseInt(val, 10); return val < acc ? val : acc; }, Number.MAX_SAFE_INTEGER); // Assume we don't have this many tabs! if (min === Number.MAX_SAFE_INTEGER) return -1; return min; }; /** * Gets the next smallest inputNum * * @param {number} inputNum - The current input number * @returns {number} */ self.getPreviousInputNum = function(inputNum) { const inputNums = Object.keys(self.inputs); if (inputNums.length === 0) return -1; return inputNums.reduce((acc, val) => { val = parseInt(val, 10); return (val < inputNum && val > acc) ? val : acc; }, self.getSmallestInputNum(inputNums)); }; /** * Gets the next largest inputNum * * @param {number} inputNum - The current input number * @returns {number} */ self.getNextInputNum = function(inputNum) { const inputNums = Object.keys(self.inputs); return inputNums.reduce((acc, val) => { val = parseInt(val, 10); return (val > inputNum && val < acc) ? val : acc; }, self.getLargestInputNum(inputNums)); }; /** * Gets a list of inputNums starting from the provided inputNum. * If direction is "left", gets the inputNums higher than the provided number. * If direction is "right", gets the inputNums lower than the provided number. * @param {number} inputNum - The inputNum we want to get the neighbours of * @param {string} direction - Either "left" or "right". Determines which direction we search for nearby numbers in * @returns {number[]} */ self.getNearbyNums = function(inputNum, direction) { const nums = []; for (let i = 0; i < self.maxTabs; i++) { let newNum; if (i === 0 && self.inputs[inputNum] !== undefined) { newNum = inputNum; } else { switch (direction) { case "left": newNum = self.getNextInputNum(nums[i - 1]); if (newNum === nums[i - 1]) { direction = "right"; newNum = self.getPreviousInputNum(nums[0]); } break; case "right": newNum = self.getPreviousInputNum(nums[i - 1]); if (newNum === nums[i - 1]) { direction = "left"; newNum = self.getNextInputNum(nums[0]); } } } if (!nums.includes(newNum) && (newNum > 0)) { nums.push(newNum); } } nums.sort(function(a, b) { return a - b; }); return nums; }; /** * Gets the data to display in the tab header for an input, and * posts it back to the inputWaiter * * @param {number} inputNum - The inputNum of the tab header */ self.updateTabHeader = function(inputNum) { const input = self.getInputObj(inputNum); if (input === null || input === undefined) return; let inputData = input.data; if (typeof inputData !== "string") { inputData = input.data.name; } inputData = inputData.replace(/[\n\r]/g, ""); self.postMessage({ action: "updateTabHeader", data: { inputNum: inputNum, input: inputData.slice(0, 100) } }); }; /** * Gets the input for a specific inputNum, and posts it to the inputWaiter * so that it can be displayed in the input area * * @param {object} inputData * @param {number} inputData.inputNum - The input to get the data for * @param {boolean} inputData.silent - If false, the manager statechange event will be fired */ self.setInput = function(inputData) { const inputNum = inputData.inputNum; const silent = inputData.silent; const input = self.getInputObj(inputNum); if (input === undefined || input === null) return; let inputVal = input.data; const inputObj = { inputNum: inputNum, input: inputVal }; if (typeof inputVal !== "string") { inputObj.name = inputVal.name; inputObj.size = inputVal.size; inputObj.type = inputVal.type; inputObj.progress = input.progress; inputObj.status = input.status; inputVal = inputVal.fileBuffer; const fileSlice = inputVal.slice(0, 512001); inputObj.input = fileSlice; self.postMessage({ action: "setInput", data: { inputObj: inputObj, silent: silent } }, [fileSlice]); } else { self.postMessage({ action: "setInput", data: { inputObj: inputObj, silent: silent } }); } self.updateTabHeader(inputNum); }; /** * Gets the nearby inputNums to the provided number, and posts them * to the inputWaiter to be displayed on the page. * * @param {number} inputNum - The inputNum to find the nearby numbers for * @param {string} direction - The direction to search for inputNums in. Either "left" or "right" */ self.refreshTabs = function(inputNum, direction) { const nums = self.getNearbyNums(inputNum, direction), inputNums = Object.keys(self.inputs), tabsLeft = (self.getSmallestInputNum(inputNums) !== nums[0] && nums.length > 0), tabsRight = (self.getLargestInputNum(inputNums) !== nums[nums.length - 1] && nums.length > 0); self.postMessage({ action: "refreshTabs", data: { nums: nums, activeTab: (nums.includes(inputNum)) ? inputNum : self.getNextInputNum(inputNum), tabsLeft: tabsLeft, tabsRight: tabsRight } }); // Update the tab headers for the new tabs for (let i = 0; i < nums.length; i++) { self.updateTabHeader(nums[i]); } }; /** * Update the stored status for an input * * @param {number} inputNum - The input that's having its status changed * @param {string} status - The status of the input */ self.updateInputStatus = function(inputNum, status) { if (self.inputs[inputNum] !== undefined) { self.inputs[inputNum].status = status; } }; /** * Update the stored load progress of an input * * @param {object} inputData * @param {number} inputData.inputNum - The input that's having its progress updated * @param {number} inputData.progress - The load progress of the input */ self.updateInputProgress = function(inputData) { const inputNum = inputData.inputNum; const progress = inputData.progress; if (self.inputs[inputNum] !== undefined) { self.inputs[inputNum].progress = progress; } }; /** * Update the stored value of an input. * * @param {object} inputData * @param {number} inputData.inputNum - The input that's having its value updated * @param {string | ArrayBuffer} inputData.value - The new value of the input * @param {boolean} inputData.force - If true, still updates the input value if the input type is different to the stored value */ self.updateInputValue = function(inputData) { const inputNum = inputData.inputNum; if (inputNum < 1) return; if (Object.prototype.hasOwnProperty.call(self.inputs[inputNum].data, "fileBuffer") && typeof inputData.value === "string" && !inputData.force) return; const value = inputData.value; if (self.inputs[inputNum] !== undefined) { if (typeof value === "string") { self.inputs[inputNum].data = value; } else { self.inputs[inputNum].data.fileBuffer = value; } self.inputs[inputNum].status = "loaded"; self.inputs[inputNum].progress = 100; return; } // If we get to here, an input for inputNum could not be found, // so create a new one. Only do this if the value is a string, as // loadFiles will create the input object for files if (typeof value === "string") { self.inputs.push({ inputNum: inputNum, data: value, status: "loaded", progress: 100 }); } }; /** * Update the stored data object for an input. * Used if we need to change a string to an ArrayBuffer * * @param {object} inputData * @param {number} inputData.inputNum - The number of the input we're updating * @param {object} inputData.data - The new data object for the input */ self.updateInputObj = function(inputData) { const inputNum = inputData.inputNum; const data = inputData.data; if (self.getInputObj(inputNum) === undefined) return; self.inputs[inputNum].data = data; }; /** * Get the index of a loader worker object. * Returns -1 if the worker could not be found * * @param {number} workerId - The ID of the worker we're searching for * @returns {number} */ self.getLoaderWorkerIdx = function(workerId) { for (let i = 0; i < self.loaderWorkers.length; i++) { if (self.loaderWorkers[i].id === workerId) { return i; } } return -1; }; /** * Fires when a loaderWorker is ready to load files. * Stores data about the new loaderWorker in the loaderWorkers array, * and sends the next file to the loaderWorker to be loaded. * * @param {object} workerData * @param {number} workerData.id - The ID of the new loaderWorker */ self.loaderWorkerReady = function(workerData) { const newWorkerObj = { id: workerData.id, inputNum: -1, active: true }; self.loaderWorkers.push(newWorkerObj); self.loadNextFile(self.loaderWorkers.indexOf(newWorkerObj)); }; /** * Handler for messages sent by loaderWorkers. * (Messages are sent between the inputWorker and * loaderWorkers via the main thread) * * @param {object} r - The data sent by the loaderWorker * @param {number} r.inputNum - The inputNum which the message corresponds to * @param {string} r.error - Present if an error is fired by the loaderWorker. Contains the error message string. * @param {ArrayBuffer} r.fileBuffer - Present if a file has finished loading. Contains the loaded file buffer. */ self.handleLoaderMessage = function(r) { let inputNum = 0; if ("inputNum" in r) { inputNum = r.inputNum; } if ("error" in r) { self.updateInputProgress(r.inputNum, 0); self.updateInputStatus(r.inputNum, "error"); log.error(r.error); self.loadingInputs--; self.terminateLoaderWorker(r.id); self.activateLoaderWorker(); self.setInput({inputNum: inputNum, silent: true}); return; } if ("fileBuffer" in r) { log.debug(`Input file ${inputNum} loaded.`); self.loadingInputs--; self.updateInputValue({ inputNum: inputNum, value: r.fileBuffer }); self.postMessage({ action: "fileLoaded", data: { inputNum: inputNum } }); const idx = self.getLoaderWorkerIdx(r.id); self.loadNextFile(idx); } else if ("progress" in r) { self.updateInputProgress(r); } }; /** * Loads the next file using a loaderWorker * * @param {number} - The loaderWorker which will load the file */ self.loadNextFile = function(workerIdx) { if (workerIdx === -1) return; const id = self.loaderWorkers[workerIdx].id; if (self.pendingFiles.length === 0) { const workerObj = self.loaderWorkers.splice(workerIdx, 1)[0]; self.terminateLoaderWorker(workerObj.id); return; } const nextFile = self.pendingFiles.splice(0, 1)[0]; self.loaderWorkers[workerIdx].inputNum = nextFile.inputNum; self.loadingInputs++; self.postMessage({ action: "loadInput", data: { file: nextFile.file, inputNum: nextFile.inputNum, workerId: id } }); }; /** * Sends a message to the inputWaiter to create a new loaderWorker. * If there's an inactive loaderWorker that already exists, use that instead. */ self.activateLoaderWorker = function() { for (let i = 0; i < self.loaderWorkers.length; i++) { if (!self.loaderWorkers[i].active) { self.loaderWorkers[i].active = true; self.loadNextFile(i); return; } } self.postMessage({ action: "activateLoaderWorker" }); }; /** * Sends a message to the inputWaiter to terminate a loaderWorker. * * @param {number} id - The ID of the worker to be terminated */ self.terminateLoaderWorker = function(id) { self.postMessage({ action: "terminateLoaderWorker", data: id }); // If we still have pending files, spawn a worker if (self.pendingFiles.length > 0) { self.activateLoaderWorker(); } }; /** * Loads files using LoaderWorkers * * @param {object} filesData * @param {FileList} filesData.files - The list of files to be loaded * @param {number} filesData.activeTab - The active tab in the UI */ self.loadFiles = function(filesData) { const files = filesData.files; const activeTab = filesData.activeTab; let lastInputNum = -1; const inputNums = []; for (let i = 0; i < files.length; i++) { if (i === 0 && self.getInputValue(activeTab) === "") { self.removeInput({ inputNum: activeTab, refreshTabs: false, removeChefWorker: false }); lastInputNum = self.addInput(false, "file", { name: files[i].name, size: files[i].size.toLocaleString(), type: files[i].type || "unknown" }, activeTab); } else { lastInputNum = self.addInput(false, "file", { name: files[i].name, size: files[i].size.toLocaleString(), type: files[i].type || "unknown" }); } inputNums.push(lastInputNum); self.pendingFiles.push({ file: files[i], inputNum: lastInputNum }); } let max = self.maxWorkers; if (self.pendingFiles.length < self.maxWorkers) max = self.pendingFiles.length; // Create loaderWorkers to load the new files for (let i = 0; i < max; i++) { self.activateLoaderWorker(); } self.getLoadProgress(); self.setInput({inputNum: activeTab, silent: true}); }; /** * Adds an input to the input dictionary * * @param {boolean} [changetab=false] - Whether or not to change to the new input * @param {string} type - Either "string" or "file" * @param {Object} fileData - Contains information about the file to be added to the input (only used when type is "file") * @param {string} fileData.name - The filename of the input being added * @param {number} fileData.size - The file size (in bytes) of the input being added * @param {string} fileData.type - The MIME type of the input being added * @param {number} inputNum - Defaults to auto-incrementing self.currentInputNum */ self.addInput = function( changeTab = false, type, fileData = { name: "unknown", size: "unknown", type: "unknown" }, inputNum = self.currentInputNum++ ) { self.numInputs++; const newInputObj = { inputNum: inputNum }; switch (type) { case "string": newInputObj.data = ""; newInputObj.status = "loaded"; newInputObj.progress = 100; break; case "file": newInputObj.data = { fileBuffer: new ArrayBuffer(), name: fileData.name, size: fileData.size, type: fileData.type }; newInputObj.status = "pending"; newInputObj.progress = 0; break; default: log.error(`Invalid type '${type}'.`); return -1; } self.inputs[inputNum] = newInputObj; // Tell the inputWaiter we've added an input, so it can create a tab to display it self.postMessage({ action: "inputAdded", data: { changeTab: changeTab, inputNum: inputNum } }); return inputNum; }; /** * Remove an input from the inputs dictionary * * @param {object} removeInputData * @param {number} removeInputData.inputNum - The number of the input to be removed * @param {boolean} removeInputData.refreshTabs - If true, refresh the tabs after removing the input * @param {boolean} removeInputData.removeChefWorker - If true, remove a chefWorker from the WorkerWaiter */ self.removeInput = function(removeInputData) { const inputNum = removeInputData.inputNum; const refreshTabs = removeInputData.refreshTabs; self.numInputs--; for (let i = 0; i < self.loaderWorkers.length; i++) { if (self.loaderWorkers[i].inputNum === inputNum) { // Terminate any loaderWorker that's loading the removed input self.loadingInputs--; self.terminateLoaderWorker(self.loaderWorkers[i].id); break; } } for (let i = 0; i < self.pendingFiles.length; i++) { // Remove the input from the pending files list if (self.pendingFiles[i].inputNum === inputNum) { self.pendingFiles.splice(i, 1); break; } } delete self.inputs[inputNum]; if (refreshTabs) { self.refreshTabs(self.getPreviousInputNum(inputNum), "left"); } if (self.numInputs < self.maxWorkers && removeInputData.removeChefWorker) { self.postMessage({ action: "removeChefWorker" }); } }; /** * Change to the next tab. * * @param {number} inputNum - The inputNum of the tab to change to */ self.changeTabRight = function(inputNum) { const newInput = self.getNextInputNum(inputNum); self.postMessage({ action: "changeTab", data: newInput }); }; /** * Change to the previous tab. * * @param {number} inputNum - The inputNum of the tab to change to */ self.changeTabLeft = function(inputNum) { const newInput = self.getPreviousInputNum(inputNum); self.postMessage({ action: "changeTab", data: newInput }); }; /** * Updates the maximum number of tabs, and refreshes them if it changes * * @param {number} maxTabs - The new max number of tabs * @param {number} activeTab - The currently selected tab */ self.updateMaxTabs = function(maxTabs, activeTab) { if (self.maxTabs !== maxTabs) { self.maxTabs = maxTabs; self.refreshTabs(activeTab, "right"); } }; /** * Search the inputs for any that match the filters provided, * posting the results back to the inputWaiter * * @param {object} searchData - Object containing the search filters * @param {boolean} searchData.showPending - If true, include pending inputs in the results * @param {boolean} searchData.showLoading - If true, include loading inputs in the results * @param {boolean} searchData.showLoaded - If true, include loaded inputs in the results * @param {string} searchData.filter - A regular expression to match the inputs on * @param {string} searchData.filterType - Either "CONTENT" or "FILENAME". Determines what should be matched with filter * @param {number} searchData.numResults - The maximum number of results to be returned */ self.filterTabs = function(searchData) { const showPending = searchData.showPending, showLoading = searchData.showLoading, showLoaded = searchData.showLoaded, filterType = searchData.filterType; let filterExp; try { filterExp = new RegExp(searchData.filter, "i"); } catch (error) { self.postMessage({ action: "filterTabError", data: error.message }); return; } const numResults = searchData.numResults; const inputs = []; const inputNums = Object.keys(self.inputs); for (let i = 0; i < inputNums.length; i++) { const iNum = inputNums[i]; let textDisplay = ""; let addInput = false; if (self.inputs[iNum].status === "pending" && showPending || self.inputs[iNum].status === "loading" && showLoading || self.inputs[iNum].status === "loaded" && showLoaded) { try { if (typeof self.inputs[iNum].data === "string") { if (filterType.toLowerCase() === "content" && filterExp.test(self.inputs[iNum].data.slice(0, 4096))) { textDisplay = self.inputs[iNum].data.slice(0, 4096); addInput = true; } } else { if ((filterType.toLowerCase() === "filename" && filterExp.test(self.inputs[iNum].data.name)) || filterType.toLowerCase() === "content" && filterExp.test(Utils.arrayBufferToStr(self.inputs[iNum].data.fileBuffer.slice(0, 4096)))) { textDisplay = self.inputs[iNum].data.name; addInput = true; } } } catch (error) { self.postMessage({ action: "filterTabError", data: error.message }); return; } } if (addInput) { if (textDisplay === "" || textDisplay === undefined) { textDisplay = "New Tab"; } const inputItem = { inputNum: iNum, textDisplay: textDisplay }; inputs.push(inputItem); } if (inputs.length >= numResults) { break; } } // Send the results back to the inputWaiter self.postMessage({ action: "displayTabSearchResults", data: inputs }); }; /** * Swaps the input and outputs, and sends the old input back to the main thread. * * @param {object} switchData * @param {number} switchData.inputNum - The inputNum of the input to be switched to * @param {string | ArrayBuffer} switchData.outputData - The data to switch to */ self.inputSwitch = function(switchData) { const currentInput = self.getInputObj(switchData.inputNum); const currentData = currentInput.data; if (currentInput === undefined || currentInput === null) return; if (typeof switchData.outputData !== "string") { const output = new Uint8Array(switchData.outputData), types = detectFileType(output); let type = "unknown", ext = "dat"; if (types.length) { type = types[0].mime; ext = types[0].extension.split(",", 1)[0]; } // ArrayBuffer self.updateInputObj({ inputNum: switchData.inputNum, data: { fileBuffer: switchData.outputData, name: `output.${ext}`, size: switchData.outputData.byteLength.toLocaleString(), type: type } }); } else { // String self.updateInputValue({ inputNum: switchData.inputNum, value: switchData.outputData, force: true }); } self.postMessage({ action: "inputSwitch", data: { data: currentData, inputNum: switchData.inputNum } }); self.postMessage({ action: "fileLoaded", data: { inputNum: switchData.inputNum } }); };