Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make accessible :) #12

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions src/web/HTMLOperation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class HTMLOperation {
this.manager = manager;

this.name = name;
this.title = name;
this.description = config.description;
this.infoURL = config.infoURL;
this.manualBake = config.manualBake || false;
Expand All @@ -46,7 +47,7 @@ class HTMLOperation {
* @returns {string}
*/
toStubHtml(removeIcon) {
let html = "<li class='operation'";
let html = `<li class='operation' data-opname="${this.name}"`;

if (this.description) {
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
Expand All @@ -56,18 +57,25 @@ class HTMLOperation {
data-boundary='viewport'`;
}

html += ">" + this.name;
html += ">" + this.title;

// Ensure add button only appears in sidebar, not fav edit
if (removeIcon) {
// Remove button
html += "<i class='material-icons remove-icon op-icon'>delete</i>";
} else {
// Add buttob
html += `<span class='float-right'>
<button type="button" class="btn btn-primary bmd-btn-icon accessibleUX" data-toggle="tooltip" data-original-title="Add to recipe">
<i class='material-icons'>add_box</i>
</button>
</span>`;
}

html += "</li>";

return html;
}


/**
* Renders the operation in HTML as a full operation with ingredients.
*
Expand All @@ -83,6 +91,9 @@ class HTMLOperation {

html += `</div>
<div class="recip-icons">
<i class="material-icons move-down accessibleUX" title="Move down">arrow_downward</i>
<i class="material-icons move-up accessibleUX">arrow_upward</i>
<i class="material-icons remove-icon accessibleUX" title="Delete operation">delete</i>
<i class="material-icons breakpoint" title="Set breakpoint" break="false">pause</i>
<i class="material-icons disable-icon" title="Disable operation" disabled="false">not_interested</i>
</div>
Expand All @@ -100,18 +111,18 @@ class HTMLOperation {
*/
highlightSearchStrings(nameIdxs, descIdxs) {
if (nameIdxs.length && typeof nameIdxs[0][0] === "number") {
let opName = "",
let title = "",
pos = 0;

nameIdxs.forEach(idxs => {
const [start, length] = idxs;
if (typeof start !== "number") return;
opName += this.name.slice(pos, start) + "<b>" +
this.name.slice(start, start + length) + "</b>";
title += this.title.slice(pos, start) + "<b>" +
this.title.slice(start, start + length) + "</b>";
pos = start + length;
});
opName += this.name.slice(pos, this.name.length);
this.name = opName;
title += this.title.slice(pos, this.title.length);
this.title = title;
}

if (this.description && descIdxs.length && descIdxs[0][0] >= 0) {
Expand Down
6 changes: 6 additions & 0 deletions src/web/Manager.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class Manager {
this.bindings.updateKeybList();
this.background.registerChefWorker();
this.seasonal.load();
this.options.load();
}


Expand Down Expand Up @@ -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));
Expand All @@ -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);
Expand Down Expand Up @@ -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));
Expand Down
7 changes: 7 additions & 0 deletions src/web/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,13 @@ <h5 class="modal-title">Options</h5>
Keep the current tab in sync between the input and output
</label>
</div>

<div class="checkbox option-item">
<label for="accessibleUX">
<input type="checkbox" option="accessibleUX" id="accessibleUX">
Accessible user experience, buttons for drag-and-drop operations
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="reset-options">Reset options to default</button>
Expand Down
3 changes: 2 additions & 1 deletion src/web/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ function main() {
autoMagic: true,
imagePreview: true,
syncTabs: true,
preserveCR: "entropy"
preserveCR: "entropy",
accessibleUX: false
};

document.removeEventListener("DOMContentLoaded", main, false);
Expand Down
4 changes: 4 additions & 0 deletions src/web/stylesheets/components/_button.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ span.btn img {
margin-right: 3px;
margin-bottom: 1px;
}

button.btn.accessibleUX {
margin-top: -5px;
}
16 changes: 14 additions & 2 deletions src/web/waiters/OperationsWaiter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class OperationsWaiter {
if (ops.length) {
selected = this.getSelectedOp(ops);
if (selected > -1) {
this.manager.recipe.addOperation(ops[selected].innerHTML);
this.manager.recipe.addOperation($(ops[selected]).data("opname"));
}
}
}
Expand Down Expand Up @@ -209,8 +209,20 @@ class OperationsWaiter {
*/
operationDblclick(e) {
const li = e.target;
// get operation name from <li> 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) {
const li = e.target.parentNode.parentNode.parentNode;
// get operation name from <li> data
this.manager.recipe.addOperation($(li).data("opname"));
}


Expand Down
15 changes: 15 additions & 0 deletions src/web/waiters/OptionsWaiter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -189,6 +191,19 @@ 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");
const UXstyle = "display:" + (checked ? "block" : "none");
$("html > head").append($("<style>.accessibleUX {" + UXstyle + "; }</style>"));
}
}

export default OptionsWaiter;
41 changes: 39 additions & 2 deletions src/web/waiters/RecipeWaiter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();

Expand All @@ -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);

Expand Down
55 changes: 45 additions & 10 deletions tests/browser/nightwatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,51 @@ 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 searchOp = "#search-results";
const down = " i.move-down.accessibleUX";
const remove = " i.remove-icon.accessibleUX";
const favCat = "#categories div:nth-child(1)";

// Switch UX on
browser
.useCss()
.click("#options")
.waitForElementVisible("#options-modal", 1000)
.click("#reset-options")
// Using label for checkbox click because nightwatch thinks #accessibleUX 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);

// Search for an op
browser
.useCss()
.clearValue("#search")
.setValue("#search", "md5")
.click(searchOp + " button")
.click(recOp + remove)
.click(favCat)
.expect.element(searchOp).text.to.contain("MD5");
},

"Recipe can be run": browser => {
const toHex = "//li[contains(@class, 'operation') and text()='To Hex']";
const op = "#rec-list .operation .op-title";
Expand Down Expand Up @@ -211,16 +256,6 @@ module.exports = {
browser.click("#clr-recipe");
},

"Search": browser => {
// Search for an op
browser
.useCss()
.clearValue("#search")
.setValue("#search", "md5")
.useXpath()
.waitForElementVisible("//ul[@id='search-results']//b[text()='MD5']", 1000);
},

after: browser => {
browser.end();
}
Expand Down