mirror of
https://github.com/gchq/CyberChef.git
synced 2024-11-02 14:11:02 +01:00
Add carriage return detection for pasted and switched inputs.
Fix switching the output to input not working properly. Add nicer confirmation boxes for zipping outputs.
This commit is contained in:
parent
9f2d1453ed
commit
f43a868607
@ -568,11 +568,18 @@
|
||||
</div>
|
||||
|
||||
<div class="checkbox option-item">
|
||||
<label for="syncTabs">
|
||||
<input type="checkbox" option="syncTabs" id="syncTabs">
|
||||
Keep the current tab in sync between the input and output.
|
||||
</label>
|
||||
</div>
|
||||
<label for="syncTabs">
|
||||
<input type="checkbox" option="syncTabs" id="syncTabs">
|
||||
Keep the current tab in sync between the input and output.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox option-item">
|
||||
<label for="preserveCR" data-toggle="tooltip" data-placement="right" data-html="true" title="As HTML textareas don't support carriage returns, editing input must be turned off to preserve them.<br><br>When this option is enabled, editing will be disabled when an input with carriage returns is detected to preserve them. Otherwise, editing will remain enabled but carriage returns will not be preserved.">
|
||||
<input type="checkbox" option="preserveCR" id="preserveCR">
|
||||
Preserve carriage returns.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" id="reset-options">Reset options to default</button>
|
||||
|
@ -53,7 +53,9 @@ function main() {
|
||||
logLevel: "info",
|
||||
autoMagic: true,
|
||||
imagePreview: true,
|
||||
syncTabs: true
|
||||
syncTabs: true,
|
||||
preserveCR: true,
|
||||
userSetCR: false
|
||||
};
|
||||
|
||||
document.removeEventListener("DOMContentLoaded", main, false);
|
||||
|
@ -222,8 +222,6 @@ class InputWaiter {
|
||||
if (Object.prototype.hasOwnProperty.call(r, "progress") &&
|
||||
Object.prototype.hasOwnProperty.call(r, "inputNum")) {
|
||||
this.manager.tabs.updateInputTabProgress(r.inputNum, r.progress, 100);
|
||||
} else if (Object.prototype.hasOwnProperty.call(r, "fileBuffer")) {
|
||||
this.manager.tabs.updateInputTabProgress(r.inputNum, 100, 100);
|
||||
}
|
||||
|
||||
const transferable = Object.prototype.hasOwnProperty.call(r, "fileBuffer") ? [r.fileBuffer] : undefined;
|
||||
@ -305,6 +303,9 @@ class InputWaiter {
|
||||
case "removeChefWorker":
|
||||
this.removeChefWorker();
|
||||
break;
|
||||
case "fileLoaded":
|
||||
this.fileLoaded(r.data.inputNum);
|
||||
break;
|
||||
default:
|
||||
log.error(`Unknown action ${r.action}.`);
|
||||
}
|
||||
@ -331,7 +332,7 @@ class InputWaiter {
|
||||
* @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 {boolean} [silent=false] - If true, fires the manager statechange event
|
||||
* @param {boolean} [silent=false] - If false, fires the manager statechange event
|
||||
*/
|
||||
async set(inputData, silent=false) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
@ -373,7 +374,7 @@ class InputWaiter {
|
||||
|
||||
if (!silent) window.dispatchEvent(this.manager.statechange);
|
||||
} else {
|
||||
this.setFile(inputData);
|
||||
this.setFile(inputData, silent);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
@ -389,8 +390,9 @@ class InputWaiter {
|
||||
* @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 {boolean} [silent=true] - If false, fires the manager statechange event
|
||||
*/
|
||||
setFile(inputData) {
|
||||
setFile(inputData, silent=true) {
|
||||
const activeTab = this.manager.tabs.getActiveInputTab();
|
||||
if (inputData.inputNum !== activeTab) return;
|
||||
|
||||
@ -414,6 +416,30 @@ class InputWaiter {
|
||||
|
||||
this.setInputInfo(inputData.size, null);
|
||||
this.displayFilePreview(inputData);
|
||||
|
||||
if (!silent) window.dispatchEvent(this.manager.statechange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file details when a file completes loading
|
||||
*
|
||||
* @param {number} inputNum - The inputNum of the input which has finished loading
|
||||
*/
|
||||
fileLoaded(inputNum) {
|
||||
this.manager.tabs.updateInputTabProgress(inputNum, 100, 100);
|
||||
|
||||
const activeTab = this.manager.tabs.getActiveInputTab();
|
||||
if (activeTab !== inputNum) return;
|
||||
|
||||
this.inputWorker.postMessage({
|
||||
action: "setInput",
|
||||
data: {
|
||||
inputNum: inputNum,
|
||||
silent: false
|
||||
}
|
||||
});
|
||||
|
||||
this.updateFileProgress(inputNum, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -495,19 +521,6 @@ class InputWaiter {
|
||||
fileLoaded.textContent = progress + "%";
|
||||
fileLoaded.style.color = "";
|
||||
}
|
||||
|
||||
if (progress === 100 && progress !== oldProgress) {
|
||||
// Don't set the input if the progress hasn't changed
|
||||
this.inputWorker.postMessage({
|
||||
action: "setInput",
|
||||
data: {
|
||||
inputNum: inputNum,
|
||||
silent: false
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(this.manager.statechange);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -711,13 +724,17 @@ class InputWaiter {
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
inputPaste(e) {
|
||||
async inputPaste(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const pastedData = e.clipboardData.getData("Text");
|
||||
if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) {
|
||||
const preserve = await this.preserveCarriageReturns(pastedData);
|
||||
|
||||
if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024) && !preserve) {
|
||||
// Pasting normally fires the inputChange() event before
|
||||
// changing the value, so instead change it here ourselves
|
||||
// and manually fire inputChange()
|
||||
e.preventDefault();
|
||||
const inputText = document.getElementById("input-text");
|
||||
const selStart = inputText.selectionStart;
|
||||
const selEnd = inputText.selectionEnd;
|
||||
@ -728,9 +745,6 @@ class InputWaiter {
|
||||
inputText.setSelectionRange(selStart + pastedData.length, selStart + pastedData.length);
|
||||
this.debounceInputChange(e);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const file = new File([pastedData], "PastedData", {
|
||||
type: "text/plain",
|
||||
lastModified: Date.now()
|
||||
@ -815,6 +829,45 @@ class InputWaiter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an input contains carriage returns.
|
||||
* If a CR is detected, checks if the preserve CR option has been set,
|
||||
* and if not, asks the user for their preference.
|
||||
*
|
||||
* @param {string} input - The input to be checked
|
||||
* @returns {boolean} - If true, the input contains a CR which should be
|
||||
* preserved, so display an overlay so it can't be edited
|
||||
*/
|
||||
async preserveCarriageReturns(input) {
|
||||
if (input.indexOf("\r") >= 0) {
|
||||
const optionsStr = "This behaviour can be changed in the <a href='#' onclick='document.getElementById(\"options\").click()'>options</a>";
|
||||
if (!this.app.options.userSetCR) {
|
||||
let preserve = await new Promise(function(resolve, reject) {
|
||||
this.app.confirm(
|
||||
"Carriage Return Detected",
|
||||
"A carriage return was detected in your input. As HTML textareas can't display carriage returns, editing must be turned off to preserve them. <br>Alternatively, you can enable editing but your carriage returns will not be preserved.<br><br>This preference will be saved, and can be toggled in the options.",
|
||||
"Preserve Carriage Returns",
|
||||
"Enable Editing", resolve, this);
|
||||
}.bind(this));
|
||||
if (preserve === undefined) {
|
||||
this.app.alert(`Not preserving carriage returns. ${optionsStr}`, 4000);
|
||||
preserve = false;
|
||||
}
|
||||
this.manager.options.updateOption("preserveCR", preserve);
|
||||
this.manager.options.updateOption("userSetCR", true);
|
||||
} else {
|
||||
if (this.app.options.preserveCR) {
|
||||
this.app.alert(`Carriage return(s) detected in input, so editing has been disabled to preserve them. ${optionsStr}`, 6000);
|
||||
} else {
|
||||
this.app.alert(`Carriage return(s) detected in input. Editing is remaining enabled, but any carriage returns will be removed. ${optionsStr}`, 6000);
|
||||
}
|
||||
}
|
||||
return this.app.options.preserveCR;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load files from the UI into the inputWorker
|
||||
*
|
||||
|
@ -1,174 +1,180 @@
|
||||
/**
|
||||
* Waiter to handle events related to the CyberChef options.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {App} app - The main view object for CyberChef.
|
||||
*/
|
||||
const OptionsWaiter = function(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads options and sets values of switches and inputs to match them.
|
||||
*
|
||||
* @param {Object} options
|
||||
* Waiter to handle events related to the CyberChef options.
|
||||
*/
|
||||
OptionsWaiter.prototype.load = function(options) {
|
||||
for (const option in options) {
|
||||
this.app.options[option] = options[option];
|
||||
class OptionsWaiter {
|
||||
|
||||
/**
|
||||
* OptionsWaiter constructor.
|
||||
*
|
||||
* @param {App} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
constructor(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
// Set options to match object
|
||||
const cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
|
||||
let i;
|
||||
for (i = 0; i < cboxes.length; i++) {
|
||||
cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")];
|
||||
}
|
||||
/**
|
||||
* Loads options and sets values of switches and inputs to match them.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
load(options) {
|
||||
for (const option in options) {
|
||||
this.app.options[option] = options[option];
|
||||
}
|
||||
|
||||
const nboxes = document.querySelectorAll("#options-body input[type=number]");
|
||||
for (i = 0; i < nboxes.length; i++) {
|
||||
nboxes[i].value = this.app.options[nboxes[i].getAttribute("option")];
|
||||
nboxes[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
|
||||
}
|
||||
// Set options to match object
|
||||
const cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
|
||||
let i;
|
||||
for (i = 0; i < cboxes.length; i++) {
|
||||
cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")];
|
||||
}
|
||||
|
||||
const selects = document.querySelectorAll("#options-body select");
|
||||
for (i = 0; i < selects.length; i++) {
|
||||
const val = this.app.options[selects[i].getAttribute("option")];
|
||||
if (val) {
|
||||
selects[i].value = val;
|
||||
selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
|
||||
} else {
|
||||
selects[i].selectedIndex = 0;
|
||||
const nboxes = document.querySelectorAll("#options-body input[type=number]");
|
||||
for (i = 0; i < nboxes.length; i++) {
|
||||
nboxes[i].value = this.app.options[nboxes[i].getAttribute("option")];
|
||||
nboxes[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
|
||||
}
|
||||
|
||||
const selects = document.querySelectorAll("#options-body select");
|
||||
for (i = 0; i < selects.length; i++) {
|
||||
const val = this.app.options[selects[i].getAttribute("option")];
|
||||
if (val) {
|
||||
selects[i].value = val;
|
||||
selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
|
||||
} else {
|
||||
selects[i].selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for options click events.
|
||||
* Dispays the options pane.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.optionsClick = function(e) {
|
||||
e.preventDefault();
|
||||
$("#options-modal").modal();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for reset options click events.
|
||||
* Resets options back to their default values.
|
||||
*/
|
||||
OptionsWaiter.prototype.resetOptionsClick = function() {
|
||||
this.load(this.app.doptions);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for switch change events.
|
||||
* Modifies the option state and saves it to local storage.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.switchChange = function(e) {
|
||||
const el = e.target;
|
||||
const option = el.getAttribute("option");
|
||||
const state = el.checked;
|
||||
|
||||
log.debug(`Setting ${option} to ${state}`);
|
||||
this.app.options[option] = state;
|
||||
|
||||
if (this.app.isLocalStorageAvailable())
|
||||
localStorage.setItem("options", JSON.stringify(this.app.options));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for number change events.
|
||||
* Modifies the option value and saves it to local storage.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.numberChange = function(e) {
|
||||
const el = e.target;
|
||||
const option = el.getAttribute("option");
|
||||
const val = parseInt(el.value, 10);
|
||||
|
||||
log.debug(`Setting ${option} to ${val}`);
|
||||
this.app.options[option] = val;
|
||||
|
||||
if (this.app.isLocalStorageAvailable())
|
||||
localStorage.setItem("options", JSON.stringify(this.app.options));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for select change events.
|
||||
* Modifies the option value and saves it to local storage.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.selectChange = function(e) {
|
||||
const el = e.target;
|
||||
const option = el.getAttribute("option");
|
||||
|
||||
log.debug(`Setting ${option} to ${el.value}`);
|
||||
this.app.options[option] = el.value;
|
||||
|
||||
if (this.app.isLocalStorageAvailable())
|
||||
localStorage.setItem("options", JSON.stringify(this.app.options));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets or unsets word wrap on the input and output depending on the wordWrap option value.
|
||||
*/
|
||||
OptionsWaiter.prototype.setWordWrap = function() {
|
||||
document.getElementById("input-text").classList.remove("word-wrap");
|
||||
document.getElementById("output-text").classList.remove("word-wrap");
|
||||
document.getElementById("output-html").classList.remove("word-wrap");
|
||||
document.getElementById("input-highlighter").classList.remove("word-wrap");
|
||||
document.getElementById("output-highlighter").classList.remove("word-wrap");
|
||||
|
||||
if (!this.app.options.wordWrap) {
|
||||
document.getElementById("input-text").classList.add("word-wrap");
|
||||
document.getElementById("output-text").classList.add("word-wrap");
|
||||
document.getElementById("output-html").classList.add("word-wrap");
|
||||
document.getElementById("input-highlighter").classList.add("word-wrap");
|
||||
document.getElementById("output-highlighter").classList.add("word-wrap");
|
||||
/**
|
||||
* Handler for options click events.
|
||||
* Dispays the options pane.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
optionsClick(e) {
|
||||
e.preventDefault();
|
||||
$("#options-modal").modal();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Changes the theme by setting the class of the <html> element.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.themeChange = function (e) {
|
||||
const themeClass = e.target.value;
|
||||
|
||||
document.querySelector(":root").className = themeClass;
|
||||
};
|
||||
/**
|
||||
* Handler for reset options click events.
|
||||
* Resets options back to their default values.
|
||||
*/
|
||||
resetOptionsClick() {
|
||||
this.load(this.app.doptions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the console logging level.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.logLevelChange = function (e) {
|
||||
const level = e.target.value;
|
||||
log.setLevel(level, false);
|
||||
this.manager.worker.setLogLevel();
|
||||
this.manager.input.setLogLevel();
|
||||
};
|
||||
/**
|
||||
* Handler for switch change events.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
switchChange(e) {
|
||||
const el = e.target;
|
||||
const option = el.getAttribute("option");
|
||||
const state = el.checked;
|
||||
|
||||
this.updateOption(option, state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for number change events.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
numberChange(e) {
|
||||
const el = e.target;
|
||||
const option = el.getAttribute("option");
|
||||
const val = parseInt(el.value, 10);
|
||||
|
||||
this.updateOption(option, val);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for select change events.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
selectChange(e) {
|
||||
const el = e.target;
|
||||
const option = el.getAttribute("option");
|
||||
|
||||
this.updateOption(option, el.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies an option value and saves it to local storage.
|
||||
*
|
||||
* @param {string} option - The option to be updated
|
||||
* @param {string|number|boolean} value - The new value of the option
|
||||
*/
|
||||
updateOption(option, value) {
|
||||
log.debug(`Setting ${option} to ${value}`);
|
||||
this.app.options[option] = value;
|
||||
|
||||
if (this.app.isLocalStorageAvailable())
|
||||
localStorage.setItem("options", JSON.stringify(this.app.options));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets or unsets word wrap on the input and output depending on the wordWrap option value.
|
||||
*/
|
||||
setWordWrap() {
|
||||
document.getElementById("input-text").classList.remove("word-wrap");
|
||||
document.getElementById("output-text").classList.remove("word-wrap");
|
||||
document.getElementById("output-html").classList.remove("word-wrap");
|
||||
document.getElementById("input-highlighter").classList.remove("word-wrap");
|
||||
document.getElementById("output-highlighter").classList.remove("word-wrap");
|
||||
|
||||
if (!this.app.options.wordWrap) {
|
||||
document.getElementById("input-text").classList.add("word-wrap");
|
||||
document.getElementById("output-text").classList.add("word-wrap");
|
||||
document.getElementById("output-html").classList.add("word-wrap");
|
||||
document.getElementById("input-highlighter").classList.add("word-wrap");
|
||||
document.getElementById("output-highlighter").classList.add("word-wrap");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the theme by setting the class of the <html> element.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
themeChange(e) {
|
||||
const themeClass = e.target.value;
|
||||
|
||||
document.querySelector(":root").className = themeClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the console logging level.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
logLevelChange(e) {
|
||||
const level = e.target.value;
|
||||
log.setLevel(level, false);
|
||||
this.manager.worker.setLogLevel();
|
||||
this.manager.input.setLogLevel();
|
||||
}
|
||||
}
|
||||
|
||||
export default OptionsWaiter;
|
||||
|
@ -217,6 +217,9 @@ class OutputWaiter {
|
||||
*/
|
||||
removeAllOutputs() {
|
||||
this.outputs = {};
|
||||
|
||||
this.resetSwitch();
|
||||
|
||||
const tabsList = document.getElementById("output-tabs");
|
||||
const tabsListChildren = tabsList.children;
|
||||
|
||||
@ -516,9 +519,10 @@ class OutputWaiter {
|
||||
this.app.alert("Could not find any output data to download. Has this output been baked?", 3000);
|
||||
return;
|
||||
}
|
||||
let fileName = window.prompt("Please enter a filename: ", "download.dat");
|
||||
const fileName = window.prompt("Please enter a filename: ", "download.dat");
|
||||
|
||||
if (fileName === null) fileName = "download.dat";
|
||||
// Assume if the user clicks cancel they don't want to download
|
||||
if (fileName === null) return;
|
||||
|
||||
const data = await dish.get(Dish.ARRAY_BUFFER),
|
||||
file = new File([data], fileName);
|
||||
@ -529,12 +533,22 @@ class OutputWaiter {
|
||||
* Handler for save all click event
|
||||
* Saves all outputs to a single archvie file
|
||||
*/
|
||||
saveAllClick() {
|
||||
async saveAllClick() {
|
||||
const downloadButton = document.getElementById("save-all-to-file");
|
||||
if (downloadButton.firstElementChild.innerHTML === "archive") {
|
||||
this.downloadAllFiles();
|
||||
} else if (window.confirm("Cancel zipping of outputs?")) {
|
||||
this.terminateZipWorker();
|
||||
} else {
|
||||
const cancel = await new Promise(function(resolve, reject) {
|
||||
this.app.confirm(
|
||||
"Cancel zipping?",
|
||||
"The outputs are currently being zipped for download.<br>Cancel zipping?",
|
||||
"Continue zipping",
|
||||
"Cancel zipping",
|
||||
resolve, this);
|
||||
}.bind(this));
|
||||
if (!cancel && cancel !== undefined) {
|
||||
this.terminateZipWorker();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -544,57 +558,61 @@ class OutputWaiter {
|
||||
* be zipped for download
|
||||
*/
|
||||
async downloadAllFiles() {
|
||||
return new Promise(resolve => {
|
||||
const inputNums = Object.keys(this.outputs);
|
||||
for (let i = 0; i < inputNums.length; i++) {
|
||||
const iNum = inputNums[i];
|
||||
if (this.outputs[iNum].status !== "baked" ||
|
||||
this.outputs[iNum].bakeId !== this.manager.worker.bakeId) {
|
||||
if (window.confirm("Not all outputs have been baked yet. Continue downloading outputs?")) {
|
||||
break;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
const inputNums = Object.keys(this.outputs);
|
||||
for (let i = 0; i < inputNums.length; i++) {
|
||||
const iNum = inputNums[i];
|
||||
if (this.outputs[iNum].status !== "baked" ||
|
||||
this.outputs[iNum].bakeId !== this.manager.worker.bakeId) {
|
||||
const continueDownloading = await new Promise(function(resolve, reject) {
|
||||
this.app.confirm(
|
||||
"Incomplete outputs",
|
||||
"Not all outputs have been baked yet. Continue downloading outputs?",
|
||||
"Download", "Cancel", resolve, this);
|
||||
}.bind(this));
|
||||
if (continueDownloading) {
|
||||
break;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fileName = window.prompt("Please enter a filename: ", "download.zip");
|
||||
let fileName = window.prompt("Please enter a filename: ", "download.zip");
|
||||
|
||||
if (fileName === null || fileName === "") {
|
||||
// Don't zip the files if there isn't a filename
|
||||
this.app.alert("No filename was specified.", 3000);
|
||||
return;
|
||||
}
|
||||
if (fileName === null || fileName === "") {
|
||||
// Don't zip the files if there isn't a filename
|
||||
this.app.alert("No filename was specified.", 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileName.match(/.zip$/)) {
|
||||
fileName += ".zip";
|
||||
}
|
||||
if (!fileName.match(/.zip$/)) {
|
||||
fileName += ".zip";
|
||||
}
|
||||
|
||||
let fileExt = window.prompt("Please enter a file extension for the files, or leave blank to detect automatically.", "");
|
||||
let fileExt = window.prompt("Please enter a file extension for the files, or leave blank to detect automatically.", "");
|
||||
|
||||
if (fileExt === null) fileExt = "";
|
||||
if (fileExt === null) fileExt = "";
|
||||
|
||||
if (this.zipWorker !== null) {
|
||||
this.terminateZipWorker();
|
||||
}
|
||||
if (this.zipWorker !== null) {
|
||||
this.terminateZipWorker();
|
||||
}
|
||||
|
||||
const downloadButton = document.getElementById("save-all-to-file");
|
||||
const downloadButton = document.getElementById("save-all-to-file");
|
||||
|
||||
downloadButton.classList.add("spin");
|
||||
downloadButton.title = `Zipping ${inputNums.length} files...`;
|
||||
downloadButton.setAttribute("data-original-title", `Zipping ${inputNums.length} files...`);
|
||||
downloadButton.classList.add("spin");
|
||||
downloadButton.title = `Zipping ${inputNums.length} files...`;
|
||||
downloadButton.setAttribute("data-original-title", `Zipping ${inputNums.length} files...`);
|
||||
|
||||
downloadButton.firstElementChild.innerHTML = "autorenew";
|
||||
downloadButton.firstElementChild.innerHTML = "autorenew";
|
||||
|
||||
log.debug("Creating ZipWorker");
|
||||
this.zipWorker = new ZipWorker();
|
||||
this.zipWorker.postMessage({
|
||||
outputs: this.outputs,
|
||||
filename: fileName,
|
||||
fileExtension: fileExt
|
||||
});
|
||||
this.zipWorker.addEventListener("message", this.handleZipWorkerMessage.bind(this));
|
||||
log.debug("Creating ZipWorker");
|
||||
this.zipWorker = new ZipWorker();
|
||||
this.zipWorker.postMessage({
|
||||
outputs: this.outputs,
|
||||
filename: fileName,
|
||||
fileExtension: fileExt
|
||||
});
|
||||
this.zipWorker.addEventListener("message", this.handleZipWorkerMessage.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1213,14 +1231,39 @@ class OutputWaiter {
|
||||
* Moves the current output into the input textarea.
|
||||
*/
|
||||
async switchClick() {
|
||||
const active = await this.getDishBuffer(this.getOutputDish(this.manager.tabs.getActiveOutputTab()));
|
||||
const activeTab = this.manager.tabs.getActiveOutputTab();
|
||||
const transferable = [];
|
||||
|
||||
const switchButton = document.getElementById("switch");
|
||||
switchButton.classList.add("spin");
|
||||
switchButton.disabled = true;
|
||||
switchButton.firstElementChild.innerHTML = "autorenew";
|
||||
$(switchButton).tooltip("hide");
|
||||
|
||||
let active = 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));
|
||||
if (!await this.manager.input.preserveCarriageReturns(dishString)) {
|
||||
active = dishString;
|
||||
}
|
||||
} else {
|
||||
transferable.push(active);
|
||||
}
|
||||
|
||||
this.manager.input.inputWorker.postMessage({
|
||||
action: "inputSwitch",
|
||||
data: {
|
||||
inputNum: this.manager.tabs.getActiveInputTab(),
|
||||
inputNum: activeTab,
|
||||
outputData: active
|
||||
}
|
||||
}, [active]);
|
||||
}, transferable);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1238,6 +1281,9 @@ class OutputWaiter {
|
||||
inputSwitch(switchData) {
|
||||
this.switchOrigData = switchData;
|
||||
document.getElementById("undo-switch").disabled = false;
|
||||
|
||||
this.resetSwitchButton();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1246,17 +1292,35 @@ class OutputWaiter {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
const undoSwitch = document.getElementById("undo-switch");
|
||||
undoSwitch.disabled = true;
|
||||
$(undoSwitch).tooltip("hide");
|
||||
|
||||
this.manager.input.inputWorker.postMessage({
|
||||
action: "setInput",
|
||||
data: {
|
||||
inputNum: this.switchOrigData.inputNum,
|
||||
silent: false
|
||||
}
|
||||
});
|
||||
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";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,6 +202,7 @@ self.bakeInput = function(inputNum, bakeId) {
|
||||
if (inputObj === null ||
|
||||
inputObj === undefined ||
|
||||
inputObj.status !== "loaded") {
|
||||
|
||||
self.postMessage({
|
||||
action: "queueInputError",
|
||||
data: {
|
||||
@ -441,7 +442,7 @@ self.updateTabHeader = function(inputNum) {
|
||||
*
|
||||
* @param {object} inputData
|
||||
* @param {number} inputData.inputNum - The input to get the data for
|
||||
* @param {boolean} inputData.silent - If false, the manager statechange event won't be fired
|
||||
* @param {boolean} inputData.silent - If false, the manager statechange event will be fired
|
||||
*/
|
||||
self.setInput = function(inputData) {
|
||||
const inputNum = inputData.inputNum;
|
||||
@ -590,7 +591,7 @@ self.updateInputObj = function(inputData) {
|
||||
const inputNum = inputData.inputNum;
|
||||
const data = inputData.data;
|
||||
|
||||
if (self.getInputObj(inputNum) === -1) return;
|
||||
if (self.getInputObj(inputNum) === undefined) return;
|
||||
|
||||
self.inputs[inputNum].data = data;
|
||||
};
|
||||
@ -663,11 +664,19 @@ self.handleLoaderMessage = function(r) {
|
||||
if ("fileBuffer" in r) {
|
||||
log.debug(`Input file ${inputNum} loaded.`);
|
||||
self.loadingInputs--;
|
||||
|
||||
self.updateInputValue({
|
||||
inputNum: inputNum,
|
||||
value: r.fileBuffer
|
||||
});
|
||||
|
||||
self.postMessage({
|
||||
action: "fileLoaded",
|
||||
data: {
|
||||
inputNum: inputNum
|
||||
}
|
||||
});
|
||||
|
||||
const idx = self.getLoaderWorkerIdx(r.id);
|
||||
self.loadNextFile(idx);
|
||||
} else if ("progress" in r) {
|
||||
@ -782,7 +791,7 @@ self.loadFiles = function(filesData) {
|
||||
}
|
||||
|
||||
self.getLoadProgress();
|
||||
self.setInput({inputNum: activeTab, silent: false});
|
||||
self.setInput({inputNum: activeTab, silent: true});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1025,7 +1034,7 @@ self.inputSwitch = function(switchData) {
|
||||
const currentData = currentInput.data;
|
||||
if (currentInput === undefined || currentInput === null) return;
|
||||
|
||||
if (typeof switchData.outputData === "object") {
|
||||
if (typeof switchData.outputData !== "string") {
|
||||
const output = new Uint8Array(switchData.outputData),
|
||||
types = detectFileType(output);
|
||||
let type = "unknown",
|
||||
@ -1036,15 +1045,22 @@ self.inputSwitch = function(switchData) {
|
||||
}
|
||||
|
||||
// ArrayBuffer
|
||||
currentInput.data = {
|
||||
fileBuffer: switchData.outputData,
|
||||
name: `output.${ext}`,
|
||||
size: switchData.outputData.byteLength.toLocaleString(),
|
||||
type: type
|
||||
};
|
||||
self.updateInputObj({
|
||||
inputNum: switchData.inputNum,
|
||||
data: {
|
||||
fileBuffer: switchData.outputData,
|
||||
name: `output.${ext}`,
|
||||
size: switchData.outputData.byteLength.toLocaleString(),
|
||||
type: type
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// String
|
||||
currentInput.data = switchData.outputData;
|
||||
self.updateInputValue({
|
||||
inputNum: switchData.inputNum,
|
||||
value: switchData.outputData,
|
||||
force: true
|
||||
});
|
||||
}
|
||||
|
||||
self.postMessage({
|
||||
@ -1055,6 +1071,11 @@ self.inputSwitch = function(switchData) {
|
||||
}
|
||||
});
|
||||
|
||||
self.setInput({inputNum: switchData.inputNum, silent: false});
|
||||
self.postMessage({
|
||||
action: "fileLoaded",
|
||||
data: {
|
||||
inputNum: switchData.inputNum
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user