diff --git a/src/web/App.mjs b/src/web/App.mjs
index 074ff88b..911f51b8 100755
--- a/src/web/App.mjs
+++ b/src/web/App.mjs
@@ -792,13 +792,12 @@ class App {
* Fires whenever the input or recipe changes in any way.
*
* @listens Manager#statechange
- * @param {Event} e
*/
- stateChange(e) {
- debounce(function() {
+ stateChange() {
+ debounce(() => {
this.progress = 0;
this.autoBake();
- this.updateURL(true, null, true);
+ this.updateURL(true);
}, 20, "stateChange", this, [])();
}
@@ -905,7 +904,7 @@ class App {
}
/**
- * @fires Manager#oplistcreate from nested c-category-li > c-operation-list build() function
+ * Build a CCategoryList element and append it to #categories
*/
buildCategoryList() {
// double-check if the c-category-list already exists,
diff --git a/src/web/HTMLCategory.mjs b/src/web/HTMLCategory.mjs
deleted file mode 100755
index 89a7ef36..00000000
--- a/src/web/HTMLCategory.mjs
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- */
-
-/**
- * Object to handle the creation of operation categories.
- */
-class HTMLCategory {
-
- /**
- * HTMLCategory constructor.
- *
- * @param {string} name - The name of the category.
- * @param {boolean} selected - Whether this category is pre-selected or not.
- */
- constructor(name, selected) {
- this.name = name;
- this.selected = selected;
- this.opList = [];
- }
-
-
- /**
- * Adds an operation to this category.
- *
- * @param {HTMLOperation} operation - The operation to add.
- */
- addOperation(operation) {
- this.opList.push(operation);
- }
-
-
- /**
- * Renders the category and all operations within it in HTML.
- *
- * @returns {HTMLElement}
- */
- toHtml() {
- const catName = "cat" + this.name.replace(/[\s/\-:_]/g, "");
-
- let html = `
-
- ${this.name}
-
-
-
`;
-
- for (let i = 0; i < this.opList.length; i++) {
- html += this.opList[i].toStubHtml();
- }
-
- html += "
";
-
- return html;
- }
-}
-
-export default HTMLCategory;
diff --git a/src/web/HTMLIngredient.mjs b/src/web/HTMLIngredient.mjs
index 2678de9b..be62f222 100755
--- a/src/web/HTMLIngredient.mjs
+++ b/src/web/HTMLIngredient.mjs
@@ -8,6 +8,9 @@ import Utils from "../core/Utils.mjs";
/**
* Object to handle the creation of operation ingredients.
+ *
+ * @TODO: would be nice to refactor this. Move everything to c-ingredient-li and
+ * implement there accordingly, delete this file
*/
class HTMLIngredient {
diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs
index 9c19b06f..0b076c34 100755
--- a/src/web/HTMLOperation.mjs
+++ b/src/web/HTMLOperation.mjs
@@ -4,12 +4,6 @@
* @license Apache-2.0
*/
-import HTMLIngredient from "./HTMLIngredient.mjs";
-import Utils from "../core/Utils.mjs";
-import url from "url";
-import {COperationLi} from "./components/c-operation-li.mjs";
-
-
/**
* Object to handle the creation of operations.
*/
@@ -32,36 +26,6 @@ class HTMLOperation {
this.infoURL = config.infoURL;
this.manualBake = config.manualBake || false;
this.config = config;
- this.ingList = [];
-
- for (let i = 0; i < config.args.length; i++) {
- const ing = new HTMLIngredient(config.args[i], this.app, this.manager);
- this.ingList.push(ing);
- }
- }
-
-
- /**
- * Renders the operation in HTML as a full operation with ingredients.
- *
- * @returns {string}
- */
- toFullHtml() {
- let html = `${Utils.escapeHtml(this.name)}
- `;
-
- for (let i = 0; i < this.ingList.length; i++) {
- html += this.ingList[i].toHtml();
- }
-
- html += `
-
- pause
- not_interested
-
-
`;
-
- return html;
}
diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs
index 52b09c1c..a7f07748 100755
--- a/src/web/Manager.mjs
+++ b/src/web/Manager.mjs
@@ -50,10 +50,6 @@ class Manager {
* @event Manager#operationremove
*/
this.operationremove = new CustomEvent("operationremove", {bubbles: true});
- /**
- * @event Manager#oplistcreate
- */
- this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true});
/**
* @event Manager#statechange
*/
@@ -89,7 +85,7 @@ class Manager {
this.input.setupInputWorker();
this.input.addInput(true);
this.worker.setupChefWorker();
- this.recipe.initialiseOperationDragNDrop();
+ this.recipe.initDragAndDrop();
this.controls.initComponents();
this.controls.autoBakeChange();
this.bindings.updateKeybList();
@@ -152,7 +148,7 @@ class Manager {
document.getElementById("close-ops-dropdown-icon").addEventListener("click", this.ops.closeOpsDropdown.bind(this.ops));
document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops));
document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops));
- this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);
+ this.addDynamicListener("c-operation-li", "operationadd", this.recipe.opAdd, this.recipe);
// Recipe
this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe);
diff --git a/src/web/TODO.md b/src/web/TODO.md
index e69de29b..1d36915c 100644
--- a/src/web/TODO.md
+++ b/src/web/TODO.md
@@ -0,0 +1,7 @@
+- ignore dropped item outside of rec-list
+- search-results dropdown
+
+- reordering recipe list
+- stupid popovers on deleting favs for instance ( dont always close nicely )
+
+- UI tests etc.
diff --git a/src/web/components/c-ingredient-li.mjs b/src/web/components/c-ingredient-li.mjs
new file mode 100644
index 00000000..64b663c5
--- /dev/null
+++ b/src/web/components/c-ingredient-li.mjs
@@ -0,0 +1,72 @@
+import Utils from "../../core/Utils.mjs";
+import HTMLIngredient from "../HTMLIngredient.mjs";
+
+export class CIngredientLi extends HTMLElement {
+ constructor(app, name, args) {
+ super();
+
+ this.app = app;
+ this.name = name;
+ this.args = [];
+
+ for (let i = 0; i < args.length; i++) {
+ const ing = new HTMLIngredient(args[i], this.app, this.app.manager);
+ this.args.push(ing);
+ }
+
+ this.build();
+ }
+
+ build() {
+ const li = document.createElement("li");
+ li.classList.add("operation");
+ li.setAttribute("data-name", this.name);
+
+ const titleDiv = document.createElement("div");
+ titleDiv.classList.add("op-title");
+ titleDiv.innerText = this.name;
+
+ const ingredientDiv = document.createElement("div");
+ ingredientDiv.classList.add("ingredients");
+
+ li.appendChild(titleDiv);
+ li.appendChild(ingredientDiv)
+
+ for (let i = 0; i < this.args.length; i++) {
+ ingredientDiv.innerHTML += (this.args[i].toHtml());
+ }
+
+ const iconsDiv = document.createElement("div");
+ iconsDiv.classList.add("recipe-icons");
+
+ const breakPointIcon = document.createElement("i");
+ breakPointIcon.classList.add("material-icons");
+ breakPointIcon.classList.add("breakpoint");
+ breakPointIcon.setAttribute("title", "Set breakpoint");
+ breakPointIcon.setAttribute("break", "false");
+ breakPointIcon.setAttribute("data-help-title", "Setting breakpoints");
+ breakPointIcon.setAttribute("data-help", "Setting a breakpoint on an operation will cause execution of the Recipe to pause when it reaches that operation.");
+ breakPointIcon.innerText = "pause";
+
+ const disableIcon = document.createElement("i");
+ disableIcon.classList.add("material-icons");
+ disableIcon.classList.add("disable-icon");
+ disableIcon.setAttribute("title", "Disable operation");
+ disableIcon.setAttribute("disabled", "false");
+ disableIcon.setAttribute("data-help-title", "Disabling operations");
+ disableIcon.setAttribute("data-help", "Disabling an operation will prevent it from being executed when the Recipe is baked. Execution will skip over the disabled operation and continue with subsequent operations.");
+ disableIcon.innerText = "not_interested";
+
+ iconsDiv.appendChild(breakPointIcon);
+ iconsDiv.appendChild(disableIcon);
+
+ const clearfixDiv = document.createElement("div");
+
+ li.appendChild(iconsDiv);
+ li.appendChild(clearfixDiv);
+
+ this.appendChild(li);
+ }
+}
+
+customElements.define("c-ingredient-li", CIngredientLi);
diff --git a/src/web/components/c-operation-li.mjs b/src/web/components/c-operation-li.mjs
index c827e7d5..365a6a2e 100644
--- a/src/web/components/c-operation-li.mjs
+++ b/src/web/components/c-operation-li.mjs
@@ -24,7 +24,6 @@ export class COperationLi extends HTMLElement {
this.includeStarIcon = includeStarIcon;
this.config = this.app.operations[name];
- // this.ingList = [];
this.isFavourite = this.app.isLocalStorageAvailable() && JSON.parse(localStorage.favourites).indexOf(name) >= 0;
@@ -37,11 +36,6 @@ export class COperationLi extends HTMLElement {
this.observer = new MutationObserver(this.updateFavourite.bind(this));
this.observer.observe(this.querySelector("li"), { attributes: true });
}
-
- // for (let i = 0; i < this.config.args.length; i++) {
- // const ing = new HTMLIngredient(this.config.args[i], this.app, this.manager);
- // this.ingList.push(ing);
- // }
}
/**
@@ -249,6 +243,15 @@ export class COperationLi extends HTMLElement {
this.querySelector("i.star-icon").innerText = "star_outline";
}
}
+
+ /**
+ * Override native cloneNode method so we can clone c-operation-li properly
+ * with constructor arguments for sortable and cloneable lists
+ */
+ cloneNode() {
+ const { app, name, icon, includeStarIcon } = this;
+ return new COperationLi( app, name, icon, includeStarIcon );
+ }
}
diff --git a/src/web/components/c-operation-list.mjs b/src/web/components/c-operation-list.mjs
index 75245ca5..ec49aed8 100644
--- a/src/web/components/c-operation-list.mjs
+++ b/src/web/components/c-operation-list.mjs
@@ -37,7 +37,7 @@ export class COperationList extends HTMLElement {
ul.classList.add("op-list");
this.opNames.forEach((opName => {
- const li = new COperationLi(
+ const cOpLi = new COperationLi(
this.app,
opName,
{
@@ -47,41 +47,91 @@ export class COperationList extends HTMLElement {
this.includeStarIcon
);
- ul.appendChild(li);
+ ul.appendChild(cOpLi);
}))
if (this.isSortable) {
- const sortableList = Sortable.create(ul, {
- group: "sorting",
- sort: true,
- draggable: "c-operation-li",
- onFilter: function (e) {
- const el = sortableList.closest(e.item);
- if (el && el.parentNode) {
- $(el).popover("dispose");
- el.parentNode.removeChild(el);
- }
- },
- onEnd: function(e) {
- if (this.removeIntent) {
- $(e.item).popover("dispose");
- e.item.remove();
- }
- }.bind(this),
- });
+ this.createSortableList(ul);
} else if (!this.app.isMobileView() && this.isCloneable) {
- const cloneableList = Sortable.create(ul, {
- group: {
- name: "recipe",
- pull: "clone",
- },
- draggable: "c-operation-li",
- sort: false
- })
+ this.createCloneableList(ul, "recipe", "rec-list"); // target name and id can be component params if needed to make it reusable
}
this.append(ul);
}
+
+ /**
+ * Create a sortable ( not cloneable ) list
+ *
+ * @param { HTMLElement } ul
+ * */
+ createSortableList(ul) {
+ const sortableList = Sortable.create(ul, {
+ group: "sorting",
+ sort: true,
+ draggable: "c-operation-li",
+ onFilter: function (e) {
+ const el = sortableList.closest(e.item);
+ if (el && el.parentNode) {
+ $(el).popover("dispose");
+ el.parentNode.removeChild(el);
+ }
+ },
+ onEnd: function(e) {
+ if (this.app.manager.recipe.removeIntent) {
+ $(e.item).popover("dispose");
+ e.item.remove();
+ }
+ }.bind(this),
+ });
+ }
+
+ /**
+ * Create a cloneable ( not sortable ) list
+ *
+ * @param { HTMLElement } ul
+ * @param { string } targetListName
+ * @param { string } targetListId
+ * */
+ createCloneableList(ul, targetListName, targetListId) {
+ Sortable.create(ul, {
+ group: {
+ name: targetListName,
+ pull: "clone",
+ put: false,
+ },
+ draggable: "c-operation-li",
+ sort: false,
+ setData: function(dataTransfer, dragEl) {
+ dataTransfer.setData("Text", dragEl.querySelector("li").getAttribute("data-name"));
+ },
+ onStart: function(e) {
+ // Removes popover element and event bindings from the dragged operation but not the
+ // event bindings from the one left in the operations list. Without manually removing
+ // these bindings, we cannot re-initialise the popover on the stub operation.
+ $(e.item)
+ .popover("dispose")
+ .removeData("bs.popover")
+ .off("mouseenter")
+ .off("mouseleave")
+ .attr("data-toggle", "popover-disabled");
+ $(e.clone)
+ .off(".popover")
+ .removeData("bs.popover");
+ },
+ // @todo: popovers dont display anymore after dragging into recipe list and then hovering the op
+ onEnd: ({item}) => {
+ if (item.parentNode.id === targetListId) {
+ this.app.manager.recipe.addOperation(item.name);
+ item.remove();
+ return;
+ }
+
+ if (item.parentNode.id !== targetListId) {
+ return;
+ }
+ }
+ });
+ }
}
customElements.define("c-operation-list", COperationList);
diff --git a/src/web/stylesheets/components/_operation.css b/src/web/stylesheets/components/_operation.css
index b994a1f4..56b581d3 100755
--- a/src/web/stylesheets/components/_operation.css
+++ b/src/web/stylesheets/components/_operation.css
@@ -15,7 +15,7 @@
border-top: none;
border-left: none;
border-right: none;
- cursor: pointer;
+ cursor: default;
}
@media only screen and (min-width: 768px){
@@ -272,14 +272,14 @@ input.toggle-string {
word-break: break-all;
}
-.recip-icons {
+.recipe-icons {
position: absolute;
top: 13px;
right: 10px;
height: 16px;
}
-.recip-icons i {
+.recipe-icons i {
margin-right: 10px;
vertical-align: baseline;
float: right;
diff --git a/src/web/waiters/RecipeWaiter.mjs b/src/web/waiters/RecipeWaiter.mjs
index f7aeb5ae..17c4f1fa 100755
--- a/src/web/waiters/RecipeWaiter.mjs
+++ b/src/web/waiters/RecipeWaiter.mjs
@@ -8,6 +8,7 @@ import HTMLOperation from "../HTMLOperation.mjs";
import Sortable from "sortablejs";
import Utils from "../../core/Utils.mjs";
import {escapeControlChars} from "../utils/editorUtils.mjs";
+import {CIngredientLi} from "../components/c-ingredient-li.mjs";
/**
@@ -31,30 +32,30 @@ class RecipeWaiter {
/**
* Sets up the drag and drop capability for operations in the operations and recipe areas.
*/
- initialiseOperationDragNDrop() {
+ initDragAndDrop() {
const recList = document.getElementById("rec-list");
- const isMobileView = this.app.isMobileView();
// Recipe list
Sortable.create(recList, {
group: "recipe",
sort: true,
- swapThreshold: isMobileView ? 0.60 : 0.3,
- animation: isMobileView ? 400 : 200,
- delay: isMobileView ? 200 : 0,
+ draggable: "c-ingredient-li",
+ swapThreshold: this.app.isMobileView ? 0.60 : 0.3,
+ animation: this.app.isMobileView ? 400 : 200,
+ delay: this.app.isMobileView ? 200 : 0,
filter: ".arg",
preventOnFilter: false,
setData: function(dataTransfer, dragEl) {
- dataTransfer.setData("Text", dragEl.getAttribute("data-name"));
+ dataTransfer.setData("Text", dragEl.querySelector("li").getAttribute("data-name"));
},
- onEnd: function(evt) {
+ onEnd: function(e) {
if (this.removeIntent) {
- evt.item.remove();
- evt.target.dispatchEvent(this.manager.operationremove);
+ e.item.remove();
+ e.target.dispatchEvent(this.manager.operationremove);
}
}.bind(this),
- onSort: function(evt) {
- if (evt.from.id === "rec-list") {
+ onSort: function(e) {
+ if (e.from.id === "rec-list") {
document.dispatchEvent(this.manager.statechange);
}
}.bind(this)
@@ -83,42 +84,6 @@ class RecipeWaiter {
}
- /**
- * Creates a drag-n-droppable seed list of operations.
- *
- * @param {element} listEl - The list to initialise
- */
- createSortableSeedList(listEl) {
- Sortable.create(listEl, {
- group: {
- name: "recipe",
- pull: "clone",
- put: false,
- },
- draggable: ".operation",
- sort: false,
- setData: function(dataTransfer, dragEl) {
- dataTransfer.setData("Text", dragEl.getAttribute("data-name"));
- },
- onStart: function(evt) {
- // Removes popover element and event bindings from the dragged operation but not the
- // event bindings from the one left in the operations list. Without manually removing
- // these bindings, we cannot re-initialise the popover on the stub operation.
- $(evt.item)
- .popover("dispose")
- .removeData("bs.popover")
- .off("mouseenter")
- .off("mouseleave")
- .attr("data-toggle", "popover-disabled");
- $(evt.clone)
- .off(".popover")
- .removeData("bs.popover");
- },
- onEnd: this.opSortEnd.bind(this)
- });
- }
-
-
/**
* Handler for operation sort end events.
* Removes the operation from the list if it has been dropped outside. If not, adds it to the list
@@ -128,6 +93,7 @@ class RecipeWaiter {
* @param {Event} evt
*/
opSortEnd(evt) {
+ console.log(evt);
if (this.removeIntent && evt.item.parentNode.id === "rec-list") {
evt.item.remove();
return;
@@ -135,21 +101,22 @@ class RecipeWaiter {
// Reinitialise the popover on the original element in the ops list because for some reason it
// gets destroyed and recreated. If the clone isn't in the ops list, we use the original item instead.
- let enableOpsElement;
- if (evt.clone?.parentNode?.classList?.contains("op-list")) {
- enableOpsElement = evt.clone;
- } else {
- enableOpsElement = evt.item;
- $(evt.item).attr("data-toggle", "popover");
- }
- this.manager.ops.enableOpPopover(enableOpsElement);
+ // let enableOpsElement;
+ // if (evt.clone?.parentNode?.classList?.contains("op-list")) {
+ // enableOpsElement = evt.clone;
+ // } else {
+ // enableOpsElement = evt.item;
+ // $(evt.item).attr("data-toggle", "popover");
+ // }
+
+ // this.manager.ops.enableOpPopover(enableOpsElement);
if (evt.item.parentNode.id !== "rec-list") {
return;
}
- this.buildRecipeOperation(evt.item);
- evt.item.dispatchEvent(this.manager.operationadd);
+ this.buildRecipeOperation(evt.item.name);
+ // evt.item.dispatchEvent(this.manager.operationadd);
}
@@ -369,15 +336,13 @@ class RecipeWaiter {
* Given an operation stub element, this function converts it into a full recipe element with
* arguments.
*
- * @param {element} el - The operation stub element from the operations pane
+ * @param {string} name - The operation stub element from the operations pane
*/
- buildRecipeOperation(el) {
- const opName = el.textContent;
- const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
- el.innerHTML = op.toFullHtml();
+ buildRecipeOperation(name) {
+ const op = new CIngredientLi(this.app, name, this.app.operations[name].args);
- if (this.app.operations[opName].flowControl) {
- el.classList.add("flow-control-op");
+ if (this.app.operations[name].flowControl) {
+ op.classList.add("flow-control-op");
}
// Disable auto-bake if this is a manual op
@@ -385,6 +350,8 @@ class RecipeWaiter {
this.manager.controls.setAutoBake(false);
this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000);
}
+
+ return op;
}
@@ -392,24 +359,19 @@ class RecipeWaiter {
* Adds the specified operation to the recipe
*
* @fires Manager#operationadd
+ * @fires Manager#statechange
* @param {string} name - The name of the operation to add
- * @returns {element}
*/
addOperation(name) {
- const item = document.createElement("li");
- item.setAttribute("data-name", name);
-
- item.classList.add("operation");
- item.innerText = name;
- this.buildRecipeOperation(item);
+ let item = this.buildRecipeOperation(name);
document.getElementById("rec-list").appendChild(item);
$(item).find("[data-toggle='tooltip']").tooltip();
item.dispatchEvent(this.manager.operationadd);
+ document.dispatchEvent(this.app.manager.statechange);
this.manager.ops.updateListItemsClasses("#rec-list", "selected");
-
return item;
}
@@ -471,7 +433,9 @@ class RecipeWaiter {
* @param {Event} e
*/
opAdd(e) {
- log.debug(`'${e.target.getAttribute("data-name")}' added to recipe`);
+ console.log(e);
+ log.debug(`'${e.target.querySelector("li").getAttribute("data-name")}' added to recipe`);
+ console.log(e.target.querySelector("li").getAttribute("data-name"));
this.triggerArgEvents(e.target);
window.dispatchEvent(this.manager.statechange);
}