From fa9dc00f6122f4c76498a9ebee862606404c33cc Mon Sep 17 00:00:00 2001 From: kodi Date: Wed, 11 Mar 2026 12:56:02 +0100 Subject: [PATCH] feat: keyboard functionaliteit shift + / Shift - toegevoegd --- .../test_ui_smoke_golden.cpython-313.pyc | Bin 4064 -> 4216 bytes .../tests/golden/test_ui_smoke_golden.py | 2 + webui/html/app.js | 132 ++++++++++++++++++ webui/html/index.html | 14 ++ webui/html/style.css | 46 ++++++ 5 files changed, 194 insertions(+) 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 4136f02f1d008b87f89de9285d4f96635c748e48..8ba0d96fa32760353e9c896fea15c36838de6a4a 100644 GIT binary patch delta 242 zcmaDL|3iWAGcPX}0}!x=Zp=*L*vR*fnMr|pvOkx~W+Rq-rpd9aB9phW34rOFY`l{X zv57MZPTt0*EGQJpY{~@E$-tn%kj|hfJoy#dV*}yL6kDb8%$$_u#G(}4g8YKg0;MWx zRKdiOlGLI+-ORj#(vr=m*{vBF-8Qpx@-RyG^LFxH5Yt`}a)ra_1_uv>YrFx-wc0Ge z#lp;}yIGXikC9cB)%d&K<_tbBMm=>#%NZQkB{VNeXntV;az2DGGO+M;R9$A4yurfL T?%U+s>i>b6fkmoF4Cqh*_?<aWaDK?XV4U! zY{dR}vpa_+BcsOVMou2a&8xW>nHjY=ujTb)WEEyL{;su&--}U5mC escapeRegExp(part)) + .join(".*"); + return new RegExp(`^${escaped}$`, "i"); +} + +function applyWildcardSelection(mode, pattern) { + const pane = state.activePane; + const model = paneState(pane); + const matcher = globToRegExp(pattern); + const candidates = model.visibleItems.filter((entry) => !entry.isParent); + const matches = candidates.filter((entry) => matcher.test(entry.name)); + const matchPaths = new Set(matches.map((entry) => entry.path)); + + let changed = 0; + if (mode === "select") { + const existing = new Set(model.selectedItems.map((item) => item.path)); + for (const entry of matches) { + if (existing.has(entry.path)) { + continue; + } + model.selectedItems.push({ path: entry.path, name: entry.name, kind: entry.kind }); + changed += 1; + } + if (matches.length > 0) { + const last = matches[matches.length - 1]; + model.selectedItem = { path: last.path, name: last.name, kind: last.kind }; + } + } else { + const before = model.selectedItems.length; + model.selectedItems = model.selectedItems.filter((item) => !matchPaths.has(item.path)); + changed = before - model.selectedItems.length; + if (!model.selectedItem || matchPaths.has(model.selectedItem.path)) { + model.selectedItem = model.selectedItems.length > 0 ? model.selectedItems[model.selectedItems.length - 1] : null; + } + } + + renderPaneItems(pane); + setStatus(`Wildcard ${mode}: ${matches.length} matched, ${changed} changed`); +} + +function closeWildcardPopup() { + const elements = wildcardPopupElements(); + elements.overlay.classList.add("hidden"); + elements.error.textContent = ""; + elements.input.value = ""; +} + +function submitWildcardPopup() { + const elements = wildcardPopupElements(); + const pattern = elements.input.value.trim(); + if (!pattern) { + elements.error.textContent = "Pattern is required"; + return; + } + try { + applyWildcardSelection(wildcardDialogMode, pattern); + closeWildcardPopup(); + } catch (err) { + elements.error.textContent = `Wildcard: ${err.message}`; + } +} + +function openWildcardPopup(mode) { + wildcardDialogMode = mode; + const pane = state.activePane; + const elements = wildcardPopupElements(); + elements.title.textContent = mode === "select" ? "Wildcard Select" : "Wildcard Deselect"; + elements.meta.textContent = `Active pane: ${pane} (visible items only, case-insensitive)`; + elements.applyButton.textContent = mode === "select" ? "Select" : "Deselect"; + elements.error.textContent = ""; + elements.input.value = ""; + elements.overlay.classList.remove("hidden"); + elements.input.focus(); +} + function moveCurrentRow(delta) { const pane = state.activePane; const model = paneState(pane); @@ -671,10 +770,23 @@ function clearSelectionForActivePane() { } function handleKeyboardShortcuts(event) { + if (isWildcardPopupOpen()) { + return; + } if (!shouldHandleShortcut(event.target)) { return; } + if (event.shiftKey && event.key === "+") { + event.preventDefault(); + openWildcardPopup("select"); + return; + } + if (event.shiftKey && event.key === "_") { + event.preventDefault(); + openWildcardPopup("deselect"); + return; + } if (event.metaKey && event.key === "ArrowUp") { event.preventDefault(); jumpCurrentRow("start"); @@ -745,6 +857,26 @@ function setupEvents() { 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; + wildcard.applyButton.onclick = submitWildcardPopup; + wildcard.input.onkeydown = (event) => { + if (event.key === "Enter") { + event.preventDefault(); + submitWildcardPopup(); + return; + } + if (event.key === "Escape") { + event.preventDefault(); + closeWildcardPopup(); + } + }; + wildcard.overlay.onclick = (event) => { + if (event.target === wildcard.overlay) { + closeWildcardPopup(); + } + }; } async function init() { diff --git a/webui/html/index.html b/webui/html/index.html index 01a2844..daf9bad 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -73,6 +73,20 @@ + + diff --git a/webui/html/style.css b/webui/html/style.css index de9cbb6..0e3263c 100644 --- a/webui/html/style.css +++ b/webui/html/style.css @@ -298,6 +298,52 @@ button:disabled { padding: 4px 10px 2px 10px; } +.popup-overlay { + position: fixed; + inset: 0; + background: rgba(20, 32, 50, 0.25); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.popup-overlay.hidden { + display: none; +} + +.popup-card { + width: min(420px, calc(100vw - 24px)); + background: #fff; + border: 1px solid var(--border); + border-radius: 6px; + padding: 10px; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.16); +} + +.popup-meta { + color: var(--muted); + font-size: 12px; + margin: 4px 0 8px 0; +} + +.popup-label { + font-size: 12px; + color: var(--muted); +} + +#wildcard-pattern-input { + width: 100%; + margin-top: 4px; + margin-bottom: 6px; +} + +.popup-actions { + display: flex; + gap: 8px; + justify-content: flex-end; +} + @media (max-width: 1200px) { .workspace { grid-template-columns: 1fr;