Revert "181 responsive UI"

This commit is contained in:
Autumn 2023-12-21 19:54:21 +00:00 committed by GitHub
parent 4cb0d2b82b
commit 2ecdd67208
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2084 additions and 3255 deletions

1
.gitignore vendored
View File

@ -12,4 +12,3 @@ src/node/index.mjs
**/*.DS_Store
tests/browser/output/*
.node-version
.idea

View File

@ -215,7 +215,6 @@ module.exports = function (grunt) {
},
devServer: {
port: grunt.option("port") || 8080,
host: "0.0.0.0",
client: {
logging: "error",
overlay: true

View File

@ -26,7 +26,7 @@ There are four main areas in CyberChef:
1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on.
2. The **output** box in the bottom right, where the outcome of your processing will be displayed.
3. The **operations** list on the far left ( or in the dropdown at the top on mobile ), where you can find all the operations that CyberChef is capable of in categorised lists, or by searching.
3. The **operations** list on the far left, where you can find all the operations that CyberChef is capable of in categorised lists, or by searching.
4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options.
You can use as many operations as you like in simple or complex ways. Some examples are as follows:
@ -48,7 +48,6 @@ You can use as many operations as you like in simple or complex ways. Some examp
- Drag and drop
- Operations can be dragged in and out of the recipe list, or reorganised.
- Files up to 2GB can be dragged over the input box to load them directly into the browser.
- On mobile devices, double-click the operations to add them to the recipe list.
- Auto Bake
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
@ -89,7 +88,7 @@ CyberChef is built to support
## Node.js support
CyberChef is built to fully support Node.js `v18`. For more information, see the Node API page in the project [wiki pages](https://github.com/gchq/CyberChef/wiki)
CyberChef is built to fully support Node.js `v16`. For more information, see the Node API page in the project [wiki pages](https://github.com/gchq/CyberChef/wiki)
## Contributing

View File

@ -26,6 +26,7 @@
"prod": {
"launch_url": "http://localhost:8000/index.html"
}
}
}

View File

@ -18,6 +18,7 @@ let modules = null;
* The Recipe controls a list of Operations and the Dish they operate on.
*/
class Recipe {
/**
* Recipe constructor
*

View File

@ -7,11 +7,11 @@
import Utils, { debounce } from "../core/Utils.mjs";
import {fromBase64} from "../core/lib/Base64.mjs";
import Manager from "./Manager.mjs";
import HTMLCategory from "./HTMLCategory.mjs";
import HTMLOperation from "./HTMLOperation.mjs";
import Split from "split.js";
import moment from "moment-timezone";
import cptable from "codepage";
import {CCategoryLi} from "./components/c-category-li.mjs";
import {CCategoryList} from "./components/c-category-list.mjs";
/**
@ -46,16 +46,8 @@ class App {
this.appLoaded = false;
this.workerLoaded = false;
this.waitersLoaded = false;
this.breakpoint = 1024;
}
/**
* Is current view < breakpoint
*/
isMobileView() {
return window.innerWidth < this.breakpoint;
}
/**
* This function sets up the stage and creates listeners for all events.
@ -65,17 +57,21 @@ class App {
setup() {
document.dispatchEvent(this.manager.appstart);
this.initialiseSplitter();
this.loadLocalStorage();
this.buildCategoryList();
this.buildUI();
this.populateOperationsList();
this.manager.setup();
this.manager.output.saveBombe();
this.adjustComponentSizes();
this.setCompileMessage();
this.uriParams = this.getURIParams();
log.debug("App loaded");
this.appLoaded = true;
this.loaded();
}
/**
* Fires once all setup activities have completed.
*
@ -245,47 +241,84 @@ class App {
/**
* Sets up the adjustable splitter to allow the user to resize areas of the page.
* Populates the operations accordion list with the categories and operations specified in the
* view constructor.
*
* @fires Manager#oplistcreate
*/
buildUI() {
if (this.isMobileView()) {
this.setMobileUI();
} else {
this.setDesktopUI();
populateOperationsList() {
// Move edit button away before we overwrite it
document.body.appendChild(document.getElementById("edit-favourites"));
let html = "";
let i;
for (i = 0; i < this.categories.length; i++) {
const catConf = this.categories[i],
selected = i === 0,
cat = new HTMLCategory(catConf.name, selected);
for (let j = 0; j < catConf.ops.length; j++) {
const opName = catConf.ops[j];
if (!(opName in this.operations)) {
log.warn(`${opName} could not be found.`);
continue;
}
const op = new HTMLOperation(opName, this.operations[opName], this, this.manager);
cat.addOperation(op);
}
html += cat.toHtml();
}
document.getElementById("categories").innerHTML = html;
const opLists = document.querySelectorAll("#categories .op-list");
for (i = 0; i < opLists.length; i++) {
opLists[i].dispatchEvent(this.manager.oplistcreate);
}
// Add edit button to first category (Favourites)
const favCat = document.querySelector("#categories a");
favCat.appendChild(document.getElementById("edit-favourites"));
favCat.setAttribute("data-help-title", "Favourite operations");
favCat.setAttribute("data-help", `<p>This category displays your favourite operations.</p>
<ul>
<li><b>To add:</b> drag an operation over the Favourites category</li>
<li><b>To reorder:</b> Click on the 'Edit favourites' button and drag operations up and down in the list provided</li>
<li><b>To remove:</b> Click on the 'Edit favourites' button and hit the delete button next to the operation you want to remove</li>
</ul>`);
}
/**
* Set splitter
* Sets up the adjustable splitter to allow the user to resize areas of the page.
*
* We don't actually functionally use splitters on mobile, but we leverage the splitters
* to create our desired layout. This prevents some problems when resizing
* from mobile to desktop and vice versa and reduces code complexity
* @param {boolean} [minimise=false] - Set this flag if attempting to minimise frames to 0 width
*/
setSplitter() {
initialiseSplitter(minimise=false) {
if (this.columnSplitter) this.columnSplitter.destroy();
if (this.ioSplitter) this.ioSplitter.destroy();
const isMobileView = this.isMobileView();
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
sizes: isMobileView ? [100, 100, 100] : [20, 40, 40],
minSize: [360, 330, 310],
gutterSize: isMobileView ? 0 : 4,
sizes: [20, 30, 50],
minSize: minimise ? [0, 0, 0] : [240, 310, 450],
gutterSize: 4,
expandToMin: true,
onDrag: debounce(() => {
if (!isMobileView) {
this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs();
}
onDrag: debounce(function() {
this.adjustComponentSizes();
}, 50, "dragSplitter", this, [])
});
this.ioSplitter = Split(["#input", "#output"], {
direction: "vertical",
gutterSize: isMobileView ? 0 : 4,
minSize: [50, 50]
gutterSize: 4,
minSize: minimise ? [0, 0] : [100, 100]
});
this.adjustComponentSizes();
}
@ -301,6 +334,7 @@ class App {
}
this.manager.options.load(lOptions);
// Load favourites
this.loadFavourites();
}
@ -378,11 +412,14 @@ class App {
/**
* Resets favourite operations to the default as specified in the view constructor and
* Resets favourite operations back to the default as specified in the view constructor and
* refreshes the operation list.
*/
resetFavourites() {
this.updateFavourites(this.dfavourites, true);
this.saveFavourites(this.dfavourites);
this.loadFavourites();
this.populateOperationsList();
this.manager.recipe.initialiseOperationDragNDrop();
}
@ -390,9 +427,8 @@ class App {
* Adds an operation to the user's favourites.
*
* @param {string} name - The name of the operation
* @param {Boolean} isExpanded - false by default
*/
addFavourite(name, isExpanded = false) {
addFavourite(name) {
const favourites = JSON.parse(localStorage.favourites);
if (favourites.indexOf(name) >= 0) {
@ -401,51 +437,10 @@ class App {
}
favourites.push(name);
this.updateFavourites(favourites, isExpanded);
}
/**
* Update favourites in localstorage, load the updated
* favourites and rebuild cat fav-list to reflect the updates
*
* @param {string[]} favourites
* @param {Boolean} isExpanded, false by default
*/
updateFavourites(favourites, isExpanded = false) {
this.saveFavourites(favourites);
this.loadFavourites();
this.buildFavouritesCategory(isExpanded);
window.dispatchEvent(this.manager.favouritesupdate);
this.manager.recipe.initDragAndDrop();
}
/**
* (Re)render only the favourites category after updating favourites
*
* @param {Boolean} isExpanded ( false by default )
*/
buildFavouritesCategory(isExpanded = false) {
// double-check if the first category is indeed "catFavourites",
if (document.querySelector("c-category-list > ul > c-category-li > li > a[data-target='#catFavourites']")) {
// then destroy
document.querySelectorAll("c-category-list > ul > c-category-li")[0].remove();
// and rebuild it
const favCatConfig = this.categories.find(catConfig => catConfig.name === "Favourites");
const favouriteCategory = new CCategoryLi(
this,
favCatConfig,
this.operations,
isExpanded,
true
);
// finally prepend it to c-category-list
document.querySelector("c-category-list > ul").prepend(favouriteCategory);
}
this.populateOperationsList();
this.manager.recipe.initialiseOperationDragNDrop();
}
/**
@ -492,7 +487,7 @@ class App {
// Search for nearest match and add it
const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false);
if (matchedOps.length) {
this.manager.recipe.addOperation(matchedOps[0][0]);
this.manager.recipe.addOperation(matchedOps[0].name);
}
// Populate search with the string
@ -561,7 +556,7 @@ class App {
/**
* Gets the current recipe configuration.
*
* @returns {Object[]} recipeConfig - The recipe configuration
* @returns {Object[]}
*/
getRecipeConfig() {
return this.manager.recipe.getConfig();
@ -608,7 +603,7 @@ class App {
item.querySelector(".disable-icon").click();
}
if (recipeConfig[i].breakpoint) {
item.querySelector(".breakpoint-icon").click();
item.querySelector(".breakpoint").click();
}
this.manager.recipe.triggerArgEvents(item);
@ -622,7 +617,27 @@ class App {
/**
* Sets the compile message ( "notice" in #banner ).
* Resets the splitter positions to default.
*/
resetLayout() {
this.columnSplitter.setSizes([20, 30, 50]);
this.ioSplitter.setSizes([50, 50]);
this.adjustComponentSizes();
}
/**
* Adjust components to fit their containers.
*/
adjustComponentSizes() {
this.manager.recipe.adjustWidth();
this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs();
this.manager.controls.calcControlsHeight();
}
/**
* Sets the compile message.
*/
setCompileMessage() {
// Display time since last build and compile message
@ -751,16 +766,17 @@ class App {
/**
* Handler for CyberChef statechange events.
* Handler for CyerChef statechange events.
* Fires whenever the input or recipe changes in any way.
*
* @listens Manager#statechange
* @param {event} e
*/
stateChange() {
debounce(() => {
stateChange(e) {
debounce(function() {
this.progress = 0;
this.autoBake();
this.updateURL(true);
this.updateURL(true, null, true);
}, 20, "stateChange", this, [])();
}
@ -808,81 +824,6 @@ class App {
this.loadURIParams();
}
/**
* Set element visibility
*
* @param {HTMLElement} elm
* @param {boolean} isVisible
*
*/
setElementVisibility(elm, isVisible) {
return isVisible ? elm.classList.remove("hidden") : elm.classList.add("hidden");
}
/**
* Set desktop UI ( on init and on window resize events )
*/
setDesktopUI() {
this.setCompileMessage();
this.setSplitter();
this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs();
}
/**
* Set mobile UI ( on init and on window resize events )
*/
setMobileUI() {
this.setSplitter(false);
this.assignAvailableHeight();
$("[data-toggle=tooltip]").tooltip("disable");
}
/**
* Due to variable available heights on mobile devices ( due to the
* address bar etc. ), we need to calculate the available space and
* set some heights programmatically based on the full view height,
* minus fixed height elements.
*
* Be mindful to update these fixed numbers accordingly in the stylesheets
* ( themes/_structure ) if you make changes to those elements' height.
*/
assignAvailableHeight() {
const bannerHeight = 40;
const controlsHeight = 50;
const operationsHeight = 80;
const remainingSpace = window.innerHeight - (bannerHeight+controlsHeight+operationsHeight - 1); // - 1 is accounting for a border
// equally divide among recipe, input and output
["recipe", "input", "output"].forEach((div) => {
document.getElementById(div).style.height = `${remainingSpace/3}px`;
});
// set the ops-dropdown height
document.getElementById("operations-dropdown").style.maxHeight = `${window.innerHeight - (bannerHeight+operationsHeight)}px`;
}
/**
* Build a CCategoryList component and append it to #categories
*/
buildCategoryList() {
// double-check if the c-category-list already exists,
if (document.querySelector("#categories > c-category-list")) {
// then destroy it
document.querySelector("#categories > c-category-list").remove();
}
const categoryList = new CCategoryList(
this,
this.categories,
this.operations,
true
);
document.querySelector("#categories").appendChild(categoryList);
}
}
export default App;

59
src/web/HTMLCategory.mjs Executable file
View File

@ -0,0 +1,59 @@
/**
* @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 {string}
*/
toHtml() {
const catName = "cat" + this.name.replace(/[\s/\-:_]/g, "");
let html = `<div class="panel category">
<a class="category-title" data-toggle="collapse" data-target="#${catName}">
${this.name}
</a>
<div id="${catName}" class="panel-collapse collapse ${(this.selected ? " show" : "")}" data-parent="#categories">
<ul class="op-list">`;
for (let i = 0; i < this.opList.length; i++) {
html += this.opList[i].toStubHtml();
}
html += "</ul></div></div>";
return html;
}
}
export default HTMLCategory;

View File

@ -8,9 +8,6 @@ import Utils from "../core/Utils.mjs";
/**
* Object to handle the creation of operation ingredients.
*
* Note: Not to be confused with the native web component c-recipe-li, which is the component that is the (parent)
* list item in recipe-list.
*/
class HTMLIngredient {
@ -131,21 +128,20 @@ class HTMLIngredient {
</div>`;
break;
case "boolean":
html += `<div class="form-group boolean-arg ing-flexible custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input arg"
id="${this.id}"
tabindex="${this.tabIndex}"
arg-name="${this.name}"
${this.value ? " checked" : ""}
${this.disabled ? " disabled" : ""}
value="${this.name}"/>
<label class="custom-control-label"
${this.hint && `data-toggle="tooltip" title="${this.hint}"`}
for="${this.id}">
${this.name}
html += `<div class="form-group inline boolean-arg ing-flexible">
<div class="checkbox">
<label ${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}>
<input type="checkbox"
class="arg"
id="${this.id}"
tabindex="${this.tabIndex}"
arg-name="${this.name}"
${this.value ? " checked" : ""}
${this.disabled ? " disabled" : ""}
value="${this.name}"> ${this.name}
</label>
</div>`;
</div>
</div>`;
break;
case "option":
html += `<div class="form-group ing-medium">
@ -323,7 +319,7 @@ class HTMLIngredient {
* Handler for populate option changes.
* Populates the relevant argument with the specified value.
*
* @param {Event} e
* @param {event} e
*/
populateOptionChange(e) {
e.preventDefault();
@ -347,7 +343,7 @@ class HTMLIngredient {
* Handler for populate multi option changes.
* Populates the relevant arguments with the specified values.
*
* @param {Event} e
* @param {event} e
*/
populateMultiOptionChange(e) {
e.preventDefault();
@ -378,7 +374,7 @@ class HTMLIngredient {
* Handler for editable option clicks.
* Populates the input box with the selected value.
*
* @param {Event} e
* @param {event} e
*/
editableOptionClick(e) {
e.preventDefault();
@ -399,7 +395,7 @@ class HTMLIngredient {
* Handler for argument selector changes.
* Shows or hides the relevant arguments for this operation.
*
* @param {Event} e
* @param {event} e
*/
argSelectorChange(e) {
e.preventDefault();

176
src/web/HTMLOperation.mjs Executable file
View File

@ -0,0 +1,176 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import HTMLIngredient from "./HTMLIngredient.mjs";
import Utils from "../core/Utils.mjs";
import url from "url";
/**
* Object to handle the creation of operations.
*/
class HTMLOperation {
/**
* HTMLOperation constructor.
*
* @param {string} name - The name of the operation.
* @param {Object} config - The configuration object for this operation.
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(name, config, app, manager) {
this.app = app;
this.manager = manager;
this.name = name;
this.description = config.description;
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 stub operation with no ingredients.
*
* @returns {string}
*/
toStubHtml(removeIcon) {
let html = "<li class='operation'";
if (this.description) {
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
html += ` data-container='body' data-toggle='popover' data-placement='right'
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
data-boundary='viewport'`;
}
html += ">" + this.name;
if (removeIcon) {
html += "<i class='material-icons remove-icon op-icon'>delete</i>";
}
html += "</li>";
return html;
}
/**
* Renders the operation in HTML as a full operation with ingredients.
*
* @returns {string}
*/
toFullHtml() {
let html = `<div class="op-title">${Utils.escapeHtml(this.name)}</div>
<div class="ingredients">`;
for (let i = 0; i < this.ingList.length; i++) {
html += this.ingList[i].toHtml();
}
html += `</div>
<div class="recip-icons">
<i class="material-icons breakpoint" title="Set breakpoint" break="false" data-help-title="Setting breakpoints" data-help="Setting a breakpoint on an operation will cause execution of the Recipe to pause when it reaches that operation.">pause</i>
<i class="material-icons disable-icon" title="Disable operation" disabled="false" data-help-title="Disabling operations" 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.">not_interested</i>
</div>
<div class="clearfix">&nbsp;</div>`;
return html;
}
/**
* Highlights searched strings in the name and description of the operation.
*
* @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]]
* @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]]
*/
highlightSearchStrings(nameIdxs, descIdxs) {
if (nameIdxs.length && typeof nameIdxs[0][0] === "number") {
let opName = "",
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>";
pos = start + length;
});
opName += this.name.slice(pos, this.name.length);
this.name = opName;
}
if (this.description && descIdxs.length && descIdxs[0][0] >= 0) {
// Find HTML tag offsets
const re = /<[^>]+>/g;
let match;
while ((match = re.exec(this.description))) {
// If the search string occurs within an HTML tag, return without highlighting it.
const inHTMLTag = descIdxs.reduce((acc, idxs) => {
const start = idxs[0];
return start >= match.index && start <= (match.index + match[0].length);
}, false);
if (inHTMLTag) return;
}
let desc = "",
pos = 0;
descIdxs.forEach(idxs => {
const [start, length] = idxs;
desc += this.description.slice(pos, start) + "<b><u>" +
this.description.slice(start, start + length) + "</u></b>";
pos = start + length;
});
desc += this.description.slice(pos, this.description.length);
this.description = desc;
}
}
}
/**
* Given a URL for a Wikipedia (or other wiki) page, this function returns a link to that page.
*
* @param {string} urlStr
* @returns {string}
*/
function titleFromWikiLink(urlStr) {
const urlObj = url.parse(urlStr);
let wikiName = "",
pageTitle = "";
switch (urlObj.host) {
case "forensicswiki.xyz":
wikiName = "Forensics Wiki";
pageTitle = urlObj.query.substr(6).replace(/_/g, " "); // Chop off 'title='
break;
case "wikipedia.org":
wikiName = "Wikipedia";
pageTitle = urlObj.pathname.substr(6).replace(/_/g, " "); // Chop off '/wiki/'
break;
default:
// Not a wiki link, return full URL
return `<a href='${urlStr}' target='_blank'>More Information<i class='material-icons inline-icon'>open_in_new</i></a>`;
}
return `<a href='${urlObj.href}' target='_blank'>${pageTitle}<i class='material-icons inline-icon'>open_in_new</i></a> on ${wikiName}`;
}
export default HTMLOperation;

View File

@ -50,19 +50,19 @@ 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
*/
this.statechange = new CustomEvent("statechange", {bubbles: true});
/**
* @event Manager#favouritesupdate
*/
this.favouritesupdate = new CustomEvent("favouritesupdate", {bubbles: true});
// Define Waiter objects to handle various areas
this.timing = new TimingWaiter(this.app, this);
this.worker = new WorkerWaiter(this.app, this);
this.window = new WindowWaiter(this.app, this);
this.window = new WindowWaiter(this.app);
this.controls = new ControlsWaiter(this.app, this);
this.recipe = new RecipeWaiter(this.app, this);
this.ops = new OperationsWaiter(this.app, this);
@ -89,7 +89,7 @@ class Manager {
this.input.setupInputWorker();
this.input.addInput(true);
this.worker.setupChefWorker();
this.recipe.initDragAndDrop();
this.recipe.initialiseOperationDragNDrop();
this.controls.initComponents();
this.controls.autoBakeChange();
this.bindings.updateKeybList();
@ -141,29 +141,31 @@ class Manager {
document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls));
document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls));
this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls);
document.getElementById("rec-list").addEventListener("click", this.controls.onMaximisedRecipeClick.bind(this.controls));
// A note for the Maximise Controls listeners below: click events via addDynamicListener don't properly bubble and the hit-box is unacceptably tiny, hence this solution
document.getElementById("maximise-recipe").addEventListener("click", this.controls.onMaximiseButtonClick.bind(this.controls));
document.getElementById("maximise-input").addEventListener("click", this.controls.onMaximiseButtonClick.bind(this.controls));
document.getElementById("maximise-output").addEventListener("click", this.controls.onMaximiseButtonClick.bind(this.controls));
// Operations
this.addMultiEventListener("#search", "keyup paste click", this.ops.searchOperations, this.ops);
document.getElementById("close-ops-dropdown-icon").addEventListener("click", this.ops.closeOpsDropdown.bind(this.ops));
this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops);
this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, 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));
this.addDynamicListener("c-operation-li", "operationadd", this.recipe.opAdd, this.recipe);
this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);
// Recipe
this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe);
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("#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);
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
document.getElementById("reset-layout").addEventListener("click", this.app.setDesktopUI.bind(this.app));
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input);
this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input);
document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input));
@ -196,6 +198,7 @@ class Manager {
document.getElementById("save-all-to-file").addEventListener("click", this.output.saveAllClick.bind(this.output));
document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output));
document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output));
document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output));
document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output));
this.addDynamicListener(".extract-file,.extract-file i", "click", this.output.extractFileClick, this.output);
this.addDynamicListener("#output-tabs-wrapper #output-tabs li .output-tab-content", "click", this.output.changeTabClick, this.output);
@ -286,7 +289,7 @@ class Manager {
* @param {Object} [scope=this] - The object to bind to the callback function
*
* @example
* // Calls the save function whenever the keyup or paste events are triggered on any element
* // Calls the save function whenever the the keyup or paste events are triggered on any element
* // with the .saveable class
* this.addMultiEventListener(".saveable", "keyup paste", this.save, this);
*/

View File

@ -1,175 +0,0 @@
import {COperationList} from "./c-operation-list.mjs";
/**
* c(ustom element)-category-li ( list item )
*/
export class CCategoryLi extends HTMLElement {
/**
* @param {App} app - The main view object for CyberChef
* @param {CatConf} category - The category and operations to be populated.
* @param {OpConfig[]} operations - The list of operation configuration objects.
* @param {Boolean} isExpanded - expand the category by default on init or not
* @param {Boolean} includeOpLiStarIcon - Include the left side 'star' icon to each of the c-category-li >
* c-operation-list > c-operation-li list items in this category
*/
constructor(
app,
category,
operations,
isExpanded,
includeOpLiStarIcon,
) {
super();
this.app = app;
this.category = category; // contains a string[] of operation names under .ops
this.operations = operations; // opConfig[]
this.label = category.name;
this.isExpanded = isExpanded;
this.includeOpLiStarIcon = includeOpLiStarIcon;
this.build();
this.addEventListener("click", this.handleClick.bind(this));
}
/**
* Remove listeners on disconnectedCallback
*/
disconnectedCallback() {
this.removeEventListener("click", this.handleClick.bind(this));
}
/**
* Handle click
*
* @param {Event} e
*/
handleClick(e) {
if (e.target === this.querySelector("button") || e.target === this.querySelector("button > i")) {
e.stopPropagation(); // stop the event from propagating to the collapsable panel
this.app.manager.ops.editFavouritesClick(e);
}
}
/**
* Build c-category-li containing a nested c-operation-list ( op-list )
*
* @returns {HTMLElement}
*/
build() {
const li = this.buildListItem();
const a = this.buildAnchor();
const div = this.buildCollapsablePanel();
li.appendChild(a);
li.appendChild(div);
this.appendChild(li);
const opList = new COperationList(
this.app,
this.category.ops.map(op => [op]),
this.includeOpLiStarIcon,
false,
true
);
div.appendChild(opList);
}
/**
* Build the li element
*
* @returns {HTMLElement}
*/
buildListItem() {
const li = document.createElement("li");
li.classList.add("category");
return li;
}
/**
* Build the anchor element
*
* @returns {HTMLElement}
*/
buildAnchor() {
const a = document.createElement("a");
a.classList.add("category-title");
a.setAttribute("data-toggle", "collapse");
a.setAttribute("data-target", `#${"cat" + this.label.replace(/[\s/\-:_]/g, "")}`);
a.innerText = this.label;
if (this.label === "Favourites") {
const editFavouritesButton = this.buildEditFavouritesButton(a);
// Note: I'm leaving this here as it was in the code originally, but it's not doing anything and it didn't
// do anything before my refactoring. I imagine we may want to fix that at some point though,
// hence I'm leaving this here.
a.setAttribute("data-help-title", "Favourite operations");
a.setAttribute("data-help", `<p>This category displays your favourite operations.</p>
<ul>
<li><b>To add:</b> Click on the star icon of an operation or drag an operation over the Favourites category on desktop devices</li>
<li><b>To reorder:</b> Click on the 'Edit favourites' button and drag operations up and down in the list provided</li>
<li><b>To remove:</b> Click on the 'Edit favourites' button and hit the delete button next to the operation you want to remove</li>
</ul>`);
a.appendChild(editFavouritesButton);
}
return a;
}
/**
* Build the collapsable panel that contains the op-list for this category
*
* @returns {HTMLElement}
*/
buildCollapsablePanel() {
const div = document.createElement("div");
div.setAttribute("id", `${"cat" + this.label.replace(/[\s/\-:_]/g, "")}`);
div.setAttribute("data-parent", "#categories");
div.classList.add("panel-collapse");
div.classList.add("collapse");
if (this.isExpanded) {
div.classList.add("show");
}
return div;
}
/**
* If this category is Favourites, build and return the star icon button
*
* @returns {HTMLElement}
*/
buildEditFavouritesButton() {
const button = document.createElement("button");
const icon = document.createElement("i");
button.setAttribute("id", "edit-favourites");
button.setAttribute("type", "button");
button.setAttribute("data-toggle", "tooltip");
button.setAttribute("title", "Edit favourites");
button.classList.add("btn");
button.classList.add("btn-warning");
button.classList.add("bmd-btn-icon");
icon.classList.add("material-icons");
icon.innerText = "star";
button.appendChild(icon);
return button;
}
}
customElements.define("c-category-li", CCategoryLi);

View File

@ -1,53 +0,0 @@
import {CCategoryLi} from "./c-category-li.mjs";
/**
* c(ustom element)-category-list
*/
export class CCategoryList extends HTMLElement {
/**
* @param {App} app - The main view object for CyberChef
* @param {CatConf[]} categories - The list of categories and operations to be populated.
* @param {OpConfig[]} operations - A list of operation configuration objects.
* @param {Boolean} includeOpLiStarIcon - Include the left side 'star' icon to each of the c-category-li >
* c-operation-list > c-operation-li list items in this c-category-list
*/
constructor(
app,
categories,
operations,
includeOpLiStarIcon
) {
super();
this.app = app;
this.categories = categories;
this.operations = operations;
this.includeOpLiStarIcon = includeOpLiStarIcon;
this.build();
}
/**
* Build c-category-list
*
* @returns {HTMLElement}
*/
build() {
const ul = document.createElement("ul");
this.categories.forEach((category, index) => {
const cat = new CCategoryLi(
this.app,
category,
this.operations,
index === 0,
this.includeOpLiStarIcon
);
ul.appendChild(cat);
});
this.append(ul);
}
}
customElements.define("c-category-list", CCategoryList);

View File

@ -1,311 +0,0 @@
import url from "url";
/**
* c(ustom element)-operation-li ( list item )
*/
export class COperationLi extends HTMLElement {
/**
* @param {App} app - The main view object for CyberChef
* @param {string} name - The name of the operation
* @param {Object} icon - { class: string, innerText: string } - The optional and customizable icon displayed on the
* right side of the operation
* @param {Boolean} includeStarIcon - Include the left-side 'star' icon to favourite an operation
* @param {[number[]]} charIndicesToHighlight - optional array of indices that indicate characters to highlight (bold)
* in the operation name, for instance when the user searches for an operation by typing
*/
constructor(
app,
name,
icon,
includeStarIcon,
charIndicesToHighlight = []
) {
super();
this.app = app;
this.operationName = name;
this.icon = icon;
this.includeStarIcon = includeStarIcon;
this.charIndicesToHighlight = charIndicesToHighlight;
this.config = this.app.operations[name];
this.isFavourite = this.app.isLocalStorageAvailable() && JSON.parse(localStorage.favourites).indexOf(name) >= 0;
this.build();
// Use mousedown event instead of click to prevent accidentally firing the handler twice on mobile
this.addEventListener("mousedown", this.handleMousedown.bind(this));
this.addEventListener("dblclick", this.handleDoubleClick.bind(this));
if (this.includeStarIcon) {
this.observer = new MutationObserver(this.updateFavourite.bind(this));
this.observer.observe(this.querySelector("li"), { attributes: true });
}
}
/**
* Remove listeners on disconnectedCallback
*/
disconnectedCallback() {
this.removeEventListener("mousedown", this.handleMousedown.bind(this));
this.removeEventListener("dblclick", this.handleDoubleClick.bind(this));
if (this.includeStarIcon) {
this.observer.disconnect();
}
$(this).find("[data-toggle=popover]").popover("dispose").popover("hide");
}
/**
* Handle double click
*
* @param {Event} e
*/
handleDoubleClick(e) {
// this span is element holding the operation title
if (e.target === this.querySelector("li") || e.target === this.querySelector("span")) {
this.app.manager.recipe.addOperation(this.operationName);
}
}
/**
* Handle mousedown
*
* @param {Event} e
*/
handleMousedown(e) {
if (e.target === this.querySelector("i.star-icon")) {
this.app.addFavourite(this.operationName);
}
// current use case: in the 'Edit favourites' modal, the c-operation-li components have a trashcan icon to the
// right
if (e.target === this.querySelector("i.remove-icon")) {
this.remove();
}
}
/**
* Disable or enable popover for an element
*
* @param {HTMLElement} el
*/
handlePopover(el) {
// never display popovers on mobile on this component
if (this.app.isMobileView()) {
$(el).popover("disable");
} else {
$(el).popover("enable");
this.setPopover(el);
}
}
/**
* Build c-operation-li
*
* @returns {HTMLElement}
*/
build() {
const li = this.buildListItem();
const icon = this.buildIcon();
if (this.includeStarIcon) {
const starIcon = this.buildStarIcon();
li.appendChild(starIcon);
}
li.appendChild(icon);
this.appendChild(li);
this.handlePopover(li);
}
/**
* Set the target operation popover itself to gain focus which
* enables scrolling and other interactions.
*
* @param {HTMLElement} el - The element to start selecting from
*/
setPopover(el) {
$(this)
.find("[data-toggle=popover]")
.addBack("[data-toggle=popover]")
.popover({trigger: "manual"})
.on("mouseenter", function(e) {
if (e.buttons > 0) return; // Mouse button held down - likely dragging an operation
$(el).popover("show");
$(".popover").on("mouseleave", function () {
$(el).popover("hide");
});
})
.on("mouseleave", function () {
setTimeout(function() {
// Determine if the popover associated with this element is being hovered over
if ($(el).data("bs.popover") && ($(el).data("bs.popover").tip && !$($(el).data("bs.popover").tip).is(":hover"))) {
$(el).popover("hide");
}
}, 50);
});
}
/**
* Given a URL for a Wikipedia (or other wiki) page, this function returns a link to that page.
*
* @param {string} urlStr
* @returns {string}
*/
titleFromWikiLink(urlStr) {
const urlObj = url.parse(urlStr);
let wikiName = "",
pageTitle = "";
switch (urlObj.host) {
case "forensicswiki.xyz":
wikiName = "Forensics Wiki";
pageTitle = urlObj.query.substr(6).replace(/_/g, " "); // Chop off 'title='
break;
case "wikipedia.org":
wikiName = "Wikipedia";
pageTitle = urlObj.pathname.substr(6).replace(/_/g, " "); // Chop off '/wiki/'
break;
default:
// Not a wiki link, return full URL
return `<a href='${urlStr}' target='_blank'>More Information<i class='material-icons inline-icon'>open_in_new</i></a>`;
}
return `<a href='${urlObj.href}' target='_blank'>${pageTitle}<i class='material-icons inline-icon'>open_in_new</i></a> on ${wikiName}`;
}
/**
* Build the li element
*
* @returns {HTMLElement}
*/
buildListItem() {
const li = document.createElement("li");
li.appendChild(this.buildOperationName());
li.setAttribute("data-name", this.operationName);
li.classList.add("operation");
if (this.isFavourite) {
li.classList.add("favourite");
}
if (this.config.description) {
let dataContent = this.config.description;
if (this.config.infoURL) {
dataContent += `<hr>${this.titleFromWikiLink(this.config.infoURL)}`;
}
li.setAttribute("data-container", "body");
li.setAttribute("data-toggle", "popover");
li.setAttribute("data-html", "true");
li.setAttribute("data-boundary", "viewport");
li.setAttribute("data-content", dataContent);
}
return li;
}
/**
* Build the operation list item right side icon
*
* @returns {HTMLElement}
*/
buildIcon() {
const icon = document.createElement("i");
icon.classList.add("material-icons");
icon.classList.add("op-icon");
icon.classList.add(this.icon.class);
icon.innerText = this.icon.innerText;
return icon;
}
/**
* Build the ( optional ) star icon
*
* @returns {HTMLElement}
*/
buildStarIcon() {
const icon = document.createElement("i");
icon.setAttribute("title", this.operationName);
icon.classList.add("material-icons");
icon.classList.add("op-icon");
icon.classList.add("star-icon");
if (this.isFavourite) {
icon.innerText = "star";
} else {
icon.innerText = "star_outline";
}
return icon;
}
/**
* Update fav icon and 'fav' class on this li
*/
updateFavourite() {
if (this.querySelector("li").classList.contains("favourite")) {
this.querySelector("i.star-icon").innerText = "star";
} else {
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. This function
* is needed for the drag and drop functionality of the Sortable lists
*/
cloneNode() {
const { app, operationName, icon, includeStarIcon, charIndicesToHighlight } = this;
return new COperationLi(app, operationName, icon, includeStarIcon, charIndicesToHighlight);
}
/**
* Highlights searched strings ( if applicable ) in the name and description of the operation
* or simply sets the name in the span element
*
* @returns {HTMLElement}
*/
buildOperationName() {
const span = document.createElement("span");
if (this.charIndicesToHighlight.length) {
let opName = "",
pos = 0;
this.charIndicesToHighlight.forEach(charIndices => {
const [start, length] = charIndices;
if (typeof start !== "number") return;
opName +=
"<span>" + this.operationName.slice(pos, start) + "</span>" + "<strong>" +
this.operationName.slice(start, start + length) + "</strong>";
pos = start + length;
});
opName += "<span>" + this.operationName.slice(pos, this.operationName.length) + "</span>";
const parser = new DOMParser();
opName = parser.parseFromString(opName, "text/html");
span.append(...opName.body.children);
} else {
span.innerText = this.operationName;
}
return span;
}
}
customElements.define("c-operation-li", COperationLi);

View File

@ -1,203 +0,0 @@
import {COperationLi} from "./c-operation-li.mjs";
import Sortable from "sortablejs";
/**
* c(ustom element)-operation-list
*/
export class COperationList extends HTMLElement {
/**
* @param {App} app - The main view object for CyberChef
* @param {[string, number[]]} operations - A list of operation names and indexes of characters to highlight
* @param {Boolean} includeStarIcon - Include the left side 'star' icon to each of the c-category-li >
* c-operation-list > c-operation-li list items in this c-category-list
* @param {Boolean} isSortable - List items may be sorted ( reordered ). False by default
* @param {Boolean} isCloneable - List items are cloneable to a target list. True by default
* @param {Object} icon ( { class: string, innerText: string } ). 'check-icon' by default
*/
constructor(
app,
operations,
includeStarIcon,
isSortable = false,
isCloneable = true,
icon
) {
super();
this.app = app;
this.operations = operations;
this.includeStarIcon = includeStarIcon;
this.isSortable = isSortable;
this.isCloneable = isCloneable;
this.icon = icon;
this.build();
window.addEventListener("operationadd", this.handleChange.bind(this));
window.addEventListener("operationremove", this.handleChange.bind(this));
window.addEventListener("favouritesupdate", this.handleChange.bind(this));
}
/**
* Remove listeners on disconnectedCallback
*/
disconnectedCallback() {
this.removeEventListener("operationadd", this.handleChange.bind(this));
this.removeEventListener("operationremove", this.handleChange.bind(this));
this.removeEventListener("favouritesupdate", this.handleChange.bind(this));
}
/**
* Handle change
* Fires on custom operationadd, operationremove, favouritesupdate events
*/
handleChange() {
this.updateListItemsClasses("#catFavourites c-operation-list ul", "favourite");
this.updateListItemsClasses("#rec-list", "selected");
}
/**
* Build c-operation-list
*
* @returns {HTMLElement}
*/
build() {
const ul = document.createElement("ul");
ul.classList.add("op-list");
this.operations.forEach((([opName, charIndicesToHighlight]) => {
const cOpLi = new COperationLi(
this.app,
opName,
{
class: this.icon ? this.icon.class : "check-icon",
innerText: this.icon ? this.icon.innerText : "check"
},
this.includeStarIcon,
charIndicesToHighlight
);
ul.appendChild(cOpLi);
}));
if (this.isSortable) {
this.createSortableList(ul);
} else if (!this.app.isMobileView() && this.isCloneable) {
this.createCloneableList(ul, "recipe", "rec-list");
}
this.append(ul);
}
/**
* Create a sortable ( but not cloneable ) list
*
* @param { HTMLElement } ul
* */
createSortableList(ul) {
const sortableList = Sortable.create(ul, {
group: "sorting",
sort: true,
draggable: "c-operation-li",
filter: "i.material-icons",
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) {
let dragOverRecList = false;
const recList = document.querySelector(`#${targetListId}`);
Sortable.utils.on(recList, "dragover", function () {
dragOverRecList = true;
});
Sortable.utils.on(recList, "dragleave", function () {
dragOverRecList = false;
});
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)
.find("[data-toggle=popover]")
.popover("dispose");
$(e.clone)
.find("[data-toggle=popover]")
.off(".popover")
.removeData("bs.popover");
},
onEnd: ({item, to, newIndex }) => {
if (item.parentNode.id === targetListId && dragOverRecList) {
this.app.manager.recipe.addOperation(item.name, newIndex);
item.remove();
} else if (!dragOverRecList && !to.classList.contains("op-list")) {
item.remove();
}
}
});
}
/**
* Update classes ( className ) on the li.operation elements in this list, based on the current state of a
* list of choice ( srcListSelector )
*
* @param {string} srcListSelector - the selector of the UL that we want to use as source of truth
* @param {string} className - the className to update
*/
updateListItemsClasses(srcListSelector, className) {
const srcListItems= document.querySelectorAll(`${srcListSelector} li`);
const listItems = this.querySelectorAll("c-operation-li li.operation");
listItems.forEach((li => {
if (li.classList.contains(`${className}`)) {
li.classList.remove(`${className}`);
}
}));
if (srcListItems.length !== 0) {
srcListItems.forEach((item => {
const targetDataName = item.getAttribute("data-name");
listItems.forEach((listItem) => {
if (targetDataName === listItem.getAttribute("data-name")) {
listItem.classList.add(`${className}`);
}
});
}));
}
}
}
customElements.define("c-operation-list", COperationList);

View File

@ -1,201 +0,0 @@
import HTMLIngredient from "../HTMLIngredient.mjs";
/**
* c(ustom element)-recipe-li ( list item ).
*
* Note: This is the #recipe-list list item component, not to be confused with HTMLIngredient which make up the smaller
* components of this list item. It would be good to eventually fuse that code into this component or alternatively, to
* turn that into a separate native web component .
*/
export class CRecipeLi extends HTMLElement {
/**
* @param {App} app - The main view object for CyberChef
* @param {string} name - The operation name
* @param {object[]} args - The args properties of the operation ( see operation config file )
*/
constructor(
app,
name,
args = []
) {
super();
this.app = app;
this.name = name;
this.args = args;
this.ingredients = [];
this.flowControl = this.app.operations[this.name].flowControl;
this.manualBake = this.app.operations[this.name].manualBake;
for (let i = 0; i < args.length; i++) {
const ing = new HTMLIngredient(args[i], this.app, this.app.manager);
this.ingredients.push(ing);
}
this.build();
// Use mousedown event instead of click to prevent accidentally firing the handler twice on mobile
this.addEventListener("mousedown", this.handleMousedown.bind(this));
this.addEventListener("dblclick", this.handleDoubleClick.bind(this));
}
/**
* Remove listeners on disconnectedCallback
*/
disconnectedCallback() {
this.removeEventListener("mousedown", this.handleMousedown.bind(this));
this.removeEventListener("dblclick", this.handleDoubleClick.bind(this));
}
/**
* Handle double click
*
* @param {Event} e
*/
handleDoubleClick(e) {
// do not remove if icons or form elements are double-clicked
if (e.target === this.querySelector("li") || e.target === this.querySelector("div.op-title")) {
this.removeOperation();
}
}
/**
* Handle mousedown
* @fires Manager#statechange
* @param {Event} e
*/
handleMousedown(e) {
const disableIcon = this.querySelector("i.disable-icon");
const breakpointIcon = this.querySelector("i.breakpoint-icon");
// handle click on 'disable-icon'
if (e.target === disableIcon) {
if (disableIcon.getAttribute("disabled") === "false") {
disableIcon.setAttribute("disabled", "true");
disableIcon.classList.add("disable-icon-selected");
this.querySelector("li.operation").classList.add("disabled");
} else {
disableIcon.setAttribute("disabled", "false");
disableIcon.classList.remove("disable-icon-selected");
this.querySelector("li.operation").classList.remove("disabled");
}
this.app.progress = 0;
window.dispatchEvent(this.app.manager.statechange);
}
// handle click on 'breakpoint-icon'
if (e.target === breakpointIcon) {
if (breakpointIcon.getAttribute("break") === "false") {
breakpointIcon.setAttribute("break", "true");
breakpointIcon.classList.add("breakpoint-selected");
} else {
breakpointIcon.setAttribute("break", "false");
breakpointIcon.classList.remove("breakpoint-selected");
}
window.dispatchEvent(this.app.manager.statechange);
}
}
/**
* Remove this operation from the recipe list
*
* @fires Manager#statechange
*/
removeOperation() {
this.remove();
log.debug("Operation removed from recipe");
window.dispatchEvent(this.app.manager.statechange);
window.dispatchEvent(this.app.manager.operationremove);
}
/**
* Build the ingredient list item
*
* @returns {HTMLElement}
*/
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.ingredients.length; i++) {
ingredientDiv.innerHTML += (this.ingredients[i].toHtml());
}
const icons = this.buildIcons();
const clearfixDiv = document.createElement("div");
if (this.flowControl) {
li.classList.add("flow-control-op");
}
if (this.manualBake && this.app.autoBake_) {
this.manager.controls.setAutoBake(false);
this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000);
}
li.appendChild(icons);
li.appendChild(clearfixDiv);
this.appendChild(li);
}
/**
* Build the icons ( disable and breakpoint / pause )
*
* @returns {HTMLElement}
*/
buildIcons() {
const div = document.createElement("div");
div.classList.add("recipe-icons");
const breakPointIcon = document.createElement("i");
breakPointIcon.classList.add("material-icons");
breakPointIcon.classList.add("breakpoint-icon");
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";
div.appendChild(breakPointIcon);
div.appendChild(disableIcon);
return div;
}
/**
* Override native cloneNode method so we can clone c-recipe-li properly
* with constructor arguments for sortable and cloneable lists. This function
* is needed for the drag and drop functionality of the Sortable lists
*/
cloneNode() {
const { app, name, args } = this;
return new CRecipeLi(app, name, args);
}
}
customElements.define("c-recipe-li", CRecipeLi);

