feat: menu layout

This commit is contained in:
kodi
2026-03-11 13:28:18 +01:00
parent fa9dc00f61
commit 31a42d34c7
6 changed files with 237 additions and 10 deletions
+188
View File
@@ -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
@@ -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)
-1
View File
@@ -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;
+9 -8
View File
@@ -60,14 +60,15 @@
</main>
<section id="footer-bar">
<div class="toolbar compact-toolbar">
<div class="pathline compact-line">A:<code id="active-pane-label">left</code></div>
<button id="rename-btn" disabled>Rename</button>
<button id="delete-btn" disabled>Delete</button>
<button id="copy-btn" disabled>Copy</button>
<button id="move-btn" disabled>Move</button>
<button id="mkdir-btn">Mkdir</button>
<button id="add-bookmark-btn">Add bookmark</button>
<div id="function-bar-meta" class="pathline compact-line">Active:<code id="active-pane-label">left</code></div>
<div id="function-bar" class="toolbar compact-toolbar">
<button id="view-btn" type="button" disabled>View</button>
<button id="edit-btn" type="button" disabled>Edit</button>
<button id="copy-btn" type="button" disabled>Copy</button>
<button id="move-btn" type="button" disabled>Move</button>
<button id="rename-btn" type="button" disabled>Rename</button>
<button id="mkdir-btn" type="button">MKdir</button>
<button id="delete-btn" type="button" disabled>Delete</button>
</div>
<div id="actions-error" class="error"></div>
</section>
+23 -1
View File
@@ -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 {