Create ChefWorker and move bake process into it

This commit is contained in:
n1474335 2017-07-05 00:11:57 +01:00
parent ff78c72d54
commit 760ab688b2
3 changed files with 150 additions and 30 deletions

81
src/core/ChefWorker.js Normal file
View File

@ -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
});
}

View File

@ -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) { App.prototype.bake = function(step) {
let response;
if (this.baking) return; if (this.baking) return;
this.setBakingStatus(true); this.setBakingStatus(true);
try { this.chefWorker.postMessage({
response = await this.chef.bake( action: "bake",
this.getInput(), // The user's input data: {
this.getRecipeConfig(), // The configuration of the recipe input: this.getInput(), // The user's input
this.options, // Options set by the user recipeConfig: this.getRecipeConfig(), // The configuration of the recipe
this.progress, // The current position in the recipe options: this.options, // Options set by the user
step // Whether or not to take one step or execute the whole recipe progress: this.progress, // The current position in the recipe
); step: step // Whether or not to take one step or execute the whole recipe
} catch (err) { }
this.handleError(err); });
} };
/**
* 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 * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant
* to do a real bake. * JavaScript code needed to do a real bake.
* *
* The output will not be modified (hence "silent" bake). This will only actually execute the * The output will not be modified (hence "silent" bake). This will only actually execute the recipe
* recipe if auto-bake is enabled, otherwise it will just load the recipe, ingredients and dish. * if auto-bake is enabled, otherwise it will just wake up the ChefWorker with an empty recipe.
*
* @returns {number} - The number of miliseconds it took to run the silent bake.
*/ */
App.prototype.silentBake = function() { App.prototype.silentBake = function() {
let startTime = new Date().getTime(), let recipeConfig = [];
recipeConfig = this.getRecipeConfig();
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
}
});
}; };

View File

@ -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);
} }
} }