Input and output character encodings can now be set

This commit is contained in:
n1474335 2022-09-02 12:56:04 +01:00
parent 7c8a185a3d
commit e93aa42697
15 changed files with 482 additions and 423 deletions

View File

@ -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,

View File

@ -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, {

View File

@ -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);
}

View File

@ -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
*

View File

@ -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 {
"<br><br>",
"Supported charsets are:",
"<ul>",
Object.keys(IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join("\n"),
"</ul>",
].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));
}

View File

@ -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 {
"<br><br>",
"Supported charsets are:",
"<ul>",
Object.keys(IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join("\n"),
"</ul>",
].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;
}

View File

@ -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 {
"<br><br>",
"Supported charsets are:",
"<ul>",
Object.keys(IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join("\n"),
"</ul>"
].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.";

View File

@ -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);

View File

@ -300,9 +300,6 @@
<button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Replace input with output">
<i class="material-icons">open_in_browser</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="undo-switch" data-toggle="tooltip" title="Undo" disabled="disabled">
<i class="material-icons">undo</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="maximise-output" data-toggle="tooltip" title="Maximise output pane">
<i class="material-icons">fullscreen</i>
</button>

View File

@ -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;
}

View File

@ -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
}
);

View File

@ -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 =>
`<a href="#" draggable="false" data-val="${CHR_ENC_SIMPLE_LOOKUP[name]}">${name}</a>`
).join("");
return `
<span class="baking-time-info" style="display: none" data-toggle="tooltip" title="Baking time">
<i class="material-icons">schedule</i>
<span class="baking-time-value"></span>ms
</span>
<span data-toggle="tooltip" title="${this.label} character encoding">
<i class="material-icons">language</i>
<span class="char-enc-value">UTF-16</span>
</span>
<div class="cm-status-bar-select chr-enc-select">
<span class="cm-status-bar-select-btn" data-toggle="tooltip" data-placement="left" title="${this.label} character encoding">
<i class="material-icons">text_fields</i> <span class="chr-enc-value">Raw Bytes</span>
</span>
<div class="cm-status-bar-select-content">
<div class="cm-status-bar-select-scroll no-select">
<a href="#" draggable="false" data-val="0">Raw Bytes</a>
${chrEncOptions}
</div>
<div class="input-group cm-status-bar-filter-search">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="material-icons">search</i>
</span>
</div>
<input type="text" class="form-control cm-status-bar-filter-input" placeholder="Filter...">
</div>
</div>
</div>
<div class="cm-status-bar-select eol-select">
<span class="cm-status-bar-select-btn" data-toggle="tooltip" data-placement="left" title="End of line sequence">
<i class="material-icons">keyboard_return</i> <span class="eol-value"></span>
</span>
<div class="cm-status-bar-select-content">
<a href="#" data-val="LF">Line Feed, U+000A</a>
<a href="#" data-val="VT">Vertical Tab, U+000B</a>
<a href="#" data-val="FF">Form Feed, U+000C</a>
<a href="#" data-val="CR">Carriage Return, U+000D</a>
<a href="#" data-val="CRLF">CR+LF, U+000D U+000A</a>
<!-- <a href="#" data-val="NL">Next Line, U+0085</a> This causes problems. -->
<a href="#" data-val="LS">Line Separator, U+2028</a>
<a href="#" data-val="PS">Paragraph Separator, U+2029</a>
<div class="cm-status-bar-select-content no-select">
<a href="#" draggable="false" data-val="LF">Line Feed, U+000A</a>
<a href="#" draggable="false" data-val="VT">Vertical Tab, U+000B</a>
<a href="#" draggable="false" data-val="FF">Form Feed, U+000C</a>
<a href="#" draggable="false" data-val="CR">Carriage Return, U+000D</a>
<a href="#" draggable="false" data-val="CRLF">CR+LF, U+000D U+000A</a>
<!-- <a href="#" draggable="false" data-val="NL">Next Line, U+0085</a> This causes problems. -->
<a href="#" draggable="false" data-val="LS">Line Separator, U+2028</a>
<a href="#" draggable="false" data-val="PS">Paragraph Separator, U+2029</a>
</div>
</div>`;
}
}
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);
}

View File

@ -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, "");

View File

@ -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";

View File

@ -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
}
});
};