diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs
index a935d75c..79172479 100755
--- a/src/core/Chef.mjs
+++ b/src/core/Chef.mjs
@@ -89,7 +89,14 @@ class Chef {
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
+ // Create a raw version of the dish, unpresented
+ const rawDish = new Dish(this.dish);
+
+ // Present the raw result
+ await recipe.present(this.dish);
+
return {
+ dish: rawDish,
result: this.dish.type === Dish.HTML ?
await this.dish.get(Dish.HTML, notUTF8) :
await this.dish.get(returnType, notUTF8),
@@ -123,7 +130,7 @@ class Chef {
const startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig),
- dish = new Dish("", Dish.STRING);
+ dish = new Dish();
try {
recipe.execute(dish);
@@ -167,6 +174,19 @@ class Chef {
};
}
+
+ /**
+ * Translates the dish to a specified type and returns it.
+ *
+ * @param {Dish} dish
+ * @param {string} type
+ * @returns {Dish}
+ */
+ async getDishAs(dish, type) {
+ const newDish = new Dish(dish);
+ return await newDish.get(type);
+ }
+
}
export default Chef;
diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js
index 604189e7..dbbda126 100644
--- a/src/core/ChefWorker.js
+++ b/src/core/ChefWorker.js
@@ -60,6 +60,9 @@ self.addEventListener("message", function(e) {
case "silentBake":
silentBake(r.data);
break;
+ case "getDishAs":
+ getDishAs(r.data);
+ break;
case "docURL":
// Used to set the URL of the current document so that scripts can be
// imported into an inline worker.
@@ -125,6 +128,22 @@ function silentBake(data) {
}
+/**
+ * Translates the dish to a given type.
+ */
+async function getDishAs(data) {
+ const value = await self.chef.getDishAs(data.dish, data.type);
+
+ self.postMessage({
+ action: "dishReturned",
+ data: {
+ value: value,
+ id: data.id
+ }
+ });
+}
+
+
/**
* Checks that all required modules are loaded and loads them if not.
*
diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs
index 6aeaf3e9..c799b122 100755
--- a/src/core/Dish.mjs
+++ b/src/core/Dish.mjs
@@ -17,14 +17,17 @@ class Dish {
/**
* Dish constructor
*
- * @param {byteArray|string|number|ArrayBuffer|BigNumber} [value=null]
- * - The value of the input data.
- * @param {number} [type=Dish.BYTE_ARRAY]
- * - The data type of value, see Dish enums.
+ * @param {Dish} [dish=null] - A dish to clone
*/
- constructor(value=null, type=Dish.BYTE_ARRAY) {
- this.value = value;
- this.type = type;
+ constructor(dish=null) {
+ this.value = [];
+ this.type = Dish.BYTE_ARRAY;
+
+ if (dish &&
+ dish.hasOwnProperty("value") &&
+ dish.hasOwnProperty("type")) {
+ this.set(dish.value, dish.type);
+ }
}
diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js
index 92440c49..f1bb8dab 100755
--- a/src/core/FlowControl.js
+++ b/src/core/FlowControl.js
@@ -68,7 +68,9 @@ const FlowControl = {
op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
});
- const dish = new Dish(inputs[i], inputType);
+ const dish = new Dish();
+ dish.set(inputs[i], inputType);
+
try {
progress = await recipe.execute(dish, 0, state);
} catch (err) {
diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs
index 006e431c..95dab22b 100755
--- a/src/core/Recipe.mjs
+++ b/src/core/Recipe.mjs
@@ -130,10 +130,12 @@ class Recipe {
* - The final progress through the recipe
*/
async execute(dish, startFrom=0, forkState={}) {
- let op, input, output, lastRunOp,
+ let op, input, output,
numJumps = 0,
numRegisters = forkState.numRegisters || 0;
+ if (startFrom === 0) this.lastRunOp = null;
+
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
for (let i = startFrom; i < this.opList.length; i++) {
@@ -169,7 +171,7 @@ class Recipe {
numRegisters = state.numRegisters;
} else {
output = await op.run(input, op.ingValues);
- lastRunOp = op;
+ this.lastRunOp = op;
dish.set(output, op.outputType);
}
} catch (err) {
@@ -188,18 +190,24 @@ class Recipe {
}
}
- // Present the results of the final operation
- if (lastRunOp) {
- // TODO try/catch
- output = await lastRunOp.present(output);
- dish.set(output, lastRunOp.presentType);
- }
-
log.debug("Recipe complete");
return this.opList.length;
}
+ /**
+ * Present the results of the final operation.
+ *
+ * @param {Dish} dish
+ */
+ async present(dish) {
+ if (!this.lastRunOp) return;
+
+ const output = await this.lastRunOp.present(await dish.get(this.lastRunOp.outputType));
+ dish.set(output, this.lastRunOp.presentType);
+ }
+
+
/**
* Returns the recipe configuration in string format.
*
diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs
index 88cfa52e..f90bc394 100755
--- a/src/core/Utils.mjs
+++ b/src/core/Utils.mjs
@@ -7,7 +7,7 @@
import utf8 from "utf8";
import moment from "moment-timezone";
import {fromBase64} from "./lib/Base64";
-import {toHexFast, fromHex} from "./lib/Hex";
+import {fromHex} from "./lib/Hex";
/**
@@ -833,39 +833,24 @@ class Utils {
const formatFile = async function(file, i) {
const buff = await Utils.readFile(file);
- const fileStr = Utils.arrayBufferToStr(buff.buffer);
const blob = new Blob(
[buff],
{type: "octet/stream"}
);
- const blobUrl = URL.createObjectURL(blob);
-
- const viewFileElem = `👁️`;
-
- const downloadFileElem = `💾`;
-
- const hexFileData = toHexFast(buff);
-
- const switchToInputElem = `⇧`;
const html = `
- ${Utils.escapeHtml(file.name)}
- ${viewFileElem}
- ${downloadFileElem}
- ${switchToInputElem}
+
${Utils.escapeHtml(file.name)}
+
💾
${file.size.toLocaleString()} bytes
@@ -875,7 +860,7 @@ class Utils {
-
${Utils.escapeHtml(fileStr)}
+
${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}
`;
diff --git a/src/core/config/scripts/generateConfig.mjs b/src/core/config/scripts/generateConfig.mjs
index f124e0d0..778b6e30 100644
--- a/src/core/config/scripts/generateConfig.mjs
+++ b/src/core/config/scripts/generateConfig.mjs
@@ -99,7 +99,7 @@ export default OpModules;
path.join(dir, `modules/${module}.mjs`),
code
);
- console.log(`Written ${module} module`);
+ console.log(`Written ${module} module`);
}
diff --git a/src/web/HighlighterWaiter.js b/src/web/HighlighterWaiter.js
index 3960e846..cfc36128 100755
--- a/src/web/HighlighterWaiter.js
+++ b/src/web/HighlighterWaiter.js
@@ -393,13 +393,13 @@ HighlighterWaiter.prototype.displayHighlights = function(pos, direction) {
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
-HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) {
+HighlighterWaiter.prototype.highlight = async function(textarea, highlighter, pos) {
if (!this.app.options.showHighlighter) return false;
if (!this.app.options.attemptHighlight) return false;
// Check if there is a carriage return in the output dish as this will not
// be displayed by the HTML textarea and will mess up highlighting offsets.
- if (this.manager.output.containsCR()) return false;
+ if (await this.manager.output.containsCR()) return false;
const startPlaceholder = "[startHighlight]";
const startPlaceholderRegex = /\[startHighlight\]/g;
diff --git a/src/web/Manager.js b/src/web/Manager.js
index 878077c1..8e5e7374 100755
--- a/src/web/Manager.js
+++ b/src/web/Manager.js
@@ -158,7 +158,6 @@ Manager.prototype.initialiseEventListeners = function() {
document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter));
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter);
- this.addDynamicListener(".file-switch", "click", this.output.fileSwitch, this.output);
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output);
document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js
index 18d64075..6d2f3840 100755
--- a/src/web/OutputWaiter.js
+++ b/src/web/OutputWaiter.js
@@ -40,7 +40,7 @@ OutputWaiter.prototype.get = function() {
* @param {number} duration - The length of time (ms) it took to generate the output
* @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer
*/
-OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
+OutputWaiter.prototype.set = async function(data, type, duration, preserveBuffer) {
log.debug("Output type: " + type);
const outputText = document.getElementById("output-text");
const outputHtml = document.getElementById("output-html");
@@ -51,6 +51,7 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
if (!preserveBuffer) {
this.closeFile();
+ this.dishStr = null;
document.getElementById("show-file-overlay").style.display = "none";
}
@@ -64,9 +65,6 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
outputText.value = "";
outputHtml.innerHTML = data;
- this.dishStr = Utils.unescapeHtml(Utils.stripHtmlTags(data, true));
- length = data.length;
- lines = this.dishStr.count("\n") + 1;
// Execute script sections
scriptElements = outputHtml.querySelectorAll("script");
@@ -77,6 +75,10 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
log.error(err);
}
}
+
+ await this.getDishStr();
+ length = this.dishStr.length;
+ lines = this.dishStr.count("\n") + 1;
break;
case "ArrayBuffer":
outputText.style.display = "block";
@@ -86,7 +88,6 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
outputText.value = "";
outputHtml.innerHTML = "";
- this.dishStr = "";
length = data.byteLength;
this.setFile(data);
@@ -151,10 +152,10 @@ OutputWaiter.prototype.closeFile = function() {
/**
* Handler for file download events.
*/
-OutputWaiter.prototype.downloadFile = function() {
+OutputWaiter.prototype.downloadFile = async function() {
this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat");
+ await this.getDishBuffer();
const file = new File([this.dishBuffer], this.filename);
-
if (this.filename) FileSaver.saveAs(file, this.filename, false);
};
@@ -254,9 +255,6 @@ OutputWaiter.prototype.adjustWidth = function() {
* Saves the current output to a file.
*/
OutputWaiter.prototype.saveClick = function() {
- if (!this.dishBuffer) {
- this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.dishStr)).buffer;
- }
this.downloadFile();
};
@@ -265,8 +263,10 @@ OutputWaiter.prototype.saveClick = function() {
* Handler for copy click events.
* Copies the output to the clipboard.
*/
-OutputWaiter.prototype.copyClick = function() {
- // Create invisible textarea to populate with the raw dishStr (not the printable version that
+OutputWaiter.prototype.copyClick = async function() {
+ await this.getDishStr();
+
+ // 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";
@@ -303,7 +303,7 @@ OutputWaiter.prototype.copyClick = function() {
* Handler for switch click events.
* Moves the current output into the input textarea.
*/
-OutputWaiter.prototype.switchClick = function() {
+OutputWaiter.prototype.switchClick = async function() {
this.switchOrigData = this.manager.input.get();
document.getElementById("undo-switch").disabled = false;
if (this.dishBuffer) {
@@ -315,6 +315,7 @@ OutputWaiter.prototype.switchClick = function() {
}
});
} else {
+ await this.getDishStr();
this.app.setInput(this.dishStr);
}
};
@@ -329,17 +330,6 @@ OutputWaiter.prototype.undoSwitchClick = function() {
document.getElementById("undo-switch").disabled = true;
};
-/**
- * Handler for file switch click events.
- * Moves a file's data for items created via Utils.displayFilesAsHTML to the input.
- */
-OutputWaiter.prototype.fileSwitch = function(e) {
- e.preventDefault();
- this.switchOrigData = this.manager.input.get();
- this.app.setInput(e.target.getAttribute("fileValue"));
- document.getElementById("undo-switch").disabled = false;
-};
-
/**
* Handler for maximise output click events.
@@ -409,8 +399,43 @@ OutputWaiter.prototype.setStatusMsg = function(msg) {
*
* @returns {boolean}
*/
-OutputWaiter.prototype.containsCR = function() {
+OutputWaiter.prototype.containsCR = async function() {
+ await this.getDishStr();
return this.dishStr.indexOf("\r") >= 0;
};
+
+/**
+ * Retrieves the current dish as a string, returning the cached version if possible.
+ *
+ * @returns {string}
+ */
+OutputWaiter.prototype.getDishStr = async function() {
+ if (this.dishStr) return this.dishStr;
+
+ this.dishStr = await new Promise(resolve => {
+ this.manager.worker.getDishAs(this.app.dish, "string", r => {
+ resolve(r.value);
+ });
+ });
+ return this.dishStr;
+};
+
+
+/**
+ * Retrieves the current dish as an ArrayBuffer, returning the cached version if possible.
+ *
+ * @returns {ArrayBuffer}
+ */
+OutputWaiter.prototype.getDishBuffer = async function() {
+ if (this.dishBuffer) return this.dishBuffer;
+
+ this.dishBuffer = await new Promise(resolve => {
+ this.manager.worker.getDishAs(this.app.dish, "ArrayBuffer", r => {
+ resolve(r.value);
+ });
+ });
+ return this.dishBuffer;
+};
+
export default OutputWaiter;
diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js
index 1473e101..4f3bad1b 100755
--- a/src/web/WorkerWaiter.js
+++ b/src/web/WorkerWaiter.js
@@ -14,6 +14,9 @@ import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.j
const WorkerWaiter = function(app, manager) {
this.app = app;
this.manager = manager;
+
+ this.callbacks = {};
+ this.callbackID = 0;
};
@@ -52,6 +55,9 @@ WorkerWaiter.prototype.handleChefMessage = function(e) {
this.app.handleError(r.data);
this.setBakingStatus(false);
break;
+ case "dishReturned":
+ this.callbacks[r.data.id](r.data);
+ break;
case "silentBakeComplete":
break;
case "workerLoaded":
@@ -117,6 +123,7 @@ WorkerWaiter.prototype.bakingComplete = function(response) {
}
this.app.progress = response.progress;
+ this.app.dish = response.dish;
this.manager.recipe.updateBreakpointIndicator(response.progress);
this.manager.output.set(response.result, response.type, response.duration);
log.debug("--- Bake complete ---");
@@ -185,6 +192,27 @@ WorkerWaiter.prototype.highlight = function(recipeConfig, direction, pos) {
};
+/**
+ * Asks the ChefWorker to return the dish as the specified type
+ *
+ * @param {Dish} dish
+ * @param {string} type
+ * @param {Function} callback
+ */
+WorkerWaiter.prototype.getDishAs = function(dish, type, callback) {
+ const id = this.callbackID++;
+ this.callbacks[id] = callback;
+ this.chefWorker.postMessage({
+ action: "getDishAs",
+ data: {
+ dish: dish,
+ type: type,
+ id: id
+ }
+ });
+};
+
+
/**
* Sets the console log level in the worker.
*
diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css
index 23f83bd8..43c5b1d2 100755
--- a/src/web/stylesheets/utils/_overrides.css
+++ b/src/web/stylesheets/utils/_overrides.css
@@ -62,6 +62,7 @@ a:focus {
.form-control,
.popover,
.alert,
+.panel,
.modal-content,
.tooltip-inner,
.dropdown-menu,