View File

@ -31,7 +31,6 @@
<link rel="icon" type="image/ico" href="<%- require('../static/images/favicon.ico') %>" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script type="application/javascript">
"use strict";
@ -142,15 +141,15 @@
<div id="preloader-error" class="loading-error"></div>
</div>
<!-- End preloader overlay -->
<button type="button" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
<i class="material-icons">star</i>
</button>
<div id="content-wrapper">
<div id="banner">
<div>
<a href="#" class="banner-link" data-toggle="modal" data-target="#download-modal" data-help-title="Downloading CyberChef" data-help="<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>">
<span>Download CyberChef</span>
<i class="material-icons">file_download</i>
</a>
<div id="banner" class="row">
<div class="col" style="text-align: left; padding-left: 10px;">
<a href="#" data-toggle="modal" data-target="#download-modal" data-help-title="Downloading CyberChef" data-help="<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>">Download CyberChef <i class="material-icons">file_download</i></a>
</div>
<div id="notice-wrapper" class="desktop-only">
<div class="col-md-6" id="notice-wrapper">
<span id="notice">
<script type="text/javascript">
// Must be text/javascript rather than application/javascript otherwise IE won't recognise it...
@ -162,62 +161,25 @@
<noscript>JavaScript is not enabled. Good luck.</noscript>
</span>
</div>
<div>
<a href="#"
id="options"
class="banner-link"
data-help-title="Options and Settings"
data-help="Configurable options to change how CyberChef behaves. These settings are stored in your browser's local storage, meaning they will persist between sessions that use the same browser profile.">
<span class="desktop-only">Options</span>
<i class="material-icons">settings</i>
</a>
<a href="#"
id="support"
class="banner-link"
data-toggle="modal"
data-target="#support-modal"
data-help-title="About / Support"
data-help="This pane provides information about the CyberChef web app, how to use some of the features, and how to raise bug reports.">
<span class="desktop-only">About / Support</span>
<i class="material-icons">help</i>
</a>
<div class="col" style="text-align: right; padding-right: 0;">
<a href="#" id="options" data-help-title="Options and Settings" data-help="Configurable options to change how CyberChef behaves. These settings are stored in your browser's local storage, meaning they will persist between sessions that use the same browser profile.">Options <i class="material-icons">settings</i></a>
<a href="#" id="support" data-toggle="modal" data-target="#support-modal" data-help-title="About / Support" data-help="This pane provides information about the CyberChef web app, how to use some of the features, and how to raise bug reports.">About / Support <i class="material-icons">help</i></a>
</div>
</div>
<div id="workspace-wrapper">
<div id="operations" class="split split-horizontal no-select">
<div class="title no-select"
data-help-title="Operations list"
data-help="<p>The Operations list contains all the operations in CyberChef arranged into categories. Some operations may be present in multiple categories. You can search for operations using the search box.</p><p>To use an operation, either double click it, or drag it into the Recipe pane. You will then be able to configure its arguments (or 'Ingredients' in CyberChef terminology).</p>">
<div class="title no-select" data-help-title="Operations list" data-help="<p>The Operations list contains all the operations in CyberChef arranged into categories. Some operations may be present in multiple categories. You can search for operations using the search box.</p><p>To use an operation, either double click it, or drag it into the Recipe pane. You will then be able to configure its arguments (or 'Ingredients' in CyberChef terminology).</p>">
Operations
<span class="pane-controls">
<button type="button"
class="btn bmd-btn-icon mobile-only hidden"
id="close-ops-dropdown-icon"
title="Close dropdown">
<i class="material-icons">close</i>
</button>
</span>
</div>
<div id="operations-wrapper">
<input id="search"
type="text"
class="form-control"
placeholder="Search..."
autocomplete="off"
tabindex="0"
data-help-title="Searching for operations"
data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>"
/>
<div id="operations-dropdown">
<div id="search-results" class="hidden" tabindex="0"></div>
<div id="categories" class="panel-group no-select hidden"></div>
</div>
</div>
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
<ul id="search-results" class="op-list"></ul>
<div id="categories" class="panel-group no-select"></div>
</div>
<div id="recipe" class="split split-horizontal no-select" data-help-title="Recipe pane" data-help="<p>The Recipe pane is where your chosen Operations are configured. If you are a programmer, think of these as functions. If you are not a programmer, these are like steps in a cake recipe. The Input data will be processed based on the Operations in your Recipe.</p><ul><li>To reorder, simply drag and drop the Operations into the order your require</li><li>To remove an operation, either double click it, or drag it outside of the Recipe pane</li></ul><p>The arguments (or 'Ingredients' in CyberChef terminology) can be configured to change how an Operation processes the data.</p>">
<div class="title no-select">
Recipe
<span class="pane-controls">
<span class="pane-controls hide-on-maximised-output">
<button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe" data-help-title="Saving a recipe" data-help="<p>Recipes can be represented in a few different formats and saved for use at a later date. You can either copy the Recipe configuration and save it somewhere offline for later use, or use your browser's local storage.</p><ul><li><b>Deep link:</b> The easiest way to share a CyberChef Recipe is to copy the deep link, either from the address bar (which is updated as the Recipe or Input changes), or from the 'Save recipe' pane. When you visit this link, the Recipe and Input should be populated from where you left off.</li><li><b>Chef format:</b> This custom format is designed to be compact and easily readable. It is the format used in CyberChef's URL, so it largely uses characters that do not have to be escaped in URL encoding, making it a little easier to understand what a CyberChef URL contains.</li><li><b>Clean JSON:</b> This JSON format uses whitespace and indentation in a way that makes the Recipe easy to read.</li><li><b>Compact JSON:</b> This is the most compact way that the Recipe can be represented in JSON.</li><li><b>Local storage:</b> Alternatively, you can enter a name into the 'Recipe name' field and save to your browser's local storage. The Recipe will then be available to load from the 'Load Recipe' pane as long as you are using the same browser profile. Be aware that if your browser profile is cleaned, you may lose this data.</li></ul>">
<i class="material-icons">save</i>
</button>
@ -227,45 +189,40 @@
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-recipe" data-toggle="tooltip" title="Clear recipe" data-help-title="Clearing a recipe" data-help="Clicking the 'Clear recipe' button will remove all operations from the Recipe. It will not clear the Input, but it will trigger a Bake if Auto-bake is turned on, which will change the value of the Output.">
<i class="material-icons">delete</i>
</button>
<button type="button" class="mobile-only btn btn-primary bmd-btn-icon btn-maximise" id="maximise-recipe" data-toggle="tooltip" title="Maximise pane" data-help-title="Maximise pane" data-help="This button allows you to view the Recipe pane at maximum size, hiding the Operations, Input and Output panes. You can restore the pane to its normal size by clicking the same button again.">
<i class="material-icons">fullscreen</i>
</button>
</span>
</div>
<ul id="rec-list" class="list-area no-select"></ul>
<div id="controls" class="no-select">
<div id="controls" class="no-select hide-on-maximised-output">
<div id="controls-content">
<div class="col-3">
<button type="button" class="btn btn-block btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe" data-help-title="Stepping through the Recipe" data-help="<p>The Step button allows you to execute one operation at a time, rather than running the whole Recipe from beginning to end.</p><p>Step allows you to inspect the data at each stage of the Recipe and understand what is being passed to the next operation.</p>">
Step
</button>
</div>
<div class="col-6">
<button type="button" class="btn btn-success btn-raised btn-block" id="bake" data-help-title="Baking" data-help="<p>Baking causes CyberChef to run the Recipe against your data. This involves three steps:</p><ol><li>The data in the Input is encoded into bytes using the character encoding selected in the Input status bar.</li><li>The data is run through each of the operations in the Recipe in turn with the output of one operation being fed into the next operation as its input.</li><li>The outcome of the final operation in the Recipe is decoded into Output text using the character encoding selected in the Output status bar.</li></ol><p>If there are multiple Inputs, the Bake button causes every Input to be baked simultaneously.</p>">
<img aria-hidden="true" class="desktop-only" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
<span>Bake!</span>
</button>
</div>
<div class="col-3">
<div class="form-group" style="display: contents;">
<div class="checkbox" data-help-title="Auto-bake" data-help="<p>When Auto-bake is turned on, CyberChef will bake the Input using the Recipe whenever anything in the Input or Recipe changes.</p>This includes:<ul><li>Adding or removing operations</li><li>Modifying operation arguments</li><li>Editing the Input</li><li>Changing the Input character encoding</li></ul><p>If there are multiple inputs, only the currently active tab will be baked when Auto-bake triggers. You can bake all inputs manually using the Bake button.</p>">
<label id="auto-bake-label">
<input type="checkbox" checked="checked" id="auto-bake">
<small>Auto</small>
</label>
</div>
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe" data-help-title="Stepping through the Recipe" data-help="<p>The Step button allows you to execute one operation at a time, rather than running the whole Recipe from beginning to end.</p><p>Step allows you to inspect the data at each stage of the Recipe and understand what is being passed to the next operation.</p>">
Step
</button>
<button type="button" class="mx-2 btn btn-lg btn-success btn-raised btn-block" id="bake" data-help-title="Baking" data-help="<p>Baking causes CyberChef to run the Recipe against your data. This involves three steps:</p><ol><li>The data in the Input is encoded into bytes using the character encoding selected in the Input status bar.</li><li>The data is run through each of the operations in the Recipe in turn with the output of one operation being fed into the next operation as its input.</li><li>The outcome of the final operation in the Recipe is decoded into Output text using the character encoding selected in the Output status bar.</li></ol><p>If there are multiple Inputs, the Bake button causes every Input to be baked simultaneously.</p>">
<img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
<span>Bake!</span>
</button>
<div class="form-group" style="display: contents;">
<div class="mx-1 checkbox" data-help-title="Auto-bake" data-help="<p>When Auto-bake is turned on, CyberChef will bake the Input using the Recipe whenever anything in the Input or Recipe changes.</p>This includes:<ul><li>Adding or removing operations</li><li>Modifying operation arguments</li><li>Editing the Input</li><li>Changing the Input character encoding</li></ul><p>If there are multiple inputs, only the currently active tab will be baked when Auto-bake triggers. You can bake all inputs manually using the Bake button.</p>">
<label id="auto-bake-label">
<input type="checkbox" checked="checked" id="auto-bake">
<br>Auto Bake
</label>
</div>
</div>
</div>
</div>
</div>
<div id="IO" class="split split-horizontal">
<div class="split split-horizontal" id="IO">
<div id="input" class="split no-select" data-help-title="Input pane" data-help="<p>Input data can be entered by typing it in, pasting it in, dragging it in, or using the 'Load file' or 'Load folder' buttons.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p>">
<div class="title no-select">
Input
<label for="input-text">Input</label>
<span class="pane-controls">
<div class="io-info" id="input-files-info"></div>
<button type="button" class="desktop-only btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab" data-help-title="Tabs" data-help="<p>New tabs can be created to support multiple Inputs. These tabs have their own associated character encodings and EOL separators, as defined in their status bars.</p><p>The deep link in the URL bar only contains information about the currently active tab.</p>">
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab" data-help-title="Tabs" data-help="<p>New tabs can be created to support multiple Inputs. These tabs have their own associated character encodings and EOL separators, as defined in their status bars.</p><p>The deep link in the URL bar only contains information about the currently active tab.</p>">
<i class="material-icons">add</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-open-folder" data-toggle="tooltip" title="Open folder as input" data-help-title="Opening a folder" data-help="<p>You can open a whole folder into CyberChef, which will result in each file being loaded into a separate Input tab.</p><p>CyberChef can handle lots of Input files, but be aware that performance may suffer, especially if the files are large in size.</p><p>Folders can also be loaded by dragging them over the Input pane and dropping them.</p>">
@ -279,10 +236,7 @@
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output" data-help-title="Clearing the Input and Output" data-help="Clicking the 'Clear input and output' button will remove all Inputs and Outputs. It will not clear the Recipe.">
<i class="material-icons">delete</i>
</button>
<button type="button" class="mobile-only btn btn-primary bmd-btn-icon btn-maximise" id="maximise-input" data-toggle="tooltip" title="Maximise pane" data-help-title="Maximise pane" data-help="This button allows you to view the Input pane at maximum size, hiding the Operations, Recipe and Output panes. You can restore the pane to its normal size by clicking the same button again.">
<i class="material-icons">fullscreen</i>
</button>
<button type="button" class="desktop-only btn btn-primary bmd-btn-icon" id="reset-layout" data-toggle="tooltip" title="Reset pane layout" data-help-title="Resetting the pane layout" data-help="CyberChef's panes can be resized to suit your area of focus. This button will reset the pane sizes to their default configuration.">
<button type="button" class="btn btn-primary bmd-btn-icon" id="reset-layout" data-toggle="tooltip" title="Reset pane layout" data-help-title="Resetting the pane layout" data-help="CyberChef's panes can be resized to suit your area of focus. This button will reset the pane sizes to their default configuration.">
<i class="material-icons">view_compact</i>
</button>
</span>
@ -316,37 +270,37 @@
<div id="input-text"></div>
</div>
</div>
<div id="output" class="split" data-help-title="Output pane" data-help="<p>This pane displays the results of the Recipe after it has processed your Input.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p><p>When copying these characters from the Output, the original byte value should be copied into your clipboard, rather than the control character picture itself.</p>">
<div class="title no-select">
Output
<div class="flex-row-reverse">
<span class="pane-controls">
<div class="io-info" id="bake-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true" data-help-title="CyberChef Magic!" data-help="<p>One of CyberChef's best features is its ability to automatically detect which Operations might make more sense of your data. The Magic button appears when CyberChef has a suggested Operation for you based on the data in the Output.</p><p>Clicking on the button will add the suggested Operation(s) to your Recipe.</p><p>This background Magic detection will inspect your Output up to three levels deep and attempt to unwrap it using a range of techniques. For more control, use the 'Magic' operation, which allows you to configure greater depth and filter based on various parameters.</p><p>Further information about CyberChef Magic can be found <a href='https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic'>here</a>.</p>">
<svg width="22" height="22" viewBox="0 0 24 24">
<path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
</svg>
</button>
<span id="stale-indicator" class="hidden" data-toggle="tooltip" title="The output is stale. The input or recipe has changed since this output was generated. Bake again to get the new value." data-help-title="Staleness indicator" data-help="The staleness indicator is displayed when the Recipe or Input has changed but the Output has not yet been updated to reflect this. It is most commonly displayed when Auto-bake is turned off and indicates that you need to Bake in order to see an accurate Output.">
<i class="material-icons">access_time</i>
</span>
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none" data-help-title="Saving all outputs to a zip file" data-help="<p>When operating with multiple tabbed Inputs and Outputs, you can use this button to save off all the Outputs at once in a ZIP file.</p><p>Use the 'Bake' button to bake all Inputs at once.</p><p>You will be given the choice to specify the file extension for the Outputs, or you can let CyberChef attempt to detect the filetype of each one. If an Output's type is not clear, CyberChef will use the '.dat' extension.</p>">
<label for="output-text">Output</label>
<span class="pane-controls">
<div class="io-info" id="bake-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none" data-help-title="Saving all outputs to a zip file" data-help="<p>When operating with multiple tabbed Inputs and Outputs, you can use this button to save off all the Outputs at once in a ZIP file.</p><p>Use the 'Bake' button to bake all Inputs at once.</p><p>You will be given the choice to specify the file extension for the Outputs, or you can let CyberChef attempt to detect the filetype of each one. If an Output's type is not clear, CyberChef will use the '.dat' extension.</p>">
<i class="material-icons">archive</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-to-file" data-toggle="tooltip" title="Save output to file" data-help-title="Saving output to a file" data-help="The currently active Output can be saved to a file. You will be asked to specify a filename. CyberChef will attempt to guess the correct file extension based on the data. If a file type cannot be detected, the extension defaults to '.dat' but can be changed manually.">
<i class="material-icons">save</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="copy-output" data-toggle="tooltip" title="Copy raw output to the clipboard" data-help-title="Copying raw output to the clipboard" data-help="<p>Data can be copied from the Output in the normal way by selecting text and copying it. This button provides a quick way of copying the entire output to the clipboard without having to select it. It directly copies the raw data rather than selecting text in the Output editor. Each method should have the same result, but the button may be more efficient for large Outputs as it does not require any DOM interaction.</p>">
<i class="material-icons">content_copy</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Replace input with output" data-help-title="Replacing input with output" data-help="<p>This button moves the currently active Output data into the currently active Input tab, overwriting whatever data was already there.</p><p>The Input character encoding and EOL sequence will be changed to match the current Output values, so that the data is interpreted correctly.</p>">
<i class="material-icons">open_in_browser</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon btn-maximise" id="maximise-output" data-toggle="tooltip" title="Maximise pane" data-help-title="Maximise pane" data-help="This button allows you to view the Output pane at maximum size, hiding the Operations, Recipe and Input panes. You can restore the pane to its normal size by clicking the same button again.">
<i class="material-icons">fullscreen</i>
</button>
</span>
</div>
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-to-file" data-toggle="tooltip" title="Save output to file" data-help-title="Saving output to a file" data-help="The currently active Output can be saved to a file. You will be asked to specify a filename. CyberChef will attempt to guess the correct file extension based on the data. If a file type cannot be detected, the extension defaults to '.dat' but can be changed manually.">
<i class="material-icons">save</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="copy-output" data-toggle="tooltip" title="Copy raw output to the clipboard" data-help-title="Copying raw output to the clipboard" data-help="<p>Data can be copied from the Output in the normal way by selecting text and copying it. This button provides a quick way of copying the entire output to the clipboard without having to select it. It directly copies the raw data rather than selecting text in the Output editor. Each method should have the same result, but the button may be more efficient for large Outputs as it does not require any DOM interaction.</p>">
<i class="material-icons">content_copy</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Replace input with output" data-help-title="Replacing input with output" data-help="<p>This button moves the currently active Output data into the currently active Input tab, overwriting whatever data was already there.</p><p>The Input character encoding and EOL sequence will be changed to match the current Output values, so that the data is interpreted correctly.</p>">
<i class="material-icons">open_in_browser</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="maximise-output" data-toggle="tooltip" title="Maximise output pane" data-help-title="Maximising the Output pane" data-help="This button allows you to view the Output pane at maximum size, hiding the Operations, Recipe and Input panes. You can restore the pane to its normal size by clicking the same button again.">
<i class="material-icons">fullscreen</i>
</button>
</span>
<button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true" data-help-title="CyberChef Magic!" data-help="<p>One of CyberChef's best features is its ability to automatically detect which Operations might make more sense of your data. The Magic button appears when CyberChef has a suggested Operation for you based on the data in the Output.</p><p>Clicking on the button will add the suggested Operation(s) to your Recipe.</p><p>This background Magic detection will inspect your Output up to three levels deep and attempt to unwrap it using a range of techniques. For more control, use the 'Magic' operation, which allows you to configure greater depth and filter based on various parameters.</p><p>Further information about CyberChef Magic can be found <a href='https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic'>here</a>.</p>">
<svg width="22" height="22" viewBox="0 0 24 24">
<path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
</svg>
</button>
<span id="stale-indicator" class="hidden" data-toggle="tooltip" title="The output is stale. The input or recipe has changed since this output was generated. Bake again to get the new value." data-help-title="Staleness indicator" data-help="The staleness indicator is displayed when the Recipe or Input has changed but the Output has not yet been updated to reflect this. It is most commonly displayed when Auto-bake is turned off and indicates that you need to Bake in order to see an accurate Output.">
<i class="material-icons">access_time</i>
</span>
</div>
<div id="output-wrapper" class="no-select">
@ -384,7 +338,6 @@
</div>
</div>
<!-- Modals -->
<div class="modal fade" id="save-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" data-help-proxy="#save">
@ -444,6 +397,7 @@
</div>
</div>
</div>
<div class="modal fade" id="load-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" data-help-proxy="#load">
@ -470,6 +424,7 @@
</div>
</div>
</div>
<div class="modal fade" id="options-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" data-help-proxy="#options">
@ -570,6 +525,7 @@
</div>
</div>
</div>
<div class="modal fade" id="favourites-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" data-help-proxy="a[data-target='#catFavourites']">
@ -578,12 +534,12 @@
</div>
<div class="modal-body" id="favourites-body">
<ul>
<li><span style="font-weight: bold">To add:</span> from the Operations list, click the star icon of the operation or, on desktop devices, drag and drop the operation over to the favourites category</li>
<li><span style="font-weight: bold">To add:</span> drag the operation over the favourites category and drop it</li>
<li><span style="font-weight: bold">To reorder:</span> drag up and down in the list below</li>
<li><span style="font-weight: bold">To remove:</span> click the trashcan icon for the operation to remove below</li>
<li><span style="font-weight: bold">To remove:</span> hit the delete button or drag out of the list below</li>
</ul>
<br>
<div id="editable-favourites"></div>
<ul id="edit-favourites-list" class="op-list"></ul>
<div class="option-item">
</div>
</div>
@ -595,6 +551,7 @@
</div>
</div>
</div>
<div class="modal fade" id="support-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" data-help-proxy="#support">
@ -738,6 +695,7 @@
</div>
</div>
</div>
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
@ -756,6 +714,7 @@
</div>
</div>
</div>
<div class="modal fade" id="input-tab-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
@ -814,6 +773,7 @@
</div>
</div>
</div>
<div class="modal fade" id="output-tab-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
@ -875,6 +835,7 @@
</div>
</div>
</div>
<div class="modal fade" id="download-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" data-help-proxy="a[data-target='#download-modal']">
@ -931,7 +892,6 @@
</div>
</div>
</div>
<!-- End modals -->
</body>
</html>

