feat: keyboard functionaliteit shift + / Shift - toegevoegd
This commit is contained in:
Binary file not shown.
@@ -37,6 +37,8 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn('id="move-btn"', body)
|
||||
self.assertIn('id="left-breadcrumbs"', body)
|
||||
self.assertIn('id="right-breadcrumbs"', body)
|
||||
self.assertIn('id="wildcard-popup"', body)
|
||||
self.assertIn('id="wildcard-pattern-input"', body)
|
||||
self.assertNotIn('id="bookmarks-panel"', body)
|
||||
self.assertNotIn('id="tasks-panel"', body)
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ let state = {
|
||||
lastTaskCount: 0,
|
||||
};
|
||||
const ROW_JUMP_STEP = 10;
|
||||
let wildcardDialogMode = "select";
|
||||
|
||||
function paneState(pane) {
|
||||
return state.panes[pane];
|
||||
@@ -616,6 +617,104 @@ function shouldHandleShortcut(target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function wildcardPopupElements() {
|
||||
return {
|
||||
overlay: document.getElementById("wildcard-popup"),
|
||||
title: document.getElementById("wildcard-popup-title"),
|
||||
meta: document.getElementById("wildcard-popup-meta"),
|
||||
input: document.getElementById("wildcard-pattern-input"),
|
||||
error: document.getElementById("wildcard-popup-error"),
|
||||
applyButton: document.getElementById("wildcard-apply-btn"),
|
||||
cancelButton: document.getElementById("wildcard-cancel-btn"),
|
||||
};
|
||||
}
|
||||
|
||||
function isWildcardPopupOpen() {
|
||||
return !wildcardPopupElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
||||
}
|
||||
|
||||
function globToRegExp(pattern) {
|
||||
const escaped = pattern
|
||||
.split("*")
|
||||
.map((part) => 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() {
|
||||
|
||||
@@ -73,6 +73,20 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="wildcard-popup" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="wildcard-popup-title">
|
||||
<div class="popup-card">
|
||||
<h3 id="wildcard-popup-title">Wildcard Select</h3>
|
||||
<div id="wildcard-popup-meta" class="popup-meta"></div>
|
||||
<label for="wildcard-pattern-input" class="popup-label">Pattern</label>
|
||||
<input id="wildcard-pattern-input" type="text" autocomplete="off" placeholder="*.mkv">
|
||||
<div id="wildcard-popup-error" class="error"></div>
|
||||
<div class="popup-actions">
|
||||
<button id="wildcard-apply-btn" type="button">Apply</button>
|
||||
<button id="wildcard-cancel-btn" type="button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/ui/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user