mirror of
https://github.com/gchq/CyberChef
synced 2025-01-04 00:38:41 +00:00
Input and output character encodings can now be set
This commit is contained in:
parent
7c8a185a3d
commit
e93aa42697
15 changed files with 482 additions and 423 deletions
|
@ -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,
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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, "");
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue