Merge branch 'master' into feature/bombe

This commit is contained in:
s2224834 2019-02-28 16:29:21 +00:00
commit a037bf6f9d
50 changed files with 2361 additions and 448 deletions

View file

@ -243,12 +243,18 @@ class App {
/**
* Sets up the adjustable splitter to allow the user to resize areas of the page.
*
* @param {boolean} [minimise=false] - Set this flag if attempting to minimuse frames to 0 width
*/
initialiseSplitter() {
initialiseSplitter(minimise=false) {
if (this.columnSplitter) this.columnSplitter.destroy();
if (this.ioSplitter) this.ioSplitter.destroy();
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
sizes: [20, 30, 50],
minSize: [240, 370, 450],
minSize: minimise ? [0, 0, 0] : [240, 370, 450],
gutterSize: 4,
expandToMin: false,
onDrag: function() {
this.manager.recipe.adjustWidth();
}.bind(this)
@ -256,7 +262,8 @@ class App {
this.ioSplitter = Split(["#input", "#output"], {
direction: "vertical",
gutterSize: 4
gutterSize: 4,
minSize: minimise ? [0, 0] : [100, 100]
});
this.resetLayout();

View file

@ -4,6 +4,8 @@
* @license Apache-2.0
*/
import Utils from "../core/Utils";
/**
* Object to handle the creation of operation ingredients.
*/
@ -25,6 +27,7 @@ class HTMLIngredient {
this.value = config.value;
this.disabled = config.disabled || false;
this.hint = config.hint || false;
this.rows = config.rows || false;
this.target = config.target;
this.defaultIndex = config.defaultIndex || 0;
this.toggleValues = config.toggleValues;
@ -159,7 +162,7 @@ class HTMLIngredient {
const val = this.type === "populateMultiOption" ?
JSON.stringify(this.value[i].value) :
this.value[i].value;
html += `<option populate-value='${val}'>${this.value[i].name}</option>`;
html += `<option populate-value='${Utils.escapeHtml(val)}'>${this.value[i].name}</option>`;
}
}
html += `</select>
@ -236,6 +239,7 @@ class HTMLIngredient {
class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
rows="${this.rows ? this.rows : 3}"
${this.disabled ? "disabled" : ""}>${this.value}</textarea>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;

View file

@ -243,14 +243,22 @@ class InputWaiter {
}
if (file) {
this.closeFile();
this.loaderWorker = new LoaderWorker();
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.loaderWorker.postMessage({"file": file});
this.set(file);
this.loadFile(file);
}
}
/**
* Handler for open input button events
* Loads the opened data into the input textarea
*
* @param {event} e
*/
inputOpen(e) {
e.preventDefault();
const file = e.srcElement.files[0];
this.loadFile(file);
}
/**
* Handler for messages sent back by the LoaderWorker.
@ -306,6 +314,22 @@ class InputWaiter {
}
/**
* Loads a file into the input.
*
* @param {File} file
*/
loadFile(file) {
if (file) {
this.closeFile();
this.loaderWorker = new LoaderWorker();
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.loaderWorker.postMessage({"file": file});
this.set(file);
}
}
/**
* Handler for clear IO events.
* Resets the input, output and info areas.

View file

@ -137,12 +137,16 @@ class Manager {
this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe);
this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe);
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
this.addDynamicListener("textarea.arg", "dragover", this.recipe.textArgDragover, this.recipe);
this.addDynamicListener("textarea.arg", "dragleave", this.recipe.textArgDragLeave, this.recipe);
this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe);
// Input
this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input);
this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input);
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
this.addListeners("#open-file", "change", this.input.inputOpen, this.input);
this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);
this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input);
this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input);

View file

@ -319,6 +319,7 @@ class OutputWaiter {
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
this.app.initialiseSplitter(true);
this.app.columnSplitter.collapse(0);
this.app.columnSplitter.collapse(1);
this.app.ioSplitter.collapse(0);
@ -328,6 +329,7 @@ class OutputWaiter {
} else {
$(el).attr("data-original-title", "Maximise output pane");
el.querySelector("i").innerHTML = "fullscreen";
this.app.initialiseSplitter(false);
this.app.resetLayout();
}
}
@ -506,7 +508,7 @@ class OutputWaiter {
*/
showMagicButton(opSequence, result, recipeConfig) {
const magicButton = document.getElementById("magic");
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.truncate(result, 30)}"</span>`);
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.escapeHtml(Utils.truncate(result), 30)}"</span>`);
magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, "");
magicButton.classList.remove("hidden");
}