View File

@ -1,32 +0,0 @@
/* Gutters and splitters */
@media only screen and (min-width: 1024px){
.split {
box-sizing: border-box;
/* overflow: auto; */
/* Removed to enable Background Magic button pulse to overflow.
Replace this rule if it seems to be causing problems. */
position: relative;
}
.gutter.gutter-horizontal,
.split.split-horizontal {
float: left;
height: 100%;
}
.gutter {
background-color: var(--secondary-border-colour);
background-repeat: no-repeat;
background-position: 50%;
}
.gutter.gutter-horizontal {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+EFBhEwBDmIiYYAAAAjSURBVBjTYzxz5sx/BgYGBiYGKGB89+4dA4oIy71790aGGgCn+DBbOcAB0wAAAABJRU5ErkJggg==');
cursor: ew-resize;
}
.gutter.gutter-vertical {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAACCAYAAABPJGxCAAAABGdBTUEAALGOfPtRkwAACkNpQ0NQSUNDIFByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/vcxDeEAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH4QUGETI0LWfbqAAAACNJREFUCNdjPHPmzH8GBgYGJSUlRgYGBoZ79+7Rhc/EMEAAAHd6H2e3/71BAAAAAElFTkSuQmCC');
cursor: ns-resize;
}
}

View File

@ -0,0 +1,43 @@
/**
* Operation list styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
.op-list {
list-style-type: none;
margin: 0;
padding: 0;
}
.category-title {
display: block;
padding: 10px;
background-color: var(--secondary-background-colour);
border-bottom: 1px solid var(--secondary-border-colour);
font-weight: var(--title-weight);
}
.category-title[href='#catFavourites'] {
border-bottom-color: var(--primary-border-colour);
}
.category-title[aria-expanded=true] {
border-bottom-color: var(--primary-border-colour);
}
.category-title.collapsed {
border-bottom-color: var(--secondary-border-colour);
}
.category-title:hover {
color: var(--op-list-operation-font-colour);
}
.category {
margin: 0 !important;
border-radius: 0 !important;
border: none;
}

View File

@ -1,29 +0,0 @@
/**
* Operation list styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
/*
Note: .op-list is used in the 'edit favourites' modal & for each
category > c-operation-list in the Operations component
*/
.op-list {
list-style-type: none;
margin: 0;
padding: 0;
}
.op-list .operation {
color: var(--op-list-operation-font-colour);
background-color: var(--op-list-operation-bg-colour);
border-color: var(--op-list-operation-border-colour);
}
.op-list .operation:hover {
filter: brightness(98%);
}

