mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-07 06:57:12 -04:00
[#181] handle the correct resetting of layouts in any maximised pane situation, i.e. window resizing while panels are maximised etc. Prevent mobile keyboard popup window resize events to trigger layout resetting when panes are maximised, use JS to calculate the innerHeight available for mobile devices as they are of variable / unreliable / inconsistent heights
This commit is contained in:
parent
f80284cb36
commit
ee9ea051d1
10 changed files with 151 additions and 126 deletions
|
@ -306,9 +306,9 @@ class App {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set desktop layout
|
||||
* Set desktop splitters
|
||||
*/
|
||||
setDesktopLayout(minimise){
|
||||
setDesktopSplitter(minimise){
|
||||
if (this.columnSplitter) this.columnSplitter.destroy();
|
||||
if (this.ioSplitter) this.ioSplitter.destroy();
|
||||
|
||||
|
@ -327,14 +327,12 @@ class App {
|
|||
gutterSize: 4,
|
||||
minSize: minimise ? [0, 0] : [50, 50]
|
||||
});
|
||||
|
||||
this.adjustComponentSizes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mobile layout
|
||||
* Set mobile splitters
|
||||
*/
|
||||
setMobileLayout() {
|
||||
setMobileSplitter() {
|
||||
if (this.columnSplitter) this.columnSplitter.destroy();
|
||||
if (this.ioSplitter) this.ioSplitter.destroy();
|
||||
|
||||
|
@ -345,7 +343,6 @@ class App {
|
|||
});
|
||||
|
||||
this.ioSplitter = Split(["#input", "#output"], {
|
||||
sizes: [45,55],
|
||||
direction: "vertical",
|
||||
gutterSize: 0,
|
||||
});
|
||||
|
@ -875,30 +872,47 @@ class App {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set desktop UI ( on init and on window resize events )
|
||||
*
|
||||
* @param {boolean} minimise
|
||||
*/
|
||||
setDesktopUI(minimise){
|
||||
// enable tooltips on desktop as normal
|
||||
$("[data-toggle=tooltip]").tooltip("enable");
|
||||
this.setDesktopLayout(minimise);
|
||||
// repopulate to enable popovers and drag events
|
||||
this.setDesktopSplitter(minimise);
|
||||
this.adjustComponentSizes();
|
||||
this.populateOperationsList();
|
||||
/**
|
||||
* We don't want to display any checkmarks on desktop, so we clear them.
|
||||
* It has no effect on the recipe list, it's purely a visual indicator
|
||||
*/
|
||||
this.manager.recipe.clearAllSelectedClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mobile UI ( on init and on window resize events )
|
||||
*/
|
||||
setMobileUI(){
|
||||
// tooltips on mobile are reducing UX, so we disable it
|
||||
$("[data-toggle=tooltip]").tooltip("disable");
|
||||
this.setMobileLayout();
|
||||
// repopulate to disable popovers and drag events
|
||||
this.setMobileSplitter();
|
||||
this.divideAvailableSpace();
|
||||
this.populateOperationsList();
|
||||
// restore the appropriate checkmarks
|
||||
this.manager.recipe.updateSelectedOperations();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* The numbers 40, 70 and 90 refer to divs with fixed heights,
|
||||
* that is: #banner, #operations and #controls. -2 is accounting for
|
||||
* some borders.
|
||||
* Be mindful to update these accordingly in the stylesheets
|
||||
* ( themes/_structure ) if you want to make changes.
|
||||
*/
|
||||
divideAvailableSpace( isMobile ){
|
||||
const remainingSpace = window.innerHeight - (40+70+90-2); // banner, operations, controls height + borders
|
||||
|
||||
["recipe", "input", "output"].forEach(( div ) => {
|
||||
document.getElementById(div).style.height = `${remainingSpace/3}px`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -62,7 +62,7 @@ class Manager {
|
|||
// 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.window = new WindowWaiter(this.app, this);
|
||||
this.controls = new ControlsWaiter(this.app, this);
|
||||
this.recipe = new RecipeWaiter(this.app, this);
|
||||
this.ops = new OperationsWaiter(this.app, this);
|
||||
|
@ -141,16 +141,10 @@ 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);
|
||||
/**
|
||||
* A note for the Controls listeners below:
|
||||
*
|
||||
* I would strongly prefer to just add one listener to all elements with the .btn-maximise class,
|
||||
* but for a reason I have not been able to uncover ( something to do with addDynamicListener? ) click events
|
||||
* don't properly bubble and the hitbox to maximise is unacceptably tiny
|
||||
*/
|
||||
document.getElementById("maximise-recipe").addEventListener("click", this.controls.handlePaneMaximising.bind(this.controls))
|
||||
document.getElementById("maximise-input").addEventListener("click", this.controls.handlePaneMaximising.bind(this.controls))
|
||||
document.getElementById("maximise-output").addEventListener("click", this.controls.handlePaneMaximising.bind(this.controls))
|
||||
// A note for the Maximise Controls listeners below: click events via addDynamicListener don't properly bubble and the hit box to maximise 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 search click", this.ops.searchOperations, this.ops);
|
||||
|
|
|
@ -4,16 +4,19 @@
|
|||
---
|
||||
|
||||
#### Mobile UI ( on real device ):
|
||||
- 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
|
||||
- need long press checks on mobile to add favourites ( recipe is done ) >
|
||||
- check on window resizing
|
||||
|
||||
- raw bites dropup thingy is unusable
|
||||
- shannon entropy thingies
|
||||
|
||||
- backspace on fs view should close max view. Keep making the same mistake and navigating away when for instance recipe is expanded and double click the window to fs > resolve. Reset layout
|
||||
|
||||
- need long press checks on mobile to add favourites and switch ingredient order
|
||||
- raw bites dropdown thingy is unusable
|
||||
|
||||
### 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
|
||||
- fix up key / tab events so UI can be navigated comfortably with keys ( inc. visual focus feedback ). Probably a lot of
|
||||
work though
|
||||
|
||||
### JS:
|
||||
- `core/Recipe.mjs`, `core/lib/Magic.js` return imports to original
|
||||
|
@ -21,6 +24,7 @@
|
|||
### Misc:
|
||||
- Gruntfile revert dev config
|
||||
- check for lingering @TODO across code
|
||||
- comb through CSS and improve organisation for better DevX. Ask repo owners to open another issue perhaps and just redo all of the stylesheets ( preferably with SASS )
|
||||
- comb through CSS and improve organisation for better DevX. Ask repo owners to open another issue perhaps and just
|
||||
redo all of the stylesheets ( preferably with SASS )
|
||||
- delete this file when done :)
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
*/
|
||||
|
||||
#controls {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
border-top: 1px solid var(--primary-border-colour);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* Status bar */
|
||||
|
||||
.cm-panel input::placeholder {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
@ -7,7 +6,6 @@
|
|||
.ͼ2 .cm-panels,
|
||||
.ͼ2 .cm-side-panels {
|
||||
background-color: var(--secondary-background-colour);
|
||||
/*border-color: var(--primary-border-colour);*/
|
||||
color: var(--primary-font-colour);
|
||||
border-bottom: 1px solid var(--primary-border-colour);
|
||||
}
|
||||
|
@ -50,6 +48,7 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
/*@TODO: update this one for mobile*/
|
||||
/* Dropup content (Hidden by Default) */
|
||||
.cm-status-bar-select-content {
|
||||
display: none;
|
||||
|
@ -97,6 +96,8 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*@TODO: and update this one for mobile*/
|
||||
.cm-status-bar-select-scroll {
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
#operations-dropdown {
|
||||
position: absolute;
|
||||
top: 40px; /* the height of the input[type="search"] with pos relative */
|
||||
top: 41px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 60vh;
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#banner { height: var(--banner-height); }
|
||||
#operations { height: var(--operations-height); }
|
||||
#recipe { height: var(--recipe-height); }
|
||||
#IO { height: var(--io-height); }
|
||||
#controls { height: var(--controls-height); }
|
||||
|
||||
#banner,
|
||||
|
@ -25,24 +23,18 @@
|
|||
}
|
||||
|
||||
#workspace-wrapper {
|
||||
height: var(--workspace-height);
|
||||
margin-top: var(--banner-height);
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#IO {
|
||||
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;
|
||||
|
@ -54,10 +46,6 @@
|
|||
}
|
||||
|
||||
@media only screen and ( min-width: 768px ) {
|
||||
#IO {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#recipe {
|
||||
padding-bottom: var(--controls-height);
|
||||
}
|
||||
|
@ -67,7 +55,7 @@
|
|||
}
|
||||
|
||||
#workspace-wrapper {
|
||||
height: calc( 100vh - var(--banner-height));
|
||||
height: calc(100vh - var(--banner-height));
|
||||
}
|
||||
|
||||
#operations-dropdown {
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
:root {
|
||||
/* Fixed heights */
|
||||
--banner-height: 40px;
|
||||
--controls-height: 70px;
|
||||
--workspace-height: calc( 100vh - var(--banner-height) - var(--controls-height));
|
||||
--recipe-height: 22vh;
|
||||
--operations-height: 89px;
|
||||
/* io gets the remaining space */
|
||||
--io-height: calc(100vh - var(--banner-height) - var(--controls-height) - var(--recipe-height) - var(--operations-height));
|
||||
/*initial mobile height*/
|
||||
--operations-height: 90px;
|
||||
}
|
||||
|
||||
/**
|
||||
* A note:
|
||||
*
|
||||
* Heights of #recipe, #input and #output are set programmatically
|
||||
* in App.js > divideAvailableSpace(), please see the docs for that
|
||||
* function for more information.
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
@ -419,90 +419,75 @@ ${navigator.userAgent}
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the maximising and resetting to default state of
|
||||
* panels.
|
||||
* Maximise control button click handler.
|
||||
*
|
||||
* On mobile UI, #recipe, #input and #output can be maximised,
|
||||
* on desktop UI it's available only for #output
|
||||
* 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
|
||||
*/
|
||||
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 )
|
||||
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);
|
||||
}
|
||||
|
||||
if (btn.getAttribute("data-original-title") === "Maximise pane") {
|
||||
this.maximisePane(btn,pane);
|
||||
/**
|
||||
* 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 {
|
||||
this.resetPane(btn, pane);
|
||||
if ( window.innerWidth < this.app.breakpoint ){
|
||||
this.app.divideAvailableSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent pane of the 'maximise' button / icon that was
|
||||
* clicked through the buttons' ID
|
||||
* Set and remove the appropriate classes on maximise / minimise actions
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {HTMLElement} pane
|
||||
* @param {boolean} maximise
|
||||
*/
|
||||
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");
|
||||
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 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
|
||||
* Set the correct icon and data title attribute text based on
|
||||
* the 'maximise' flag
|
||||
*
|
||||
* @param {HTMLElement} btn
|
||||
* @param {HTMLElement} pane
|
||||
* @param {boolean} maximise
|
||||
*/
|
||||
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");
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,28 +15,61 @@ class WindowWaiter {
|
|||
* WindowWaiter constructor.
|
||||
*
|
||||
* @param {App} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
constructor(app) {
|
||||
constructor(app, manager) {
|
||||
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 ( window.innerWidth >= this.app.breakpoint ) {
|
||||
this.app.setDesktopUI(false);
|
||||
this.onResizeToDesktop();
|
||||
} else {
|
||||
this.app.setMobileUI();
|
||||
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 );
|
||||
}
|
||||
|
||||
debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])();
|
||||
}
|
||||
|
||||
|
||||
onResizeToDesktop(){
|
||||
this.app.setDesktopUI(false);
|
||||
|
||||
// if a window is resized past breakpoint while #recipe or #input is maximised,
|
||||
// the maximised pane is set to its default ( non-maximised ) state
|
||||
["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%";
|
||||
}
|
||||
|
||||
|
||||
onResizeToMobile(){
|
||||
this.app.setMobileUI();
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for window blur events.
|
||||
* Saves the current time so that we can calculate how long the window was unfocused for when
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue