This commit is contained in:
Toby Lorne 2017-02-18 16:15:34 +00:00 committed by GitHub
commit b2814a5740
9 changed files with 356 additions and 118 deletions

View file

@ -430,3 +430,35 @@ span.btn img {
border-top: none; border-top: none;
margin-top: 0; margin-top: 0;
} }
@-moz-keyframes spinner {
from { -moz-transform: rotate(0deg); }
to { -moz-transform: rotate(359deg); }
}
@-webkit-keyframes spinner {
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(359deg); }
}
@keyframes spinner {
from {transform:rotate(0deg);}
to {transform:rotate(359deg);}
}
.loading-icon::before {
content: "\21bb";
}
.loading-icon {
-webkit-animation-name: spinner;
-webkit-animation-duration: 1000ms;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-moz-animation-name: spinner;
-moz-animation-duration: 1000ms;
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: linear;
-ms-animation-name: spinner;
-ms-animation-duration: 1000ms;
-ms-animation-iteration-count: infinite;
-ms-animation-timing-function: linear;
}

View file

@ -97,6 +97,9 @@
<div id="input" class="split no-select"> <div id="input" class="split no-select">
<div class="title no-select"> <div class="title no-select">
Input Input
<div class="loading-icon"
style="display: none">
</div>
<div class="btn-group io-btn-group"> <div class="btn-group io-btn-group">
<button type="button" class="btn btn-default btn-sm" id="clr-io"><img src="images/recycle-16x16.png" /> Clear I/O</button> <button type="button" class="btn btn-default btn-sm" id="clr-io"><img src="images/recycle-16x16.png" /> Clear I/O</button>
<button type="button" class="btn btn-default btn-sm" id="reset-layout"><img src="images/layout-16x16.png" /> Reset layout</button> <button type="button" class="btn btn-default btn-sm" id="reset-layout"><img src="images/layout-16x16.png" /> Reset layout</button>
@ -113,6 +116,9 @@
<div id="output" class="split"> <div id="output" class="split">
<div class="title no-select"> <div class="title no-select">
Output Output
<div class="loading-icon"
style="display: none">
</div>
<div class="btn-group io-btn-group"> <div class="btn-group io-btn-group">
<button type="button" class="btn btn-default btn-sm" id="save-to-file" title="Save to file"><img src="images/save_as-16x16.png" /> Save to file</button> <button type="button" class="btn btn-default btn-sm" id="save-to-file" title="Save to file"><img src="images/save_as-16x16.png" /> Save to file</button>
<button type="button" class="btn btn-default btn-sm" id="switch" title="Move output to input"><img src="images/switch-16x16.png" /> Move output to input</button> <button type="button" class="btn btn-default btn-sm" id="switch" title="Move output to input"><img src="images/switch-16x16.png" /> Move output to input</button>

View file

@ -287,6 +287,7 @@ var Categories = [
"Jump", "Jump",
"Conditional Jump", "Conditional Jump",
"Return", "Return",
"Wait",
] ]
}, },
]; ];

View file

@ -3120,5 +3120,19 @@ var OperationConfig = {
outputType: "html", outputType: "html",
args: [ args: [
] ]
} },
"Wait": {
description: "Waits for a number of milliseconds.",
run: FlowControl.runWait,
inputType: "string",
outputType: "string",
flowControl: true,
args: [
{
name: "Sleep time in milliseconds",
type: "number",
value: FlowControl.SLEEP_TIME,
}
]
},
}; };

View file

