Ver Fonte

Create ChefWorker and move bake process into it

n1474335 há 8 anos atrás
pai
commit
760ab688b2
3 ficheiros alterados com 149 adições e 29 exclusões
  1. 81 0
      src/core/ChefWorker.js
  2. 64 27
      src/web/App.js
  3. 4 2
      src/web/stylesheets/layout/_io.css

+ 81 - 0
src/core/ChefWorker.js

@@ -0,0 +1,81 @@
+/**
+ * Web Worker to handle communications between the front-end and the core.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import "babel-polyfill";
+import Chef from "./Chef.js";
+
+// Set up Chef instance
+self.chef = new Chef();
+
+/**
+ * Respond to message from parent thread.
+ *
+ * Messages should have the following format:
+ * {
+ *     action: "bake" | "silentBake",
+ *     data: {
+ *         input: {string},
+ *         recipeConfig: {[Object]},
+ *         options: {Object},
+ *         progress: {number},
+ *         step: {boolean}
+ *     } | undefined
+ * }
+ */
+self.addEventListener("message", function(e) {
+    // Handle message
+    switch (e.data.action) {
+        case "bake":
+            bake(e.data.data);
+            break;
+        case "silentBake":
+            silentBake(e.data.data);
+            break;
+        default:
+            break;
+    }
+});
+
+
+/**
+ * Baking handler
+ */
+async function bake(data) {
+    try {
+        const response = await self.chef.bake(
+            data.input,          // The user's input
+            data.recipeConfig,   // The configuration of the recipe
+            data.options,        // Options set by the user
+            data.progress,       // The current position in the recipe
+            data.step            // Whether or not to take one step or execute the whole recipe
+        );
+
+        self.postMessage({
+            action: "bakeSuccess",
+            data: response
+        });
+    } catch (err) {
+        self.postMessage({
+            action: "bakeError",
+            data: err
+        });
+    }
+}
+
+
+/**
+ * Silent baking handler
+ */
+function silentBake(data) {
+    const duration = self.chef.silentBake(data.recipeConfig);
+
+    self.postMessage({
+        action: "silentBakeComplete",
+        data: duration
+    });
+}

+ 64 - 27
src/web/App.js

@@ -1,5 +1,5 @@
 import Utils from "../core/Utils.js";
 import Utils from "../core/Utils.js";
-import Chef from "../core/Chef.js";
+import ChefWorker from "worker-loader!../core/ChefWorker.js";
 import Manager from "./Manager.js";
 import Manager from "./Manager.js";
 import HTMLCategory from "./HTMLCategory.js";
 import HTMLCategory from "./HTMLCategory.js";
 import HTMLOperation from "./HTMLOperation.js";
 import HTMLOperation from "./HTMLOperation.js";
@@ -27,7 +27,7 @@ const App = function(categories, operations, defaultFavourites, defaultOptions)
     this.doptions      = defaultOptions;
     this.doptions      = defaultOptions;
     this.options       = Utils.extend({}, defaultOptions);
     this.options       = Utils.extend({}, defaultOptions);
 
 
-    this.chef          = new Chef();
+    this.chefWorker    = new ChefWorker();
     this.manager       = new Manager(this);
     this.manager       = new Manager(this);
 
 
     this.baking        = false;
     this.baking        = false;
@@ -36,7 +36,7 @@ const App = function(categories, operations, defaultFavourites, defaultOptions)
     this.progress      = 0;
     this.progress      = 0;
     this.ingId         = 0;
     this.ingId         = 0;
 
 
-    window.chef        = this.chef;
+    this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));
 };
 };
 
 
 
 
