diff --git a/project_docs/UI_FUNCTION_BAR_V2_DESIGN.md b/project_docs/UI_FUNCTION_BAR_V2_DESIGN.md new file mode 100644 index 0000000..1f83c8d --- /dev/null +++ b/project_docs/UI_FUNCTION_BAR_V2_DESIGN.md @@ -0,0 +1,188 @@ +# UI_FUNCTION_BAR_V2_DESIGN.md + +## 1. Doel en scope + +Deze stap beschrijft de evolutie van de huidige onderbalk onder de twee panelen naar een compactere, duidelijkere functiebalk in Midnight Commander-stijl. + +Doel: +- de balk onder de panelen wordt visueel en functioneel herkenbaar als vaste actiebalk +- acties blijven direct gekoppeld aan het actieve paneel +- de balk wordt voorbereid op latere functietoets-labeling zonder nu al functietoetsen te implementeren + +In scope: +- compactere horizontale functiebalk onder de twee panelen +- vaste knopvolgorde +- duidelijkere relatie tussen actie, actief paneel en selectie +- ontwerpvoorbereiding voor latere F3-F8 koppeling + +Out of scope: +- geen implementatie van `View` +- geen implementatie van `Edit` +- geen functietoetsen +- geen backendwijzigingen +- geen nieuwe dependencies + +--- + +## 2. Positie en layout + +De functiebalk staat vast onder de twee panelen, op de plek van de huidige onderste actiebalk. + +Layoutdoelen: +- horizontaal gecentreerd in de beschikbare breedte +- compact in hoogte +- kleine, gelijkmatige spacing tussen knoppen +- visueel duidelijk gescheiden van de paneelzone, maar zonder grote verticale band +- uitbreidbaar naar extra labels of functietoetsbadges zonder redesign + +Voorstel: +- één compacte horizontale rij +- de rij krijgt een eigen container binnen de footerzone +- links en rechts geen brede utilityblokken +- status/fouttekst blijft buiten of onder de functiebalk, zodat de knoppenrij zelf compact blijft + +--- + +## 3. Vaste volgorde van de knoppen + +De functiebalk gebruikt exact deze volgorde: + +1. `View` +2. `Edit` +3. `Copy` +4. `Move` +5. `Rename` +6. `MKdir` +7. `Delete` + +Reden: +- sluit aan op klassieke file-manager verwachtingen +- groepeert navigatie-/inhoudsacties eerst, daarna muterende file-acties +- houdt de destructieve actie `Delete` aan het einde + +--- + +## 4. Relatie met toekomstig functietoetsgebruik + +De functiebalk moet later zonder structurele herbouw koppelbaar zijn aan: + +- `F3 = View` +- `F4 = Edit` +- `F5 = Copy` +- `F6 = Move` +- `F7 = MKdir` +- `F8 = Delete` + +Ontwerpimplicatie: +- elke knop moet later een compacte functietoetsbadge of prefix kunnen tonen +- `Rename` blijft voorlopig zonder vaste F-toets in deze mapping +- deze stap implementeert nog geen keyboardbindingen of badgegedrag + +--- + +## 5. Relatie met actief paneel en selectie + +Alle acties in de functiebalk werken altijd vanuit het actieve paneel. + +### Geen selectie +- `View`: disabled +- `Edit`: disabled +- `Copy`: disabled +- `Move`: disabled +- `Rename`: disabled +- `MKdir`: enabled +- `Delete`: disabled + +### Exact 1 selectie +- `View`: later afhankelijk van file-type; in deze stap nog niet functioneel +- `Edit`: later afhankelijk van file-type; in deze stap nog niet functioneel +- `Copy`: enabled als huidige backendactie geldig is +- `Move`: enabled als huidige backendactie geldig is +- `Rename`: enabled +- `MKdir`: enabled +- `Delete`: enabled + +### Meerdere selecties +- `View`: disabled +- `Edit`: disabled +- `Copy`: enabled als alle geselecteerde items compatibel zijn met bestaande backendscope +- `Move`: enabled als alle geselecteerde items compatibel zijn met bestaande backendscope +- `Rename`: disabled +- `MKdir`: enabled +- `Delete`: enabled + +### File- of directoryselectie +- `Copy` en `Move` blijven gebonden aan de huidige backendbeperking +- zolang backend `file-only` is voor copy/move: + - selectie met directories blokkeert `Copy` + - selectie met directories blokkeert `Move` +- `Delete` blijft werken volgens bestaande backendregels +- `Rename` volgt bestaande rename-semantiek + +Belangrijk: +- de functiebalk toont niet alleen acties, maar reflecteert ook duidelijk welke acties in de huidige context geldig zijn via enabled/disabled toestand + +--- + +## 6. Scopebeperking + +Nog niet in deze stap: +- geen `View`-implementatie +- geen `Edit`-implementatie +- geen functietoetsen +- geen backendwijzigingen +- geen extra UI-frameworks + +Deze ontwerpstap gaat dus alleen over de functiebalk zelf, niet over nieuwe actiecapaciteit. + +--- + +## 7. Impactanalyse + +Waarschijnlijk te wijzigen bestanden: +- `webui/html/index.html` +- `webui/html/style.css` +- `webui/html/app.js` +- `webui/backend/tests/golden/test_ui_smoke_golden.py` + +### Regressierisico + +Belangrijkste risico's: +- huidige actieknoppen verliezen hun correcte enabled/disabled logica +- de relatie tussen actief paneel en actiebalk wordt visueel minder duidelijk +- keyboard- en klikflows kunnen breken als knop-ids of handlers onzorgvuldig worden vervangen +- extra compactheid kan ten koste gaan van leesbaarheid op smallere schermen + +Mitigatie: +- bestaande action handlers hergebruiken waar mogelijk +- ids voor bestaande werkende acties stabiel houden of gecontroleerd migreren +- disabled-logica centraal laten in plaats van per knop ad hoc +- smoke tests uitbreiden met de nieuwe functiebalkstructuur + +--- + +## 8. Teststrategie + +### Smoke/regressietests + +Aan te passen: +- `test_ui_smoke_golden.py` + +Nieuwe of aangepaste checks: +- functiebalkcontainer aanwezig onder de panelen +- knopvolgorde in HTML komt overeen met het ontwerp +- bestaande actieknoppen voor werkende backendacties blijven aanwezig +- assets blijven correct gemount + +### Handmatige validatie + +Te valideren bij implementatie: +- functiebalk blijft compact op desktop +- functiebalk blijft bruikbaar op smallere breedte +- actief paneel blijft bepalend voor de actiecontext +- disabled/enable toestand klopt bij: + - geen selectie + - 1 selectie + - meerdere selecties + - gemixte file/directory-selectie +- bestaande keyboard- en klikselectieflow blijft intact 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 8ba0d96..18fabcd 100644 Binary files a/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc and b/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc differ diff --git a/webui/backend/tests/golden/test_ui_smoke_golden.py b/webui/backend/tests/golden/test_ui_smoke_golden.py index 0d1dc77..51f086c 100644 --- a/webui/backend/tests/golden/test_ui_smoke_golden.py +++ b/webui/backend/tests/golden/test_ui_smoke_golden.py @@ -32,9 +32,14 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('id="right-pane"', body) self.assertIn('id="left-items"', body) self.assertIn('id="right-items"', body) + self.assertIn('id="function-bar"', body) + self.assertIn('id="view-btn"', body) + self.assertIn('id="edit-btn"', body) self.assertIn('id="mkdir-btn"', body) self.assertIn('id="copy-btn"', body) self.assertIn('id="move-btn"', body) + self.assertIn('id="rename-btn"', body) + self.assertIn('id="delete-btn"', body) self.assertIn('id="left-breadcrumbs"', body) self.assertIn('id="right-breadcrumbs"', body) self.assertIn('id="wildcard-popup"', body) @@ -42,6 +47,18 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertNotIn('id="bookmarks-panel"', body) self.assertNotIn('id="tasks-panel"', body) + ordered_ids = [ + 'id="view-btn"', + 'id="edit-btn"', + 'id="copy-btn"', + 'id="move-btn"', + 'id="rename-btn"', + 'id="mkdir-btn"', + 'id="delete-btn"', + ] + positions = [body.index(marker) for marker in ordered_ids] + self.assertEqual(positions, sorted(positions)) + def test_ui_static_assets_are_present_and_mapped(self) -> None: mount = self._ui_mount() static_root = Path(mount.app.directory) diff --git a/webui/html/app.js b/webui/html/app.js index f348ba5..a58daac 100644 --- a/webui/html/app.js +++ b/webui/html/app.js @@ -856,7 +856,6 @@ function setupEvents() { document.getElementById("copy-btn").onclick = startCopySelected; document.getElementById("move-btn").onclick = startMoveSelected; document.getElementById("mkdir-btn").onclick = createFolderForActivePane; - document.getElementById("add-bookmark-btn").onclick = addBookmark; const wildcard = wildcardPopupElements(); wildcard.cancelButton.onclick = closeWildcardPopup; diff --git a/webui/html/index.html b/webui/html/index.html index daf9bad..b4cdfc5 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -60,14 +60,15 @@ diff --git a/webui/html/style.css b/webui/html/style.css index 0e3263c..654550a 100644 --- a/webui/html/style.css +++ b/webui/html/style.css @@ -295,7 +295,29 @@ button:disabled { #footer-bar { border-top: 1px solid var(--border); background: var(--panel); - padding: 4px 10px 2px 10px; + padding: 4px 10px 3px 10px; + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; +} + +#function-bar-meta { + margin-bottom: 0; + justify-content: center; +} + +#function-bar { + margin-bottom: 0; + justify-content: center; + gap: 5px; + width: 100%; + max-width: 760px; +} + +#function-bar button { + min-width: 72px; + padding: 3px 8px; } .popup-overlay {