@ -34,7 +34,7 @@ Chef.prototype.bake = function(inputText, recipeConfig, options, progress, step)
var startTime = new Date().getTime(), var startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig), recipe = new Recipe(recipeConfig),
containsFc = recipe.containsFlowControl(), containsFc = recipe.containsFlowControl(),
error = false; chef = this;
// Reset attemptHighlight flag // Reset attemptHighlight flag
if (options.hasOwnProperty("attemptHighlight")) { if (options.hasOwnProperty("attemptHighlight")) {
@ -64,28 +64,41 @@ Chef.prototype.bake = function(inputText, recipeConfig, options, progress, step)
// If starting from scratch, load data // If starting from scratch, load data
if (progress === 0) { if (progress === 0) {
this.dish.set(inputText, Dish.STRING); chef.dish.set(inputText, Dish.STRING);
} }
try { var ret = {
progress = recipe.execute(this.dish, progress);
} catch (err) {
// Return the error in the result so that everything else gets correctly updated
// rather than throwing it here and losing state info.
error = err;
progress = err.progress;
}
return {
result: this.dish.type === Dish.HTML ?
this.dish.get(Dish.HTML) :
this.dish.get(Dish.STRING),
type: Dish.enumLookup(this.dish.type),
progress: progress,
options: options, options: options,
duration: new Date().getTime() - startTime, error: false,
error: error
}; };
return new Promise(function(resolve) {
recipe.execute(chef.dish, progress)
.then(function(progress) {
ret.result = chef.dish.type === Dish.HTML ?
chef.dish.get(Dish.HTML) :
chef.dish.get(Dish.STRING);
ret.type = Dish.enumLookup(chef.dish.type);
ret.duration = new Date().getTime() - startTime;
ret.progress = progress;
resolve(ret);
})
.catch(function(err) {
ret.result = chef.dish.type === Dish.HTML ?
chef.dish.get(Dish.HTML) :
chef.dish.get(Dish.STRING);
ret.type = Dish.enumLookup(chef.dish.type);
ret.duration = new Date().getTime() - startTime;
ret.progress = err.progress;
ret.error = err;
// Resolve not reject: we are packaging the error as a value.
resolve(ret);
});
});
}; };

View file

@ -59,29 +59,53 @@ var FlowControl = {
} }
} }
var recipe = new Recipe(), var recipe = new Recipe();
output = "",
progress = 0;
recipe.addOperations(subOpList); recipe.addOperations(subOpList);
// Run recipe over each tranche return new Promise(function(resolve, reject) {
for (i = 0; i < inputs.length; i++) { var promises = inputs.map(function(input, i) {
var dish = new Dish(inputs[i], inputType); var forkDish = new Dish(input, inputType);
try {
progress = recipe.execute(dish, 0);
} catch (err) {
if (!ignoreErrors) {
throw err;
}
progress = err.progress + 1;
}
output += dish.get(outputType) + mergeDelim;
}
state.dish.set(output, outputType); return new Promise(function(resolve, reject) {
state.progress += progress; recipe.execute(forkDish, 0)
return state; .then(function(progress) {
resolve({
progress: progress,
dish: forkDish,
});
})
.catch(function(err) {
if (ignoreErrors) {
resolve({
progress: err.progress + 1,
dish: forkDish,
});
} else {
reject(err);
}
});
});
});
Promise.all(promises)
.then(function(values) {
var progress;
var output = values.map(function(value) {
progress = value.progress;
return value.dish.get(outputType);
}).join(mergeDelim);
state.progress += progress;
state.dish.set(output, outputType);
resolve(state);
})
.catch(function(err) {
reject(err);
});
});
}, },
@ -183,4 +207,32 @@ var FlowControl = {
return state; return state;
}, },
/**
* @constant
* @default
*/
SLEEP_TIME: 2500,
/**
* Wait operation.
*
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on.
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @returns {Object} The updated state of the recipe.
*/
runWait: function(state) {
var ings = state.opList[state.progress].getIngValues(),
sleepTime = ings[0];
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(state);
}, sleepTime);
});
},
}; };

View file