View File

@ -6,12 +6,8 @@
* @license Apache-2.0
*/
/*
Note: .operation is used in the c-operation-li custom web components, but also
for list items in #recipe-list
*/
.operation {
cursor: grab;
padding: 10px;
list-style-type: none;
position: relative;
@ -20,60 +16,307 @@
border-top: none;
border-left: none;
border-right: none;
cursor: default;
}
@media only screen and (min-width: 1024px){
.operation {
cursor: grab;
}
}
.op-icon {
float: right;
font-size: 18px;
cursor: pointer;
}
.op-icon.star-icon {
float: left;
margin: 0 10px 0 0;
padding: 0;
color: var(--op-list-operation-font-colour);
opacity: .35;
cursor: pointer;
}
li.operation.favourite > .op-icon.star-icon {
opacity: 1;
}
.op-icon.remove-icon {
color: #f44336;
}
.op-icon.check-icon {
display: none;
color: var(--checkmark-color);
}
.operation.selected {
background-color: var(--selected-operation-bg-colour);
}
.operation.selected > i.op-icon.check-icon {
display: inline-block;
#rec-list .operation {
padding: 14px;
}
.op-title {
font-weight: var(--op-title-font-weight);
}
c-operation-li li.operation.focused-op {
color: var(--focused-operation-font-color) !important;
background-color: var(--focused-operation-bg-colour) !important;
border-color: var(--focused-operation-border-colour) !important;
.ingredients {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
column-gap: 14px;
row-gap: 0;
}
.ing-very-wide {
flex: 4 400px;
}
.ing-wide {
flex: 3 200px;
}
.ing-medium {
flex: 2 120px;
}
.ing-short {
flex: 1 80px;
}
.ing-flexible {
flex-grow: 1;
}
.ingredients .form-group {
margin-top: 1rem;
padding-top: 0;
}
.arg {
font-family: var(--fixed-width-font-family);
text-overflow: ellipsis;
}
select.arg {
font-family: var(--primary-font-family);
min-width: 100px;
}
textarea.arg {
min-height: 74px;
resize: vertical;
}
div.toggle-string {
flex: 1;
}
input.toggle-string {
border-top-right-radius: 0 !important;
height: 42px !important;
}
.operation [class^='bmd-label'],
.operation [class*=' bmd-label'] {
top: 13px !important;
left: 12px;
z-index: 10;
}
.operation label,
.operation .checkbox label {
color: var(--arg-label-colour);
}
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused label,
.operation .checkbox label:hover {
color: var(--input-highlight-colour);
}
.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before {
border-color: var(--input-border-colour);
color: var(--input-highlight-colour);
}
.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--input-highlight-colour);
color: var(--input-highlight-colour);
}
.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,
.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--disabled-font-colour);
color: var(--disabled-font-colour);
}
.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,
.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--breakpoint-font-colour);
color: var(--breakpoint-font-colour);
}
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--fc-breakpoint-operation-font-colour);
color: var(--fc-breakpoint-operation-font-colour);
}
.operation .form-control {
padding: 20px 12px 6px 12px !important;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-image: none;
background-color: var(--arg-background);
background-position-y: 100%, 100%;
color: var(--arg-font-colour);
}
.operation .form-control:hover {
background-image:
linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
filter: brightness(97%);
}
.operation .form-control:focus {
background-color: var(--arg-background);
background-image:
linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
filter: brightness(100%);
}
.operation .bmd-form-group.is-filled label.bmd-label-floating,
.operation .bmd-form-group.is-focused label.bmd-label-floating {
top: 4px !important;
left: 12px;
}
.operation label.bmd-label-floating {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: calc(100% - 13px);
}
.input-group .form-control {
border-top-left-radius: 4px;
}
.input-group-append button {
border-top-right-radius: 4px;
background-color: var(--arg-background) !important;
margin: unset;
}
.input-group-append button:hover {
filter: brightness(97%);
}
.editable-option-menu {
height: auto;
max-height: 350px;
overflow-x: hidden;
}
.editable-option-menu .dropdown-item {
padding: 0.3rem 1rem 0.3rem 1rem;
min-height: 1.6rem;
max-width: 20rem;
}
.ingredients .dropdown-toggle-split {
height: 40px !important;
}
.boolean-arg {
height: 46px;
}
.boolean-arg .checkbox {
height: 100%;
}
.boolean-arg .checkbox label {
height: 100%;
display: flex;
align-items: center;
}
.boolean-arg .checkbox-decorator {
top: 13px;
}
.register-list {
background-color: var(--fc-operation-border-colour);
font-family: var(--fixed-width-font-family);
padding: 10px;
word-break: break-all;
}
.op-icon {
float: right;
color: #f44336;
font-size: 18px;
cursor: pointer;
}
.recip-icons {
position: absolute;
top: 13px;
right: 10px;
height: 16px;
}
.recip-icons i {
margin-right: 10px;
vertical-align: baseline;
float: right;
font-size: 18px;
cursor: pointer;
}
.disable-icon {
color: var(--disable-icon-colour);
}
.disable-icon-selected {
color: var(--disable-icon-selected-colour);
}
.breakpoint {
color: var(--breakpoint-icon-colour);
}
.breakpoint-selected {
color: var(--breakpoint-icon-selected-colour);
}
.break {
color: var(--breakpoint-font-colour) !important;
background-color: var(--breakpoint-bg-colour) !important;
border-color: var(--breakpoint-border-colour) !important;
}
.break .form-group * { color: var(--breakpoint-font-colour) !important; }
.selected-op {
color: var(--selected-operation-font-color) !important;
background-color: var(--selected-operation-bg-colour) !important;
border-color: var(--selected-operation-border-colour) !important;
}
.selected-op .form-group * { color: var(--selected-operation-font-color) !important; }
.flow-control-op {
color: var(--fc-operation-font-colour) !important;
background-color: var(--fc-operation-bg-colour) !important;
border-color: var(--fc-operation-border-colour) !important;
}
.flow-control-op .form-group *:not(.arg) { color: var(--fc-operation-font-colour) }
.flow-control-op.break {
color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-bg-colour) !important;
border-color: var(--fc-breakpoint-operation-border-colour) !important;
}
.flow-control-op.break .form-group * { color: var(--fc-breakpoint-operation-font-colour) !important; }
.disabled {
color: var(--disabled-font-colour) !important;
background-color: var(--disabled-bg-colour) !important;
border-color: var(--disabled-border-colour) !important;
}
.disabled .form-group * { color: var(--disabled-font-colour) !important; }
.break .register-list {
color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-border-colour) !important;
}
.disabled .register-list {
color: var(--disabled-font-colour) !important;
background-color: var(--disabled-border-colour) !important;
}

View File

@ -6,24 +6,30 @@
* @license Apache-2.0
*/
:root {
--title-height: 48px;
--tab-height: 40px;
}
.title {
padding: 8px 12px;
padding: 8px;
padding-left: 12px;
padding-right: 12px;
height: var(--title-height);
border-bottom: 1px solid var(--primary-border-colour);
font-weight: var(--title-weight);
font-size: var(--title-size);
color: var(--title-colour);
background-color: var(--title-background-colour);
display: flex;
justify-content: space-between;
height: var(--title-height);
align-items: center;
line-height: calc(var(--title-height) - 14px);
}
.pane-controls {
position: absolute;
right: 8px;
top: 8px;
display: inline-flex;
align-items: center;
display: flex;
flex-direction: row;
}
.pane-controls .btn {

View File

@ -1,20 +0,0 @@
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background-color: var(--scrollbar-track);
}
::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-thumb);
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--scrollbar-hover);
}
::-webkit-scrollbar-corner {
background-color: var(--scrollbar-track);
}

View File

@ -1,75 +0,0 @@
/* File details panel */
.cm-file-details {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;
padding-bottom: 21px;
height: 100%;
}
.file-details-toggle-shown,
.file-details-toggle-hidden {
width: 8px;
height: 40px;
border: 1px solid var(--secondary-border-colour);
position: absolute;
top: calc(50% - 20px);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--secondary-border-colour);
color: var(--subtext-font-colour);
z-index: 1;
}
.file-details-toggle-shown {
left: 0;
border-left: none;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.file-details-toggle-hidden {
left: -8px;
border-right: none;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.file-details-toggle-shown:hover,
.file-details-toggle-hidden:hover {
background-color: var(--primary-border-colour);
border-color: var(--primary-border-colour);
color: var(--primary-font-colour);
}
.file-details-heading {
font-weight: bold;
margin: 10px 0 10px 0;
}
.file-details-data {
text-align: left;
margin: 10px 2px;
}
.file-details-data td {
padding: 0 3px;
max-width: 130px;
min-width: 60px;
overflow: hidden;
vertical-align: top;
word-break: break-all;
}
.file-details-error {
color: #f00;
}
.file-details-thumbnail {
max-width: 180px;
}

View File

@ -1,21 +0,0 @@
/* Highlighting */
.ͼ2.cm-focused .cm-selectionBackground {
background-color: var(--hl5);
}
.ͼ2 .cm-selectionBackground {
background-color: var(--hl1);
}
.ͼ1 .cm-selectionMatch {
background-color: var(--hl2);
}
.ͼ1.cm-focused .cm-cursor.cm-cursor-primary {
border-color: var(--primary-font-colour);
}
.ͼ1 .cm-cursor.cm-cursor-primary {
display: block;
border-color: var(--subtext-font-colour);
}

View File

@ -1,35 +0,0 @@
#stale-indicator {
opacity: 1;
visibility: visible;
transition: margin 0s, opacity 0.3s;
margin-left: 5px;
cursor: help;
}
#stale-indicator i {
vertical-align: middle;
margin-bottom: 5px;
}
#magic {
opacity: 1;
visibility: visible;
transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s;
margin-left: 5px;
margin-bottom: 5px;
}
#magic.hidden,
#stale-indicator.hidden {
visibility: hidden;
transition: opacity 0.3s, margin 0.3s 0.3s, visibility 0.3s;
opacity: 0;
}
#magic.hidden {
margin-left: -32px;
}
#magic svg path {
fill: var(--primary-font-colour);
}

View File

