mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-07 06:57:12 -04:00
[#181] add maximiser icons to recipe, input and output, and handle maximising of targets accordingly. Desktop view remains as normal, where only output can be maximised
This commit is contained in:
parent
093a3c4b77
commit
4c2c934cd4
13 changed files with 164 additions and 51 deletions
|
@ -315,7 +315,7 @@ class App {
|
|||
|
||||
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
|
||||
sizes: [20, 30, 50],
|
||||
minSize: minimise ? [0, 0, 0] : [240, 310, 450],
|
||||
minSize: minimise ? [0, 0, 0] : [20, 30, 50],
|
||||
gutterSize: 4,
|
||||
expandToMin: true,
|
||||
onDrag: debounce(function() {
|
||||
|
@ -326,7 +326,7 @@ class App {
|
|||
this.ioSplitter = Split(["#input", "#output"], {
|
||||
direction: "vertical",
|
||||
gutterSize: 4,
|
||||
minSize: minimise ? [0, 0] : [100, 100]
|
||||
minSize: minimise ? [0, 0] : [50, 50]
|
||||
});
|
||||
|
||||
this.adjustComponentSizes();
|
||||
|
@ -341,7 +341,6 @@ class App {
|
|||
|
||||
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
|
||||
sizes: [100, 100, 100],
|
||||
minSize: [0, 0, 0],
|
||||
gutterSize: 0,
|
||||
expandToMin: true,
|
||||
});
|
||||
|
@ -649,7 +648,7 @@ class App {
|
|||
|
||||
|
||||
/**
|
||||
* Resets the splitter positions to default.
|
||||
* Resets the splitter positions to default for desktop UI.
|
||||
*/
|
||||
resetLayout() {
|
||||
this.columnSplitter.setSizes([20, 30, 50]);
|
||||
|
@ -657,6 +656,7 @@ class App {
|
|||
this.adjustComponentSizes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjust components to fit their containers.
|
||||
*/
|
||||
|
@ -857,13 +857,13 @@ class App {
|
|||
|
||||
|
||||
/**
|
||||
* Set element visibility
|
||||
* Update element visibility
|
||||
*
|
||||
* @param {HTMLElement} elm
|
||||
* @param {boolean} isVisible
|
||||
*
|
||||
*/
|
||||
setVisibility( elm, isVisible ){
|
||||
updateVisibility( elm, isVisible ){
|
||||
if ( isVisible ) {
|
||||
if ( elm.classList.contains("hidden")) {
|
||||
elm.classList.remove("hidden");
|
||||
|
@ -876,10 +876,6 @@ class App {
|
|||
}
|
||||
|
||||
/**
|
||||
* A collection of function calls that need to fire on
|
||||
* window resizing when the window inner width >= the
|
||||
* breakpoint
|
||||
*
|
||||
* @param {boolean} minimise
|
||||
*/
|
||||
setDesktopUI(minimise){
|
||||
|
@ -893,11 +889,6 @@ class App {
|
|||
this.manager.recipe.clearAllSelectedClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of function calls that need to fire on
|
||||
* window resizing when the window inner width < the
|
||||
* breakpoint
|
||||
*/
|
||||
setMobileUI(){
|
||||
this.setMobileLayout();
|
||||
// repopulate to disable popovers and drag events
|
||||
|
|
|
@ -141,6 +141,7 @@ 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);
|
||||
this.addDynamicListener(".btn-maximise", "click", this.controls.handlePaneMaximising, this.controls);
|
||||
|
||||
// Operations
|
||||
this.addMultiEventListener("#search", "keyup paste search click", this.ops.searchOperations, this.ops);
|
||||
|
@ -199,7 +200,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("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);
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
---
|
||||
|
||||
#### Mobile UI ( on real device ):
|
||||
- How to add operations to favourites since drag and drop is now disabled on mobile ( maybe a star icon...? )
|
||||
- Recipe list on mobile panel is too small to comfortably scroll and change order of recipes
|
||||
|
||||
- test *thoroughly* with keyboard popping up because that messes with view-heights on mobile probably and might make it a very frustrating experience
|
||||
- test drag and drop etc. Regular mobile events / UX
|
||||
- view-heights not correct due to variable taskbar on mobile devices
|
||||
|
||||
- loading gears on mobile ( some ingredient labels 'shine through' and the cancel button isnt visible )
|
||||
- need long press checks on mobile to add favourites and switch ingredient order
|
||||
|
||||
### Desktop UI:
|
||||
### General UI:
|
||||
- fix up key / tab events so UI can be navigated comfortably with keys ( inc. visual focus feedback ). Probably a lot of work though
|
||||
|
|
|
@ -198,7 +198,7 @@
|
|||
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 hide-on-maximised-output">
|
||||
<span class="pane-controls">
|
||||
<button type="button"
|
||||
class="btn bmd-btn-icon mobile-only hidden"
|
||||
id="close-operations-dropdown"
|
||||
|
@ -226,7 +226,7 @@
|
|||
<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 hide-on-maximised-output">
|
||||
<span class="pane-controls">
|
||||
<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>
|
||||
|
@ -236,10 +236,13 @@
|
|||
<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 hide-on-maximised-output">
|
||||
<div id="controls" class="no-select">
|
||||
<div id="controls-content">
|
||||
<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
|
||||
|
@ -280,6 +283,9 @@
|
|||
</button>
|
||||
<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.">
|
||||
<i class="material-icons">view_compact</i>
|
||||
|
@ -341,7 +347,7 @@
|
|||
<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="desktop-only 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.">
|
||||
<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>
|
||||
|
|
|
@ -52,8 +52,8 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.output-maximised .hide-on-maximised-output {
|
||||
display: none !important;
|
||||
.top-zindex {
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#recipe {
|
||||
position: relative;
|
||||
background-color: var(--primary-background-colour);
|
||||
}
|
||||
|
||||
#rec-list {
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
#input,
|
||||
#output {
|
||||
background-color: var(--primary-background-colour);
|
||||
}
|
||||
|
||||
#input-text,
|
||||
#output-text {
|
||||
position: relative;
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* can ( should ) be made into a reusable, generic component
|
||||
*/
|
||||
|
||||
#operations {
|
||||
background-color: var(--primary-background-colour);
|
||||
}
|
||||
|
||||
#operations-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#content-wrapper {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#workspace-wrapper {
|
||||
|
@ -45,6 +46,22 @@
|
|||
padding-bottom: var(--controls-height);
|
||||
}
|
||||
|
||||
#recipe.maximised-pane,
|
||||
#input.maximised-pane,
|
||||
#output.maximised-pane {
|
||||
position: fixed;
|
||||
min-height: calc( 100vh - var(--banner-height ));
|
||||
height: auto;
|
||||
top: var(--banner-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#recipe.maximised-pane #controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and ( min-width: 768px ) {
|
||||
#IO {
|
||||
padding-bottom: 0;
|
||||
|
|
|
@ -418,6 +418,92 @@ ${navigator.userAgent}
|
|||
bakeButton.classList.add("btn-success");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the maximising and resetting to default state of
|
||||
* panels.
|
||||
*
|
||||
* On mobile UI, #recipe, #input and #output can be maximised,
|
||||
* on desktop UI it's available only for #output
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
handlePaneMaximising(e){
|
||||
// the event target btn can be one of ( currently ) 3 'maximiser' buttons
|
||||
const btn = e.target.classList.contains("btn-maximise") ? e.target : e.target.parentNode;
|
||||
// find the parent ( target ) pane to be maximised that belongs to the btn
|
||||
const pane = this.resolveMaximiserParentPane( btn.id )
|
||||
|
||||
if (btn.getAttribute("data-original-title") === "Maximise pane") {
|
||||
this.maximisePane(btn,pane);
|
||||
} else {
|
||||
this.resetPane(btn, pane);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent pane of the 'maximise' button / icon that was
|
||||
* clicked through the buttons' ID
|
||||
*
|
||||
* @param {string} id
|
||||
*/
|
||||
resolveMaximiserParentPane(id){
|
||||
switch(id) {
|
||||
case "maximise-recipe":
|
||||
return document.getElementById("recipe");
|
||||
case "maximise-input":
|
||||
return document.getElementById("input");
|
||||
case "maximise-output":
|
||||
return document.getElementById("output");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pane from default state to Maximised
|
||||
*
|
||||
* @param {HTMLElement} btn
|
||||
* @param {HTMLElement} pane
|
||||
*/
|
||||
maximisePane(btn, pane){
|
||||
this.togglePane(pane,true);
|
||||
this.toggleIcon(btn, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the pane from Maximised state to default
|
||||
*
|
||||
* @param {HTMLElement} btn
|
||||
* @param {HTMLElement} pane
|
||||
*/
|
||||
resetPane(btn, pane) {
|
||||
this.togglePane(pane, false);
|
||||
this.toggleIcon(btn, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the pane to or from maximised size,
|
||||
* based on the 'isMaximised' flag
|
||||
*
|
||||
* @param {HTMLElement} pane
|
||||
* @param {boolean} isMaximised
|
||||
*/
|
||||
togglePane(pane, isMaximised) {
|
||||
isMaximised ? pane.classList.add("top-zindex") : pane.classList.remove("top-zindex");
|
||||
isMaximised ? pane.classList.add("maximised-pane") : pane.classList.remove("maximised-pane");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the 'maximise' icon and attribute text based on
|
||||
* the 'isMaximised' flag
|
||||
*
|
||||
* @param {HTMLElement} btn
|
||||
* @param {boolean} isMaximised
|
||||
*/
|
||||
toggleIcon(btn, isMaximised ) {
|
||||
btn.querySelector("i").innerHTML = isMaximised ? "fullscreen_exit" : "fullscreen";
|
||||
$(btn).attr("data-original-title", isMaximised ? "Reset pane" : "Maximise pane");
|
||||
}
|
||||
}
|
||||
|
||||
export default ControlsWaiter;
|
||||
|
|
|
@ -45,7 +45,7 @@ class OperationsWaiter {
|
|||
this.openOperationsDropdown();
|
||||
|
||||
if ( e.target.value.length !== 0 ){
|
||||
this.app.setVisibility(searchResults, true );
|
||||
this.app.updateVisibility(searchResults, true );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,8 +329,8 @@ class OperationsWaiter {
|
|||
const closeOperationsDropdown = document.getElementById("close-operations-dropdown");
|
||||
const categories = document.getElementById("categories");
|
||||
|
||||
this.app.setVisibility(categories, true);
|
||||
this.app.setVisibility(closeOperationsDropdown, true);
|
||||
this.app.updateVisibility(categories, true);
|
||||
this.app.updateVisibility(closeOperationsDropdown, true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -346,9 +346,9 @@ class OperationsWaiter {
|
|||
search.value = '';
|
||||
}
|
||||
|
||||
this.app.setVisibility(document.getElementById( "categories"), false );
|
||||
this.app.setVisibility(document.getElementById( "search-results"), false );
|
||||
this.app.setVisibility(document.getElementById("close-operations-dropdown"), false );
|
||||
this.app.updateVisibility(document.getElementById( "categories"), false );
|
||||
this.app.updateVisibility(document.getElementById( "search-results"), false );
|
||||
this.app.updateVisibility(document.getElementById("close-operations-dropdown"), false );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1404,26 +1404,29 @@ class OutputWaiter {
|
|||
* 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;
|
||||
// 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);
|
||||
// // if ( window.innerWidth >= this.app.breakpoint ){
|
||||
// // this.app.resetLayout();
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
||||
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
|
||||
|
|
|
@ -27,7 +27,6 @@ class WindowWaiter {
|
|||
* continuous resetting).
|
||||
*/
|
||||
windowResize() {
|
||||
// @TODO: maybe a debounce is desirable although generally people won't be resizing like crazy.. I think
|
||||
if ( window.innerWidth >= this.app.breakpoint ) {
|
||||
this.app.setDesktopUI(false);
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue