diff --git a/package.json b/package.json index 02be5cbc..d99e4e54 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "autoprefixer": "^10.4.4", "babel-loader": "^8.2.4", "babel-plugin-dynamic-import-node": "^2.3.3", - "chromedriver": "^99.0.0", + "chromedriver": "^101.0.0", "cli-progress": "^3.10.0", "colors": "^1.4.0", "copy-webpack-plugin": "^10.2.4", diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs index 04e9b3cc..67c07788 100755 --- a/src/web/HTMLOperation.mjs +++ b/src/web/HTMLOperation.mjs @@ -46,7 +46,7 @@ class HTMLOperation { * @returns {string} */ toStubHtml(removeIcon) { - let html = "
  • ${titleFromWikiLink(this.infoURL)}` : ""; @@ -58,6 +58,12 @@ class HTMLOperation { html += ">" + this.name; + html += ` + + `; + if (removeIcon) { html += "delete"; } @@ -83,6 +89,9 @@ class HTMLOperation { html += `
    + arrow_downward + arrow_upward + delete pause not_interested
    diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index e1e07dfd..779546c1 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -93,6 +93,7 @@ class Manager { this.bindings.updateKeybList(); this.background.registerChefWorker(); this.seasonal.load(); + this.options.load(); } @@ -126,6 +127,7 @@ class Manager { // Operations this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops); this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops); + this.addDynamicListener(".op-list li.operation button i", "click", this.ops.operationAdd, this.ops); document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.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)); @@ -137,6 +139,9 @@ class Manager { this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); + this.addDynamicListener(".remove-icon", "click", this.recipe.removeClick, this.recipe); + this.addDynamicListener(".move-down", "click", this.recipe.moveDownClick, this.recipe); + this.addDynamicListener(".move-up", "click", this.recipe.moveUpClick, this.recipe); this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); 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); @@ -233,6 +238,7 @@ class Manager { document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options)); document.getElementById("imagePreview").addEventListener("change", this.input.renderFileThumb.bind(this.input)); + document.getElementById("accessibleUX").addEventListener("change", this.options.uxChange.bind(this.options)); // Misc window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); diff --git a/src/web/html/index.html b/src/web/html/index.html index 8a7adac7..59a6b9b4 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -584,6 +584,13 @@ Keep the current tab in sync between the input and output + +
    + +
  • data + this.manager.recipe.addOperation($(li).data("opname")); + } - this.manager.recipe.addOperation(li.textContent); + /** + * Handler for operation add events. + * Adds the operation to the recipe and auto bakes. + * + * @param {event} e + */ + operationAdd(e) { + log.info("add"); + const li = e.target.parentNode.parentNode.parentNode; + // get operation name from
  • data + this.manager.recipe.addOperation($(li).data("opname")); } diff --git a/src/web/waiters/OptionsWaiter.mjs b/src/web/waiters/OptionsWaiter.mjs index 5ef517d4..eb46e767 100755 --- a/src/web/waiters/OptionsWaiter.mjs +++ b/src/web/waiters/OptionsWaiter.mjs @@ -36,6 +36,8 @@ class OptionsWaiter { for (i = 0; i < cboxes.length; i++) { cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")]; } + // init ux option - ux is last cbox + cboxes[i-1].dispatchEvent(new Event("change")); const nboxes = document.querySelectorAll("#options-body input[type=number]"); for (i = 0; i < nboxes.length; i++) { @@ -189,6 +191,18 @@ class OptionsWaiter { this.manager.worker.setLogLevel(); this.manager.input.setLogLevel(); } + + + /** + * Changes the UX using CSS change so that actions can be managed + * regardless of visibility + * + * @param {Event} e + */ + uxChange(e) { + const checked = $("#accessibleUX").is(":checked"); + $(".accessibleUX").css("display", checked ? "block" : "none"); + } } export default OptionsWaiter; diff --git a/src/web/waiters/RecipeWaiter.mjs b/src/web/waiters/RecipeWaiter.mjs index f4107e66..b15e3a03 100755 --- a/src/web/waiters/RecipeWaiter.mjs +++ b/src/web/waiters/RecipeWaiter.mjs @@ -261,6 +261,43 @@ class RecipeWaiter { window.dispatchEvent(this.manager.statechange); } + /** + * Handler for remove click events. + * Removes the operation from the recipe and auto bakes. + * + * @fires Manager#statechange + * @param {event} e + */ + removeClick(e) { + e.target.parentNode.parentNode.remove(); + this.opRemove(e); + } + + /** + * Handler for remove click events. + * Removes the operation from the recipe and auto bakes. + * + * @fires Manager#statechange + * @param {event} e + */ + moveUpClick(e) { + const li = $(e.target.parentNode.parentNode); + li.prev().before(li); + window.dispatchEvent(this.manager.statechange); + } + + /** + * Handler for down click events. + * Moves the operation in the recipe and auto bakes. + * + * @fires Manager#statechange + * @param {event} e + */ + moveDownClick(e) { + const li = $(e.target.parentNode.parentNode); + li.next().after(li); + window.dispatchEvent(this.manager.statechange); + } /** * Handler for operation doubleclick events. @@ -368,7 +405,7 @@ class RecipeWaiter { * @param {element} el - The operation stub element from the operations pane */ buildRecipeOperation(el) { - const opName = el.textContent; + const opName = $(el).data('opname'); const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); el.innerHTML = op.toFullHtml(); @@ -395,7 +432,7 @@ class RecipeWaiter { const item = document.createElement("li"); item.classList.add("operation"); - item.innerHTML = name; + $(item).data("opname", name); this.buildRecipeOperation(item); document.getElementById("rec-list").appendChild(item); diff --git a/tests/browser/nightwatch.js b/tests/browser/nightwatch.js index 41aff9b2..2373354a 100644 --- a/tests/browser/nightwatch.js +++ b/tests/browser/nightwatch.js @@ -56,6 +56,40 @@ module.exports = { browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present; }, + "Accessible user experience": browser => { + const addOp = "#catFavourites li.operation"; + const recOp = "#rec-list li:nth-child(1)"; + const down = " i.move-down.accessibleUX"; + const remove = " i.remove-icon.accessibleUX"; + + // Switch UX on + browser + .useCss() + .click("#options") + .waitForElementVisible("#options-modal", 1000) + .click("#reset-options") + .pause(500) + // Using label for checkbox click because nightwatch thinks #acessibleUX isn't visible + .click('label[for="accessibleUX"]') + .click("#options-modal .modal-footer button:last-child") + .waitForElementNotVisible("#options-modal") + .expect.element(addOp).to.be.visible; + + // add Operations & move them + browser + .useCss() + .click(addOp + ":nth-child(1) button") + .click(addOp + ":nth-child(2) button") + .click(recOp + down) + .expect.element(recOp).text.to.contain("From Base64"); + + // delete operations + browser + .click(recOp + remove) + .click(recOp + remove) + .waitForElementNotPresent(recOp); + }, + "Recipe can be run": browser => { const toHex = "//li[contains(@class, 'operation') and text()='To Hex']"; const op = "#rec-list .operation .op-title";