@ -1,205 +0,0 @@
/**
* Input/Output area styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
#input,
#output {
background-color: var(--primary-background-colour);
}
#input-text,
#output-text {
position: relative;
width: 100%;
height: 100%;
margin: 0;
background-color: transparent;
overflow: hidden;
user-select: auto;
}
/* To vertically align the title nicely in the center */
label[for="input-text"],
label[for="output-text"] {
line-height: revert;
}
#input .cm-scroller,
#output .cm-scroller {
overflow-y: auto;
}
#input .cm-scroller:hover,
#output .cm-scroller:hover {
cursor: initial;
}
#output-text.html-output .cm-content,
#output-text.html-output .cm-line,
#output-html {
display: block;
height: 100%;
user-select: auto;
}
#output-text.html-output .cm-line .cm-widgetBuffer,
#output-text.html-output .cm-line>br {
display: none;
}
.cm-editor {
height: 100%;
}
.cm-editor .cm-content {
font-family: var(--fixed-width-font-family);
font-size: var(--fixed-width-font-size);
color: var(--fixed-width-font-colour);
}
#input-wrapper,
#output-wrapper {
height: calc(100% - var(--title-height));
}
#input-wrapper.show-tabs,
#output-wrapper.show-tabs {
height: calc(100% - var(--tab-height) - var(--title-height));
}
#output-loader {
position: absolute;
bottom: var(--controls-height);
z-index: 100;
left: 0;
width: 100%;
height: 100%;
margin: 0;
background-color: var(--secondary-background-colour);
opacity: 0;
visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.5s ease;
}
@media only screen and (min-width: 1024px){
#output-loader {
bottom: 0;
}
}
#output-loader-animation {
display: block;
position: absolute;
width: 60%;
height: 60%;
top: 10%;
transition: all 0.5s ease;
}
#output-loader .loading-msg {
opacity: 1;
font-family: var(--primary-font-family);
line-height: var(--primary-line-height);
color: var(--primary-font-colour);
left: unset;
top: 30%;
position: relative;
transition: all 0.5s ease;
}
.io-info {
margin-right: 18px;
margin-top: 1px;
height: 30px;
text-align: right;
line-height: 12px;
font-family: var(--fixed-width-font-family);
font-weight: normal;
font-size: 8pt;
display: flex;
align-items: center;
}
.dropping-file {
border: 5px dashed var(--drop-file-border-colour) !important;
}
#input-find-options,
#output-find-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
#input-tab-body .form-group.input-group,
#output-tab-body .form-group.input-group {
width: 70%;
float: left;
margin-bottom: 2rem;
}
.input-find-option .toggle-string {
width: 70%;
display: inline-block;
}
.input-find-option-append button {
border-top-right-radius: 4px;
background-color: var(--arg-background) !important;
margin: unset;
}
.input-find-option-append button:hover {
filter: brightness(97%);
}
.form-group.output-find-option {
width: 70%;
float: left;
}
#input-num-results-container,
#output-num-results-container {
width: 20%;
float: right;
margin: 0 0 0 10%;
}
#input-find-options-checkboxes,
#output-find-options-checkboxes {
list-style: none;
padding: 0;
margin: auto;
overflow-x: auto;
overflow-y: hidden;
text-align: center;
width: fit-content;
}
#input-find-options-checkboxes li,
#output-find-options-checkboxes li {
display: flex;
flex-direction: row;
float: left;
padding: 10px;
text-align: center;
}
#input .cm-panels,
#output .cm-panels {
border-color: var(--primary-border-colour);
}
/* maximise pane and lay on top of everything ( mobile UI )*/
.top-zindex {
z-index: 200;
}

View File

@ -1,34 +0,0 @@
/* Not to be confused with #search-results in Operations */
#input-search-results,
#output-search-results {
list-style: none;
width: 75%;
min-width: 200px;
margin-left: auto;
margin-right: auto;
}
#input-search-results li,
#output-search-results li {
padding: 10px 5px;
text-align: center;
width: 100%;
color: var(--op-list-operation-font-colour);
background-color: var(--op-list-operation-bg-colour);
border-bottom: 2px solid var(--op-list-operation-border-colour);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#input-search-results li:first-of-type,
#output-search-results li:first-of-type {
border-top: 2px solid var(--op-list-operation-border-colour);
}
#input-search-results li:hover,
#output-search-results li:hover {
cursor: pointer;
filter: brightness(98%);
}

View File

@ -1,118 +0,0 @@
/* Status bar */
.cm-panel input::placeholder {
font-size: 12px !important;
}
.ͼ2 .cm-panels,
.ͼ2 .cm-side-panels {
background-color: var(--secondary-background-colour);
color: var(--primary-font-colour);
border-bottom: 1px solid var(--primary-border-colour);
}
.cm-status-bar {
font-family: var(--fixed-width-font-family);
font-weight: normal;
font-size: 8pt;
margin: 5px 15px;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
}
.cm-status-bar i {
font-size: 12pt;
vertical-align: middle;
margin-left: 8px;
}
.cm-status-bar>div>span:first-child i {
margin-left: 0;
}
.cm-status-bar .disabled {
background-color: unset !important;
cursor: not-allowed;
}
/* Dropup Button */
.cm-status-bar-select-btn {
border: none;
cursor: pointer;
}
/* The container <div> - needed to position the dropup content */
.cm-status-bar-select {
position: relative;
display: inline-block;
}
/* Dropup content (Hidden by Default) */
.cm-status-bar-select-content {
display: none;
position: absolute;
bottom: 20px;
right: 0;
background-color: #f1f1f1;
min-width: 200px;
box-shadow: 0 4px 4px 0 rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropup */
.cm-status-bar-select-content a {
color: black;
padding: 2px 5px;
text-decoration: none;
display: block;
}
/* Change color of dropup links on hover */
.cm-status-bar-select-content a:hover {
background-color: #ddd
}
/* Change the background color of the dropup button when the dropup content is shown */
.cm-status-bar-select:hover .cm-status-bar-select-btn {
background-color: #f1f1f1;
}
/* The search field */
.cm-status-bar-filter-input {
box-sizing: border-box;
font-size: 12px;
padding-left: 10px !important;
border: none;
}
.cm-status-bar-filter-search {
border-top: 1px solid #ddd;
}
/* Show the dropup menu */
.cm-status-bar-select .show {
display: block;
}
.cm-status-bar-select-scroll {
overflow-y: auto;
height: auto;
}
.chr-enc-value {
max-width: 150px;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
/* Right hand side icons */
.rhs {
position: fixed;
right: 15px;
}

View File

@ -1,112 +0,0 @@
#input-tabs-wrapper #input-tabs,
#output-tabs-wrapper #output-tabs {
list-style: none;
background-color: var(--title-background-colour);
padding: 0;
margin: 0;
overflow-x: auto;
overflow-y: hidden;
display: flex;
flex-direction: row;
border-bottom: 1px solid var(--primary-border-colour);
border-left: 1px solid var(--primary-border-colour);
height: var(--tab-height);
clear: none;
}
#input-tabs li,
#output-tabs li {
display: flex;
flex-direction: row;
width: 100%;
min-width: 120px;
float: left;
padding: 0;
text-align: center;
border-right: 1px solid var(--primary-border-colour);
height: var(--tab-height);
vertical-align: middle;
}
#input-tabs li:hover,
#output-tabs li:hover {
cursor: pointer;
background-color: var(--primary-background-colour);
}
.active-input-tab,
.active-output-tab {
font-weight: bold;
background-color: var(--primary-background-colour);
}
.input-tab-content+.btn-close-tab {
display: block;
margin-top: auto;
margin-bottom: auto;
margin-right: 2px;
}
.input-tab-content+.btn-close-tab i {
font-size: 0.8em;
}
.input-tab-buttons,
.output-tab-buttons {
width: 25px;
text-align: center;
margin: 0;
height: var(--tab-height);
line-height: var(--tab-height);
font-weight: bold;
background-color: var(--title-background-colour);
border-bottom: 1px solid var(--primary-border-colour);
}
.input-tab-buttons:hover,
.output-tab-buttons:hover {
cursor: pointer;
background-color: var(--primary-background-colour);
}
#btn-next-input-tab,
#btn-input-tab-dropdown,
#btn-next-output-tab,
#btn-output-tab-dropdown {
float: right;
}
#btn-previous-input-tab,
#btn-previous-output-tab {
float: left;
}
#btn-close-all-tabs {
color: var(--breakpoint-font-colour) !important;
}
.input-tab-content,
.output-tab-content {
width: 100%;
max-width: 100%;
padding: 10px 5px;
height: var(--tab-height);
vertical-align: middle;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.btn-close-tab {
height: var(--tab-height);
vertical-align: middle;
width: fit-content;
}
.tabs-left > li:first-child {
box-shadow: 15px 0 15px -15px var(--primary-border-colour) inset;
}
.tabs-right > li:last-child {
box-shadow: -15px 0 15px -15px var(--primary-border-colour) inset;
}

View File

@ -1,61 +0,0 @@
/**
* Operations - Categories list
*/
c-category-list > ul {
list-style-type: none;
padding-left: 0;
margin-bottom: 0;
}
.category-title {
display: flex;
justify-content: space-between;
padding: 10px;
background-color: var(--secondary-background-colour);
border-bottom: 1px solid var(--secondary-border-colour);
font-weight: var(--title-weight);
}
.category-title[data-target="#catFavourites"] {
height: 41px;
}
#edit-favourites {
transform: translateY(-7px);
}
.category-title[aria-expanded=true] {
border-bottom-color: var(--primary-border-colour);
}
.category-title.collapsed {
border-bottom-color: var(--secondary-border-colour);
}
.category-title:hover {
color: var(--op-list-operation-font-colour);
}
.category {
margin: 0 !important;
border-radius: 0 !important;
border: none;
}
#categories a {
color: var(--category-list-font-colour);
cursor: pointer;
}
#categories a:hover {
filter: brightness(98%);
}
.favourites-hover {
color: var(--rec-list-operation-font-colour);
background-color: var(--rec-list-operation-bg-colour);
border: 2px dashed var(--rec-list-operation-font-colour) !important;
padding: 8px 8px 9px 8px;
}

View File

@ -1,49 +0,0 @@
/**
* Operations component styles
*
* For consistency sake I'm using an ID, but the custom wrapper / dropdown component
* can ( should ) be made into a reusable, generic component
*/
#operations {
background-color: var(--primary-background-colour);
}
#operations > .title {
padding: 8px 8px 8px 12px;
}
#operations-wrapper {
position: relative;
}
#operations-dropdown {
position: absolute;
top: var(--banner-height);
width: 100%;
height: auto;
overflow: auto;
z-index: 20; /* to dropdown on top of all the other elements */
background-color: var(--secondary-background-colour);
}
/* 'hidden' is used to handle the visibility of a variety of mobile only elements */
#close-ops-dropdown-icon.hidden,
#search-results.hidden,
#categories.hidden {
z-index: -10;
display: none;
}
@media only screen and ( min-width: 1024px ){
#operations-dropdown {
border-bottom: none;
}
/* On desktop UI, the categories are always visible */
/*#search-results.hidden,*/
#categories.hidden {
z-index: initial;
display: block;
}
}

View File

@ -1,12 +0,0 @@
/**
* Operations - Search component
*/
#search {
padding-left: 10px;
padding-right: 10px;
background-image:
linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px),
linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);
}

View File

@ -1,219 +0,0 @@
.ingredients {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
column-gap: 14px;
row-gap: 0;
}
.ing-very-wide {
flex: 4 400px;
}
.ing-wide {
flex: 3 200px;
}
.ing-medium {
flex: 2 120px;
}
.ing-short {
flex: 1 80px;
}
.ing-flexible {
flex-grow: 1;
}
.ingredients .form-group {
margin-top: 1rem;
padding-top: 0;
}
.arg {
font-family: var(--fixed-width-font-family);
text-overflow: ellipsis;
}
select.arg {
font-family: var(--primary-font-family);
min-width: 100px;
}
textarea.arg {
min-height: 74px;
resize: vertical;
}
div.toggle-string {
flex: 1;
}
input.toggle-string {
border-top-right-radius: 0 !important;
height: 42px !important;
}
.operation label,
.operation .checkbox label {
color: var(--arg-label-colour);
}
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused label,
.operation .checkbox label:hover {
color: var(--input-highlight-colour);
}
.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before {
border-color: var(--input-border-colour);
color: var(--input-highlight-colour);
}
.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--input-highlight-colour);
color: var(--input-highlight-colour);
}
.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,
.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--disabled-font-colour);
color: var(--disabled-font-colour);
}
.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,
.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--breakpoint-font-colour);
color: var(--breakpoint-font-colour);
}
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,
.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {
border-color: var(--fc-breakpoint-operation-font-colour);
color: var(--fc-breakpoint-operation-font-colour);
}
.operation .form-control {
padding: 20px 12px 6px 12px !important;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-image: none;
background-color: var(--arg-background);
background-position-y: 100%, 100%;
color: var(--arg-font-colour);
}
.operation .form-control:hover {
background-image:
linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
filter: brightness(97%);
}
.operation .form-control:focus {
background-color: var(--arg-background);
background-image:
linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
filter: brightness(100%);
}
.operation .bmd-form-group.is-filled label.bmd-label-floating,
.operation .bmd-form-group.is-focused label.bmd-label-floating {
top: 4px !important;
left: 12px;
}
.operation label.bmd-label-floating {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: calc(100% - 13px);
}
.input-group .form-control {
border-top-left-radius: 4px;
}
.input-group-append button {
border-top-right-radius: 4px;
background-color: var(--arg-background) !important;
margin: unset;
}
.input-group-append button:hover {
filter: brightness(97%);
}
.editable-option-menu {
height: auto;
max-height: 350px;
overflow-x: hidden;
}
.editable-option-menu .dropdown-item {
padding: 0.3rem 1rem 0.3rem 1rem;
min-height: 1.6rem;
max-width: 20rem;
}
.ingredients .dropdown-toggle-split {
height: 40px !important;
}
.boolean-arg {
height: 46px;
}
.boolean-arg .checkbox {
height: 100%;
}
.boolean-arg .checkbox label {
height: 100%;
display: flex;
align-items: center;
}
.boolean-arg .checkbox-decorator {
top: 13px;
}
.custom-control-label {
line-height: 1.5rem;
}
.custom-control-label:hover {
cursor: pointer;
}
.custom-control-label::before {
height: 20px;
width: 20px;
border: 1px solid var(--input-border-colour);
}
.custom-control-input:checked + .custom-control-label::before {
background-color: var(--input-highlight-colour);
border: 1px solid var(--input-highlight-colour);
}
.custom-control-label::after {
height: 20px;
width: 20px;
}

View File

@ -1,110 +0,0 @@
#rec-list {
overflow-y: auto;
}
#rec-list .operation {
color: var(--rec-list-operation-font-colour);
background-color: var(--rec-list-operation-bg-colour);
border-color: var(--rec-list-operation-border-colour);
padding: 14px;
cursor: grab;
}
#rec-list li.sortable-chosen{
filter: brightness(0.8);
}
.operation [class^='bmd-label'],
.operation [class*=' bmd-label'] {
top: 13px !important;
left: 12px;
z-index: 10;
}
.register-list {
background-color: var(--fc-operation-border-colour);
font-family: var(--fixed-width-font-family);
padding: 10px;
word-break: break-all;
}
.recipe-icons {
position: absolute;
top: 13px;
right: 10px;
height: 16px;
}
.recipe-icons i {
height: 30px;
width: 30px;
text-align: center;
vertical-align: baseline;
float: right;
font-size: 18px;
cursor: pointer;
}
.recipe-icons i:last-child {
margin-right: 10px;
}
.disable-icon {
color: var(--disable-icon-colour);
}
.disable-icon-selected {
color: var(--disable-icon-selected-colour);
}
.breakpoint-icon {
color: var(--breakpoint-icon-colour);
}
.breakpoint-selected {
color: var(--breakpoint-icon-selected-colour);
}
.break {
color: var(--breakpoint-font-colour) !important;
background-color: var(--breakpoint-bg-colour) !important;
border-color: var(--breakpoint-border-colour) !important;
}
.break .form-group * { color: var(--breakpoint-font-colour) !important; }
/*.focused-op .form-group * { color: var(--focused-operation-font-color) !important; }*/
.flow-control-op {
color: var(--fc-operation-font-colour) !important;
background-color: var(--fc-operation-bg-colour) !important;
border-color: var(--fc-operation-border-colour) !important;
}
.flow-control-op .form-group *:not(.arg) { color: var(--fc-operation-font-colour) }
.flow-control-op.break {
color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-bg-colour) !important;
border-color: var(--fc-breakpoint-operation-border-colour) !important;
}
.flow-control-op.break .form-group * { color: var(--fc-breakpoint-operation-font-colour) !important; }
.disabled {
color: var(--disabled-font-colour) !important;
background-color: var(--disabled-bg-colour) !important;
border-color: var(--disabled-border-colour) !important;
}
.disabled .form-group * { color: var(--disabled-font-colour) !important; }
.break .register-list {
color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-border-colour) !important;
}
.disabled .register-list {
color: var(--disabled-font-colour) !important;
background-color: var(--disabled-border-colour) !important;
}

View File

@ -1,13 +0,0 @@
/**
* Recipe area styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
#recipe {
position: relative;
background-color: var(--primary-background-colour);
border-bottom: 1px solid var(--primary-border-colour);
}

View File

@ -7,13 +7,11 @@
*/
/* Themes */
@import "./themes/_structure.css";
@import "./themes/_classic.css";
@import "./themes/_dark.css";
@import "./themes/_geocities.css";
@import "./themes/_solarizedDark.css";
@import "./themes/_solarizedLight.css";
@import "./themes/_animations.css";
/* Utilities */
@import "./utils/_overrides.css";
@ -24,32 +22,17 @@
/* Components */
@import "./components/_button.css";
@import "./components/_op-list.css";
@import "./components/_list.css";
@import "./components/_operation.css";
@import "./components/_pane.css";
@import "./components/_scrollbar.css";
@import "./components/_dividers.css";
@import "./components/_modals.css";
@import "./components/_banner.css";
@import "./components/_controls.css";
/* Components: Operations */
@import "./components/operations/_operations.css";
@import "./components/operations/_search.css";
@import "./components/operations/_categories.css";
/* Components: Recipe */
@import "./components/recipe/_recipe.css";
@import "./components/recipe/_recipe-list.css";
@import "./components/recipe/_ingredients.css";
/* Components: IO */
@import "./components/io/_io.css";
@import "./components/io/_search-results.css";
@import "./components/io/_tabs.css";
@import "./components/io/_icons.css";
@import "./components/io/_highlighting.css";
@import "./components/io/_status-bar.css";
@import "./components/io/_file-details.css";
/* Layout */
@import "./layout/_banner.css";
@import "./layout/_controls.css";
@import "./layout/_io.css";
@import "./layout/_modals.css";
@import "./layout/_operations.css";
@import "./layout/_recipe.css";
@import "./layout/_structure.css";
/* Operations */

View File

@ -7,14 +7,14 @@
*/
#banner {
display: flex;
justify-content: space-between;
padding-left: 12px;
padding-right: 4px;
line-height: var(--banner-height);
position: absolute;
height: 30px;
width: 100%;
line-height: 30px;
border-bottom: 1px solid var(--primary-border-colour);
color: var(--banner-font-colour);
background-color: var(--banner-bg-colour);
border-bottom: 1px solid var(--primary-border-colour);
margin: 0;
}
#banner i {
@ -22,30 +22,13 @@
padding-right: 10px;
}
#banner a {
color: var(--banner-url-colour);
}
/* Notice wrapper */
#notice-wrapper {
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Banner links ( options, about / support ) */
.banner-link {
color: var(--banner-url-colour);
}
.banner-link:hover {
text-decoration: none;
}
@media only screen and (min-width: 1024px){
.banner-link:hover span {
text-decoration: underline;
}
}

View File

