feat: contextmenu deel 2
This commit is contained in:
+96
-1
@@ -52,6 +52,12 @@ let deleteConfirmState = {
|
||||
items: [],
|
||||
recursivePaths: [],
|
||||
};
|
||||
let contextMenuState = {
|
||||
open: false,
|
||||
pane: "left",
|
||||
items: [],
|
||||
anchorPath: null,
|
||||
};
|
||||
let batchMoveState = {
|
||||
destinationBase: "",
|
||||
count: 0,
|
||||
@@ -315,6 +321,62 @@ function feedbackElements() {
|
||||
};
|
||||
}
|
||||
|
||||
function contextMenuElements() {
|
||||
return {
|
||||
menu: document.getElementById("context-menu"),
|
||||
scope: document.getElementById("context-menu-scope"),
|
||||
target: document.getElementById("context-menu-target"),
|
||||
placeholder: document.getElementById("context-menu-open-placeholder"),
|
||||
};
|
||||
}
|
||||
|
||||
function isContextMenuOpen() {
|
||||
return contextMenuState.open && !contextMenuElements().menu.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function closeContextMenu() {
|
||||
const elements = contextMenuElements();
|
||||
contextMenuState.open = false;
|
||||
contextMenuState.pane = "left";
|
||||
contextMenuState.items = [];
|
||||
contextMenuState.anchorPath = null;
|
||||
if (!elements.menu) {
|
||||
return;
|
||||
}
|
||||
elements.menu.classList.add("hidden");
|
||||
elements.scope.textContent = "";
|
||||
elements.target.textContent = "";
|
||||
}
|
||||
|
||||
function openContextMenu(pane, entry, event) {
|
||||
if (!entry || entry.isParent) {
|
||||
return;
|
||||
}
|
||||
const elements = contextMenuElements();
|
||||
const selectedItems = paneState(pane).selectedItems || [];
|
||||
const selectedPathsSet = new Set(selectedItems.map((item) => item.path));
|
||||
const items = selectedPathsSet.has(entry.path)
|
||||
? selectedItems.map((item) => ({ ...item }))
|
||||
: [selectedEntryFromItem(entry)];
|
||||
|
||||
contextMenuState.open = true;
|
||||
contextMenuState.pane = pane;
|
||||
contextMenuState.items = items;
|
||||
contextMenuState.anchorPath = entry.path;
|
||||
|
||||
const isMulti = items.length > 1;
|
||||
elements.scope.textContent = isMulti ? "Multi-selection" : "Single item";
|
||||
elements.target.textContent = isMulti ? `${items.length} selected items` : entry.name;
|
||||
|
||||
const menuWidth = 220;
|
||||
const menuHeight = 120;
|
||||
const x = Math.min(event.clientX, window.innerWidth - menuWidth - 12);
|
||||
const y = Math.min(event.clientY, window.innerHeight - menuHeight - 12);
|
||||
elements.menu.style.left = `${Math.max(8, x)}px`;
|
||||
elements.menu.style.top = `${Math.max(8, y)}px`;
|
||||
elements.menu.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function settingsElements() {
|
||||
return {
|
||||
overlay: document.getElementById("settings-modal"),
|
||||
@@ -1689,6 +1751,7 @@ function updatePaneFocusLine(pane) {
|
||||
}
|
||||
|
||||
function renderPaneItems(pane) {
|
||||
closeContextMenu();
|
||||
const model = paneState(pane);
|
||||
const items = document.getElementById(`${pane}-items`);
|
||||
items.innerHTML = "";
|
||||
@@ -1718,6 +1781,9 @@ function renderPaneItems(pane) {
|
||||
clearSelectionAnchor(pane);
|
||||
renderPaneItems(pane);
|
||||
};
|
||||
up.oncontextmenu = (event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
const upNameCell = document.createElement("span");
|
||||
upNameCell.className = "entry-name entry-dir";
|
||||
upNameCell.append(createSelectionSlot(pane, { ...entry, isParent: true }, index));
|
||||
@@ -1789,6 +1855,12 @@ function renderPaneItems(pane) {
|
||||
}
|
||||
renderPaneItems(pane);
|
||||
};
|
||||
row.oncontextmenu = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setActivePane(pane);
|
||||
openContextMenu(pane, entry, event);
|
||||
};
|
||||
if (entry.kind === "file" && isImageSelection({ path: entry.path, name: entry.name, kind: entry.kind })) {
|
||||
row.ondblclick = (ev) => {
|
||||
ev.stopPropagation();
|
||||
@@ -1873,6 +1945,7 @@ async function loadBrowsePane(pane) {
|
||||
}
|
||||
|
||||
function navigateTo(pane, path) {
|
||||
closeContextMenu();
|
||||
const model = paneState(pane);
|
||||
model.currentPath = path;
|
||||
model.currentRowIndex = 0;
|
||||
@@ -3335,6 +3408,13 @@ function clearSelectionForActivePane() {
|
||||
}
|
||||
|
||||
function handleKeyboardShortcuts(event) {
|
||||
if (isContextMenuOpen()) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
closeContextMenu();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape" && !uploadElements().menuPopup.classList.contains("hidden")) {
|
||||
event.preventDefault();
|
||||
closeUploadMenu();
|
||||
@@ -3617,9 +3697,24 @@ function setupEvents() {
|
||||
document.addEventListener("click", (event) => {
|
||||
const elements = uploadElements();
|
||||
if (!elements.menu || elements.menu.contains(event.target)) {
|
||||
} else {
|
||||
closeUploadMenu();
|
||||
}
|
||||
const contextMenu = contextMenuElements().menu;
|
||||
if (contextMenu && !contextMenu.contains(event.target)) {
|
||||
closeContextMenu();
|
||||
}
|
||||
});
|
||||
document.addEventListener("contextmenu", (event) => {
|
||||
const contextMenu = contextMenuElements().menu;
|
||||
if (contextMenu && contextMenu.contains(event.target)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
closeUploadMenu();
|
||||
const row = event.target instanceof Element ? event.target.closest("li[data-row-index]") : null;
|
||||
if (!row) {
|
||||
closeContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
const rename = renameElements();
|
||||
|
||||
@@ -727,6 +727,45 @@ button:disabled {
|
||||
width: min(440px, calc(100vw - 24px));
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
min-width: 220px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface-elevated);
|
||||
box-shadow: var(--shadow-elevated);
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
.context-menu-scope {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.context-menu-target {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.context-menu-separator {
|
||||
height: 1px;
|
||||
margin: 8px 0;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.context-menu button {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
#upload-modal .popup-card {
|
||||
max-width: 320px;
|
||||
padding: 12px 14px;
|
||||
|
||||
@@ -118,6 +118,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="context-menu" class="context-menu hidden" role="menu" aria-label="Item context menu">
|
||||
<div id="context-menu-scope" class="context-menu-scope"></div>
|
||||
<div id="context-menu-target" class="context-menu-target"></div>
|
||||
<div class="context-menu-separator"></div>
|
||||
<button id="context-menu-open-placeholder" type="button" role="menuitem" disabled>Actions coming next</button>
|
||||
</div>
|
||||
|
||||
<div id="settings-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="settings-title">
|
||||
<div class="popup-card settings-card">
|
||||
<button id="settings-close-btn" class="viewer-close" type="button" aria-label="Close settings">X</button>
|
||||
|
||||
Reference in New Issue
Block a user