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:", "", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; @@ -36,7 +36,7 @@ class DecodeText extends Operation { { "name": "Encoding", "type": "option", - "value": Object.keys(IO_FORMAT) + "value": Object.keys(CHR_ENC_CODE_PAGES) } ]; } @@ -47,7 +47,7 @@ class DecodeText extends Operation { * @returns {string} */ run(input, args) { - const format = IO_FORMAT[args[0]]; + const format = CHR_ENC_CODE_PAGES[args[0]]; return cptable.utils.decode(format, new Uint8Array(input)); } diff --git a/src/core/operations/EncodeText.mjs b/src/core/operations/EncodeText.mjs index 8fc61fce..8cc1450f 100644 --- a/src/core/operations/EncodeText.mjs +++ b/src/core/operations/EncodeText.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"; /** * Encode text operation @@ -26,7 +26,7 @@ class EncodeText extends Operation { "

", "Supported charsets are:", "", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; @@ -36,7 +36,7 @@ class EncodeText extends Operation { { "name": "Encoding", "type": "option", - "value": Object.keys(IO_FORMAT) + "value": Object.keys(CHR_ENC_CODE_PAGES) } ]; } @@ -47,7 +47,7 @@ class EncodeText extends Operation { * @returns {ArrayBuffer} */ run(input, args) { - const format = IO_FORMAT[args[0]]; + const format = CHR_ENC_CODE_PAGES[args[0]]; const encoded = cptable.utils.encode(format, input); return new Uint8Array(encoded).buffer; } diff --git a/src/core/operations/TextEncodingBruteForce.mjs b/src/core/operations/TextEncodingBruteForce.mjs index ef8b7f80..ae96fd0a 100644 --- a/src/core/operations/TextEncodingBruteForce.mjs +++ b/src/core/operations/TextEncodingBruteForce.mjs @@ -8,7 +8,7 @@ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import cptable from "codepage"; -import {IO_FORMAT} from "../lib/ChrEnc.mjs"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; /** * Text Encoding Brute Force operation @@ -28,7 +28,7 @@ class TextEncodingBruteForce extends Operation { "

", "Supported charsets are:", "" ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; @@ -51,15 +51,15 @@ class TextEncodingBruteForce extends Operation { */ run(input, args) { const output = {}, - charsets = Object.keys(IO_FORMAT), + charsets = Object.keys(CHR_ENC_CODE_PAGES), mode = args[0]; charsets.forEach(charset => { try { if (mode === "Decode") { - output[charset] = cptable.utils.decode(IO_FORMAT[charset], input); + output[charset] = cptable.utils.decode(CHR_ENC_CODE_PAGES[charset], input); } else { - output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(IO_FORMAT[charset], input)); + output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(CHR_ENC_CODE_PAGES[charset], input)); } } catch (err) { output[charset] = "Could not decode."; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 820b1a8d..793b61de 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -180,7 +180,6 @@ class Manager { document.getElementById("save-all-to-file").addEventListener("click", this.output.saveAllClick.bind(this.output)); document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output)); document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); - document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); diff --git a/src/web/html/index.html b/src/web/html/index.html index a7931de5..68d69a78 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -300,9 +300,6 @@ - diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index ea15b6ac..185b3bdb 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -224,7 +224,7 @@ #output-file { position: absolute; left: 0; - bottom: 0; + top: 50%; width: 100%; display: none; } @@ -446,6 +446,10 @@ /* Status bar */ +.cm-panel input::placeholder { + font-size: 12px !important; +} + .ͼ2 .cm-panels { background-color: var(--secondary-background-colour); border-color: var(--secondary-border-colour); @@ -509,12 +513,38 @@ background-color: #ddd } -/* Show the dropup menu on hover */ -.cm-status-bar-select:hover .cm-status-bar-select-content { - display: block; -} - /* Change the background color of the dropup button when the dropup content is shown */ .cm-status-bar-select:hover .cm-status-bar-select-btn { background-color: #f1f1f1; } + +/* The search field */ +.cm-status-bar-filter-input { + box-sizing: border-box; + font-size: 12px; + padding-left: 10px !important; + border: none; +} + +.cm-status-bar-filter-search { + border-top: 1px solid #ddd; +} + +/* Show the dropup menu */ +.cm-status-bar-select .show { + display: block; +} + +.cm-status-bar-select-scroll { + overflow-y: auto; + max-height: 300px; +} + +.chr-enc-value { + max-width: 150px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; +} \ No newline at end of file diff --git a/src/web/utils/htmlWidget.mjs b/src/web/utils/htmlWidget.mjs index 5e5c41c1..34800933 100644 --- a/src/web/utils/htmlWidget.mjs +++ b/src/web/utils/htmlWidget.mjs @@ -65,9 +65,11 @@ class HTMLWidget extends WidgetType { */ replaceControlChars(textNode) { const val = escapeControlChars(textNode.nodeValue, true, this.view.state.lineBreak); - const node = document.createElement("null"); - node.innerHTML = val; - textNode.parentNode.replaceChild(node, textNode); + if (val.length !== textNode.nodeValue.length) { + const node = document.createElement("span"); + node.innerHTML = val; + textNode.parentNode.replaceChild(node, textNode); + } } } @@ -119,8 +121,7 @@ export function htmlPlugin(htmlOutput) { } } }, { - decorations: v => v.decorations, - + decorations: v => v.decorations } ); diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 431d8a3d..f9be5006 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -5,6 +5,7 @@ */ import {showPanel} from "@codemirror/view"; +import {CHR_ENC_SIMPLE_LOOKUP, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs"; /** * A Status bar extension for CodeMirror @@ -19,6 +20,10 @@ class StatusBarPanel { this.label = opts.label; this.bakeStats = opts.bakeStats ? opts.bakeStats : null; this.eolHandler = opts.eolHandler; + this.chrEncHandler = opts.chrEncHandler; + + this.eolVal = null; + this.chrEncVal = null; this.dom = this.buildDOM(); } @@ -40,19 +45,42 @@ class StatusBarPanel { dom.appendChild(rhs); // Event listeners - dom.addEventListener("click", this.eolSelectClick.bind(this), false); + dom.querySelectorAll(".cm-status-bar-select-btn").forEach( + el => el.addEventListener("click", this.showDropUp.bind(this), false) + ); + dom.querySelector(".eol-select").addEventListener("click", this.eolSelectClick.bind(this), false); + dom.querySelector(".chr-enc-select").addEventListener("click", this.chrEncSelectClick.bind(this), false); + dom.querySelector(".cm-status-bar-filter-input").addEventListener("keyup", this.chrEncFilter.bind(this), false); return dom; } + /** + * Handler for dropup clicks + * Shows/Hides the dropup + * @param {Event} e + */ + showDropUp(e) { + const el = e.target + .closest(".cm-status-bar-select") + .querySelector(".cm-status-bar-select-content"); + + el.classList.add("show"); + + // Focus the filter input if present + const filter = el.querySelector(".cm-status-bar-filter-input"); + if (filter) filter.focus(); + + // Set up a listener to close the menu if the user clicks outside of it + hideOnClickOutside(el, e); + } + /** * Handler for EOL Select clicks * Sets the line separator * @param {Event} e */ eolSelectClick(e) { - e.preventDefault(); - const eolLookup = { "LF": "\u000a", "VT": "\u000b", @@ -65,8 +93,46 @@ class StatusBarPanel { }; const eolval = eolLookup[e.target.getAttribute("data-val")]; + if (eolval === undefined) return; + // Call relevant EOL change handler this.eolHandler(eolval); + hideElement(e.target.closest(".cm-status-bar-select-content")); + } + + /** + * Handler for Chr Enc Select clicks + * Sets the character encoding + * @param {Event} e + */ + chrEncSelectClick(e) { + const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); + + if (isNaN(chrEncVal)) return; + + this.chrEncHandler(chrEncVal); + this.updateCharEnc(chrEncVal); + hideElement(e.target.closest(".cm-status-bar-select-content")); + } + + /** + * Handler for Chr Enc keyup events + * Filters the list of selectable character encodings + * @param {Event} e + */ + chrEncFilter(e) { + const input = e.target; + const filter = input.value.toLowerCase(); + const div = input.closest(".cm-status-bar-select-content"); + const a = div.getElementsByTagName("a"); + for (let i = 0; i < a.length; i++) { + const txtValue = a[i].textContent || a[i].innerText; + if (txtValue.toLowerCase().includes(filter)) { + a[i].style.display = "block"; + } else { + a[i].style.display = "none"; + } + } } /** @@ -121,33 +187,48 @@ class StatusBarPanel { } /** - * Gets the current character encoding of the document - * @param {EditorState} state - */ - updateCharEnc(state) { - // const charenc = this.dom.querySelector("#char-enc-value"); - // TODO - // charenc.textContent = "TODO"; - } - - /** - * Returns what the current EOL separator is set to + * Sets the current EOL separator in the status bar * @param {EditorState} state */ updateEOL(state) { + if (state.lineBreak === this.eolVal) return; + const eolLookup = { - "\u000a": "LF", - "\u000b": "VT", - "\u000c": "FF", - "\u000d": "CR", - "\u000d\u000a": "CRLF", - "\u0085": "NEL", - "\u2028": "LS", - "\u2029": "PS" + "\u000a": ["LF", "Line Feed"], + "\u000b": ["VT", "Vertical Tab"], + "\u000c": ["FF", "Form Feed"], + "\u000d": ["CR", "Carriage Return"], + "\u000d\u000a": ["CRLF", "Carriage Return + Line Feed"], + "\u0085": ["NEL", "Next Line"], + "\u2028": ["LS", "Line Separator"], + "\u2029": ["PS", "Paragraph Separator"] }; const val = this.dom.querySelector(".eol-value"); - val.textContent = eolLookup[state.lineBreak]; + const button = val.closest(".cm-status-bar-select-btn"); + const eolName = eolLookup[state.lineBreak]; + val.textContent = eolName[0]; + button.setAttribute("title", `End of line sequence: ${eolName[1]}`); + button.setAttribute("data-original-title", `End of line sequence: ${eolName[1]}`); + this.eolVal = state.lineBreak; + } + + + /** + * Gets the current character encoding of the document + * @param {number} chrEncVal + */ + updateCharEnc(chrEncVal) { + if (chrEncVal === this.chrEncVal) return; + + const name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] : "Raw Bytes"; + + const val = this.dom.querySelector(".chr-enc-value"); + const button = val.closest(".cm-status-bar-select-btn"); + val.textContent = name; + button.setAttribute("title", `${this.label} character encoding: ${name}`); + button.setAttribute("data-original-title", `${this.label} character encoding: ${name}`); + this.chrEncVal = chrEncVal; } /** @@ -168,6 +249,19 @@ class StatusBarPanel { } } + /** + * Updates the sizing of elements that need to fit correctly + * @param {EditorView} view + */ + updateSizing(view) { + const viewHeight = view.contentDOM.clientHeight; + this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach( + el => { + el.style.maxHeight = (viewHeight - 50) + "px"; + } + ); + } + /** * Builds the Left-hand-side widgets * @returns {string} @@ -197,39 +291,98 @@ class StatusBarPanel { /** * Builds the Right-hand-side widgets * Event listener set up in Manager + * * @returns {string} */ constructRHS() { + const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP).map(name => + `${name}` + ).join(""); + return ` - - language - UTF-16 - +
+ + text_fields Raw Bytes + +
+
+ Raw Bytes + ${chrEncOptions} +
+ +
+
keyboard_return -
- Line Feed, U+000A - Vertical Tab, U+000B - Form Feed, U+000C - Carriage Return, U+000D - CR+LF, U+000D U+000A - - Line Separator, U+2028 - Paragraph Separator, U+2029 +
`; } } +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 - } - }); - -};