@ -138,58 +138,112 @@ Recipe.prototype.lastOpIndex = function(startIndex) {
* Executes each operation in the recipe over the given Dish. * Executes each operation in the recipe over the given Dish.
* *
* @param {Dish} dish * @param {Dish} dish
* @param {number} [startFrom=0] - The index of the Operation to start executing from * @param {number} [currentStep=0] - The index of the Operation to start executing from
* @returns {number} - The final progress through the recipe * @returns {number} - The final progress through the recipe
*/ */
Recipe.prototype.execute = function(dish, startFrom) { Recipe.prototype.execute = function(dish, currentStep, state) {
startFrom = startFrom || 0; var recipe = this;
var op, input, output, numJumps = 0;
for (var i = startFrom; i < this.opList.length; i++) { var formatErrMsg = function(err, step, op) {
op = this.opList[i]; var e = typeof err == "string" ? { message: err } : err;
if (op.isDisabled()) {
continue; e.progress = step;
} if (e.fileName) {
if (op.isBreakpoint()) { e.displayStr = op.name + " - " + e.name + " in " +
return i; e.fileName + " on line " + e.lineNumber +
".<br><br>Message: " + (e.displayStr || e.message);
} else {
e.displayStr = op.name + " - " + (e.displayStr || e.message);
} }
try { return e;
};
// Operations can be asynchronous so we have to return a Promise to a
// future value.
return new Promise(function(resolve, reject) {
// Helper function to clean up recursing to the next recipe step.
// It is a closure to avoid having to pass in resolve and reject.
var execRecipe = function(recipe, dish, step, state) {
return recipe.execute(dish, step, state)
.then(function(progress) {
resolve(progress);
})
.catch(function(err) {
// Pass back the error to the previous caller.
// We don't want to handle the error here as the current
// operation did not cause the error, and so it should
// not appear in the error message.
reject(err);
});
};
currentStep = currentStep || 0;
if (currentStep === recipe.opList.length) {
resolve(currentStep);
return;
}
var op = recipe.opList[currentStep],
input = dish.get(op.inputType); input = dish.get(op.inputType);
if (op.isDisabled()) {
// Skip to next operation
var nextStep = currentStep + 1;
execRecipe(recipe, dish, nextStep, state);
} else if (op.isBreakpoint()) {
// We are at a breakpoint, we shouldn't recurse to the next op.
resolve(currentStep);
} else {
var operationResult;
// We must try/catch here because op.run can either return
// A) a value
// B) a promise
// Promise.resolve -> .catch will handle errors from promises
// try/catch will handle errors from values
try {
if (op.isFlowControl()) {
state = {
progress: currentStep,
dish: dish,
opList: recipe.opList,
numJumps: (state && state.numJumps) || 0,
};
operationResult = op.run(state);
} else {
operationResult = op.run(input, op.getIngValues());
}
} catch (err) {
reject(formatErrMsg(err, currentStep, op));
return;
}
if (op.isFlowControl()) { if (op.isFlowControl()) {
// Package up the current state Promise.resolve(operationResult)
var state = { .then(function(state) {
"progress" : i, return recipe.execute(state.dish, state.progress + 1);
"dish" : dish, })
"opList" : this.opList, .then(function(progress) {
"numJumps" : numJumps resolve(progress);
}; })
.catch(function(err) {
state = op.run(state); reject(formatErrMsg(err, currentStep, op));
i = state.progress; });
numJumps = state.numJumps;
} else { } else {
output = op.run(input, op.getIngValues()); Promise.resolve(operationResult)
dish.set(output, op.outputType); .then(function(output) {
dish.set(output, op.outputType);
var nextStep = currentStep + 1;
execRecipe(recipe, dish, nextStep, state);
})
.catch(function(err) {
reject(formatErrMsg(err, currentStep, op));
});
} }
} catch (err) {
var e = typeof err == "string" ? { message: err } : err;
e.progress = i;
if (e.fileName) {
e.displayStr = op.name + " - " + e.name + " in " +
e.fileName + " on line " + e.lineNumber +
".<br><br>Message: " + (e.displayStr || e.message);
} else {
e.displayStr = op.name + " - " + (e.displayStr || e.message);
}
throw e;
} }
} });
return this.opList.length;
}; };

View file

@ -28,6 +28,9 @@ var HTMLApp = function(categories, operations, defaultFavourites, defaultOptions
this.progress = 0; this.progress = 0;
this.ingId = 0; this.ingId = 0;
this.baking = false;
this.rebake = false;
window.chef = this.chef; window.chef = this.chef;
}; };
@ -61,6 +64,36 @@ HTMLApp.prototype.handleError = function(err) {
}; };
/**
* Updates the UI to show if baking is in process or not.
*
* @param {bakingStatus}
*/
HTMLApp.prototype.setBakingStatus = function(bakingStatus) {
var inputLoadingIcon = document.querySelector("#input .title .loading-icon");
var outputLoadingIcon = document.querySelector("#output .title .loading-icon");
var inputElement = document.querySelector("#input-text");
var outputElement = document.querySelector("#output-text");
if (bakingStatus) {
inputLoadingIcon.style.display = "inline-block";
outputLoadingIcon.style.display = "inline-block";
inputElement.classList.add("disabled");
outputElement.classList.add("disabled");
inputElement.disabled = true;
outputElement.disabled = true;
} else {
inputLoadingIcon.style.display = "none";
outputLoadingIcon.style.display = "none";
inputElement.classList.remove("disabled");
outputElement.classList.remove("disabled");
inputElement.disabled = false;
outputElement.disabled = false;
}
};
/** /**
* Calls the Chef to bake the current input using the current recipe. * Calls the Chef to bake the current input using the current recipe.
* *
@ -68,37 +101,70 @@ HTMLApp.prototype.handleError = function(err) {
* whole recipe. * whole recipe.
*/ */
HTMLApp.prototype.bake = function(step) { HTMLApp.prototype.bake = function(step) {
var response; var app = this;
if (app.baking) {
if (!app.rebake) {
// We do not want to keep autobaking
// Say that we will rebake and then try again later
app.rebake = true;
setTimeout(function() {
app.bake(step);
}, 500);
}
return;
}
app.rebake = false;
app.baking = true;
app.setBakingStatus(true);
try { try {
response = this.chef.bake( app.chef.bake(
this.getInput(), // The user's input app.getInput(), // The user's input
this.getRecipeConfig(), // The configuration of the recipe app.getRecipeConfig(), // The configuration of the recipe
this.options, // Options set by the user app.options, // Options set by the user
this.progress, // The current position in the recipe app.progress, // The current position in the recipe
step // Whether or not to take one step or execute the whole recipe step // Whether or not to take one step or execute the whole recipe
); )
.then(function(response) {
app.baking = false;
app.setBakingStatus(false);
if (!response) {
return;
}
if (response.error) {
app.handleError(response.error);
}
app.options = response.options;
if (response.type === "html") {
app.dishStr = Utils.stripHtmlTags(response.result, true);
} else {
app.dishStr = response.result;
}
app.progress = response.progress;
app.manager.recipe.updateBreakpointIndicator(response.progress);
app.manager.output.set(response.result, response.type, response.duration);
// If baking took too long, disable auto-bake
if (response.duration > app.options.autoBakeThreshold && app.autoBake_) {
app.manager.controls.setAutoBake(false);
app.alert("Baking took longer than " + app.options.autoBakeThreshold +
"ms, Auto Bake has been disabled.", "warning", 5000);
}
})
.catch(function(err) {
console.error("Chef's promise was rejected, should never occur");
});
} catch (err) { } catch (err) {
this.handleError(err); app.baking = false;
} app.setBakingStatus(false);
app.handleError(err);
if (!response) return;
if (response.error) {
this.handleError(response.error);
}
this.options = response.options;
this.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result;
this.progress = response.progress;
this.manager.recipe.updateBreakpointIndicator(response.progress);
this.manager.output.set(response.result, response.type, response.duration);
// If baking took too long, disable auto-bake
if (response.duration > this.options.autoBakeThreshold && this.autoBake_) {
this.manager.controls.setAutoBake(false);
this.alert("Baking took longer than " + this.options.autoBakeThreshold +
"ms, Auto Bake has been disabled.", "warning", 5000);
} }
}; };

View file

@ -1,21 +1,21 @@
214 source files 214 source files
115904 lines 116142 lines
4.3M size size
144 JavaScript source files 144 JavaScript source files
106712 lines 106912 lines
3.8M size 4.9M size
83 third party JavaScript source files 83 third party JavaScript source files
86259 lines 86259 lines
3.0M size 3.7M size
61 first party JavaScript source files 61 first party JavaScript source files
20453 lines 20653 lines
764K size 1.3M size
3.5M uncompressed JavaScript size uncompressed JavaScript size
1.9M compressed JavaScript size compressed JavaScript size
15 categories 15 categories
176 operations 177 operations