The raw, unpresented dish is now returned to the app after baking, where it can be retrieved as various different data types.

This commit is contained in:
n1474335 2018-04-21 12:25:48 +01:00 committed by d98762625
parent d5b5443a84
commit fbec0a1c7d
12 changed files with 163 additions and 73 deletions

View file

@ -89,7 +89,14 @@ class Chef {
const threshold = (options.ioDisplayThreshold || 1024) * 1024; const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; 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 { return {
dish: rawDish,
result: this.dish.type === Dish.HTML ? result: this.dish.type === Dish.HTML ?
await this.dish.get(Dish.HTML, notUTF8) : await this.dish.get(Dish.HTML, notUTF8) :
await this.dish.get(returnType, notUTF8), await this.dish.get(returnType, notUTF8),
@ -123,7 +130,7 @@ class Chef {
const startTime = new Date().getTime(), const startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig), recipe = new Recipe(recipeConfig),
dish = new Dish("", Dish.STRING); dish = new Dish();
try { try {
recipe.execute(dish); 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; export default Chef;

View file

@ -60,6 +60,9 @@ self.addEventListener("message", function(e) {
case "silentBake": case "silentBake":
silentBake(r.data); silentBake(r.data);
break; break;
case "getDishAs":
getDishAs(r.data);
break;
case "docURL": case "docURL":
// Used to set the URL of the current document so that scripts can be // Used to set the URL of the current document so that scripts can be
// imported into an inline worker. // 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. * Checks that all required modules are loaded and loads them if not.
* *

View file

@ -17,14 +17,17 @@ class Dish {
/** /**
* Dish constructor * Dish constructor
* *
* @param {byteArray|string|number|ArrayBuffer|BigNumber} [value=null] * @param {Dish} [dish=null] - A dish to clone
* - The value of the input data.
* @param {number} [type=Dish.BYTE_ARRAY]
* - The data type of value, see Dish enums.
*/ */
constructor(value=null, type=Dish.BYTE_ARRAY) { constructor(dish=null) {
this.value = value; this.value = [];
this.type = type; this.type = Dish.BYTE_ARRAY;
if (dish &&
dish.hasOwnProperty("value") &&
dish.hasOwnProperty("type")) {
this.set(dish.value, dish.type);
}
} }

View file

@ -68,7 +68,9 @@ const FlowControl = {
op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); 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 { try {
progress = await recipe.execute(dish, 0, state); progress = await recipe.execute(dish, 0, state);
} catch (err) { } catch (err) {

View file

@ -130,10 +130,12 @@ class Recipe {
* - The final progress through the recipe * - The final progress through the recipe
*/ */
async execute(dish, startFrom=0, forkState={}) { async execute(dish, startFrom=0, forkState={}) {
let op, input, output, lastRunOp, let op, input, output,
numJumps = 0, numJumps = 0,
numRegisters = forkState.numRegisters || 0; numRegisters = forkState.numRegisters || 0;
if (startFrom === 0) this.lastRunOp = null;
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
for (let i = startFrom; i < this.opList.length; i++) { for (let i = startFrom; i < this.opList.length; i++) {
@ -169,7 +171,7 @@ class Recipe {
numRegisters = state.numRegisters; numRegisters = state.numRegisters;
} else { } else {
output = await op.run(input, op.ingValues); output = await op.run(input, op.ingValues);
lastRunOp = op; this.lastRunOp = op;
dish.set(output, op.outputType); dish.set(output, op.outputType);
} }
} catch (err) { } 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"); log.debug("Recipe complete");
return this.opList.length; 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. * Returns the recipe configuration in string format.
* *

View file

@ -7,7 +7,7 @@
import utf8 from "utf8"; import utf8 from "utf8";
import moment from "moment-timezone"; import moment from "moment-timezone";
import {fromBase64} from "./lib/Base64"; 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 formatFile = async function(file, i) {
const buff = await Utils.readFile(file); const buff = await Utils.readFile(file);
const fileStr = Utils.arrayBufferToStr(buff.buffer);
const blob = new Blob( const blob = new Blob(
[buff], [buff],
{type: "octet/stream"} {type: "octet/stream"}
); );
const blobUrl = URL.createObjectURL(blob);
const viewFileElem = `<a href='#collapse${i}'
class='collapsed'
data-toggle='collapse'
aria-expanded='true'
aria-controls='collapse${i}'
title="Show/hide contents of '${Utils.escapeHtml(file.name)}'">&#x1f441;&#xfe0f;</a>`;
const downloadFileElem = `<a href='${blobUrl}'
title='Download ${Utils.escapeHtml(file.name)}'
download='${Utils.escapeHtml(file.name)}'>&#x1f4be;</a>`;
const hexFileData = toHexFast(buff);
const switchToInputElem = `<a href='#switchFileToInput${i}'
class='file-switch'
title='Move file to input as hex'
fileValue='${hexFileData}'>&#x21e7;</a>`;
const html = `<div class='panel panel-default' style='white-space: normal;'> const html = `<div class='panel panel-default' style='white-space: normal;'>
<div class='panel-heading' role='tab' id='heading${i}'> <div class='panel-heading' role='tab' id='heading${i}'>
<h4 class='panel-title'> <h4 class='panel-title'>
<div> <div>
${Utils.escapeHtml(file.name)} <a href='#collapse${i}'
${viewFileElem} class='collapsed'
${downloadFileElem} data-toggle='collapse'
${switchToInputElem} aria-expanded='true'
aria-controls='collapse${i}'
title="Show/hide contents of '${Utils.escapeHtml(file.name)}'">${Utils.escapeHtml(file.name)}</a>
<a href='${URL.createObjectURL(blob)}'
title='Download ${Utils.escapeHtml(file.name)}'
download='${Utils.escapeHtml(file.name)}'>&#x1f4be;</a>
<span class='pull-right'> <span class='pull-right'>
${file.size.toLocaleString()} bytes ${file.size.toLocaleString()} bytes
</span> </span>
@ -875,7 +860,7 @@ class Utils {
<div id='collapse${i}' class='panel-collapse collapse' <div id='collapse${i}' class='panel-collapse collapse'
role='tabpanel' aria-labelledby='heading${i}'> role='tabpanel' aria-labelledby='heading${i}'>
<div class='panel-body'> <div class='panel-body'>
<pre><code>${Utils.escapeHtml(fileStr)}</code></pre> <pre><code>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</code></pre>
</div> </div>
</div> </div>
</div>`; </div>`;

View file

@ -99,7 +99,7 @@ export default OpModules;
path.join(dir, `modules/${module}.mjs`), path.join(dir, `modules/${module}.mjs`),
code code
); );
console.log(`Written ${module} module`); console.log(`Written ${module} module`);
} }

View file

@ -393,13 +393,13 @@ HighlighterWaiter.prototype.displayHighlights = function(pos, direction) {
* @param {number} pos.start - The start offset. * @param {number} pos.start - The start offset.
* @param {number} pos.end - The end 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.showHighlighter) return false;
if (!this.app.options.attemptHighlight) return false; if (!this.app.options.attemptHighlight) return false;
// Check if there is a carriage return in the output dish as this will not // 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. // 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 startPlaceholder = "[startHighlight]";
const startPlaceholderRegex = /\[startHighlight\]/g; const startPlaceholderRegex = /\[startHighlight\]/g;

View file

@ -158,7 +158,6 @@ Manager.prototype.initialiseEventListeners = function() {
document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter)); 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-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, 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-download", "click", this.output.downloadFile, this.output);
this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, 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)); document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));

View file

@ -40,7 +40,7 @@ OutputWaiter.prototype.get = function() {
* @param {number} duration - The length of time (ms) it took to generate the output * @param {number} duration - The length of time (ms) it took to generate the output
* @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer * @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); log.debug("Output type: " + type);
const outputText = document.getElementById("output-text"); const outputText = document.getElementById("output-text");
const outputHtml = document.getElementById("output-html"); const outputHtml = document.getElementById("output-html");
@ -51,6 +51,7 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
if (!preserveBuffer) { if (!preserveBuffer) {
this.closeFile(); this.closeFile();
this.dishStr = null;
document.getElementById("show-file-overlay").style.display = "none"; document.getElementById("show-file-overlay").style.display = "none";
} }
@ -64,9 +65,6 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
outputText.value = ""; outputText.value = "";
outputHtml.innerHTML = data; outputHtml.innerHTML = data;
this.dishStr = Utils.unescapeHtml(Utils.stripHtmlTags(data, true));
length = data.length;
lines = this.dishStr.count("\n") + 1;
// Execute script sections // Execute script sections
scriptElements = outputHtml.querySelectorAll("script"); scriptElements = outputHtml.querySelectorAll("script");
@ -77,6 +75,10 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
log.error(err); log.error(err);
} }
} }
await this.getDishStr();
length = this.dishStr.length;
lines = this.dishStr.count("\n") + 1;
break; break;
case "ArrayBuffer": case "ArrayBuffer":
outputText.style.display = "block"; outputText.style.display = "block";
@ -86,7 +88,6 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
outputText.value = ""; outputText.value = "";
outputHtml.innerHTML = ""; outputHtml.innerHTML = "";
this.dishStr = "";
length = data.byteLength; length = data.byteLength;
this.setFile(data); this.setFile(data);
@ -151,10 +152,10 @@ OutputWaiter.prototype.closeFile = function() {
/** /**
* Handler for file download events. * 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"); this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat");
await this.getDishBuffer();
const file = new File([this.dishBuffer], this.filename); const file = new File([this.dishBuffer], this.filename);
if (this.filename) FileSaver.saveAs(file, this.filename, false); if (this.filename) FileSaver.saveAs(file, this.filename, false);
}; };
@ -254,9 +255,6 @@ OutputWaiter.prototype.adjustWidth = function() {
* Saves the current output to a file. * Saves the current output to a file.
*/ */
OutputWaiter.prototype.saveClick = function() { OutputWaiter.prototype.saveClick = function() {
if (!this.dishBuffer) {
this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.dishStr)).buffer;
}
this.downloadFile(); this.downloadFile();
}; };
@ -265,8 +263,10 @@ OutputWaiter.prototype.saveClick = function() {
* Handler for copy click events. * Handler for copy click events.
* Copies the output to the clipboard. * Copies the output to the clipboard.
*/ */
OutputWaiter.prototype.copyClick = function() { OutputWaiter.prototype.copyClick = async function() {
// Create invisible textarea to populate with the raw dishStr (not the printable version that 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) // contains dots instead of the actual bytes)
const textarea = document.createElement("textarea"); const textarea = document.createElement("textarea");
textarea.style.position = "fixed"; textarea.style.position = "fixed";
@ -303,7 +303,7 @@ OutputWaiter.prototype.copyClick = function() {
* Handler for switch click events. * Handler for switch click events.
* Moves the current output into the input textarea. * Moves the current output into the input textarea.
*/ */
OutputWaiter.prototype.switchClick = function() { OutputWaiter.prototype.switchClick = async function() {
this.switchOrigData = this.manager.input.get(); this.switchOrigData = this.manager.input.get();
document.getElementById("undo-switch").disabled = false; document.getElementById("undo-switch").disabled = false;
if (this.dishBuffer) { if (this.dishBuffer) {
@ -315,6 +315,7 @@ OutputWaiter.prototype.switchClick = function() {
} }
}); });
} else { } else {
await this.getDishStr();
this.app.setInput(this.dishStr); this.app.setInput(this.dishStr);
} }
}; };
@ -329,17 +330,6 @@ OutputWaiter.prototype.undoSwitchClick = function() {
document.getElementById("undo-switch").disabled = true; 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. * Handler for maximise output click events.
@ -409,8 +399,43 @@ OutputWaiter.prototype.setStatusMsg = function(msg) {
* *
* @returns {boolean} * @returns {boolean}
*/ */
OutputWaiter.prototype.containsCR = function() { OutputWaiter.prototype.containsCR = async function() {
await this.getDishStr();
return this.dishStr.indexOf("\r") >= 0; 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; export default OutputWaiter;

View file

@ -14,6 +14,9 @@ import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.j
const WorkerWaiter = function(app, manager) { const WorkerWaiter = function(app, manager) {
this.app = app; this.app = app;
this.manager = manager; this.manager = manager;
this.callbacks = {};
this.callbackID = 0;
}; };
@ -52,6 +55,9 @@ WorkerWaiter.prototype.handleChefMessage = function(e) {
this.app.handleError(r.data); this.app.handleError(r.data);
this.setBakingStatus(false); this.setBakingStatus(false);
break; break;
case "dishReturned":
this.callbacks[r.data.id](r.data);
break;
case "silentBakeComplete": case "silentBakeComplete":
break; break;
case "workerLoaded": case "workerLoaded":
@ -117,6 +123,7 @@ WorkerWaiter.prototype.bakingComplete = function(response) {
} }
this.app.progress = response.progress; this.app.progress = response.progress;
this.app.dish = response.dish;
this.manager.recipe.updateBreakpointIndicator(response.progress); this.manager.recipe.updateBreakpointIndicator(response.progress);
this.manager.output.set(response.result, response.type, response.duration); this.manager.output.set(response.result, response.type, response.duration);
log.debug("--- Bake complete ---"); 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. * Sets the console log level in the worker.
* *

View file

@ -62,6 +62,7 @@ a:focus {
.form-control, .form-control,
.popover, .popover,
.alert, .alert,
.panel,
.modal-content, .modal-content,
.tooltip-inner, .tooltip-inner,
.dropdown-menu, .dropdown-menu,