瀏覽代碼

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

n1474335 7 年之前
父節點
當前提交
76a066ab74

+ 21 - 1
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;

+ 19 - 0
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.
  *

+ 10 - 7
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);
+        }
     }
 
 

+ 3 - 1
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) {

+ 17 - 9
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.
      *

+ 11 - 26
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 = `<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;'>
                     <div class='panel-heading' role='tab' id='heading${i}'>
                         <h4 class='panel-title'>
                             <div>
-                                ${Utils.escapeHtml(file.name)}
-                                ${viewFileElem}
-                                ${downloadFileElem}
-                                ${switchToInputElem}
+                                <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)}'">${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'>
                                     ${file.size.toLocaleString()} bytes
                                 </span>
@@ -875,7 +860,7 @@ class Utils {
                     <div id='collapse${i}' class='panel-collapse collapse'
                         role='tabpanel' aria-labelledby='heading${i}'>
                         <div class='panel-body'>
-                            <pre><code>${Utils.escapeHtml(fileStr)}</code></pre>
+                            <pre><code>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</code></pre>
                         </div>
                     </div>
                 </div>`;

+ 1 - 1
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`);
 }
 
 

+ 2 - 2
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;

+ 0 - 1
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));

+ 50 - 25
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;

+ 28 - 0
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.
  *

+ 1 - 0
src/web/stylesheets/utils/_overrides.css

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