@ -7,54 +7,29 @@
*/
#controls {
position: absolute;
width: 100%;
bottom: 0;
padding: 10px 0;
border-top: 1px solid var(--primary-border-colour);
background-color: var(--secondary-background-colour);
overflow: hidden;
}
#controls-content {
position: relative;
display: flex;
padding: 10px 0;
height: 100%;
}
#controls-content > div {
height: 100%;
}
/* bake button */
#bake {
box-shadow: none;
}
/* bake and step buttons */
#controls .btn {
border-radius: 30px;
margin: 0;
font-size: initial;
line-height: 0;
height: 100%;
}
/* auto bake */
#controls-content .form-group {
text-align: center;
height: 100%;
}
#controls-content .form-group > .checkbox {
height: 100%;
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: center;
}
#auto-bake-label {
display: inline-block;
width: 100px;
padding: 0;
margin: 0;
font-size: initial;
text-align: center;
color: var(--primary-font-colour);
font-size: 14px;
cursor: pointer;
}
@ -66,9 +41,29 @@
#auto-bake-label .checkbox-decorator {
position: relative;
margin: 0;
padding: 0;
}
#bake {
box-shadow: none;
}
#controls .btn {
border-radius: 30px;
margin: 0;
}
.output-maximised .hide-on-maximised-output {
display: none !important;
}
.spin {
animation-name: spin;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes spin {
0% {transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}

View File

@ -0,0 +1,592 @@
/**
* Input/Output area styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
#input-text,
#output-text {
position: relative;
width: 100%;
height: 100%;
margin: 0;
background-color: transparent;
overflow: hidden;
user-select: auto;
}
#output-text.html-output .cm-content,
#output-text.html-output .cm-line,
#output-html {
display: block;
height: 100%;
user-select: auto;
}
#output-text.html-output .cm-line .cm-widgetBuffer,
#output-text.html-output .cm-line>br {
display: none;
}
.cm-editor {
height: 100%;
}
.cm-editor .cm-content {
font-family: var(--fixed-width-font-family);
font-size: var(--fixed-width-font-size);
color: var(--fixed-width-font-colour);
}
#input-tabs-wrapper #input-tabs,
#output-tabs-wrapper #output-tabs {
list-style: none;
background-color: var(--title-background-colour);
padding: 0;
margin: 0;
overflow-x: auto;
overflow-y: hidden;
display: flex;
flex-direction: row;
border-bottom: 1px solid var(--primary-border-colour);
border-left: 1px solid var(--primary-border-colour);
height: var(--tab-height);
clear: none;
}
#input-tabs li,
#output-tabs li {
display: flex;
flex-direction: row;
width: 100%;
min-width: 120px;
float: left;
padding: 0px;
text-align: center;
border-right: 1px solid var(--primary-border-colour);
height: var(--tab-height);
vertical-align: middle;
}
#input-tabs li:hover,
#output-tabs li:hover {
cursor: pointer;
background-color: var(--primary-background-colour);
}
.active-input-tab,
.active-output-tab {
font-weight: bold;
background-color: var(--primary-background-colour);
}
.input-tab-content+.btn-close-tab {
display: block;
margin-top: auto;
margin-bottom: auto;
margin-right: 2px;
}
.input-tab-content+.btn-close-tab i {
font-size: 0.8em;
}
.input-tab-buttons,
.output-tab-buttons {
width: 25px;
text-align: center;
margin: 0;
height: var(--tab-height);
line-height: var(--tab-height);
font-weight: bold;
background-color: var(--title-background-colour);
border-bottom: 1px solid var(--primary-border-colour);
}
.input-tab-buttons:hover,
.output-tab-buttons:hover {
cursor: pointer;
background-color: var(--primary-background-colour);
}
#btn-next-input-tab,
#btn-input-tab-dropdown,
#btn-next-output-tab,
#btn-output-tab-dropdown {
float: right;
}
#btn-previous-input-tab,
#btn-previous-output-tab {
float: left;
}
#btn-close-all-tabs {
color: var(--breakpoint-font-colour) !important;
}
.input-tab-content,
.output-tab-content {
width: 100%;
max-width: 100%;
padding-left: 5px;
padding-right: 5px;
padding-top: 10px;
padding-bottom: 10px;
height: var(--tab-height);
vertical-align: middle;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.btn-close-tab {
height: var(--tab-height);
vertical-align: middle;
width: fit-content;
}
.tabs-left > li:first-child {
box-shadow: 15px 0px 15px -15px var(--primary-border-colour) inset;
}
.tabs-right > li:last-child {
box-shadow: -15px 0px 15px -15px var(--primary-border-colour) inset;
}
#input-wrapper,
#output-wrapper {
height: calc(100% - var(--title-height));
}
#input-wrapper.show-tabs,
#output-wrapper.show-tabs {
height: calc(100% - var(--tab-height) - var(--title-height));
}
#output-loader {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
background-color: var(--secondary-background-colour);
opacity: 0;
visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.5s ease;
}
#output-loader-animation {
display: block;
position: absolute;
width: 60%;
height: 60%;
top: 10%;
transition: all 0.5s ease;
}
#output-loader .loading-msg {
opacity: 1;
font-family: var(--primary-font-family);
line-height: var(--primary-line-height);
color: var(--primary-font-colour);
left: unset;
top: 30%;
position: relative;
transition: all 0.5s ease;
}
.io-info {
margin-right: 18px;
margin-top: 1px;
height: 30px;
text-align: right;
line-height: 12px;
font-family: var(--fixed-width-font-family);
font-weight: normal;
font-size: 8pt;
display: flex;
align-items: center;
}
.dropping-file {
border: 5px dashed var(--drop-file-border-colour) !important;
}
#stale-indicator {
opacity: 1;
visibility: visible;
transition: margin 0s, opacity 0.3s;
margin-left: 5px;
cursor: help;
}
#stale-indicator i {
vertical-align: middle;
margin-bottom: 5px;
}
#magic {
opacity: 1;
visibility: visible;
transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s;
margin-left: 5px;
margin-bottom: 5px;
}
#magic.hidden,
#stale-indicator.hidden {
visibility: hidden;
transition: opacity 0.3s, margin 0.3s 0.3s, visibility 0.3s;
opacity: 0;
}
#magic.hidden {
margin-left: -32px;
}
#magic svg path {
fill: var(--primary-font-colour);
}
.pulse {
box-shadow: 0 0 0 0 rgba(90, 153, 212, .3);
animation: pulse 1.5s 1;
}
.pulse:hover {
animation-play-state: paused;
}
@keyframes pulse {
0% {
transform: scale(1);
}
70% {
transform: scale(1.1);
box-shadow: 0 0 0 20px rgba(90, 153, 212, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(90, 153, 212, 0);
}
}
#input-find-options,
#output-find-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
#input-tab-body .form-group.input-group,
#output-tab-body .form-group.input-group {
width: 70%;
float: left;
margin-bottom: 2rem;
}
.input-find-option .toggle-string {
width: 70%;
display: inline-block;
}
.input-find-option-append button {
border-top-right-radius: 4px;
background-color: var(--arg-background) !important;
margin: unset;
}
.input-find-option-append button:hover {
filter: brightness(97%);
}
.form-group.output-find-option {
width: 70%;
float: left;
}
#input-num-results-container,
#output-num-results-container {
width: 20%;
float: right;
margin: 0;
margin-left: 10%;
}
#input-find-options-checkboxes,
#output-find-options-checkboxes {
list-style: none;
padding: 0;
margin: auto;
overflow-x: auto;
overflow-y: hidden;
text-align: center;
width: fit-content;
}
#input-find-options-checkboxes li,
#output-find-options-checkboxes li {
display: flex;
flex-direction: row;
float: left;
padding: 10px;
text-align: center;
}
#input-search-results,
#output-search-results {
list-style: none;
width: 75%;
min-width: 200px;
margin-left: auto;
margin-right: auto;
}
#input-search-results li,
#output-search-results li {
padding-left: 5px;
padding-right: 5px;
padding-top: 10px;
padding-bottom: 10px;
text-align: center;
width: 100%;
color: var(--op-list-operation-font-colour);
background-color: var(--op-list-operation-bg-colour);
border-bottom: 2px solid var(--op-list-operation-border-colour);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#input-search-results li:first-of-type,
#output-search-results li:first-of-type {
border-top: 2px solid var(--op-list-operation-border-colour);
}
#input-search-results li:hover,
#output-search-results li:hover {
cursor: pointer;
filter: brightness(98%);
}
/* Highlighting */
.ͼ2.cm-focused .cm-selectionBackground {
background-color: var(--hl5);
}
.ͼ2 .cm-selectionBackground {
background-color: var(--hl1);
}
.ͼ1 .cm-selectionMatch {
background-color: var(--hl2);
}
.ͼ1.cm-focused .cm-cursor.cm-cursor-primary {
border-color: var(--primary-font-colour);
}
.ͼ1 .cm-cursor.cm-cursor-primary {
display: block;
border-color: var(--subtext-font-colour);
}
/* Status bar */
.cm-panel input::placeholder {
font-size: 12px !important;
}
.ͼ2 .cm-panels,
.ͼ2 .cm-side-panels {
background-color: var(--secondary-background-colour);
border-color: var(--primary-border-colour);
color: var(--primary-font-colour);
}
.cm-status-bar {
font-family: var(--fixed-width-font-family);
font-weight: normal;
font-size: 8pt;
margin: 0 5px;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
}
.cm-status-bar i {
font-size: 12pt;
vertical-align: middle;
margin-left: 8px;
}
.cm-status-bar>div>span:first-child i {
margin-left: 0;
}
.cm-status-bar .disabled {
background-color: unset !important;
cursor: not-allowed;
}
/* Dropup Button */
.cm-status-bar-select-btn {
border: none;
cursor: pointer;
}
/* The container <div> - needed to position the dropup content */
.cm-status-bar-select {
position: relative;
display: inline-block;
}
/* Dropup content (Hidden by Default) */
.cm-status-bar-select-content {
display: none;
position: absolute;
bottom: 20px;
right: 0;
background-color: #f1f1f1;
min-width: 200px;
box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropup */
.cm-status-bar-select-content a {
color: black;
padding: 2px 5px;
text-decoration: none;
display: block;
}
/* Change color of dropup links on hover */
.cm-status-bar-select-content a:hover {
background-color: #ddd
}
/* Change the background color of the dropup button when the dropup content is shown */
.cm-status-bar-select:hover .cm-status-bar-select-btn {
background-color: #f1f1f1;
}
/* The search field */
.cm-status-bar-filter-input {
box-sizing: border-box;
font-size: 12px;
padding-left: 10px !important;
border: none;
}
.cm-status-bar-filter-search {
border-top: 1px solid #ddd;
}
/* Show the dropup menu */
.cm-status-bar-select .show {
display: block;
}
.cm-status-bar-select-scroll {
overflow-y: auto;
max-height: 300px;
}
.chr-enc-value {
max-width: 150px;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
/* File details panel */
.cm-file-details {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;
padding-bottom: 21px;
height: 100%;
}
.file-details-toggle-shown,
.file-details-toggle-hidden {
width: 8px;
height: 40px;
border: 1px solid var(--secondary-border-colour);
position: absolute;
top: calc(50% - 20px);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--secondary-border-colour);
color: var(--subtext-font-colour);
z-index: 1;
}
.file-details-toggle-shown {
left: 0;
border-left: none;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.file-details-toggle-hidden {
left: -8px;
border-right: none;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.file-details-toggle-shown:hover,
.file-details-toggle-hidden:hover {
background-color: var(--primary-border-colour);
border-color: var(--primary-border-colour);
color: var(--primary-font-colour);
}
.file-details-heading {
font-weight: bold;
margin: 10px 0 10px 0;
}
.file-details-data {
text-align: left;
margin: 10px 2px;
}
.file-details-data td {
padding: 0 3px;
max-width: 130px;
min-width: 60px;
overflow: hidden;
vertical-align: top;
word-break: break-all;
}
.file-details-error {
color: #f00;
}
.file-details-thumbnail {
max-width: 180px;
}

View File

@ -14,17 +14,17 @@
margin-bottom: 20px;
}
#editable-favourites {
#edit-favourites-list {
margin: 10px;
border: 1px solid var(--op-list-operation-border-colour);
}
#editable-favourites c-operation-li > li {
#edit-favourites-list .operation {
border-left: none;
border-right: none;
}
#editable-favourites c-operation-list > li:last-child {
#edit-favourites-list .operation:last-child {
border-bottom: none;
}

View File

@ -0,0 +1,43 @@
/**
* Operation area styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
.op-list .operation {
color: var(--op-list-operation-font-colour);
background-color: var(--op-list-operation-bg-colour);
border-color: var(--op-list-operation-border-colour);
}
#search {
padding-left: 10px;
padding-right: 10px;
background-image:
linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px),
linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);
}
#edit-favourites {
float: right;
margin-top: -7px;
}
.favourites-hover {
color: var(--rec-list-operation-font-colour);
background-color: var(--rec-list-operation-bg-colour);
border: 2px dashed var(--rec-list-operation-font-colour) !important;
padding: 8px 8px 9px 8px;
}
#categories a {
color: var(--category-list-font-colour);
cursor: pointer;
}
#categories a:hover,
.op-list .operation:hover {
filter: brightness(98%);
}

View File

@ -0,0 +1,17 @@
/**
* Recipe area styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
#rec-list {
overflow: auto;
}
#rec-list .operation {
color: var(--rec-list-operation-font-colour);
background-color: var(--rec-list-operation-bg-colour);
border-color: var(--rec-list-operation-border-colour);
}

View File

@ -6,77 +6,66 @@
* @license Apache-2.0
*/
#banner { height: var(--banner-height); }
#operations { height: var(--operations-height); }
#controls { height: var(--controls-height); }
@media only screen and ( min-width: 1024px ) {
#controls { height: var(--desktop-controls-height); }
}
#banner,
#content-wrapper {
position: absolute;
width: 100%;
}
#content-wrapper {
top: 0;
bottom: 0;
body {
overflow: hidden;
}
#workspace-wrapper {
margin-top: var(--banner-height);
}
#controls {
position: fixed;
bottom: 0;
}
#recipe.maximised-pane,
#input.maximised-pane,
#output.maximised-pane {
position: fixed;
top: var(--banner-height);
#content-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
}
#workspace-wrapper {
position: absolute;
top: 30px;
bottom: 0;
width: 100%;
}
#recipe.maximised-pane #controls {
display: none;
div#operations,
div#recipe {
width: 50%;
height: 100%;
}
@media only screen and ( min-width: 1024px ) {
#recipe {
padding-bottom: var(--desktop-controls-height);
}
#recipe .list-area {
bottom: var(--desktop-controls-height);
}
#workspace-wrapper {
height: calc(100vh - var(--banner-height));
}
#operations-dropdown {
max-height: max-content;
overflow: revert;
}
#operations, #recipe, #IO {
height: 100%;
}
#operations {
overflow: auto;
}
#controls {
position: absolute;
}
div#input,
div#output {
width: 100%;
height: 50%;
}
.split {
box-sizing: border-box;
/* overflow: auto; */
/* Removed to enable Background Magic button pulse to overflow.
Replace this rule if it seems to be causing problems. */
position: relative;
}
#operations.split {
overflow: auto;
}
.split.split-horizontal, .gutter.gutter-horizontal {
height: 100%;
float: left;
}
.gutter {
background-color: var(--secondary-border-colour);
background-repeat: no-repeat;
background-position: 50%;
}
.gutter.gutter-horizontal {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+EFBhEwBDmIiYYAAAAjSURBVBjTYzxz5sx/BgYGBiYGKGB89+4dA4oIy71790aGGgCn+DBbOcAB0wAAAABJRU5ErkJggg==');
cursor: ew-resize;
}
.gutter.gutter-vertical {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAACCAYAAABPJGxCAAAABGdBTUEAALGOfPtRkwAACkNpQ0NQSUNDIFByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/vcxDeEAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH4QUGETI0LWfbqAAAACNJREFUCNdjPHPmzH8GBgYGJSUlRgYGBoZ79+7Rhc/EMEAAAHd6H2e3/71BAAAAAElFTkSuQmCC');
cursor: ns-resize;
}

View File

