mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-09 07:45:00 -04:00
introducing and refactoring to using custom components for building category / cat lists / operation lists. This allows us to group relevant functionality more efficiently, easier maintenance in the future. We could, one by one, refactor and encapsulate components across CC in this manner. A very convenient and much needed effect of this implementation is that these components can hold functionality and references exclusively pertaining to themselves. Separating said functionality increases code compartmentalisation while references and event listeners requiring these references are guaranteed to be up to date with the component at all times.
This commit is contained in:
parent
1e5190cd7d
commit
b1b0be254b
17 changed files with 489 additions and 313 deletions
184
src/web/components/c-category-li.mjs
Normal file
184
src/web/components/c-category-li.mjs
Normal file
|
@ -0,0 +1,184 @@
|
|||
import {COperationLi} from "./c-operation-li.mjs";
|
||||
|
||||
/**
|
||||
* c(ustom element)-category-li ( list item )
|
||||
*
|
||||
* @param {App} app - The main view object for CyberChef
|
||||
* @param {CatConf} category - The category and operations to be populated.
|
||||
* @param {Object.<string, OpConf>} operations - The list of operation configuration objects.
|
||||
* @param {Boolean} isExpanded - expand the category on init or not
|
||||
* */
|
||||
export class CCategoryLi extends HTMLElement {
|
||||
constructor(
|
||||
app,
|
||||
category,
|
||||
operations,
|
||||
isExpanded
|
||||
) {
|
||||
super();
|
||||
|
||||
this.app = app;
|
||||
this.category = category;
|
||||
this.operations = operations;
|
||||
this.label = category.name;
|
||||
this.isExpanded = isExpanded;
|
||||
|
||||
this.build();
|
||||
|
||||
this.addEventListener("click", this.handleClick.bind(this));
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Handle click
|
||||
// *
|
||||
// * @param {Event} e
|
||||
// */
|
||||
// handleClick(e) {
|
||||
// if (e.target === this.querySelector("button")) {
|
||||
// // todo back to this "hitbox" issue w the icon inside the button
|
||||
// this.app.manager.ops.editFavouritesClick(e);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Build the li element
|
||||
*/
|
||||
buildListItem() {
|
||||
const li = document.createElement("li");
|
||||
|
||||
li.classList.add("panel");
|
||||
li.classList.add("category");
|
||||
|
||||
return li;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the anchor element
|
||||
*/
|
||||
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.buildEditFavourites(a);
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the op-list for this category
|
||||
*
|
||||
* @param {string[]} opNames
|
||||
*/
|
||||
buildOperationList(opNames) {
|
||||
return opNames.map(opName => {
|
||||
if (!(opName in this.operations)) {
|
||||
log.warn(`${opName} could not be found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
return new COperationLi(
|
||||
this.app,
|
||||
opName,
|
||||
{
|
||||
class: "check-icon",
|
||||
innerText: "check"
|
||||
},
|
||||
this.operations[opName]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build c-category-li and dispatch event oplistcreate
|
||||
*/
|
||||
build() {
|
||||
const ul = document.createElement("ul");
|
||||
ul.classList.add("op-list");
|
||||
|
||||
const li = this.buildListItem();
|
||||
const a = this.buildAnchor();
|
||||
const div = this.buildCollapsablePanel();
|
||||
|
||||
li.appendChild(a);
|
||||
li.appendChild(div);
|
||||
div.appendChild(ul);
|
||||
this.appendChild(li);
|
||||
|
||||
this.buildOperationList(this.category.ops).forEach(operationListItem =>
|
||||
ul.appendChild(operationListItem)
|
||||
);
|
||||
|
||||
ul.dispatchEvent(this.app.manager.oplistcreate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a c-operation-li to this op-list
|
||||
*
|
||||
* @param {HTMLElement} cOperationLiElement
|
||||
*/
|
||||
appendOperation(cOperationLiElement) {
|
||||
this.querySelector('li > div > ul').appendChild(cOperationLiElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this category is Favourites, build and return the star icon to the category
|
||||
*/
|
||||
buildEditFavourites() {
|
||||
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);
|
40
src/web/components/c-category-list.mjs
Normal file
40
src/web/components/c-category-list.mjs
Normal file
|
@ -0,0 +1,40 @@
|
|||
import {CCategoryLi} from "./c-category-li.mjs";
|
||||
|
||||
/**
|
||||
* c(ustom element)-category-list
|
||||
*
|
||||
* @param {App} app - The main view object for CyberChef
|
||||
* @param {CatConf[]} categories - The list of categories and operations to be populated.
|
||||
* @param {Object.<string, OpConf>} operations - The list of operation configuration objects.
|
||||
**/
|
||||
export class CCategoryList extends HTMLElement {
|
||||
constructor( app, categories, operations ) {
|
||||
super();
|
||||
|
||||
this.app = app;
|
||||
this.categories = categories;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build c-category-list
|
||||
*/
|
||||
build() {
|
||||
const ul = document.createElement("ul");
|
||||
|
||||
this.categories.forEach((category, index) => {
|
||||
const cat = new CCategoryLi(
|
||||
this.app,
|
||||
category,
|
||||
this.operations,
|
||||
index === 0
|
||||
);
|
||||
|
||||
ul.appendChild(cat);
|
||||
});
|
||||
|
||||
this.append(ul);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("c-category-list", CCategoryList);
|
176
src/web/components/c-operation-li.mjs
Normal file
176
src/web/components/c-operation-li.mjs
Normal file
|
@ -0,0 +1,176 @@
|
|||
import url from "url";
|
||||
|
||||
/**
|
||||
* c(ustom element)-operation-li ( list item )
|
||||
*
|
||||
* @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 {Object} config - The configuration object for this operation.
|
||||
*/
|
||||
export class COperationLi extends HTMLElement {
|
||||
constructor(
|
||||
app,
|
||||
name,
|
||||
icon,
|
||||
config
|
||||
) {
|
||||
super();
|
||||
|
||||
this.app = app;
|
||||
this.name = name;
|
||||
this.isFavourite = this.app.isLocalStorageAvailable() && localStorage.favourites?.includes(name);
|
||||
this.icon = icon;
|
||||
this.config = config;
|
||||
|
||||
this.build();
|
||||
|
||||
this.addEventListener('click', this.handleClick.bind(this));
|
||||
this.addEventListener('dblclick', this.handleDoubleClick.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @fires OperationsWaiter#operationDblclick on double click
|
||||
* @param {Event} e
|
||||
*/
|
||||
handleDoubleClick(e) {
|
||||
if (e.target === this.querySelector("li")) {
|
||||
this.querySelector("li.operation").classList.add("selected");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click
|
||||
* @param {Event} e
|
||||
*/
|
||||
handleClick(e) {
|
||||
if (e.target === this.querySelector("i.star-icon")) {
|
||||
this.app.addFavourite(this.name);
|
||||
this.updateFavourite(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
buildListItem() {
|
||||
const li = document.createElement("li");
|
||||
|
||||
li.setAttribute("data-name", this.name);
|
||||
li.classList.add("operation");
|
||||
|
||||
if (this.isFavourite) {
|
||||
li.classList.add("favourite");
|
||||
}
|
||||
|
||||
li.textContent = this.name;
|
||||
|
||||
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-placement", "left");
|
||||
li.setAttribute("data-html", "true");
|
||||
li.setAttribute("data-trigger", "hover");
|
||||
li.setAttribute("data-boundary", "viewport");
|
||||
li.setAttribute("data-content", dataContent);
|
||||
}
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the operation list item right side icon
|
||||
*/
|
||||
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 star icon
|
||||
*/
|
||||
buildStarIcon() {
|
||||
const icon = document.createElement("i");
|
||||
icon.setAttribute("title", this.name);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build c-operation-li
|
||||
*/
|
||||
build() {
|
||||
const li = this.buildListItem();
|
||||
const icon = this.buildIcon();
|
||||
const starIcon = this.buildStarIcon();
|
||||
|
||||
li.appendChild(icon);
|
||||
li.appendChild(starIcon);
|
||||
|
||||
this.appendChild(li);
|
||||
}
|
||||
|
||||
updateFavourite(isFavourite) {
|
||||
if (isFavourite) {
|
||||
this.querySelector("li").classList.add("favourite");
|
||||
this.querySelector("i.star-icon").innerText = "star";
|
||||
} else {
|
||||
this.querySelector("li").classList.remove("favourite");
|
||||
this.querySelector("i.star-icon").innerText = "star_outline";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
customElements.define("c-operation-li", COperationLi);
|
Loading…
Add table
Add a link
Reference in a new issue