CyberChef/src/web/OutputWaiter.mjs

954 lines
31 KiB
JavaScript
Raw Normal View History

2018-05-15 19:36:45 +02:00
/**
* @author n1474335 [n1474335@gmail.com]
* @author j433866 [j433866@gmail.com]
2018-05-15 19:36:45 +02:00
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Utils from "../core/Utils";
import FileSaver from "file-saver";
import zip from "zlibjs/bin/zip.min";
const Zlib = zip.Zlib;
2018-05-15 19:36:45 +02:00
/**
* Waiter to handle events related to the output
*/
2018-05-15 19:36:45 +02:00
class OutputWaiter {
/**
* OutputWaiter constructor.
*
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager
2018-05-15 19:36:45 +02:00
*/
constructor(app, manager) {
this.app = app;
this.manager = manager;
this.outputs = {};
this.activeTab = -1;
this.maxTabs = 4; // Calculate this
2018-05-15 19:36:45 +02:00
}
/**
* Calculates the maximum number of tabs to display
*/
calcMaxTabs() {
const numTabs = Math.floor((document.getElementById("IO").offsetWidth - 75) / 120);
this.maxTabs = numTabs;
}
2018-05-15 19:36:45 +02:00
/**
* Gets the output for the specified input number
2018-05-15 19:36:45 +02:00
*
* @param {number} inputNum
* @returns {string | ArrayBuffer}
2018-05-15 19:36:45 +02:00
*/
getOutput(inputNum) {
if (this.outputs[inputNum] === undefined || this.outputs[inputNum] === null) return -1;
2018-05-15 19:36:45 +02:00
if (this.outputs[inputNum].data === null) return "";
2018-05-15 19:36:45 +02:00
if (typeof this.outputs[inputNum].data.dish.value === "string") {
return this.outputs[inputNum].data.dish.value;
} else {
return this.outputs[inputNum].data.dish.value || "";
}
}
2018-05-15 19:36:45 +02:00
/**
* Gets the output string or FileBuffer for the active input
2018-05-15 19:36:45 +02:00
*
* @returns {string | ArrayBuffer}
2018-05-15 19:36:45 +02:00
*/
getActive() {
return this.getOutput(this.getActiveTab());
2018-05-15 19:36:45 +02:00
}
/**
* Adds a new output to the output array.
* Creates a new tab if we have less than maxtabs tabs open
2018-05-15 19:36:45 +02:00
*
* @param {number} inputNum
* @param {boolean} [changeTab=true]
2018-05-15 19:36:45 +02:00
*/
addOutput(inputNum, changeTab = true) {
const output = this.getOutput(inputNum);
if (output !== -1) {
// Remove the output if it already exists
delete this.outputs[inputNum];
}
const newOutput = {
data: null,
inputNum: inputNum,
// statusMessage: `Input ${inputNum} has not been baked yet.`,
statusMessage: "",
error: null,
status: "inactive"
};
this.outputs[inputNum] = newOutput;
// add new tab
this.addTab(inputNum, changeTab);
2018-05-15 19:36:45 +02:00
}
/**
* Updates the value for the output in the output array.
* If this is the active output tab, updates the output textarea
*
* @param {Object} data
* @param {number} inputNum
2018-05-15 19:36:45 +02:00
*/
updateOutputValue(data, inputNum) {
if (this.getOutput(inputNum) === -1) {
this.addOutput(inputNum);
}
2018-05-15 19:36:45 +02:00
this.outputs[inputNum].data = data;
2018-05-15 19:36:45 +02:00
// set output here
this.set(inputNum);
2018-05-15 19:36:45 +02:00
}
/**
* Updates the status message for the output in the output array.
* If this is the active output tab, updates the output textarea
*
* @param {string} statusMessage
* @param {number} inputNum
2018-05-15 19:36:45 +02:00
*/
updateOutputMessage(statusMessage, inputNum) {
if (this.getOutput(inputNum) === -1) return;
this.outputs[inputNum].statusMessage = statusMessage;
this.set(inputNum);
2018-05-15 19:36:45 +02:00
}
/**
* Updates the error value for the output in the output array.
* If this is the active output tab, calls app.handleError.
* Otherwise, the error will be handled when the output is switched to
2018-05-15 19:36:45 +02:00
*
* @param {Error} error
* @param {number} inputNum
2018-05-15 19:36:45 +02:00
*/
updateOutputError(error, inputNum) {
if (this.getOutput(inputNum) === -1) return;
2018-05-15 19:36:45 +02:00
this.outputs[inputNum].error = error;
// call handle error here
// or make the error handling part of set()
this.set(inputNum);
}
2018-05-15 19:36:45 +02:00
/**
* Updates the status value for the output in the output array
2018-05-15 19:36:45 +02:00
*
* @param {string} status
* @param {number} inputNum
2018-05-15 19:36:45 +02:00
*/
updateOutputStatus(status, inputNum) {
if (this.getOutput(inputNum) === -1) return;
this.outputs[inputNum].status = status;
2018-05-15 19:36:45 +02:00
this.set(inputNum);
2018-05-15 19:36:45 +02:00
}
/**
* Removes an output from the output array.
*
* @param {number} inputNum
2018-05-15 19:36:45 +02:00
*/
removeOutput(inputNum) {
if (this.getOutput(inputNum) === -1) return;
2018-05-15 19:36:45 +02:00
delete (this.outputs[inputNum]);
}
2018-05-15 19:36:45 +02:00
2019-04-30 14:18:22 +02:00
/**
* Removes all output tabs
*/
removeAllOutputs() {
this.outputs = {};
const tabs = document.getElementById("output-tabs").children;
for (let i = tabs.length - 1; i >= 0; i--) {
tabs.item(i).remove();
}
}
2018-05-15 19:36:45 +02:00
/**
* Sets the output in the output textarea.
*
* @param {number} inputNum
2018-05-15 19:36:45 +02:00
*/
set(inputNum) {
const output = this.outputs[inputNum];
if (output === undefined || output === null) return;
if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10);
const outputText = document.getElementById("output-text");
const outputHtml = document.getElementById("output-html");
const outputFile = document.getElementById("output-file");
const outputHighlighter = document.getElementById("output-highlighter");
const inputHighlighter = document.getElementById("input-highlighter");
// If inactive, show blank
// If pending or baking, show loader and status message
// If error, style the tab and handle the error
// If done, display the output if it's the active tab
if (output.status === "inactive" || output.status === "stale") {
this.manager.controls.showStaleIndicator();
} else {
this.manager.controls.hideStaleIndicator();
}
if (output.status === "inactive") {
// An output is inactive when it has been created but has not been baked at all
// show a blank here
if (inputNum === this.getActiveTab()) {
this.toggleLoader(false);
outputText.style.display = "block";
outputHtml.style.display = "none";
outputFile.style.display = "none";
outputHighlighter.display = "block";
inputHighlighter.display = "block";
2018-05-15 19:36:45 +02:00
outputText.value = "";
outputHtml.innerHTML = "";
2018-05-15 19:36:45 +02:00
}
} else if (output.status === "pending" || output.status === "baking") {
// show the loader and the status message if it's being shown
// otherwise don't do anything
if (inputNum === this.getActiveTab()) {
this.toggleLoader(true);
document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage;
}
2018-05-15 19:36:45 +02:00
} else if (output.status === "error") {
// style the tab if it's being shown
// run app.handleError()
if (inputNum === this.getActiveTab()) {
this.toggleLoader(false);
}
} else if (output.status === "baked") {
// Display the output if it's the active tab
this.displayTabInfo(inputNum);
if (inputNum === this.getActiveTab()) {
this.toggleLoader(false);
this.closeFile();
let scriptElements, lines, length;
const duration = output.data.duration;
switch (output.data.type) {
case "html":
outputText.style.display = "none";
outputHtml.style.display = "block";
outputFile.style.display = "none";
outputHighlighter.style.display = "none";
inputHighlighter.style.display = "none";
outputText.value = "";
outputHtml.innerHTML = output.data.result;
// Execute script sections
scriptElements = outputHtml.querySelectorAll("script");
for (let i = 0; i < scriptElements.length; i++) {
try {
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
} catch (err) {
log.error(err);
}
}
length = output.data.dish.value.length;
break;
case "ArrayBuffer":
outputText.style.display = "block";
outputHtml.style.display = "none";
outputHighlighter.display = "none";
inputHighlighter.display = "none";
outputText.value = "";
outputHtml.innerHTML = "";
length = output.data.result.length;
this.setFile(output.data.result);
break;
case "string":
default:
outputText.style.display = "block";
outputHtml.style.display = "none";
outputFile.style.display = "none";
outputHighlighter.display = "block";
inputHighlighter.display = "block";
outputText.value = Utils.printable(output.data.result, true);
outputHtml.innerHTML = "";
lines = output.data.result.count("\n") + 1;
length = output.data.result.length;
break;
2018-05-15 19:36:45 +02:00
}
this.setOutputInfo(length, lines, duration);
this.backgroundMagic();
}
2018-05-15 19:36:45 +02:00
}
}
/**
* Shows file details
*
* @param {ArrayBuffer} buf
2018-05-15 19:36:45 +02:00
*/
setFile(buf) {
const file = new File([buf], "output.dat");
2018-05-15 19:36:45 +02:00
// Display file overlay in output area with details
const fileOverlay = document.getElementById("output-file"),
fileSize = document.getElementById("output-file-size"),
outputText = document.getElementById("output-text"),
fileSlice = buf.slice(0, 4096);
2018-05-15 19:36:45 +02:00
fileOverlay.style.display = "block";
fileSize.textContent = file.size.toLocaleString() + " bytes";
2018-05-15 19:36:45 +02:00
outputText.classList.add("blur");
outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice));
2018-05-15 19:36:45 +02:00
}
/**
* Clears output file details
*/
closeFile() {
document.getElementById("output-file").style.display = "none";
document.getElementById("output-text").classList.remove("blur");
}
2018-05-15 19:36:45 +02:00
/**
* Save bombe object then remove it from the DOM so that it does not cause performance issues.
*/
saveBombe() {
this.bombeEl = document.getElementById("bombe");
this.bombeEl.parentNode.removeChild(this.bombeEl);
}
/**
* Shows or hides the output loading screen.
* The animated Bombe SVG, whilst quite aesthetically pleasing, is reasonably CPU
* intensive, so we remove it from the DOM when not in use. We only show it if the
* recipe is taking longer than 200ms. We add it to the DOM just before that so that
* it is ready to fade in without stuttering.
2018-05-15 19:36:45 +02:00
*
* @param {boolean} value - true == show loader
2018-05-15 19:36:45 +02:00
*/
toggleLoader(value) {
clearTimeout(this.appendBombeTimeout);
clearTimeout(this.outputLoaderTimeout);
2018-05-15 19:36:45 +02:00
const outputLoader = document.getElementById("output-loader"),
outputElement = document.getElementById("output-text"),
animation = document.getElementById("output-loader-animation");
2018-05-15 19:36:45 +02:00
if (value) {
this.manager.controls.hideStaleIndicator();
// Start a timer to add the Bombe to the DOM just before we make it
// visible so that there is no stuttering
this.appendBombeTimeout = setTimeout(function() {
animation.appendChild(this.bombeEl);
}.bind(this), 150);
// Show the loading screen
this.outputLoaderTimeout = setTimeout(function() {
2018-05-15 19:36:45 +02:00
outputElement.disabled = true;
outputLoader.style.visibility = "visible";
outputLoader.style.opacity = 1;
}, 200);
2018-05-15 19:36:45 +02:00
} else {
// Remove the Bombe from the DOM to save resources
this.outputLoaderTimeout = setTimeout(function () {
try {
animation.removeChild(this.bombeEl);
} catch (err) {}
}.bind(this), 500);
2018-05-15 19:36:45 +02:00
outputElement.disabled = false;
outputLoader.style.opacity = 0;
outputLoader.style.visibility = "hidden";
// this.setStatusMsg("");
2018-05-15 19:36:45 +02:00
}
}
/**
* Handler for save click events.
* Saves the current output to a file.
*/
saveClick() {
this.downloadFile();
}
/**
* Handler for file download events.
*/
async downloadFile() {
const fileName = window.prompt("Please enter a filename: ", "download.dat");
const file = new File([this.getActive()], fileName);
FileSaver.saveAs(file, fileName, false);
}
/**
* Handler for save all click event
* Saves all outputs to a single archvie file
*/
saveAllClick() {
this.downloadAllFiles();
}
/**
* Handler for download all files events.
*/
async downloadAllFiles() {
const fileName = window.prompt("Please enter a filename: ", "download.zip");
const fileExt = window.prompt("Please enter a file extension for the files: ", ".txt");
const zip = new Zlib.Zip();
for (let i = 0; i < this.outputs.length; i++) {
const name = Utils.strToByteArray(this.outputs[i].inputNum + fileExt);
let out = this.getOutput(this.outputs[i].inputNum);
if (typeof out === "string") {
out = Utils.strToUtf8ByteArray(out);
}
out = new Uint8Array(out);
// options.filename = Utils.strToByteArray(this.outputs[i].inputNum + ".dat");
zip.addFile(out, {filename: name});
}
const file = new File([zip.compress()], fileName);
FileSaver.saveAs(file, fileName, false);
}
2018-05-15 19:36:45 +02:00
/**
* Adds a new output tab.
2018-05-15 19:36:45 +02:00
*
* @param {number} inputNum
* @param {boolean} [changeTab=true]
2018-05-15 19:36:45 +02:00
*/
addTab(inputNum, changeTab = true) {
const tabsWrapper = document.getElementById("output-tabs");
const numTabs = tabsWrapper.children.length;
2018-05-15 19:36:45 +02:00
2019-04-30 14:18:22 +02:00
if (this.getTabItem(inputNum) === undefined && numTabs < this.maxTabs) {
// Create a new tab element
const newTab = this.createTabElement(inputNum);
2018-05-15 19:36:45 +02:00
tabsWrapper.appendChild(newTab);
2018-05-15 19:36:45 +02:00
if (numTabs > 0) {
tabsWrapper.parentElement.style.display = "block";
document.getElementById("output-wrapper").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("output-highlighter").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("output-file").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("output-loader").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("save-all-to-file").style.display = "inline-block";
2019-04-30 14:18:22 +02:00
} else {
tabsWrapper.parentElement.style.display = "none";
document.getElementById("output-wrapper").style.height = "calc(100% - var(--title-height))";
document.getElementById("output-highlighter").style.height = "calc(100% - var(--title-height))";
document.getElementById("output-file").style.height = "calc(100% - var(--title-height))";
document.getElementById("output-loader").style.height = "calc(100% - var(--title-height))";
document.getElementById("save-all-to-file").style.display = "none";
}
}
2018-05-15 19:36:45 +02:00
if (changeTab) {
this.changeTab(inputNum, false);
}
2018-05-15 19:36:45 +02:00
}
/**
* Changes the active tab
2018-05-15 19:36:45 +02:00
*
* @param {number} inputNum
* @param {boolean} [changeInput = false]
2018-05-15 19:36:45 +02:00
*/
changeTab(inputNum, changeInput = false) {
const currentNum = this.getActiveTab();
if (this.getOutput(inputNum) === -1) return;
const tabsWrapper = document.getElementById("output-tabs");
const tabs = tabsWrapper.children;
let found = false;
for (let i = 0; i < tabs.length; i++) {
if (tabs.item(i).getAttribute("inputNum") === inputNum.toString()) {
tabs.item(i).classList.add("active-output-tab");
this.activeTab = inputNum;
found = true;
} else {
tabs.item(i).classList.remove("active-output-tab");
}
}
if (!found) {
let direction = "right";
if (currentNum > inputNum) {
direction = "left";
}
2018-05-15 19:36:45 +02:00
const newOutputs = this.getNearbyNums(inputNum, direction);
for (let i = 0; i < newOutputs.length; i++) {
tabs.item(i).setAttribute("inputNum", newOutputs[i].toString());
this.displayTabInfo(newOutputs[i]);
if (newOutputs[i] === inputNum) {
this.activeTab = inputNum;
tabs.item(i).classList.add("active-output-tab");
}
}
}
2018-05-15 19:36:45 +02:00
this.set(inputNum);
if (changeInput) {
this.manager.input.changeTab(inputNum, false);
}
}
/**
* Handler for changing tabs event
*
* @param {event} mouseEvent
*/
changeTabClick(mouseEvent) {
if (!mouseEvent.target) return;
const tabNum = mouseEvent.target.parentElement.getAttribute("inputNum");
if (tabNum) {
this.changeTab(parseInt(tabNum, 10), this.app.options.syncTabs);
}
}
/**
* Handler for changing to the left tab
*/
changeTabLeft() {
const currentTab = this.getActiveTab();
this.changeTab(this.getPreviousInputNum(currentTab), this.app.options.syncTabs);
}
/**
* Handler for changing to the right tab
*/
changeTabRight() {
const currentTab = this.getActiveTab();
this.changeTab(this.getNextInputNum(currentTab), this.app.options.syncTabs);
}
/**
* Handler for go to tab button clicked
*/
goToTab() {
const tabNum = parseInt(window.prompt("Enter tab number:", this.getActiveTab().toString()), 10);
2019-04-04 15:42:21 +02:00
if (this.getOutputIndex(tabNum) >= 0) {
this.changeTab(tabNum, this.app.options.syncTabs);
}
}
/**
* Generates a list of the nearby inputNums
* @param inputNum
* @param direction
*/
getNearbyNums(inputNum, direction) {
const nums = [];
for (let i = 0; i < this.maxTabs; i++) {
let newNum;
if (i === 0) {
newNum = inputNum;
} else {
switch (direction) {
case "left":
newNum = this.getNextInputNum(nums[i - 1]);
if (newNum === nums[i - 1]) {
direction = "right";
newNum = this.getPreviousInputNum(nums[i - 1]);
}
break;
case "right":
newNum = this.getPreviousInputNum(nums[i - 1]);
if (newNum === nums[i - 1]) {
direction = "left";
newNum = this.getNextInputNum(nums[i - 1]);
}
}
}
if (!nums.includes(newNum) && (newNum > 0)) {
nums.push(newNum);
}
}
nums.sort(function(a, b) {
return a - b;
});
return nums;
}
/**
* Gets the largest inputNum
*
* @returns {number}
*/
getLargestInputNum() {
let largest = 0;
const inputNums = Object.keys(this.outputs);
for (let i = 0; i < inputNums.length; i++) {
const iNum = parseInt(inputNums[i], 10);
if (iNum > largest) {
largest = iNum;
}
}
return largest;
}
/**
* Gets the smallest inputNum
*
* @returns {number}
*/
getSmallestInputNum() {
let smallest = this.getLargestInputNum();
const inputNums = Object.keys(this.outputs);
for (let i = 0; i < inputNums.length; i++) {
const iNum = parseInt(inputNums[i], 10);
if (iNum < smallest) {
smallest = iNum;
}
}
return smallest;
}
/**
* Gets the previous inputNum
*
* @param {number} inputNum - The current input number
* @returns {number}
*/
getPreviousInputNum(inputNum) {
let num = this.getSmallestInputNum();
const inputNums = Object.keys(this.outputs);
for (let i = 0; i < inputNums.length; i++) {
const iNum = parseInt(inputNums[i], 10);
if (iNum < inputNum) {
if (iNum > num) {
num = iNum;
}
}
}
return num;
}
/**
* Gets the next inputNum
*
* @param {number} inputNum - The current input number
* @returns {number}
*/
getNextInputNum(inputNum) {
let num = this.getLargestInputNum();
const inputNums = Object.keys(this.outputs);
for (let i = 0; i < inputNums.length; i++) {
const iNum = parseInt(inputNums[i], 10);
if (iNum > inputNum) {
if (iNum < num) {
num = iNum;
}
}
}
return num;
}
/**
* Removes a tab and it's corresponding output
*
* @param {number} inputNum
*/
removeTab(inputNum) {
let activeTab = this.getActiveTab();
if (this.getOutput(inputNum) === -1) return;
const tabElement = this.getTabItem(inputNum);
this.removeOutput(inputNum);
if (tabElement !== null) {
// find new tab number?
if (inputNum === activeTab) {
activeTab = this.getPreviousInputNum(activeTab);
if (activeTab === this.getActiveTab()) {
activeTab = this.getNextInputNum(activeTab);
}
}
this.refreshTabs(activeTab);
}
}
/**
* Redraw the entire tab bar to remove any outdated tabs
* @param {number} activeTab
*/
refreshTabs(activeTab) {
const tabsList = document.getElementById("output-tabs");
let newInputs = this.getNearbyNums(activeTab, "right");
if (newInputs.length < this.maxTabs) {
newInputs = this.getNearbyNums(activeTab, "left");
}
for (let i = tabsList.children.length - 1; i >= 0; i--) {
tabsList.children.item(i).remove();
}
for (let i = 0; i < newInputs.length; i++) {
tabsList.appendChild(this.createTabElement(newInputs[i]));
this.displayTabInfo(newInputs[i]);
}
if (newInputs.length > 1) {
tabsList.parentElement.style.display = "block";
document.getElementById("output-wrapper").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("output-highlighter").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("output-file").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("output-loader").style.height = "calc(100% - var(--tab-height) - var(--title-height))";
document.getElementById("save-all-to-file").style.display = "inline-block";
} else {
tabsList.parentElement.style.display = "none";
document.getElementById("output-wrapper").style.height = "calc(100% - var(--title-height))";
document.getElementById("output-highlighter").style.height = "calc(100% - var(--title-height))";
document.getElementById("output-file").style.height = "calc(100% - var(--title-height))";
document.getElementById("output-loader").style.height = "calc(100% - var(--title-height))";
document.getElementById("save-all-to-file").style.display = "none";
}
this.changeTab(activeTab);
}
/**
* Creates a new tab element to be added to the tab bar
*
* @param {number} inputNum
*/
createTabElement(inputNum) {
const newTab = document.createElement("li");
newTab.setAttribute("inputNum", inputNum.toString());
const newTabContent = document.createElement("div");
newTabContent.classList.add("output-tab-content");
newTabContent.innerText = `Tab ${inputNum.toString()}`;
// Do we want remove tab button on output?
newTab.appendChild(newTabContent);
return newTab;
}
/**
* Gets the number of the current active tab
*
* @returns {number}
*/
getActiveTab() {
return this.activeTab;
}
/**
* Gets the li element for a tab
*
* @param {number} inputNum
*/
getTabItem(inputNum) {
const tabs = document.getElementById("output-tabs").children;
for (let i = 0; i < tabs.length; i++) {
if (parseInt(tabs.item(i).getAttribute("inputNum"), 10) === inputNum) {
return tabs.item(i);
}
}
}
/**
* Display output information in the tab header
*
* @param {number} inputNum
*/
displayTabInfo(inputNum) {
const tabItem = this.getTabItem(inputNum);
if (!tabItem) return;
const tabContent = tabItem.firstElementChild;
tabContent.innerText = `Tab ${inputNum}`;
}
/**
* Displays information about the output.
*
* @param {number} length - The length of the current output string
* @param {number} lines - The number of the lines in the current output string
* @param {number} duration - The length of time (ms) it took to generate the output
*/
setOutputInfo(length, lines, duration) {
if (!length) return;
let width = length.toString().length;
width = width < 4 ? 4 : width;
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, "&nbsp;");
let msg = "time: " + timeStr + "<br>length: " + lengthStr;
if (typeof lines === "number") {
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
msg += "<br>lines: " + linesStr;
}
document.getElementById("output-info").innerHTML = msg;
document.getElementById("input-selection-info").innerHTML = "";
document.getElementById("output-selection-info").innerHTML = "";
}
/**
* Triggers the BackgroundWorker to attempt Magic on the current output.
*/
backgroundMagic() {
this.hideMagicButton();
if (!this.app.options.autoMagic || this.getActive()) return;
const sample = this.getActive().slice(0, 1000) || "";
if (sample.length) {
this.manager.background.magic(sample);
}
}
/**
* Handles the results of a background Magic call.
*
* @param {Object[]} options
*/
backgroundMagicResult(options) {
if (!options.length ||
!options[0].recipe.length)
return;
const currentRecipeConfig = this.app.getRecipeConfig();
const newRecipeConfig = currentRecipeConfig.concat(options[0].recipe);
const opSequence = options[0].recipe.map(o => o.op).join(", ");
this.showMagicButton(opSequence, options[0].data, newRecipeConfig);
}
/**
* Handler for Magic click events.
*
* Loads the Magic recipe.
*
* @fires Manager#statechange
*/
magicClick() {
const magicButton = document.getElementById("magic");
this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute("data-recipe")));
window.dispatchEvent(this.manager.statechange);
this.hideMagicButton();
}
/**
* Displays the Magic button with a title and adds a link to a complete recipe.
*
* @param {string} opSequence
* @param {string} result
* @param {Object[]} recipeConfig
*/
showMagicButton(opSequence, result, recipeConfig) {
const magicButton = document.getElementById("magic");
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.escapeHtml(Utils.truncate(result), 30)}"</span>`);
magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, "");
magicButton.classList.remove("hidden");
}
/**
* Hides the Magic button and resets its values.
*/
hideMagicButton() {
const magicButton = document.getElementById("magic");
magicButton.classList.add("hidden");
magicButton.setAttribute("data-recipe", "");
magicButton.setAttribute("data-original-title", "Magic!");
}
/**
* Handler for file slice display events.
*/
displayFileSlice() {
const startTime = new Date().getTime(),
showFileOverlay = document.getElementById("show-file-overlay"),
sliceFromEl = document.getElementById("output-file-slice-from"),
sliceToEl = document.getElementById("output-file-slice-to"),
sliceFrom = parseInt(sliceFromEl.value, 10),
sliceTo = parseInt(sliceToEl.value, 10),
str = Utils.arrayBufferToStr(this.getActive().slice(sliceFrom, sliceTo));
document.getElementById("output-text").classList.remove("blur");
showFileOverlay.style.display = "block";
}
/**
* Handler for copy click events.
* Copies the output to the clipboard
*/
copyClick() {
const output = this.getActive();
// Create invisible textarea to populate with the raw dish string (not the printable version that
// contains dots instead of the actual bytes)
const textarea = document.createElement("textarea");
textarea.style.position = "fixed";
textarea.style.top = 0;
textarea.style.left = 0;
textarea.style.width = 0;
textarea.style.height = 0;
textarea.style.border = "none";
textarea.value = output;
document.body.appendChild(textarea);
let success = false;
try {
textarea.select();
success = output && document.execCommand("copy");
} catch (err) {
success = false;
}
if (success) {
this.app.alert("Copied raw output successfully.", 2000);
} else {
this.app.alert("Sorry, the output could not be copied.", 3000);
}
// Clean up
document.body.removeChild(textarea);
}
2018-05-15 19:36:45 +02:00
}
export default OutputWaiter;