@@ -99,16 +99,21 @@ App.prototype.setBakingStatus = function(bakingStatus) {
 
 
     let inputLoadingIcon = document.querySelector("#input .title .loading-icon"),
     let inputLoadingIcon = document.querySelector("#input .title .loading-icon"),
         outputLoadingIcon = document.querySelector("#output .title .loading-icon"),
         outputLoadingIcon = document.querySelector("#output .title .loading-icon"),
+        inputElement = document.querySelector("#input-text"),
         outputElement = document.querySelector("#output-text");
         outputElement = document.querySelector("#output-text");
 
 
     if (bakingStatus) {
     if (bakingStatus) {
         inputLoadingIcon.style.display = "inline-block";
         inputLoadingIcon.style.display = "inline-block";
         outputLoadingIcon.style.display = "inline-block";
         outputLoadingIcon.style.display = "inline-block";
+        inputElement.classList.add("disabled");
+        inputElement.disabled = true;
         outputElement.classList.add("disabled");
         outputElement.classList.add("disabled");
         outputElement.disabled = true;
         outputElement.disabled = true;
     } else {
     } else {
         inputLoadingIcon.style.display = "none";
         inputLoadingIcon.style.display = "none";
         outputLoadingIcon.style.display = "none";
         outputLoadingIcon.style.display = "none";
+        inputElement.classList.remove("disabled");
+        inputElement.disabled = false;
         outputElement.classList.remove("disabled");
         outputElement.classList.remove("disabled");
         outputElement.disabled = false;
         outputElement.disabled = false;
     }
     }
@@ -116,30 +121,58 @@ App.prototype.setBakingStatus = function(bakingStatus) {
 
 
 
 
 /**
 /**
- * Calls the Chef to bake the current input using the current recipe.
+ * Asks the ChefWorker to bake the current input using the current recipe.
  *
  *
  * @param {boolean} [step] - Set to true if we should only execute one operation instead of the
  * @param {boolean} [step] - Set to true if we should only execute one operation instead of the
  *   whole recipe.
  *   whole recipe.
  */
  */
-App.prototype.bake = async function(step) {
-    let response;
-
+App.prototype.bake = function(step) {
     if (this.baking) return;
     if (this.baking) return;
 
 
     this.setBakingStatus(true);
     this.setBakingStatus(true);
 
 
-    try {
-        response = await this.chef.bake(
-            this.getInput(),          // The user's input
-            this.getRecipeConfig(),   // The configuration of the recipe
-            this.options,             // Options set by the user
-            this.progress,            // The current position in the recipe
-            step                      // Whether or not to take one step or execute the whole recipe
-        );
-    } catch (err) {
-        this.handleError(err);
+    this.chefWorker.postMessage({
+        action: "bake",
+        data: {
+            input: this.getInput(),                 // The user's input
+            recipeConfig: this.getRecipeConfig(),   // The configuration of the recipe
+            options: this.options,                  // Options set by the user
+            progress: this.progress,                // The current position in the recipe
+            step: step                              // Whether or not to take one step or execute the whole recipe
+        }
+    });
+};
+
+
+/**
+ * Handler for messages sent back by the ChefWorker.
+ *
+ * @param {MessageEvent} e
+ */
+App.prototype.handleChefMessage = function(e) {
+    switch (e.data.action) {
+        case "bakeSuccess":
+            this.bakingComplete(e.data.data);
+            break;
+        case "bakeError":
+            this.handleError(e.data.data);
+            this.setBakingStatus(false);
+            break;
+        case "silentBakeComplete":
+            break;
+        default:
+            console.error("Unrecognised message from ChefWorker", e);
+            break;
     }
     }
+};
 
 
+
+/**
+ * Handler for completed bakes.
+ *
+ * @param {Object} response
+ */
+App.prototype.bakingComplete = function(response) {
     this.setBakingStatus(false);
     this.setBakingStatus(false);
 
 
     if (!response) return;
     if (!response) return;
@@ -174,23 +207,27 @@ App.prototype.autoBake = function() {
 
 
 
 
 /**
 /**
- * Runs a silent bake forcing the browser to load and cache all the relevant JavaScript code needed
- * to do a real bake.
- *
- * The output will not be modified (hence "silent" bake). This will only actually execute the
- * recipe if auto-bake is enabled, otherwise it will just load the recipe, ingredients and dish.
+ * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant
+ * JavaScript code needed to do a real bake.
  *
  *
- * @returns {number} - The number of miliseconds it took to run the silent bake.
+ * The output will not be modified (hence "silent" bake). This will only actually execute the recipe
+ * if auto-bake is enabled, otherwise it will just wake up the ChefWorker with an empty recipe.
  */
  */
 App.prototype.silentBake = function() {
 App.prototype.silentBake = function() {
-    let startTime = new Date().getTime(),
-        recipeConfig = this.getRecipeConfig();
+    let recipeConfig = [];
 
 
     if (this.autoBake_) {
     if (this.autoBake_) {
-        this.chef.silentBake(recipeConfig);
+        // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled
+        // for a good reason.
+        recipeConfig = this.getRecipeConfig();
     }
     }
 
 
-    return new Date().getTime() - startTime;
+    this.chefWorker.postMessage({
+        action: "silentBake",
+        data: {
+            recipeConfig: recipeConfig
+        }
+    });
 };
 };
 
 
 
 

+ 4 - 2
src/web/stylesheets/layout/_io.css

@@ -22,6 +22,8 @@
     background-color: transparent;
     background-color: transparent;
     white-space: pre-wrap;
     white-space: pre-wrap;
     word-wrap: break-word;
     word-wrap: break-word;
+
+    transition: all 0.5s ease;
 }
 }
 
 
 #output-html {
 #output-html {
@@ -90,10 +92,10 @@
 
 
 @keyframes spinner {
 @keyframes spinner {
     from {
     from {
-        transform:rotate(0deg);
+        transform: rotate(0deg);
     }
     }
     to {
     to {
-        transform:rotate(359deg);
+        transform: rotate(359deg);
     }
     }
 }
 }