@ -1,40 +0,0 @@
/* Spin */
.spin {
animation-name: spin;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes spin {
0% {transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}
/* Pulse */
.pulse {
box-shadow: 0 0 0 0 rgba(90, 153, 212, .3);
animation: pulse 1.5s 1;
}
.pulse:hover {
animation-play-state: paused;
}
@keyframes pulse {
0% {
transform: scale(1);
}
70% {
transform: scale(1.1);
box-shadow: 0 0 0 20px rgba(90, 153, 212, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(90, 153, 212, 0);
}
}

View File

@ -46,21 +46,17 @@
/* Operation colours */
--op-list-operation-font-colour: #2f6d8c;
--op-list-operation-bg-colour: #e7f3f8;
--op-list-operation-font-colour: #3a87ad;
--op-list-operation-bg-colour: #d9edf7;
--op-list-operation-border-colour: #bce8f1;
--rec-list-operation-font-colour: #468847;
--rec-list-operation-bg-colour: #dff0d8;
--rec-list-operation-border-colour: #d3e8c0;
--focused-operation-font-color: #c09853;
--focused-operation-bg-colour: #fcf8e3;
--focused-operation-border-colour: #fbeed5;
--selected-operation-bg-colour: #d5ebf5;
--checkmark-color: var(--op-list-operation-font-colour);
--selected-operation-font-color: #c09853;
--selected-operation-bg-colour: #fcf8e3;
--selected-operation-border-colour: #fbeed5;
--breakpoint-font-colour: #b94a48;
--breakpoint-bg-colour: #f2dede;
@ -137,4 +133,3 @@
--input-highlight-colour: #1976d2;
--input-border-colour: #424242;
}

View File

@ -50,13 +50,9 @@
--rec-list-operation-bg-colour: #252525;
--rec-list-operation-border-colour: #444;
--focused-operation-font-color: #c5c5c5;
--focused-operation-bg-colour: #475663;
--focused-operation-border-colour: #444;
--selected-operation-font-color: #c5c5c5;
--selected-operation-bg-colour: #3f3f3f;
--checkmark-color: #47d047;
--selected-operation-border-colour: #444;
--breakpoint-font-colour: #ddd;
--breakpoint-bg-colour: #073655;

View File

@ -50,9 +50,9 @@
--rec-list-operation-bg-colour: purple;
--rec-list-operation-border-colour: green;
--focused-operation-font-color: white;
--focused-operation-bg-colour: pink;
--focused-operation-border-colour: blue;
--selected-operation-font-color: white;
--selected-operation-bg-colour: pink;
--selected-operation-border-colour: blue;
--breakpoint-font-colour: white;
--breakpoint-bg-colour: red;

View File

@ -69,9 +69,9 @@
--rec-list-operation-bg-colour: var(--base02);
--rec-list-operation-border-colour: var(--base01);
--focused-operation-font-color: var(--base1);
--focused-operation-bg-colour: var(--base02);
--focused-operation-border-colour: var(--base01);
--selected-operation-font-color: var(--base1);
--selected-operation-bg-colour: var(--base02);
--selected-operation-border-colour: var(--base01);
--breakpoint-font-colour: var(--sol-red);
--breakpoint-bg-colour: var(--base02);

View File

@ -69,9 +69,9 @@
--rec-list-operation-bg-colour: var(--base2);
--rec-list-operation-border-colour: var(--base1);
--focused-operation-font-color: var(--base01);
--focused-operation-bg-colour: var(--base2);
--focused-operation-border-colour: var(--base1);
--selected-operation-font-color: var(--base01);
--selected-operation-bg-colour: var(--base2);
--selected-operation-border-colour: var(--base1);
--breakpoint-font-colour: var(--sol-red);
--breakpoint-bg-colour: var(--base2);

View File

@ -1,22 +0,0 @@
:root {
/* Fixed heights */
--banner-height: 40px;
/* Mobile height */
--title-height: 40px;
--controls-height: 50px;
--operations-height: 80px;
/* Desktop specific height */
--desktop-controls-height: 70px;
--tab-height: 40px;
}
/**
* A note:
*
* Heights of #recipe, #input and #output are set programmatically
* in App.js
*/

View File

@ -59,6 +59,26 @@ body {
transform: rotate(180deg);
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background-color: var(--scrollbar-track);
}
::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-thumb);
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--scrollbar-hover);
}
::-webkit-scrollbar-corner {
background-color: var(--scrollbar-track);
}
/* Highlighters */
.hl1 { background-color: var(--hl1); }
@ -66,21 +86,3 @@ body {
.hl3 { background-color: var(--hl3); } /* Half-Life 3 confirmed :O */
.hl4 { background-color: var(--hl4); }
.hl5 { background-color: var(--hl5); }
/* Screen-bound UI visibility */
.desktop-only {
display: none;
}
@media only screen and ( min-width: 1024px ) {
.desktop-only {
display: inline-block;
}
}
@media only screen and ( min-width: 1024px ){
.mobile-only {
display: none;
}
}

View File

@ -17,7 +17,7 @@
}
.material-icons {
font-family: 'Material Icons', sans-serif;
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;

View File

@ -45,7 +45,6 @@ class StatusBarPanel {
dom.setAttribute("data-help", `This status bar provides information about data in the ${this.label}. Help topics are available for each of the components by activating help when hovering over them.`);
lhs.innerHTML = this.constructLHS();
rhs.innerHTML = this.constructRHS();
rhs.classList.add("rhs");
dom.appendChild(lhs);
dom.appendChild(rhs);
@ -289,10 +288,9 @@ class StatusBarPanel {
*/
updateSizing(view) {
const viewHeight = view.contentDOM.parentNode.clientHeight;
this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach(
el => {
el.style.maxHeight = window.innerWidth >= 1024 ? (viewHeight - 50) + "px" : "250px";
el.style.maxHeight = (viewHeight - 50) + "px";
}
);
}

View File

@ -26,7 +26,7 @@ class BindingsWaiter {
* Checks whether valid keyboard shortcut has been instated
*
* @fires Manager#statechange
* @param {Event} e
* @param {event} e
*/
parseInput(e) {
const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey;
@ -64,7 +64,7 @@ class BindingsWaiter {
case "KeyB": // Set breakpoint
e.preventDefault();
try {
elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint-icon")[0];
elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0];
if (elem.getAttribute("break") === "false") {
elem.setAttribute("break", "true"); // add break point if not already enabled
elem.classList.add("breakpoint-selected");

View File

@ -347,7 +347,7 @@ class ControlsWaiter {
/**
* Populates the bug report information box with useful technical info.
*
* @param {Event} e
* @param {event} e
*/
supportButtonClick(e) {
e.preventDefault();
@ -420,89 +420,16 @@ ${navigator.userAgent}
}
/**
* Maximise control button click handler.
*
* The buttons have IDs like 'maximise-input', 'maximise-output' etc. We grab the
* to-be-maximised pane ID itself by stripping the pane ID from the button ID.
*
* @param {Event} e
* Calculates the height of the controls area and adjusts the recipe
* height accordingly.
*/
onMaximiseButtonClick(e) {
// the target pane is not already maximised because it does not have the 'maximised-pane' class..
const maximise = !document.getElementById(e.currentTarget.id.replace("maximise-", "")).classList.contains("maximised-pane");
this.setPaneMaximised(e.currentTarget.id.replace("maximise-", ""), maximise);
calcControlsHeight() {
const controls = document.getElementById("controls"),
recList = document.getElementById("rec-list");
recList.style.bottom = controls.clientHeight + "px";
}
/**
* Handle the maximising ( and resetting to default state ) of
* panes.
*
* @param {string} paneId
* @param {boolean} maximise
*/
setPaneMaximised(paneId, maximise) {
const pane = document.getElementById(paneId);
const btn = document.getElementById(`maximise-${paneId}`);
this.setMaximiseControlButton(btn, maximise);
this.setPaneMaximisedClasses(pane, maximise);
if (maximise) {
pane.style.height = `${window.innerHeight - 40}px`;
} else {
if (this.app.isMobileView()) {
this.app.assignAvailableHeight();
}
}
}
/**
* Set and remove the appropriate classes on maximise / minimise actions
*
* @param {HTMLElement} pane
* @param {boolean} maximise
*/
setPaneMaximisedClasses(pane, maximise) {
if (maximise) {
pane.classList.add("top-zindex");
pane.classList.add("maximised-pane");
} else {
pane.classList.remove("top-zindex");
pane.classList.remove("maximised-pane");
}
}
/**
* Set the correct icon and data title attribute text based on
* the 'maximise' flag
*
* @param {HTMLElement} btn
* @param {boolean} maximise
*/
setMaximiseControlButton(btn, maximise) {
if (maximise) {
btn.querySelector("i").innerHTML = "fullscreen_exit";
btn.setAttribute("data-original-title", "Reset pane");
} else {
btn.querySelector("i").innerHTML = "fullscreen";
btn.setAttribute("data-original-title", "Maximise pane");
}
}
/**
* If #recipe is maximised and #rec-list is empty, clicking / tapping on
* the ( empty ) list will open #operations-dropdown to help guide users
* with that they are supposed to do
*/
onMaximisedRecipeClick() {
// if #recipe is maximised & rec-list is empty on mobile UI
if (this.app.isMobileView() && document.querySelector("#recipe.maximised-pane") && document.querySelectorAll("#rec-list > c-recipe-li").length === 0) {
// close max pane and display the expanded #operations-dropdown
this.setPaneMaximised("recipe", false);
this.manager.ops.openOpsDropdown();
}
}
}
export default ControlsWaiter;

View File

@ -654,7 +654,7 @@ class InputWaiter {
/**
* Handler for file details toggle clicks
* @param {Event} e
* @param {event} e
*/
toggleFileDetails(e) {
$("[data-toggle='tooltip']").tooltip("hide");
@ -838,7 +838,7 @@ class InputWaiter {
* Updates the value stored in the inputWorker
* Debounces the input so we don't call autobake too often.
*
* @param {Event} e
* @param {event} e
*
* @fires Manager#statechange
*/
@ -870,7 +870,7 @@ class InputWaiter {
* Handler for input dragover events.
* Gives the user a visual cue to show that items can be dropped here.
*
* @param {Event} e
* @param {event} e
*/
inputDragover(e) {
// This will be set if we're dragging an operation
@ -886,7 +886,7 @@ class InputWaiter {
* Handler for input dragleave events.
* Removes the visual cue.
*
* @param {Event} e
* @param {event} e
*/
inputDragleave(e) {
e.stopPropagation();
@ -903,7 +903,7 @@ class InputWaiter {
* Handler for input drop events.
* Loads the dragged data.
*
* @param {Event} e
* @param {event} e
*/
async inputDrop(e) {
// This will be set if we're dragging an operation
@ -1013,7 +1013,7 @@ class InputWaiter {
* Handler for open input button events
* Loads the opened data into the input textarea
*
* @param {Event} e
* @param {event} e
*/
inputOpen(e) {
e.preventDefault();
@ -1178,7 +1178,7 @@ class InputWaiter {
/**
* Handler for clicking on a tab
*
* @param {Event} mouseEvent
* @param {event} mouseEvent
*/
changeTabClick(mouseEvent) {
if (!mouseEvent.target) return;
@ -1361,7 +1361,7 @@ class InputWaiter {
/**
* Handler for clicking on a remove tab button
*
* @param {Event} mouseEvent
* @param {event} mouseEvent
*/
removeTabClick(mouseEvent) {
if (!mouseEvent.target) {
@ -1376,7 +1376,7 @@ class InputWaiter {
/**
* Handler for scrolling on the input tabs area
*
* @param {Event} wheelEvent
* @param {event} wheelEvent
*/
scrollTab(wheelEvent) {
wheelEvent.preventDefault();
@ -1509,7 +1509,7 @@ class InputWaiter {
/**
* Handle when an option in the filter drop down box is clicked
*
* @param {Event} mouseEvent
* @param {event} mouseEvent
*/
filterOptionClick(mouseEvent) {
document.getElementById("input-filter-button").innerText = mouseEvent.target.innerText;
@ -1542,7 +1542,7 @@ class InputWaiter {
/**
* Handler for clicking on a filter result
*
* @param {Event} e
* @param {event} e
*/
filterItemClick(e) {
if (!e.target) return;

View File

@ -4,8 +4,10 @@
* @license Apache-2.0
*/
import HTMLOperation from "../HTMLOperation.mjs";
import Sortable from "sortablejs";
import {fuzzyMatch, calcMatchRanges} from "../../core/lib/FuzzyMatch.mjs";
import {COperationList} from "../components/c-operation-list.mjs";
/**
* Waiter to handle events related to the operations.
@ -23,6 +25,7 @@ class OperationsWaiter {
this.manager = manager;
this.options = {};
this.removeIntent = false;
}
@ -30,56 +33,43 @@ class OperationsWaiter {
* Handler for search events.
* Finds operations which match the given search term and displays them under the search box.
*
* @param {Event} e
* @param {event} e
*/
searchOperations(e) {
let ops, focused;
if (e.type === "keyup") {
const searchResults = document.getElementById("search-results");
let ops, selected;
this.openOpsDropdown();
if (e.target.value.length !== 0) {
this.app.setElementVisibility(searchResults, true);
}
}
if (e.key === "Enter") { // Search or Return ( enter )
if (e.type === "search" || e.keyCode === 13) { // Search or Return
e.preventDefault();
ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li");
ops = document.querySelectorAll("#search-results li");
if (ops.length) {
focused = this.getFocusedOp(ops);
if (focused > -1) {
this.manager.recipe.addOperation(ops[focused].getAttribute("data-name"));
selected = this.getSelectedOp(ops);
if (selected > -1) {
this.manager.recipe.addOperation(ops[selected].innerHTML);
}
}
}
if (e.type === "click" && !e.target.value.length) {
this.openOpsDropdown();
} else if (e.key === "Escape") { // Escape
this.closeOpsDropdown();
} else if (e.key === "ArrowDown") { // Down
if (e.keyCode === 40) { // Down
e.preventDefault();
ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li");
ops = document.querySelectorAll("#search-results li");
if (ops.length) {
focused = this.getFocusedOp(ops);
if (focused > -1) {
ops[focused].classList.remove("focused-op");
selected = this.getSelectedOp(ops);
if (selected > -1) {
ops[selected].classList.remove("selected-op");
}
if (focused === ops.length-1) focused = -1;
ops[focused+1].classList.add("focused-op");
if (selected === ops.length-1) selected = -1;
ops[selected+1].classList.add("selected-op");
}
} else if (e.key === "ArrowUp") { // Up
} else if (e.keyCode === 38) { // Up
e.preventDefault();
ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li");
ops = document.querySelectorAll("#search-results li");
if (ops.length) {
focused = this.getFocusedOp(ops);
if (focused > -1) {
ops[focused].classList.remove("focused-op");
selected = this.getSelectedOp(ops);
if (selected > -1) {
ops[selected].classList.remove("selected-op");
}
if (focused === 0) focused = ops.length;
ops[focused-1].classList.add("focused-op");
if (selected === 0) selected = ops.length;
ops[selected-1].classList.add("selected-op");
}
} else {
const searchResultsEl = document.getElementById("search-results");
@ -94,23 +84,14 @@ class OperationsWaiter {
}
$("#categories .show").collapse("hide");
if (str) {
const matchedOps = this.filterOperations(str, true);
const matchedOpsHtml = matchedOps
.map(v => v.toStubHtml())
.join("");
const cOpList = new COperationList(
this.app,
matchedOps,
true,
false,
true,
{
class: "check-icon",
innerText: "check"
}
);
searchResultsEl.append(cOpList);
searchResultsEl.innerHTML = matchedOpsHtml;
searchResultsEl.dispatchEvent(this.manager.oplistcreate);
}
}
}
@ -120,17 +101,17 @@ class OperationsWaiter {
* Filters operations based on the search string and returns the matching ones.
*
* @param {string} searchStr
* @param {boolean} highlight - Whether to highlight the matching string in the operation
* @param {boolean} highlight - Whether or not to highlight the matching string in the operation
* name and description
* @returns {[[string, number[]]]}
* @returns {string[]}
*/
filterOperations(searchStr, highlight) {
filterOperations(inStr, highlight) {
const matchedOps = [];
const matchedDescs = [];
// Create version with no whitespace for the fuzzy match
// Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
const inStrNWS = searchStr.replace(/\s/g, "");
const inStrNWS = inStr.replace(/\s/g, "");
for (const opName in this.app.operations) {
const op = this.app.operations[opName];
@ -139,13 +120,18 @@ class OperationsWaiter {
const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
// Match description based on exact match
const descPos = op.description.toLowerCase().indexOf(searchStr.toLowerCase());
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
if (nameMatch || descPos >= 0) {
const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
if (highlight) {
operation.highlightSearchStrings(calcMatchRanges(idxs), [[descPos, inStr.length]]);
}
if (nameMatch) {
matchedOps.push([[opName, calcMatchRanges(idxs)], score]);
matchedOps.push([operation, score]);
} else {
matchedDescs.push([opName]);
matchedDescs.push(operation);
}
}
}
@ -158,101 +144,144 @@ class OperationsWaiter {
/**
* Finds the operation which has been focused on using keyboard shortcuts. This will have the class
* 'focused-op' set. Returns the index of the operation within the given list.
* Finds the operation which has been selected using keyboard shortcuts. This will have the class
* 'selected-op' set. Returns the index of the operation within the given list.
*
* @param {element[]} ops
* @returns {number}
*/
getFocusedOp(ops) {
getSelectedOp(ops) {
for (let i = 0; i < ops.length; i++) {
if (ops[i].classList.contains("focused-op")) {
if (ops[i].classList.contains("selected-op")) {
return i;
}
}
return -1;
}
/**
* Handler for oplistcreate events.
*
* @listens Manager#oplistcreate
* @param {event} e
*/
opListCreate(e) {
this.manager.recipe.createSortableSeedList(e.target);
this.enableOpsListPopovers(e.target);
}
/**
* Sets up popovers, allowing the popover itself to gain focus which enables scrolling
* and other interactions.
*
* @param {Element} el - The element to start selecting from
*/
enableOpsListPopovers(el) {
$(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]")
.popover({trigger: "manual"})
.on("mouseenter", function(e) {
if (e.buttons > 0) return; // Mouse button held down - likely dragging an operation
const _this = this;
$(this).popover("show");
$(".popover").on("mouseleave", function () {
$(_this).popover("hide");
});
}).on("mouseleave", function () {
const _this = this;
setTimeout(function() {
// Determine if the popover associated with this element is being hovered over
if ($(_this).data("bs.popover") &&
($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) {
$(_this).popover("hide");
}
}, 50);
});
}
/**
* Handler for operation doubleclick events.
* Adds the operation to the recipe and auto bakes.
*
* @param {event} e
*/
operationDblclick(e) {
const li = e.target;
this.manager.recipe.addOperation(li.textContent);
}
/**
* Handler for edit favourites click events.
* Displays the 'Edit favourites' modal and handles the c-operation-list in the modal
* Sets up the 'Edit favourites' pane and displays it.
*
* @param {Event} e
* @param {event} e
*/
editFavouritesClick(e) {
const div = document.getElementById("editable-favourites");
e.preventDefault();
e.stopPropagation();
// First remove the existing c-operation-list if there already was one
if (div.querySelector("c-operation-list")) {
div.removeChild(div.querySelector("c-operation-list"));
// Add favourites to modal
const favCat = this.app.categories.filter(function(c) {
return c.name === "Favourites";
})[0];
let html = "";
for (let i = 0; i < favCat.ops.length; i++) {
const opName = favCat.ops[i];
const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
html += operation.toStubHtml(true);
}
// Get list of Favourite operation names
const favCatConfig = this.app.categories.find(catConfig => catConfig.name === "Favourites");
const editFavouritesList = document.getElementById("edit-favourites-list");
editFavouritesList.innerHTML = html;
this.removeIntent = false;
if (favCatConfig !== undefined) {
const opList = new COperationList(
this.app,
favCatConfig.ops.map(op => [op]),
false,
true,
false,
{
class: "remove-icon",
innerText: "delete"
},
);
const editableList = Sortable.create(editFavouritesList, {
filter: ".remove-icon",
onFilter: function (evt) {
const el = editableList.closest(evt.item);
if (el && el.parentNode) {
$(el).popover("dispose");
el.parentNode.removeChild(el);
}
},
onEnd: function(evt) {
if (this.removeIntent) {
$(evt.item).popover("dispose");
evt.item.remove();
}
}.bind(this),
});
div.appendChild(opList);
}
Sortable.utils.on(editFavouritesList, "dragleave", function() {
this.removeIntent = true;
}.bind(this));
if (!this.app.isMobileView()) {
$("#editable-favourites [data-toggle=popover]").popover();
}
Sortable.utils.on(editFavouritesList, "dragover", function() {
this.removeIntent = false;
}.bind(this));
$("#edit-favourites-list [data-toggle=popover]").popover();
$("#favourites-modal").modal();
}
/**
* Open operations dropdown
*/
openOpsDropdown() {
// the 'close' ( dropdown ) icon in Operations component mobile UI
const closeOpsDropdownIcon = document.getElementById("close-ops-dropdown-icon");
const categories = document.getElementById("categories");
this.app.setElementVisibility(categories, true);
this.app.setElementVisibility(closeOpsDropdownIcon, true);
}
/**
* Hide any operation lists ( #categories or #search-results ) and the close-operations-dropdown
* icon itself, clear any search input
*/
closeOpsDropdown() {
const search = document.getElementById("search");
// if any input remains in #search, clear it
if (search.value.length) {
search.value = "";
}
this.app.setElementVisibility(document.getElementById("categories"), false);
this.app.setElementVisibility(document.getElementById("search-results"), false);
this.app.setElementVisibility(document.getElementById("close-ops-dropdown-icon"), false);
}
/**
* Handler for save favourites click events.
* Saves the selected favourites and reloads them.
*/
saveFavouritesClick() {
const listItems = document.querySelectorAll("#editable-favourites c-operation-li > li");
const favourites = Array.from(listItems, li => li.getAttribute("data-name"));
const favs = document.querySelectorAll("#edit-favourites-list li");
const favouritesList = Array.from(favs, e => e.childNodes[0].textContent);
this.app.updateFavourites(favourites, true);
this.app.saveFavourites(favouritesList);
this.app.loadFavourites();
this.app.populateOperationsList();
this.manager.recipe.initialiseOperationDragNDrop();
}
@ -263,6 +292,7 @@ class OperationsWaiter {
resetFavouritesClick() {
this.app.resetFavourites();
}
}
export default OperationsWaiter;

View File

@ -57,7 +57,7 @@ class OptionsWaiter {
* Handler for options click events.
* Displays the options pane.
*
* @param {Event} e
* @param {event} e
*/
optionsClick(e) {
e.preventDefault();
@ -77,7 +77,7 @@ class OptionsWaiter {
/**
* Handler for switch change events.
*
* @param {Event} e
* @param {event} e
*/
switchChange(e) {
const el = e.target;
@ -91,7 +91,7 @@ class OptionsWaiter {
/**
* Handler for number change events.
*
* @param {Event} e
* @param {event} e
*/
numberChange(e) {
const el = e.target;
@ -105,7 +105,7 @@ class OptionsWaiter {
/**
* Handler for select change events.
*
* @param {Event} e
* @param {event} e
*/
selectChange(e) {
const el = e.target;

View File

@ -982,7 +982,7 @@ class OutputWaiter {
/**
* Handler for changing tabs event
*
* @param {Event} mouseEvent
* @param {event} mouseEvent
*/
changeTabClick(mouseEvent) {
if (!mouseEvent.target) return;
@ -996,7 +996,7 @@ class OutputWaiter {
/**
* Handler for scrolling on the output tabs area
*
* @param {Event} wheelEvent
* @param {event} wheelEvent
*/
scrollTab(wheelEvent) {
wheelEvent.preventDefault();
@ -1400,6 +1400,30 @@ class OutputWaiter {
switchButton.firstElementChild.innerHTML = "open_in_browser";
}
/**
* Handler for maximise output click events.
* Resizes the output frame to be as large as possible, or restores it to its original size.
*/
maximiseOutputClick(e) {
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
document.body.classList.add("output-maximised");
this.app.initialiseSplitter(true);
this.app.columnSplitter.collapse(0);
this.app.columnSplitter.collapse(1);
this.app.ioSplitter.collapse(0);
$(el).attr("data-original-title", "Restore output pane");
el.querySelector("i").innerHTML = "fullscreen_exit";
} else {
document.body.classList.remove("output-maximised");
$(el).attr("data-original-title", "Maximise output pane");
el.querySelector("i").innerHTML = "fullscreen";
this.app.initialiseSplitter(false);
this.app.resetLayout();
}
}
/**
* Handler for find tab button clicked
@ -1510,7 +1534,7 @@ class OutputWaiter {
* Handler for clicking on a filter result.
* Changes to the clicked output
*
* @param {Event} e
* @param {event} e
*/
filterItemClick(e) {
if (!e.target) return;

View File

@ -4,10 +4,10 @@
* @license Apache-2.0
*/
import HTMLOperation from "../HTMLOperation.mjs";
import Sortable from "sortablejs";
import Utils from "../../core/Utils.mjs";
import {escapeControlChars} from "../utils/editorUtils.mjs";
import {CRecipeLi} from "../components/c-recipe-li.mjs";
/**
@ -29,45 +29,30 @@ class RecipeWaiter {
/**
* Sets up the drag and drop capability for recipe-list
* Sets up the drag and drop capability for operations in the operations and recipe areas.
*/
initDragAndDrop() {
initialiseOperationDragNDrop() {
const recList = document.getElementById("rec-list");
let swapThreshold, animation, delay;
// tweak these values for better user experiences per device type and UI
if (this.app.isMobileView()) {
swapThreshold = 0.30;
animation = 300;
delay = 50;
} else {
swapThreshold = 1;
animation = 100;
delay = 0;
}
// Recipe list
Sortable.create(recList, {
group: "recipe",
sort: true,
draggable: "c-recipe-li",
swapThreshold: swapThreshold,
animation: animation,
delay: delay,
animation: 0,
delay: 0,
filter: ".arg",
preventOnFilter: false,
setData: function(dataTransfer, dragEl) {
dataTransfer.setData("Text", dragEl.querySelector("li").getAttribute("data-name"));
dataTransfer.setData("Text", dragEl.querySelector(".op-title").textContent);
},
onEnd: function(e) {
onEnd: function(evt) {
if (this.removeIntent) {
e.item.remove();
e.target.dispatchEvent(this.manager.operationremove);
evt.item.remove();
evt.target.dispatchEvent(this.manager.operationremove);
}
}.bind(this),
onSort: function(e) {
if (e.from.id === "rec-list") {
onSort: function(evt) {
if (evt.from.id === "rec-list") {
document.dispatchEvent(this.manager.statechange);
}
}.bind(this)
@ -95,12 +80,82 @@ class RecipeWaiter {
document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this));
}
/**
* 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,
},
sort: false,
setData: function(dataTransfer, dragEl) {
dataTransfer.setData("Text", dragEl.textContent);
},
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
* at the appropriate place and initialises it.
*
* @fires Manager#operationadd
* @param {event} evt
*/
opSortEnd(evt) {
if (this.removeIntent && evt.item.parentNode.id === "rec-list") {
evt.item.remove();
return;
}
// 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.enableOpsListPopovers(enableOpsElement);
if (evt.item.parentNode.id !== "rec-list") {
return;
}
this.buildRecipeOperation(evt.item);
evt.item.dispatchEvent(this.manager.operationadd);
}
/**
* Handler for favourite dragover events.
* If the element being dragged is an operation, displays a visual cue so that the user knows it can
* be dropped here.
*
* @param {Event} e
* @param {event} e
*/
favDragover(e) {
if (e.dataTransfer.effectAllowed !== "move")
@ -125,7 +180,7 @@ class RecipeWaiter {
* Handler for favourite dragleave events.
* Removes the visual cue.
*
* @param {Event} e
* @param {event} e
*/
favDragleave(e) {
e.stopPropagation();
@ -138,7 +193,7 @@ class RecipeWaiter {
* Handler for favourite drop events.
* Adds the dragged operation to the favourites list.
*
* @param {Event} e
* @param {event} e
*/
favDrop(e) {
e.stopPropagation();
@ -161,51 +216,124 @@ class RecipeWaiter {
}
/**
* Handler for disable click events.
* Updates the icon status.
*
* @fires Manager#statechange
* @param {event} e
*/
disableClick(e) {
const icon = e.target;
if (icon.getAttribute("disabled") === "false") {
icon.setAttribute("disabled", "true");
icon.classList.add("disable-icon-selected");
icon.parentNode.parentNode.classList.add("disabled");
} else {
icon.setAttribute("disabled", "false");
icon.classList.remove("disable-icon-selected");
icon.parentNode.parentNode.classList.remove("disabled");
}
this.app.progress = 0;
window.dispatchEvent(this.manager.statechange);
}
/**
* Handler for breakpoint click events.
* Updates the icon status.
*
* @fires Manager#statechange
* @param {event} e
*/
breakpointClick(e) {
const bp = e.target;
if (bp.getAttribute("break") === "false") {
bp.setAttribute("break", "true");
bp.classList.add("breakpoint-selected");
} else {
bp.setAttribute("break", "false");
bp.classList.remove("breakpoint-selected");
}
window.dispatchEvent(this.manager.statechange);
}
/**
* Handler for operation doubleclick events.
* Removes the operation from the recipe and auto bakes.
*
* @fires Manager#statechange
* @param {event} e
*/
operationDblclick(e) {
e.target.remove();
this.opRemove(e);
}
/**
* Handler for operation child doubleclick events.
* Removes the operation from the recipe.
*
* @fires Manager#statechange
* @param {event} e
*/
operationChildDblclick(e) {
e.target.parentNode.remove();
this.opRemove(e);
}
/**
* Generates a configuration object to represent the current recipe.
*
* @returns {Object[]} recipeConfig - The recipe configuration
* @returns {recipeConfig}
*/
getConfig() {
const config = [];
let ingredients, args, disableIcon, breakPointIcon, item;
let ingredients, ingList, disabled, bp, item;
const operations = document.querySelectorAll("#rec-list li.operation");
for (let i = 0; i < operations.length; i++) {
ingredients = [];
disableIcon = operations[i].querySelector(".disable-icon");
breakPointIcon = operations[i].querySelector(".breakpoint-icon");
args = operations[i].querySelectorAll(".arg");
disabled = operations[i].querySelector(".disable-icon");
bp = operations[i].querySelector(".breakpoint");
ingList = operations[i].querySelectorAll(".arg");
for (let j = 0; j < args.length; j++) {
if (args[j].getAttribute("type") === "checkbox") {
for (let j = 0; j < ingList.length; j++) {
if (ingList[j].getAttribute("type") === "checkbox") {
// checkbox
ingredients[j] = args[j].checked;
} else if (args[j].classList.contains("toggle-string")) {
ingredients[j] = ingList[j].checked;
} else if (ingList[j].classList.contains("toggle-string")) {
// toggleString
ingredients[j] = {
option: args[j].parentNode.parentNode.querySelector("button").textContent,
string: args[j].value
option: ingList[j].parentNode.parentNode.querySelector("button").textContent,
string: ingList[j].value
};
} else if (args[j].getAttribute("type") === "number") {
} else if (ingList[j].getAttribute("type") === "number") {
// number
ingredients[j] = parseFloat(args[j].value);
ingredients[j] = parseFloat(ingList[j].value);
} else {
// all others
ingredients[j] = args[j].value;
ingredients[j] = ingList[j].value;
}
}
item = {
op: operations[i].getAttribute("data-name"),
op: operations[i].querySelector(".op-title").textContent,
args: ingredients
};
if (disableIcon && disableIcon.getAttribute("disabled") === "true") {
if (disabled && disabled.getAttribute("disabled") === "true") {
item.disabled = true;
}
if (breakPointIcon && breakPointIcon.getAttribute("break") === "true") {
if (bp && bp.getAttribute("break") === "true") {
item.breakpoint = true;
}
@ -235,28 +363,46 @@ class RecipeWaiter {
/**
* Adds the specified operation to the recipe
* 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
*/
buildRecipeOperation(el) {
const opName = el.textContent;
const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
el.innerHTML = op.toFullHtml();
if (this.app.operations[opName].flowControl) {
el.classList.add("flow-control-op");
}
// Disable auto-bake if this is a manual op
if (op.manualBake && this.app.autoBake_) {
this.manager.controls.setAutoBake(false);
this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000);
}
}
/**
* Adds the specified operation to the recipe.
*
* @fires Manager#operationadd
* @fires Manager#statechange
* @param {string} name - The name of the operation to add
* @param {number} index - The index where the operation should be displayed
* @returns {element}
*/
addOperation(name, index = undefined) {
const item = new CRecipeLi(this.app, name, this.app.operations[name].args);
addOperation(name) {
const item = document.createElement("li");
const recipeList = document.getElementById("rec-list");
if (index !== undefined) {
recipeList.insertBefore(item, recipeList.children[index + 1]);
} else {
recipeList.appendChild(item);
}
item.classList.add("operation");
item.innerHTML = name;
this.buildRecipeOperation(item);
document.getElementById("rec-list").appendChild(item);
$(item).find("[data-toggle='tooltip']").tooltip();
item.dispatchEvent(this.manager.operationadd);
document.dispatchEvent(this.app.manager.statechange);
return item;
}
@ -272,8 +418,6 @@ class RecipeWaiter {
recList.removeChild(recList.firstChild);
}
recList.dispatchEvent(this.manager.operationremove);
window.dispatchEvent(this.app.manager.statechange);
}
@ -281,7 +425,7 @@ class RecipeWaiter {
* Handler for operation dropdown events from toggleString arguments.
* Sets the selected option as the name of the button.
*
* @param {Event} e
* @param {event} e
*/
dropdownToggleClick(e) {
e.stopPropagation();
@ -317,21 +461,34 @@ class RecipeWaiter {
*
* @listens Manager#operationadd
* @fires Manager#statechange
* @param {Event} e
* @param {event} e
*/
opAdd(e) {
console.log(e);
log.debug(`'${e.target.querySelector("li").getAttribute("data-name")}' added to recipe`);
log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`);
this.triggerArgEvents(e.target);
window.dispatchEvent(this.manager.statechange);
}
/**
* Handler for operationremove events.
*
* @listens Manager#operationremove
* @fires Manager#statechange
* @param {event} e
*/
opRemove(e) {
log.debug("Operation removed from recipe");
window.dispatchEvent(this.manager.statechange);
}
/**
* Handler for text argument dragover events.
* Gives the user a visual cue to show that items can be dropped here.
*
* @param {Event} e
* @param {event} e
*/
textArgDragover (e) {
// This will be set if we're dragging an operation
@ -348,7 +505,7 @@ class RecipeWaiter {
* Handler for text argument dragleave events.
* Removes the visual cue.
*
* @param {Event} e
* @param {event} e
*/
textArgDragLeave (e) {
e.stopPropagation();
@ -361,7 +518,7 @@ class RecipeWaiter {
* Handler for text argument drop events.
* Loads the dragged data into the argument textarea.
*
* @param {Event} e
* @param {event} e
*/
textArgDrop(e) {
// This will be set if we're dragging an operation
@ -420,6 +577,32 @@ class RecipeWaiter {
op.insertAdjacentHTML("beforeend", registerListEl);
}
/**
* Adjusts the number of ingredient columns as the width of the recipe changes.
*/
adjustWidth() {
const recList = document.getElementById("rec-list");
// Hide Chef icon on Bake button if the page is compressed
const bakeIcon = document.querySelector("#bake img");
if (recList.clientWidth < 370) {
// Hide Chef icon on Bake button
bakeIcon.style.display = "none";
} else {
bakeIcon.style.display = "inline-block";
}
// Scale controls to fit pane width
const controls = document.getElementById("controls");
const controlsContent = document.getElementById("controls-content");
const scale = (controls.clientWidth - 1) / controlsContent.scrollWidth;
controlsContent.style.transform = `scale(${scale})`;
}
}
export default RecipeWaiter;

View File

@ -4,6 +4,8 @@
* @license Apache-2.0
*/
import { debounce } from "../../core/Utils.mjs";
/**
* Waiter to handle events related to the window object.
*/
@ -13,64 +15,19 @@ class WindowWaiter {
* WindowWaiter constructor.
*
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(app, manager) {
constructor(app) {
this.app = app;
this.manager = manager;
}
/**
* Handler for window resize events.
* Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause
* continuous resetting).
*/
windowResize() {
if (!this.app.isMobileView()) {
this.onResizeToDesktop();
} else {
this.onResizeToMobile();
}
// #output can be maximised on all screen sizes, so if it was open while resizing,
// it can be kept maximised until minimised manually
if (document.getElementById("output").classList.contains("maximised-pane")) {
this.manager.controls.setPaneMaximised("output", true);
}
}
/**
* Set desktop UI and close #recipe / #input maximised panes if there were any.
* Correct the height of #recipe
*/
onResizeToDesktop() {
this.app.setDesktopUI();
// enable popovers on li.operation elements
$(document.querySelectorAll("li.operation")).popover("enable");
// if a window is resized past breakpoint while #recipe or #input is maximised, close these maxed panes
["recipe", "input"].forEach(paneId => this.manager.controls.setPaneMaximised(paneId, false));
// to prevent #recipe from keeping the height set in divideAvailableSpace
document.getElementById("recipe").style.height = "100%";
}
/**
* Set mobile UI and close any maximised panes if there were any
*/
onResizeToMobile() {
this.app.setMobileUI();
// disable app popovers on li.operation elements
$(document.querySelectorAll("li.operation")).popover("disable");
// when mobile devices' keyboards pop up, it triggers a window resize event. Here
// we keep the maximised panes open until the minimise button is clicked / tapped
["recipe", "input", "output"]
.map(paneId => document.getElementById(paneId))
.filter(pane => pane.classList.contains("maximised-pane"))
.forEach(pane => this.manager.controls.setPaneMaximised(pane.id, true));
debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])();
}

View File

@ -9,10 +9,9 @@
const utils = require("./browserUtils.js");
module.exports = {
// desktop UI
before: browser => {
browser
.resizeWindow(1024, 800)
.resizeWindow(1280, 800)
.url(browser.launchUrl);
},
@ -27,18 +26,12 @@ module.exports = {
browser.useCss();
// Check that various important elements are loaded
browser.expect.element("#operations").to.be.visible;
browser.expect.element("#operations-dropdown").to.be.visible;
browser.expect.element("#search").to.be.visible;
browser.expect.element("#categories").to.be.visible;
browser.expect.element("c-category-list").to.be.visible;
browser.expect.element("c-category-li").to.be.present;
browser.expect.element("c-operation-list").to.be.present;
browser.expect.element("c-operation-li").to.be.present;
browser.expect.element("#recipe").to.be.visible;
browser.expect.element("#input").to.be.present;
browser.expect.element("#output").to.be.present;
browser.expect.element(".op-list").to.be.present;
browser.expect.element("#rec-list").to.be.visible;
browser.expect.element("#controls").to.be.visible;
browser.expect.element("#input").to.be.visible;
browser.expect.element("#output").to.be.visible;
browser.expect.element("#input-text").to.be.visible;
browser.expect.element("#output-text").to.be.visible;
},
@ -46,27 +39,27 @@ module.exports = {
"Operations loaded": browser => {
browser.useXpath();
// Check that an operation in every category has been populated
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='To Base64']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='To Binary']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='AES Decrypt']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='PEM to Hex']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Power Set']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Parse IP range']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Remove Diacritics']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Sort']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='To UNIX Timestamp']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Extract dates']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Gzip']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Keccak']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='JSON Beautify']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Detect File Type']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Play Media']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Disassemble x86']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation')]/span[text()='Register']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='To Base64']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='To Binary']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='AES Decrypt']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='PEM to Hex']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Power Set']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Parse IP range']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Remove Diacritics']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Sort']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='To UNIX Timestamp']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Extract dates']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Gzip']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Keccak']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='JSON Beautify']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Detect File Type']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present;
browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present;
},
"Recipe can be run": browser => {
const toHex = "//li[contains(@class, 'operation')]/span[text()='To Hex']";
const toHex = "//li[contains(@class, 'operation') and text()='To Hex']";
const op = "#rec-list .operation .op-title";
// Check that operation is visible
@ -190,7 +183,7 @@ module.exports = {
"Move around the UI": browser => {
const otherCat = "//a[contains(@class, 'category-title') and contains(@data-target, '#catOther')]",
genUUID = "//li[contains(@class, 'operation')]/span[text()='Generate UUID']";
genUUID = "//li[contains(@class, 'operation') and text()='Generate UUID']";
browser.useXpath();
@ -227,7 +220,7 @@ module.exports = {
.clearValue("#search")
.setValue("#search", "md5")
.useXpath()
.waitForElementVisible("//div[@id='search-results']//strong[text()='MD5']", 1000);
.waitForElementVisible("//ul[@id='search-results']//b[text()='MD5']", 1000);
},
"Alert bar": browser => {

View File

@ -1,154 +0,0 @@
/**
* Tests to ensure that the app loads correctly in a reasonable time and that operations can be run.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
module.exports = {
// desktop UI
before: browser => {
browser
.resizeWindow(500, 800)
.url(browser.launchUrl);
},
"Loading screen": browser => {
// Check that the loading screen appears and then disappears within a reasonable time
browser
.waitForElementVisible("#preloader", 300)
.waitForElementNotPresent("#preloader", 10000);
},
"App loaded": browser => {
browser.useCss();
// Check that various important elements are loaded
browser.expect.element("#operations").to.be.visible;
browser.expect.element("#operations-dropdown").to.be.present;
browser.expect.element("#search").to.be.visible;
browser.expect.element("#categories").to.be.present;
browser.expect.element("c-category-list").to.be.present;
browser.expect.element("c-category-li").to.be.present;
browser.expect.element("c-operation-list").to.be.present;
browser.expect.element("c-operation-li").to.be.present;
browser.expect.element("#recipe").to.be.visible;
browser.expect.element("#rec-list").to.be.visible;
browser.expect.element("#controls").to.be.visible;
browser.expect.element("#input").to.be.visible;
browser.expect.element("#output").to.be.visible;
browser.expect.element("#input-text").to.be.visible;
browser.expect.element("#output-text").to.be.visible;
},
"Recipe can be run": browser => {
const toHex = "//li[contains(@class, 'operation')]/span[text()='To Hex']";
const op = "#rec-list .operation .op-title";
browser
.useCss()
.click("#search")
.expect.element("#categories").to.be.visible;
// Check that operation is visible
browser
.useXpath()
.expect.element(toHex).to.be.visible;
// Add it to the recipe by double clicking
browser
.useXpath()
.moveToElement(toHex, 10, 10)
.doubleClick("xpath", toHex)
.expect.element("//li[contains(@class, 'selected')]").to.be.visible;
browser
.useCss()
.click("#close-ops-dropdown-icon")
.waitForElementNotVisible("#categories", 1000);
// Confirm that it has been added to the recipe
browser
.useCss()
.waitForElementVisible(op, 100)
.expect.element(op).text.to.contain("To Hex");
// Enter input
browser
.useCss()
.sendKeys("#input-text .cm-content", "Don't Panic.")
.pause(1000)
.click("#bake");
// Check output
browser
.useCss()
.waitForElementNotVisible("#stale-indicator", 1000)
.expect.element("#output-text .cm-content").text.that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e");
// Clear recipe
browser
.useCss()
.moveToElement(op, 10, 10)
.click("#clr-recipe")
.waitForElementNotPresent(op);
},
"Move around the UI": browser => {
const otherCat = "//a[contains(@class, 'category-title') and contains(@data-target, '#catOther')]",
genUUID = "//li[contains(@class, 'operation')]/span[text()='Generate UUID']";
browser
.useCss()
.setValue("#search", "")
.click("#search")
.expect.element("#categories").to.be.visible;
browser.useXpath();
// Scroll to a lower category
browser
.getLocationInView(otherCat)
.expect.element(otherCat).to.be.visible;
// Open category
browser
.click(otherCat)
.expect.element(genUUID).to.be.visible;
// Add op to recipe
/* mouseButtonUp drops wherever the actual cursor is, not necessarily in the right place,
so we can't test Sortable.js properly using Nightwatch. html-dnd doesn't work either.
Instead of relying on drag and drop, we double click on the op to load it. */
browser
.getLocationInView(genUUID)
.doubleClick("xpath", genUUID)
.useCss()
.click("#close-ops-dropdown-icon")
.waitForElementNotVisible("#categories", 1000)
.waitForElementVisible(".operation .op-title", 1000)
.waitForElementNotVisible("#stale-indicator", 1000)
.expect.element("#output-text .cm-content").text.which.matches(/[\da-f-]{36}/);
browser.click("#clr-recipe");
},
"Search": browser => {
// Search for an op
browser
.useCss()
.clearValue("#search")
.setValue("#search", "md5")
.useXpath()
.waitForElementVisible("//div[@id='search-results']//strong[text()='MD5']", 1000)
.useCss()
.setValue("#search", "")
.click("#close-ops-dropdown-icon")
.waitForElementNotVisible("#categories", 1000);
},
after: browser => {
browser.end();
}
};

View File

@ -93,7 +93,7 @@ const CONTROL_CHAR_NAMES = {
module.exports = {
before: browser => {
browser
.resizeWindow(1024, 800)
.resizeWindow(1280, 800)
.url(browser.launchUrl)
.useCss()
.waitForElementNotPresent("#preloader", 10000)

View File

@ -18,7 +18,7 @@ const utils = require("./browserUtils.js");
module.exports = {
before: browser => {
browser
.resizeWindow(1024, 800)
.resizeWindow(1280, 800)
.url(browser.launchUrl)
.useCss()
.waitForElementNotPresent("#preloader", 10000)

View File

@ -17,7 +17,7 @@ function clear(browser) {
.click("#clr-recipe")
.click("#clr-io")
.waitForElementNotPresent("#rec-list li.operation")
.expect.element("#input-text .cm-content .cm-line").text.that.equals("");
.expect.element("#input-text .cm-content").text.that.equals("");
}
/** @function