diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs
index 36998cec..140774bc 100755
--- a/src/core/Chef.mjs
+++ b/src/core/Chef.mjs
@@ -68,16 +68,10 @@ class Chef {
// Present the raw result
await recipe.present(this.dish);
- // Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
- // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
- // The threshold is specified in KiB.
- const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType =
this.dish.type === Dish.HTML ?
Dish.HTML :
- this.dish.size > threshold ?
- Dish.ARRAY_BUFFER :
- Dish.STRING;
+ Dish.ARRAY_BUFFER;
return {
dish: rawDish,
diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js
index d46a705d..8989875a 100644
--- a/src/core/ChefWorker.js
+++ b/src/core/ChefWorker.js
@@ -101,14 +101,17 @@ async function bake(data) {
// Ensure the relevant modules are loaded
self.loadRequiredModules(data.recipeConfig);
try {
- self.inputNum = (data.inputNum !== undefined) ? data.inputNum : -1;
+ self.inputNum = data.inputNum === undefined ? -1 : data.inputNum;
const response = await self.chef.bake(
data.input, // The user's input
data.recipeConfig, // The configuration of the recipe
data.options // Options set by the user
);
- const transferable = (data.input instanceof ArrayBuffer) ? [data.input] : undefined;
+ const transferable = (response.dish.value instanceof ArrayBuffer) ?
+ [response.dish.value] :
+ undefined;
+
self.postMessage({
action: "bakeComplete",
data: Object.assign(response, {
diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs
index b72a6028..604b7b8c 100755
--- a/src/core/Utils.mjs
+++ b/src/core/Utils.mjs
@@ -406,6 +406,7 @@ class Utils {
* Utils.strToArrayBuffer("你好");
*/
static strToArrayBuffer(str) {
+ log.debug("Converting string to array buffer");
const arr = new Uint8Array(str.length);
let i = str.length, b;
while (i--) {
@@ -432,6 +433,7 @@ class Utils {
* Utils.strToUtf8ArrayBuffer("你好");
*/
static strToUtf8ArrayBuffer(str) {
+ log.debug("Converting string to UTF8 array buffer");
const utf8Str = utf8.encode(str);
if (str.length !== utf8Str.length) {
@@ -461,6 +463,7 @@ class Utils {
* Utils.strToByteArray("你好");
*/
static strToByteArray(str) {
+ log.debug("Converting string to byte array");
const byteArray = new Array(str.length);
let i = str.length, b;
while (i--) {
@@ -487,6 +490,7 @@ class Utils {
* Utils.strToUtf8ByteArray("你好");
*/
static strToUtf8ByteArray(str) {
+ log.debug("Converting string to UTF8 byte array");
const utf8Str = utf8.encode(str);
if (str.length !== utf8Str.length) {
@@ -515,6 +519,7 @@ class Utils {
* Utils.strToCharcode("你好");
*/
static strToCharcode(str) {
+ log.debug("Converting string to charcode");
const charcode = [];
for (let i = 0; i < str.length; i++) {
@@ -549,6 +554,7 @@ class Utils {
* Utils.byteArrayToUtf8([228,189,160,229,165,189]);
*/
static byteArrayToUtf8(byteArray) {
+ log.debug("Converting byte array to UTF8");
const str = Utils.byteArrayToChars(byteArray);
try {
const utf8Str = utf8.decode(str);
@@ -581,6 +587,7 @@ class Utils {
* Utils.byteArrayToChars([20320,22909]);
*/
static byteArrayToChars(byteArray) {
+ log.debug("Converting byte array to chars");
if (!byteArray) return "";
let str = "";
// String concatenation appears to be faster than an array join
@@ -603,6 +610,7 @@ class Utils {
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
*/
static arrayBufferToStr(arrayBuffer, utf8=true) {
+ log.debug("Converting array buffer to str");
const arr = new Uint8Array(arrayBuffer);
return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr);
}
diff --git a/src/core/lib/ChrEnc.mjs b/src/core/lib/ChrEnc.mjs
index c5cb5605..8934d137 100644
--- a/src/core/lib/ChrEnc.mjs
+++ b/src/core/lib/ChrEnc.mjs
@@ -9,7 +9,7 @@
/**
* Character encoding format mappings.
*/
-export const IO_FORMAT = {
+export const CHR_ENC_CODE_PAGES = {
"UTF-8 (65001)": 65001,
"UTF-7 (65000)": 65000,
"UTF-16LE (1200)": 1200,
@@ -164,6 +164,17 @@ export const IO_FORMAT = {
"Simplified Chinese GB18030 (54936)": 54936,
};
+
+export const CHR_ENC_SIMPLE_LOOKUP = {};
+export const CHR_ENC_SIMPLE_REVERSE_LOOKUP = {};
+
+for (const name in CHR_ENC_CODE_PAGES) {
+ const simpleName = name.match(/(^.+)\([\d/]+\)$/)[1];
+
+ CHR_ENC_SIMPLE_LOOKUP[simpleName] = CHR_ENC_CODE_PAGES[name];
+ CHR_ENC_SIMPLE_REVERSE_LOOKUP[CHR_ENC_CODE_PAGES[name]] = simpleName;
+}
+
/**
* Unicode Normalisation Forms
*
diff --git a/src/core/operations/DecodeText.mjs b/src/core/operations/DecodeText.mjs
index 9b01b79f..0fc9d2b5 100644
--- a/src/core/operations/DecodeText.mjs
+++ b/src/core/operations/DecodeText.mjs
@@ -6,7 +6,7 @@
import Operation from "../Operation.mjs";
import cptable from "codepage";
-import {IO_FORMAT} from "../lib/ChrEnc.mjs";
+import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs";
/**
* Decode text operation
@@ -26,7 +26,7 @@ class DecodeText extends Operation {
"
",
"Supported charsets are:",
"
keyboard_return
-
`;
}
}
+const elementsWithListeners = {};
+
+/**
+ * Hides the provided element when a click is made outside of it
+ * @param {Element} element
+ * @param {Event} instantiatingEvent
+ */
+function hideOnClickOutside(element, instantiatingEvent) {
+ /**
+ * Handler for document click events
+ * Closes element if click is outside it.
+ * @param {Event} event
+ */
+ const outsideClickListener = event => {
+ // Don't trigger if we're clicking inside the element, or if the element
+ // is not visible, or if this is the same click event that opened it.
+ if (!element.contains(event.target) &&
+ event.timeStamp !== instantiatingEvent.timeStamp) {
+ hideElement(element);
+ }
+ };
+
+ if (!Object.keys(elementsWithListeners).includes(element)) {
+ document.addEventListener("click", outsideClickListener);
+ elementsWithListeners[element] = outsideClickListener;
+ }
+}
+
+/**
+ * Hides the specified element and removes the click listener for it
+ * @param {Element} element
+ */
+function hideElement(element) {
+ element.classList.remove("show");
+ document.removeEventListener("click", elementsWithListeners[element]);
+ delete elementsWithListeners[element];
+}
+
+
/**
* A panel constructor factory building a panel that re-counts the stats every time the document changes.
* @param {Object} opts
@@ -240,7 +393,7 @@ function makePanel(opts) {
return (view) => {
sbPanel.updateEOL(view.state);
- sbPanel.updateCharEnc(view.state);
+ sbPanel.updateCharEnc(opts.initialChrEncVal);
sbPanel.updateBakeStats();
sbPanel.updateStats(view.state.doc);
sbPanel.updateSelection(view.state, false);
@@ -250,8 +403,10 @@ function makePanel(opts) {
update(update) {
sbPanel.updateEOL(update.state);
sbPanel.updateSelection(update.state, update.selectionSet);
- sbPanel.updateCharEnc(update.state);
sbPanel.updateBakeStats();
+ if (update.geometryChanged) {
+ sbPanel.updateSizing(update.view);
+ }
if (update.docChanged) {
sbPanel.updateStats(update.state.doc);
}
diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs
index ed8f174b..caa1a098 100644
--- a/src/web/waiters/InputWaiter.mjs
+++ b/src/web/waiters/InputWaiter.mjs
@@ -10,6 +10,7 @@ import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker
import Utils, {debounce} from "../../core/Utils.mjs";
import {toBase64} from "../../core/lib/Base64.mjs";
import {isImage} from "../../core/lib/FileType.mjs";
+import cptable from "codepage";
import {
EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, dropCursor
@@ -39,6 +40,7 @@ class InputWaiter {
this.manager = manager;
this.inputTextEl = document.getElementById("input-text");
+ this.inputChrEnc = 0;
this.initEditor();
this.inputWorker = null;
@@ -84,7 +86,9 @@ class InputWaiter {
// Custom extensions
statusBar({
label: "Input",
- eolHandler: this.eolChange.bind(this)
+ eolHandler: this.eolChange.bind(this),
+ chrEncHandler: this.chrEncChange.bind(this),
+ initialChrEncVal: this.inputChrEnc
}),
// Mutable state
@@ -122,19 +126,30 @@ class InputWaiter {
/**
* Handler for EOL change events
* Sets the line separator
+ * @param {string} eolVal
*/
- eolChange(eolval) {
+ eolChange(eolVal) {
const oldInputVal = this.getInput();
// Update the EOL value
this.inputEditorView.dispatch({
- effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval))
+ effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal))
});
// Reset the input so that lines are recalculated, preserving the old EOL values
this.setInput(oldInputVal);
}
+ /**
+ * Handler for Chr Enc change events
+ * Sets the input character encoding
+ * @param {number} chrEncVal
+ */
+ chrEncChange(chrEncVal) {
+ this.inputChrEnc = chrEncVal;
+ this.inputChange();
+ }
+
/**
* Sets word wrap on the input editor
* @param {boolean} wrap
@@ -380,7 +395,7 @@ class InputWaiter {
this.showLoadingInfo(r.data, true);
break;
case "setInput":
- this.set(r.data.inputObj, r.data.silent);
+ this.set(r.data.inputNum, r.data.inputObj, r.data.silent);
break;
case "inputAdded":
this.inputAdded(r.data.changeTab, r.data.inputNum);
@@ -403,9 +418,6 @@ class InputWaiter {
case "setUrl":
this.setUrl(r.data);
break;
- case "inputSwitch":
- this.manager.output.inputSwitch(r.data);
- break;
case "getInput":
case "getInputNums":
this.callbacks[r.data.id](r.data);
@@ -435,22 +447,36 @@ class InputWaiter {
/**
* Sets the input in the input area
*
- * @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
+ * @param {number} inputNum
+ * @param {Object} inputData - Object containing the input and its metadata
+ * @param {string} type
+ * @param {ArrayBuffer} buffer
+ * @param {string} stringSample
+ * @param {Object} file
+ * @param {string} file.name
+ * @param {number} file.size
+ * @param {string} file.type
+ * @param {string} status
+ * @param {number} progress
* @param {boolean} [silent=false] - If false, fires the manager statechange event
*/
- async set(inputData, silent=false) {
+ async set(inputNum, inputData, silent=false) {
return new Promise(function(resolve, reject) {
const activeTab = this.manager.tabs.getActiveInputTab();
- if (inputData.inputNum !== activeTab) return;
+ if (inputNum !== activeTab) return;
- if (typeof inputData.input === "string") {
- this.setInput(inputData.input);
+ if (inputData.file) {
+ this.setFile(inputNum, inputData, silent);
+ } else {
+ // TODO Per-tab encodings?
+ let inputVal;
+ if (this.inputChrEnc > 0) {
+ inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer));
+ } else {
+ inputVal = Utils.arrayBufferToStr(inputData.buffer);
+ }
+
+ this.setInput(inputVal);
const fileOverlay = document.getElementById("input-file"),
fileName = document.getElementById("input-file-name"),
fileSize = document.getElementById("input-file-size"),
@@ -466,8 +492,8 @@ class InputWaiter {
this.inputTextEl.classList.remove("blur");
// Set URL to current input
- const inputStr = toBase64(inputData.input, "A-Za-z0-9+/");
- if (inputStr.length >= 0 && inputStr.length <= 68267) {
+ if (inputVal.length >= 0 && inputVal.length <= 51200) {
+ const inputStr = toBase64(inputVal, "A-Za-z0-9+/");
this.setUrl({
includeInput: true,
input: inputStr
@@ -475,8 +501,6 @@ class InputWaiter {
}
if (!silent) window.dispatchEvent(this.manager.statechange);
- } else {
- this.setFile(inputData, silent);
}
}.bind(this));
@@ -485,18 +509,22 @@ class InputWaiter {
/**
* Displays file details
*
- * @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
+ * @param {number} inputNum
+ * @param {Object} inputData - Object containing the input and its metadata
+ * @param {string} type
+ * @param {ArrayBuffer} buffer
+ * @param {string} stringSample
+ * @param {Object} file
+ * @param {string} file.name
+ * @param {number} file.size
+ * @param {string} file.type
+ * @param {string} status
+ * @param {number} progress
* @param {boolean} [silent=true] - If false, fires the manager statechange event
*/
- setFile(inputData, silent=true) {
+ setFile(inputNum, inputData, silent=true) {
const activeTab = this.manager.tabs.getActiveInputTab();
- if (inputData.inputNum !== activeTab) return;
+ if (inputNum !== activeTab) return;
const fileOverlay = document.getElementById("input-file"),
fileName = document.getElementById("input-file-name"),
@@ -505,9 +533,9 @@ class InputWaiter {
fileLoaded = document.getElementById("input-file-loaded");
fileOverlay.style.display = "block";
- fileName.textContent = inputData.name;
- fileSize.textContent = inputData.size + " bytes";
- fileType.textContent = inputData.type;
+ fileName.textContent = inputData.file.name;
+ fileSize.textContent = inputData.file.size + " bytes";
+ fileType.textContent = inputData.file.type;
if (inputData.status === "error") {
fileLoaded.textContent = "Error";
fileLoaded.style.color = "#FF0000";
@@ -516,7 +544,7 @@ class InputWaiter {
fileLoaded.textContent = inputData.progress + "%";
}
- this.displayFilePreview(inputData);
+ this.displayFilePreview(inputNum, inputData);
if (!silent) window.dispatchEvent(this.manager.statechange);
}
@@ -583,19 +611,18 @@ class InputWaiter {
/**
* Shows a chunk of the file in the input behind the file overlay
*
+ * @param {number} inputNum - The inputNum of the file being displayed
* @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
+ * @param {string} inputData.stringSample - The first 4096 bytes of input as a string
*/
- displayFilePreview(inputData) {
+ displayFilePreview(inputNum, inputData) {
const activeTab = this.manager.tabs.getActiveInputTab(),
- input = inputData.input;
- if (inputData.inputNum !== activeTab) return;
+ input = inputData.buffer;
+ if (inputNum !== activeTab) return;
this.inputTextEl.classList.add("blur");
- this.setInput(Utils.arrayBufferToStr(input.slice(0, 4096)));
+ this.setInput(input.stringSample);
this.renderFileThumb();
-
}
/**
@@ -623,46 +650,40 @@ class InputWaiter {
*
* @param {number} inputNum
* @param {string | ArrayBuffer} value
- * @param {boolean} [force=false] - If true, forces the value to be updated even if the type is different to the currently stored type
*/
updateInputValue(inputNum, value, force=false) {
- 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;
+ // Prepare the value as a buffer (full value) and a string sample (up to 4096 bytes)
+ let buffer;
+ let stringSample = "";
+
+ // If value is a string, interpret it using the specified character encoding
+ if (typeof value === "string") {
+ stringSample = value.slice(0, 4096);
+ if (this.inputChrEnc > 0) {
+ buffer = cptable.utils.encode(this.inputChrEnc, value);
+ buffer = new Uint8Array(buffer).buffer;
+ } else {
+ buffer = Utils.strToArrayBuffer(value);
+ }
+ } else {
+ buffer = value;
+ stringSample = Utils.arrayBufferToStr(value.slice(0, 4096));
}
+
+
+ const recipeStr = buffer.byteLength < 51200 ? toBase64(buffer, "A-Za-z0-9+/") : ""; // B64 alphabet with no padding
this.setUrl({
- includeInput: includeInput,
+ includeInput: recipeStr.length > 0 && buffer.byteLength < 51200,
input: recipeStr
});
- // 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;
+ const transferable = [buffer];
this.inputWorker.postMessage({
action: "updateInputValue",
data: {
inputNum: inputNum,
- value: value,
- force: force
- }
- }, transferable);
- }
-
- /**
- * Updates the .data property for the input of the specified inputNum.
- * Used for switching the output into the input
- *
- * @param {number} inputNum - The inputNum of the input we're changing
- * @param {object} inputData - The new data object
- */
- updateInputObj(inputNum, inputData) {
- const transferable = (typeof inputData !== "string") ? [inputData.fileBuffer] : undefined;
- this.inputWorker.postMessage({
- action: "updateInputObj",
- data: {
- inputNum: inputNum,
- data: inputData
+ buffer: buffer,
+ stringSample: stringSample
}
}, transferable);
}
@@ -1052,9 +1073,8 @@ class InputWaiter {
this.updateInputValue(inputNum, "", true);
- this.set({
- inputNum: inputNum,
- input: ""
+ this.set(inputNum, {
+ buffer: new ArrayBuffer()
});
this.manager.tabs.updateInputTabHeader(inputNum, "");
diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs
index deaeaed3..f0b03d72 100755
--- a/src/web/waiters/OutputWaiter.mjs
+++ b/src/web/waiters/OutputWaiter.mjs
@@ -9,6 +9,7 @@ import Utils, {debounce} from "../../core/Utils.mjs";
import Dish from "../../core/Dish.mjs";
import FileSaver from "file-saver";
import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs";
+import cptable from "codepage";
import {
EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor
@@ -48,6 +49,7 @@ class OutputWaiter {
html: "",
changed: false
};
+ this.outputChrEnc = 0;
this.initEditor();
this.outputs = {};
@@ -86,7 +88,9 @@ class OutputWaiter {
statusBar({
label: "Output",
bakeStats: this.bakeStats,
- eolHandler: this.eolChange.bind(this)
+ eolHandler: this.eolChange.bind(this),
+ chrEncHandler: this.chrEncChange.bind(this),
+ initialChrEncVal: this.outputChrEnc
}),
htmlPlugin(this.htmlOutput),
copyOverride(),
@@ -119,19 +123,29 @@ class OutputWaiter {
/**
* Handler for EOL change events
* Sets the line separator
+ * @param {string} eolVal
*/
- eolChange(eolval) {
+ eolChange(eolVal) {
const oldOutputVal = this.getOutput();
// Update the EOL value
this.outputEditorView.dispatch({
- effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval))
+ effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal))
});
// Reset the output so that lines are recalculated, preserving the old EOL values
this.setOutput(oldOutputVal);
}
+ /**
+ * Handler for Chr Enc change events
+ * Sets the output character encoding
+ * @param {number} chrEncVal
+ */
+ chrEncChange(chrEncVal) {
+ this.outputChrEnc = chrEncVal;
+ }
+
/**
* Sets word wrap on the output editor
* @param {boolean} wrap
@@ -193,7 +207,8 @@ class OutputWaiter {
});
// Execute script sections
- const scriptElements = document.getElementById("output-html").querySelectorAll("script");
+ const outputHTML = document.getElementById("output-html");
+ const scriptElements = outputHTML ? outputHTML.querySelectorAll("script") : [];
for (let i = 0; i < scriptElements.length; i++) {
try {
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
@@ -405,8 +420,6 @@ class OutputWaiter {
removeAllOutputs() {
this.outputs = {};
- this.resetSwitch();
-
const tabsList = document.getElementById("output-tabs");
const tabsListChildren = tabsList.children;
@@ -418,19 +431,18 @@ class OutputWaiter {
}
/**
- * Sets the output in the output textarea.
+ * Sets the output in the output pane.
*
* @param {number} inputNum
*/
async set(inputNum) {
+ inputNum = parseInt(inputNum, 10);
if (inputNum !== this.manager.tabs.getActiveOutputTab() ||
!this.outputExists(inputNum)) return;
this.toggleLoader(true);
return new Promise(async function(resolve, reject) {
- const output = this.outputs[inputNum],
- activeTab = this.manager.tabs.getActiveOutputTab();
- if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10);
+ const output = this.outputs[inputNum];
const outputFile = document.getElementById("output-file");
@@ -491,17 +503,33 @@ class OutputWaiter {
switch (output.data.type) {
case "html":
outputFile.style.display = "none";
+ // TODO what if the HTML content needs to be in a certain character encoding?
+ // Grey out chr enc selection? Set back to Raw Bytes?
this.setHTMLOutput(output.data.result);
break;
- case "ArrayBuffer":
+ case "ArrayBuffer": {
this.outputTextEl.style.display = "block";
+ outputFile.style.display = "none";
this.clearHTMLOutput();
- this.setOutput("");
- this.setFile(await this.getDishBuffer(output.data.dish), activeTab);
+ let outputVal = "";
+ if (this.outputChrEnc === 0) {
+ outputVal = Utils.arrayBufferToStr(output.data.result);
+ } else {
+ try {
+ outputVal = cptable.utils.decode(this.outputChrEnc, new Uint8Array(output.data.result));
+ } catch (err) {
+ outputVal = err;
+ }
+ }
+
+ this.setOutput(outputVal);
+
+ // this.setFile(await this.getDishBuffer(output.data.dish), activeTab);
break;
+ }
case "string":
default:
this.outputTextEl.style.display = "block";
@@ -1333,7 +1361,6 @@ class OutputWaiter {
*/
async switchClick() {
const activeTab = this.manager.tabs.getActiveOutputTab();
- const transferable = [];
const switchButton = document.getElementById("switch");
switchButton.classList.add("spin");
@@ -1341,82 +1368,15 @@ class OutputWaiter {
switchButton.firstElementChild.innerHTML = "autorenew";
$(switchButton).tooltip("hide");
- let active = await this.getDishBuffer(this.getOutputDish(activeTab));
+ const activeData = await this.getDishBuffer(this.getOutputDish(activeTab));
- if (!this.outputExists(activeTab)) {
- this.resetSwitchButton();
- return;
- }
-
- if (this.outputs[activeTab].data.type === "string" &&
- active.byteLength <= this.app.options.ioDisplayThreshold * 1024) {
- const dishString = await this.getDishStr(this.getOutputDish(activeTab));
- active = dishString;
- } else {
- transferable.push(active);
- }
-
- this.manager.input.inputWorker.postMessage({
- action: "inputSwitch",
- data: {
+ if (this.outputExists(activeTab)) {
+ this.manager.input.set({
inputNum: activeTab,
- outputData: active
- }
- }, transferable);
- }
-
- /**
- * 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;
-
- this.resetSwitchButton();
-
- }
-
- /**
- * 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);
-
- this.manager.input.fileLoaded(this.switchOrigData.inputNum);
-
- this.resetSwitch();
- }
-
- /**
- * Removes the switch data and resets the switch buttons
- */
- resetSwitch() {
- if (this.switchOrigData !== undefined) {
- delete this.switchOrigData;
+ input: activeData
+ });
}
- const undoSwitch = document.getElementById("undo-switch");
- undoSwitch.disabled = true;
- $(undoSwitch).tooltip("hide");
-
- this.resetSwitchButton();
- }
-
- /**
- * Resets the switch button to its usual state
- */
- resetSwitchButton() {
- const switchButton = document.getElementById("switch");
switchButton.classList.remove("spin");
switchButton.disabled = false;
switchButton.firstElementChild.innerHTML = "open_in_browser";
diff --git a/src/web/workers/InputWorker.mjs b/src/web/workers/InputWorker.mjs
index 9912995b..e1c75de9 100644
--- a/src/web/workers/InputWorker.mjs
+++ b/src/web/workers/InputWorker.mjs
@@ -3,12 +3,12 @@
* Handles storage, modification and retrieval of the inputs.
*
* @author j433866 [j433866@gmail.com]
+ * @author n1474335 [n1474335@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
@@ -16,6 +16,21 @@ self.maxWorkers = 4;
self.maxTabs = 1;
self.pendingFiles = [];
+
+/**
+ * Dictionary of inputs keyed on the inputNum
+ * Each entry is an object with the following type:
+ * @typedef {Object} Input
+ * @property {string} type
+ * @property {ArrayBuffer} buffer
+ * @property {string} stringSample
+ * @property {Object} file
+ * @property {string} file.name
+ * @property {number} file.size
+ * @property {string} file.type
+ * @property {string} status
+ * @property {number} progress
+ */
self.inputs = {};
self.loaderWorkers = [];
self.currentInputNum = 1;
@@ -53,9 +68,6 @@ self.addEventListener("message", function(e) {
case "updateInputValue":
self.updateInputValue(r.data);
break;
- case "updateInputObj":
- self.updateInputObj(r.data);
- break;
case "updateInputProgress":
self.updateInputProgress(r.data);
break;
@@ -75,7 +87,7 @@ self.addEventListener("message", function(e) {
log.setLevel(r.data, false);
break;
case "addInput":
- self.addInput(r.data, "string");
+ self.addInput(r.data, "userinput");
break;
case "refreshTabs":
self.refreshTabs(r.data.inputNum, r.data.direction);
@@ -98,9 +110,6 @@ self.addEventListener("message", function(e) {
case "loaderWorkerMessage":
self.handleLoaderMessage(r.data);
break;
- case "inputSwitch":
- self.inputSwitch(r.data);
- break;
case "updateTabHeader":
self.updateTabHeader(r.data);
break;
@@ -213,13 +222,10 @@ self.bakeInput = function(inputNum, bakeId) {
return;
}
- let inputData = inputObj.data;
- if (typeof inputData !== "string") inputData = inputData.fileBuffer;
-
self.postMessage({
action: "queueInput",
data: {
- input: inputData,
+ input: inputObj.buffer,
inputNum: inputNum,
bakeId: bakeId
}
@@ -236,23 +242,6 @@ 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.
*
@@ -263,7 +252,7 @@ self.getInputValue = function(inputNum) {
*/
self.getInput = function(inputData) {
const inputNum = inputData.inputNum,
- data = (inputData.getObj) ? self.getInputObj(inputNum) : self.getInputValue(inputNum);
+ data = (inputData.getObj) ? self.getInputObj(inputNum) : self.inputs[inputNum].buffer;
self.postMessage({
action: "getInput",
data: {
@@ -421,17 +410,15 @@ self.getNearbyNums = function(inputNum, direction) {
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, "");
+
+ let header = input.type === "file" ? input.file.name : input.stringSample;
+ header = header.slice(0, 100).replace(/[\n\r]/g, "");
self.postMessage({
action: "updateTabHeader",
data: {
inputNum: inputNum,
- input: inputData.slice(0, 100)
+ input: header
}
});
};
@@ -450,37 +437,15 @@ self.setInput = function(inputData) {
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: {
+ inputNum: inputNum,
+ inputObj: input,
+ silent: silent
+ }
+ });
- self.postMessage({
- action: "setInput",
- data: {
- inputObj: inputObj,
- silent: silent
- }
- }, [fileSlice]);
- } else {
- self.postMessage({
- action: "setInput",
- data: {
- inputObj: inputObj,
- silent: silent
- }
- });
- }
self.updateTabHeader(inputNum);
};
@@ -546,54 +511,23 @@ self.updateInputProgress = function(inputData) {
*
* @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
+ * @param {ArrayBuffer} inputData.buffer - The new value of the input as a buffer
+ * @param {string} [inputData.stringSample] - A sample of the value as a string (truncated to 4096 chars)
*/
self.updateInputValue = function(inputData) {
- const inputNum = inputData.inputNum;
+ const inputNum = parseInt(inputData.inputNum, 10);
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 (!Object.prototype.hasOwnProperty.call(self.inputs, inputNum))
+ throw new Error(`No input with ID ${inputNum} exists`);
+
+ self.inputs[inputNum].buffer = inputData.buffer;
+ if (!("stringSample" in inputData)) {
+ inputData.stringSample = Utils.arrayBufferToStr(inputData.buffer.slice(0, 4096));
}
-
- // 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;
+ self.inputs[inputNum].stringSample = inputData.stringSample;
+ self.inputs[inputNum].status = "loaded";
+ self.inputs[inputNum].progress = 100;
};
/**
@@ -632,8 +566,7 @@ self.loaderWorkerReady = function(workerData) {
/**
* Handler for messages sent by loaderWorkers.
- * (Messages are sent between the inputWorker and
- * loaderWorkers via the main thread)
+ * (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
@@ -667,7 +600,7 @@ self.handleLoaderMessage = function(r) {
self.updateInputValue({
inputNum: inputNum,
- value: r.fileBuffer
+ buffer: r.fileBuffer
});
self.postMessage({
@@ -757,7 +690,8 @@ self.loadFiles = function(filesData) {
let lastInputNum = -1;
const inputNums = [];
for (let i = 0; i < files.length; i++) {
- if (i === 0 && self.getInputValue(activeTab) === "") {
+ // If the first input is empty, replace it rather than adding a new one
+ if (i === 0 && (!self.inputs[activeTab].buffer || self.inputs[activeTab].buffer.byteLength === 0)) {
self.removeInput({
inputNum: activeTab,
refreshTabs: false,
@@ -798,7 +732,7 @@ self.loadFiles = function(filesData) {
* 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 {string} type - Either "userinput" 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
@@ -810,25 +744,30 @@ self.addInput = function(
type,
fileData = {
name: "unknown",
- size: "unknown",
+ size: 0,
type: "unknown"
},
inputNum = self.currentInputNum++
) {
self.numInputs++;
const newInputObj = {
- inputNum: inputNum
+ type: null,
+ buffer: new ArrayBuffer(),
+ stringSample: "",
+ file: null,
+ status: "pending",
+ progress: 0
};
switch (type) {
- case "string":
- newInputObj.data = "";
+ case "userinput":
+ newInputObj.type = "userinput";
newInputObj.status = "loaded";
newInputObj.progress = 100;
break;
case "file":
- newInputObj.data = {
- fileBuffer: new ArrayBuffer(),
+ newInputObj.type = "file";
+ newInputObj.file = {
name: fileData.name,
size: fileData.size,
type: fileData.type
@@ -837,7 +776,7 @@ self.addInput = function(
newInputObj.progress = 0;
break;
default:
- log.error(`Invalid type '${type}'.`);
+ log.error(`Invalid input type '${type}'.`);
return -1;
}
self.inputs[inputNum] = newInputObj;
@@ -976,18 +915,18 @@ self.filterTabs = function(searchData) {
self.inputs[iNum].status === "loading" && showLoading ||
self.inputs[iNum].status === "loaded" && showLoaded) {
try {
- if (typeof self.inputs[iNum].data === "string") {
+ if (self.inputs[iNum].type === "userinput") {
if (filterType.toLowerCase() === "content" &&
- filterExp.test(self.inputs[iNum].data.slice(0, 4096))) {
- textDisplay = self.inputs[iNum].data.slice(0, 4096);
+ filterExp.test(self.inputs[iNum].stringSample)) {
+ textDisplay = self.inputs[iNum].stringSample;
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;
+ filterExp.test(self.inputs[iNum].file.name)) ||
+ (filterType.toLowerCase() === "content" &&
+ filterExp.test(self.inputs[iNum].stringSample))) {
+ textDisplay = self.inputs[iNum].file.name;
addInput = true;
}
}
@@ -1021,61 +960,3 @@ self.filterTabs = function(searchData) {
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
- }
- });
-
-};