feat: menu layout
This commit is contained in:
@@ -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
|
||||||
Binary file not shown.
@@ -32,9 +32,14 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
|||||||
self.assertIn('id="right-pane"', body)
|
self.assertIn('id="right-pane"', body)
|
||||||
self.assertIn('id="left-items"', body)
|
self.assertIn('id="left-items"', body)
|
||||||
self.assertIn('id="right-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="mkdir-btn"', body)
|
||||||
self.assertIn('id="copy-btn"', body)
|
self.assertIn('id="copy-btn"', body)
|
||||||
self.assertIn('id="move-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="left-breadcrumbs"', body)
|
||||||
self.assertIn('id="right-breadcrumbs"', body)
|
self.assertIn('id="right-breadcrumbs"', body)
|
||||||
self.assertIn('id="wildcard-popup"', body)
|
self.assertIn('id="wildcard-popup"', body)
|
||||||
@@ -42,6 +47,18 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
|||||||
self.assertNotIn('id="bookmarks-panel"', body)
|
self.assertNotIn('id="bookmarks-panel"', body)
|
||||||
self.assertNotIn('id="tasks-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:
|
def test_ui_static_assets_are_present_and_mapped(self) -> None:
|
||||||
mount = self._ui_mount()
|
mount = self._ui_mount()
|
||||||
static_root = Path(mount.app.directory)
|
static_root = Path(mount.app.directory)
|
||||||
|
|||||||
@@ -856,7 +856,6 @@ function setupEvents() {
|
|||||||
document.getElementById("copy-btn").onclick = startCopySelected;
|
document.getElementById("copy-btn").onclick = startCopySelected;
|
||||||
document.getElementById("move-btn").onclick = startMoveSelected;
|
document.getElementById("move-btn").onclick = startMoveSelected;
|
||||||
document.getElementById("mkdir-btn").onclick = createFolderForActivePane;
|
document.getElementById("mkdir-btn").onclick = createFolderForActivePane;
|
||||||
document.getElementById("add-bookmark-btn").onclick = addBookmark;
|
|
||||||
|
|
||||||
const wildcard = wildcardPopupElements();
|
const wildcard = wildcardPopupElements();
|
||||||
wildcard.cancelButton.onclick = closeWildcardPopup;
|
wildcard.cancelButton.onclick = closeWildcardPopup;
|
||||||
|
|||||||
@@ -60,14 +60,15 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<section id="footer-bar">
|
<section id="footer-bar">
|
||||||
<div class="toolbar compact-toolbar">
|
<div id="function-bar-meta" class="pathline compact-line">Active:<code id="active-pane-label">left</code></div>
|
||||||
<div class="pathline compact-line">A:<code id="active-pane-label">left</code></div>
|
<div id="function-bar" class="toolbar compact-toolbar">
|
||||||
<button id="rename-btn" disabled>Rename</button>
|
<button id="view-btn" type="button" disabled>View</button>
|
||||||
<button id="delete-btn" disabled>Delete</button>
|
<button id="edit-btn" type="button" disabled>Edit</button>
|
||||||
<button id="copy-btn" disabled>Copy</button>
|
<button id="copy-btn" type="button" disabled>Copy</button>
|
||||||
<button id="move-btn" disabled>Move</button>
|
<button id="move-btn" type="button" disabled>Move</button>
|
||||||
<button id="mkdir-btn">Mkdir</button>
|
<button id="rename-btn" type="button" disabled>Rename</button>
|
||||||
<button id="add-bookmark-btn">Add bookmark</button>
|
<button id="mkdir-btn" type="button">MKdir</button>
|
||||||
|
<button id="delete-btn" type="button" disabled>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="actions-error" class="error"></div>
|
<div id="actions-error" class="error"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+23
-1
@@ -295,7 +295,29 @@ button:disabled {
|
|||||||
#footer-bar {
|
#footer-bar {
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
background: var(--panel);
|
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 {
|
.popup-overlay {
|
||||||
|
|||||||
Reference in New Issue
Block a user