View file

@ -378,6 +378,7 @@ class RecipeWaiter {
}
}
/**
* Adds the specified operation to the recipe.
*
@ -475,6 +476,75 @@ class RecipeWaiter {
}
/**
* Handler for text argument dragover events.
* Gives the user a visual cue to show that items can be dropped here.
*
* @param {event} e
*/
textArgDragover (e) {
// This will be set if we're dragging an operation
if (e.dataTransfer.effectAllowed === "move")
return false;
e.stopPropagation();
e.preventDefault();
e.target.closest("textarea.arg").classList.add("dropping-file");
}
/**
* Handler for text argument dragleave events.
* Removes the visual cue.
*
* @param {event} e
*/
textArgDragLeave (e) {
e.stopPropagation();
e.preventDefault();
e.target.classList.remove("dropping-file");
}
/**
* Handler for text argument drop events.
* Loads the dragged data into the argument textarea.
*
* @param {event} e
*/
textArgDrop(e) {
// This will be set if we're dragging an operation
if (e.dataTransfer.effectAllowed === "move")
return false;
e.stopPropagation();
e.preventDefault();
const targ = e.target;
const file = e.dataTransfer.files[0];
const text = e.dataTransfer.getData("Text");
targ.classList.remove("dropping-file");
if (text) {
targ.value = text;
return;
}
if (file) {
const reader = new FileReader();
const self = this;
reader.onload = function (e) {
targ.value = e.target.result;
// Trigger floating label move
const changeEvent = new Event("change");
targ.dispatchEvent(changeEvent);
window.dispatchEvent(self.manager.statechange);
};
reader.readAsText(file);
}
}
/**
* Sets register values.
*
@ -500,6 +570,7 @@ class RecipeWaiter {
op.insertAdjacentHTML("beforeend", registerListEl);
}
/**
* Adjusts the number of ingredient columns as the width of the recipe changes.
*/
@ -511,20 +582,25 @@ class RecipeWaiter {
this.ingredientChildRuleID = null;
// Find relevant rules in the stylesheet
for (const i in document.styleSheets[0].cssRules) {
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
this.ingredientRuleID = i;
}
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
this.ingredientChildRuleID = i;
// try/catch for chrome 64+ CORS error on cssRules.
try {
for (const i in document.styleSheets[0].cssRules) {
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
this.ingredientRuleID = i;
}
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
this.ingredientChildRuleID = i;
}
}
} catch (e) {
// Do nothing.
}
}
if (!this.ingredientRuleID || !this.ingredientChildRuleID) return;
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID],
ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID];
const ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
if (recList.clientWidth < 450) {
ingredientRule.style.gridTemplateColumns = "auto auto";

View file

@ -229,6 +229,10 @@
<div class="title no-select">
<label for="input-text">Input</label>
<span class="float-right">
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-open-file" data-toggle="tooltip" title="Open file as input" onclick="document.getElementById('open-file').click();">
<i class="material-icons">input</i>
<input type="file" id="open-file" style="display: none">
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output">
<i class="material-icons">delete</i>
</button>

View file

@ -147,6 +147,7 @@
.dropping-file {
border: 5px dashed var(--drop-file-border-colour) !important;
margin: -5px;
}
#stale-indicator {