From 6a1a57538330da67847d6ec2d7d0cba80ff15b2d Mon Sep 17 00:00:00 2001 From: kodi Date: Wed, 11 Mar 2026 14:21:58 +0100 Subject: [PATCH] feat: function key mapping --- project_docs/UI_ACTION_SHORTCUTS_V1_DESIGN.md | 288 ++++++++++++++++++ .../test_ui_smoke_golden.cpython-313.pyc | Bin 5283 -> 5666 bytes .../tests/golden/test_ui_smoke_golden.py | 7 + webui/html/app.js | 71 +++++ webui/html/index.html | 14 +- webui/html/style.css | 12 + 6 files changed, 385 insertions(+), 7 deletions(-) create mode 100644 project_docs/UI_ACTION_SHORTCUTS_V1_DESIGN.md diff --git a/project_docs/UI_ACTION_SHORTCUTS_V1_DESIGN.md b/project_docs/UI_ACTION_SHORTCUTS_V1_DESIGN.md new file mode 100644 index 0000000..cdd7857 --- /dev/null +++ b/project_docs/UI_ACTION_SHORTCUTS_V1_DESIGN.md @@ -0,0 +1,288 @@ +# UI_ACTION_SHORTCUTS_V1_DESIGN.md + +## 1. Doel en scope + +Action shortcuts v1 maken de bestaande functiebalk sneller bruikbaar zonder de UI-semantiek te veranderen. Het doel is niet om nieuwe acties te ontwerpen, maar om de bestaande acties via toetsen te starten op exact dezelfde manier als via de functiebalk. + +Doel: +- de huidige functiebalkacties sneller bereikbaar maken +- de dual-pane workflow dichter bij een Midnight Commander-achtige bediening brengen +- een bruikbare eerste set bieden die ook op Mac praktisch inzetbaar blijft + +In scope: +- keyboard-start van bestaande functiebalkacties +- centrale shortcut-dispatch op basis van `activePane` en huidige selectie +- veilige focusguards zodat invoervelden en modals niet worden verstoord +- voorbereiding op latere visuele hints in de functiebalk + +Out of scope: +- geen backendwijzigingen +- geen nieuwe acties buiten de bestaande functiebalk +- geen globale override van agressieve browser- of OS-shortcuts +- geen nieuwe dependencies +- geen extra keyboardnavigatie buiten action-triggering + +--- + +## 2. Acties in scope + +Deze ontwerpstap werkt uit voor: + +- `View` +- `Edit` +- `Copy` +- `Move` +- `Rename` +- `MKdir` +- `Delete` + +Semantisch uitgangspunt: +- elke shortcut triggert exact dezelfde action-handler als de corresponderende knop in de functiebalk +- enabled/disabled logica blijft centraal en wordt niet dubbel geïmplementeerd in aparte keyboardlogica +- als een knop in de huidige context disabled zou zijn, doet de shortcut geen destructieve fallback en start geen alternatieve flow + +--- + +## 3. Toetsmapping + +### Primaire mapping: F3 t/m F8 + +Voorgestelde primaire mapping: + +- `F3` = `View` +- `F4` = `Edit` +- `F5` = `Copy` +- `F6` = `Move` +- `F7` = `MKdir` +- `F8` = `Delete` + +Aanvullend: +- `Rename` krijgt in v1 geen vaste F-toets, omdat de klassieke mapping al dicht bezet is en `Rename` functioneel dichter bij contextactie dan bij de vaste MC-kern ligt +- `Rename` krijgt daarom een aparte, browser-veilige alternatieve shortcut + +### Mac-vriendelijke alternatieven + +F-toetsen zijn op Mac-toetsenborden vaak verborgen achter systeemfuncties of alleen bruikbaar met extra modifiers. Daarom krijgt v1 ook expliciete alternatieven. + +Voorstel: +- `Alt+3` = `View` +- `Alt+4` = `Edit` +- `Alt+5` = `Copy` +- `Alt+6` = `Move` +- `Alt+7` = `MKdir` +- `Alt+8` = `Delete` +- `Alt+R` = `Rename` + +Motivatie: +- numerieke mapping blijft mentaal gekoppeld aan `F3`-`F8` +- `Alt+R` is kort, semantisch duidelijk en minder conflictgevoelig dan veel andere browsercombinaties +- deze combinaties zijn compacter en realistischer op Mac dan het afdwingen van functietoetsgebruik + +### Veiligheid in browsercontext + +V1 kiest bewust geen mappings die sterk botsen met standaard browsergedrag, zoals: +- `Cmd+L` +- `Cmd+R` +- `Cmd+W` +- `Cmd+F` +- `Cmd+S` +- `Cmd+P` +- `Cmd+Backspace` + +Ook geen pure lettershortcuts zonder modifier, omdat die te snel botsen met focus, tekstinvoer en toekomstige editorinteractie. + +Belangrijk: +- `F3`-`F8` worden alleen gebruikt als extra shortcutlaag waar de browser/het OS dit toelaat +- de `Alt+...` varianten vormen de praktische browservriendelijke fallback + +--- + +## 4. Focusregels + +Shortcuts zijn alleen actief als de UI in browse-/paneelmodus zit en focus niet in een interactieve control staat. + +### Shortcuts actief wanneer +- focus op `body`, paneelcontainer of bestandslijst staat +- geen modal met eigen invoer open is +- geen wildcard-popup open is +- geen editor-textarea focus heeft +- geen inputcontrol focus heeft + +### Shortcuts niet actief wanneer focus in +- `input` +- `textarea` +- `select` +- `button` +- checkbox +- elk element met `contenteditable` + +### Extra modalregels +- als `View` modal open is: geen action shortcuts voor onderliggende paneelacties +- als `Edit` modal open is: geen action shortcuts voor onderliggende paneelacties +- als wildcard popup open is: action shortcuts uitgeschakeld behalve popup-eigen `Enter`/`Escape` + +### Browser/OS-collision mitigatie +- alleen exact ondersteunde combinaties afvangen +- geen brede `preventDefault()` voor andere toetsen +- bij twijfel niet afvangen; browser/OS behoudt dan prioriteit + +--- + +## 5. Relatie met functiebalk en selectie + +Kernregel: +- keyboard shortcuts gebruiken exact dezelfde centrale action-functies als de functiebalkknoppen + +Dat betekent: +- `View` shortcut roept dezelfde open-viewer flow aan als de `View` knop +- `Edit` shortcut roept dezelfde open-editor flow aan als de `Edit` knop +- `Copy`/`Move` shortcut gebruiken dezelfde batch-startlogica als de bestaande knoppen +- `Rename`, `MKdir`, `Delete` shortcut gebruiken dezelfde validatie- en UI-flow als de bestaande knoppen + +### Geen selectie +- `View`: geen actie +- `Edit`: geen actie +- `Copy`: geen actie +- `Move`: geen actie +- `Rename`: geen actie +- `MKdir`: wel toegestaan, op actief paneel +- `Delete`: geen actie + +### Exact 1 selectie +- `View`: alleen voor ondersteunde file +- `Edit`: alleen voor ondersteunde tekstfile +- `Copy`: alleen als selectie backend-geldig is +- `Move`: alleen als selectie backend-geldig is +- `Rename`: toegestaan +- `MKdir`: toegestaan +- `Delete`: toegestaan + +### Meerdere selecties +- `View`: niet toegestaan +- `Edit`: niet toegestaan +- `Copy`: toegestaan als alle geselecteerde items voldoen aan huidige backend-scope +- `Move`: toegestaan als alle geselecteerde items voldoen aan huidige backend-scope +- `Rename`: niet toegestaan +- `MKdir`: toegestaan +- `Delete`: toegestaan + +### Directory/file-selectie +- zolang backend `copy/move` file-only is: + - selectie die directories bevat blokkeert `Copy` + - selectie die directories bevat blokkeert `Move` +- `View` en `Edit` blokkeren directories +- `Delete` volgt bestaande backendregels +- `Rename` volgt bestaande single-selection regel + +### Disabled gedrag bij toetsgebruik +- geen verborgen fallback +- geen half-uitgevoerde actie +- optioneel compacte feedback via bestaande statusregel, maar v1 mag ook stil no-op blijven als dit consistenter is met huidige knopdisabled-semantiek + +Voorkeur v1: +- als actie disabled is, shortcut doet niets en toont hooguit compacte statusfeedback als dat al in bestaande UI-patterns past + +--- + +## 6. UX-regels + +### Visuele hints in de functiebalk + +Voorstel v1: +- subtiele shortcut-hints per knop toelaten, maar klein houden +- voorbeeld later mogelijk: + - `F3 View` + - `F4 Edit` + - `F5 Copy` +- Mac-alternatieven hoeven niet permanent zichtbaar in de hoofdbalk om ruis te vermijden + +Voor deze ontwerpstap geldt: +- de functiebalk moet voorbereid zijn op zulke hints +- de daadwerkelijke implementatie kan starten zonder direct alle hints zichtbaar te maken + +### Niet-beschikbare acties +- als actie disabled is in de huidige context, mag de shortcut die actie niet forceren +- gedrag moet gelijk zijn aan klikken op een disabled knop: er gebeurt functioneel niets + +### Unsupported acties +- `View` of `Edit` op niet-ondersteunde filetypes: + - shortcut opent geen alternatieve flow + - zelfde fout- of disabled-behandeling als via knop +- `Copy`/`Move` op directoryselectie terwijl backend dit niet ondersteunt: + - zelfde blokkade als via knop + +### Consistentie-eis +- shortcuts zijn geen tweede API-laag +- de functiebalk blijft leidend; keyboard is alleen een alternatieve triggerroute + +--- + +## 7. Impactanalyse + +Waarschijnlijk te wijzigen frontendbestanden: +- `webui/html/app.js` +- `webui/html/index.html` +- `webui/html/style.css` +- `webui/backend/tests/golden/test_ui_smoke_golden.py` + +### Verwachte code-impact + +`app.js`: +- uitbreiding van centrale keyboard dispatcher +- mappingtabel voor action shortcuts +- centrale guards voor modal/input/focusstatus +- hergebruik van bestaande action handlers, niet dupliceren + +`index.html`: +- mogelijk kleine markup-aanpassingen om shortcutlabels in functiebalk te kunnen tonen +- geen structurele layoutwijziging nodig + +`style.css`: +- eventueel compacte styling voor functietoetsbadges of hints +- geen redesign nodig + +### Regressierisico + +Belangrijkste risico's: +- conflicten met bestaande keyboard navigation (`Tab`, `Arrow`, `Space`, `Escape`) +- shortcuts vuren terwijl input of modal focus heeft +- dubbele actie-aanroep als keyboard en knoplogica niet centraal gedeeld worden +- browser/OS-shortcuts per platform gedragen zich anders + +Mitigatie: +- centrale `shouldHandleActionShortcut(event)` guard +- bestaande knophandlers of action-functions als single source of truth +- alleen exacte combinaties afvangen +- platform-onafhankelijke fallbacklogica beperkt en expliciet houden + +--- + +## 8. Teststrategie + +### Smoke/regressietests + +Geautomatiseerd uit te breiden waar passend: +- UI smoke test blijft controleren dat functiebalk aanwezig is +- optioneel extra check dat knoplabels of data-attributes voor actions stabiel aanwezig zijn +- geen zware browser-E2E vereist voor deze stap + +### Handmatige validatie + +Essentieel bij implementatie: +- `F3`-`F8` triggeren de juiste acties waar ondersteund +- `Alt+3`-`Alt+8` werken als Mac-vriendelijke fallback +- `Alt+R` triggert `Rename` +- shortcuts werken alleen op actief paneel +- shortcuts respecteren disabled toestand bij: + - geen selectie + - meerdere selecties + - directoryselectie + - unsupported filetypes +- editor modal, viewer modal en wildcard popup blokkeren onderliggende action shortcuts correct +- bestaande keyboard navigation blijft intact + +### Regressiechecks +- klikgedrag in de bestandslijst blijft intact +- current-row navigatie blijft intact +- wildcard popup shortcuts blijven werken +- functiebalkknoppen en keyboardtrigger geven identiek resultaat diff --git a/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc b/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc index b1c2629793a9a2c1c47a7ee5b739df39aa860af6..c67e5551178d2dd010d370b743e5f1b981cb87ae 100644 GIT binary patch delta 478 zcmZ3ixk!iaGcPX}0}!0e-k5otXCvP~W+nr+&3r8BER$ndMJ9K02!QFe9K4gGSj8FT zCi}4}Gs=TW1u&@yCY2_4aVRq?gGfP@P-asmkeLh&3Jm%T!K#z@akMk4O?KnlCVz{` z%@~MHfY=m>&4Ab(h%KsE9dk;wgEs%+OkosN0_rSM1`#SCLJ>r$ZZ72xVPx!{e2V8& z^n{cdE>}1dZ*cJR^LFx5iI+a3PLjI5`nrF?`p@v|^#se>%n01=u%;ucS7 zQBHh8Vo64PT7FTH9#BY=wMY;oZ7^9+a0R3HTZlX-=wLQ8aR9+mgH8Yd delta 304 zcmZ3avsjbwGcPX}0}#x}*qHf>dn4aJW+oHX&3r8BER&~licIe05CGF_Ie1z08G_{| z@8WD{l%MRxwQaLFcM7AZ3{Y#4EQpW;5z-(+e)9yL5JpDL$xnGdO%CT**c{0(&&YaG zR?16sbFBaila?Y#l@f?h1`@Y;N{e#h3ld8*;?weriZp;inyf{FAZe}1tA$oDx=aoc z-ot1;Sy04{%>X29F*#O5n$c=REt^OaF8Cayih5-P3%T3q- diff --git a/webui/backend/tests/golden/test_ui_smoke_golden.py b/webui/backend/tests/golden/test_ui_smoke_golden.py index 921881b..77f02ee 100644 --- a/webui/backend/tests/golden/test_ui_smoke_golden.py +++ b/webui/backend/tests/golden/test_ui_smoke_golden.py @@ -35,6 +35,13 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('id="function-bar"', body) self.assertIn('id="view-btn"', body) self.assertIn('id="edit-btn"', body) + self.assertIn("F3", body) + self.assertIn("F4", body) + self.assertIn("F5", body) + self.assertIn("F6", body) + self.assertIn("F7", body) + self.assertIn("F8", body) + self.assertIn("Alt+R", body) self.assertIn('id="viewer-modal"', body) self.assertIn('id="viewer-content"', body) self.assertIn('id="editor-modal"', body) diff --git a/webui/html/app.js b/webui/html/app.js index 9744379..40dc26c 100644 --- a/webui/html/app.js +++ b/webui/html/app.js @@ -662,6 +662,72 @@ function shouldHandleShortcut(target) { return false; } +function actionButton(id) { + return document.getElementById(id); +} + +function triggerActionButton(id) { + const button = actionButton(id); + if (!button || button.disabled) { + return false; + } + button.click(); + return true; +} + +function actionShortcutHandled(event) { + const altOnly = event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey; + const noModifiers = !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey; + + if (noModifiers) { + if (event.key === "F3") { + return triggerActionButton("view-btn"); + } + if (event.key === "F4") { + return triggerActionButton("edit-btn"); + } + if (event.key === "F5") { + return triggerActionButton("copy-btn"); + } + if (event.key === "F6") { + return triggerActionButton("move-btn"); + } + if (event.key === "F7") { + return triggerActionButton("mkdir-btn"); + } + if (event.key === "F8") { + return triggerActionButton("delete-btn"); + } + } + + if (altOnly) { + const key = event.key.toLowerCase(); + if (key === "3") { + return triggerActionButton("view-btn"); + } + if (key === "4") { + return triggerActionButton("edit-btn"); + } + if (key === "5") { + return triggerActionButton("copy-btn"); + } + if (key === "6") { + return triggerActionButton("move-btn"); + } + if (key === "7") { + return triggerActionButton("mkdir-btn"); + } + if (key === "8") { + return triggerActionButton("delete-btn"); + } + if (key === "r") { + return triggerActionButton("rename-btn"); + } + } + + return false; +} + function wildcardPopupElements() { return { overlay: document.getElementById("wildcard-popup"), @@ -958,6 +1024,11 @@ function handleKeyboardShortcuts(event) { return; } + if (actionShortcutHandled(event)) { + event.preventDefault(); + return; + } + if (event.shiftKey && event.key === "+") { event.preventDefault(); openWildcardPopup("select"); diff --git a/webui/html/index.html b/webui/html/index.html index a47c820..d7230ba 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -62,13 +62,13 @@ diff --git a/webui/html/style.css b/webui/html/style.css index e0a239b..39e9986 100644 --- a/webui/html/style.css +++ b/webui/html/style.css @@ -318,6 +318,18 @@ button:disabled { #function-bar button { min-width: 72px; padding: 3px 8px; + display: inline-flex; + align-items: baseline; + gap: 6px; + justify-content: center; +} + +.shortcut-hint { + color: var(--muted); + font-size: 10px; + line-height: 1; + letter-spacing: 0.02em; + text-transform: uppercase; } .popup-overlay {