feat: contextmenu deel 1
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -274,7 +274,10 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
|||||||
self.assertIn("function mediaIconSvg(type)", app_js)
|
self.assertIn("function mediaIconSvg(type)", app_js)
|
||||||
self.assertIn('const iconType = iconTypeForEntry(entry);', app_js)
|
self.assertIn('const iconType = iconTypeForEntry(entry);', app_js)
|
||||||
self.assertIn('function createMediaSlot(entry)', app_js)
|
self.assertIn('function createMediaSlot(entry)', app_js)
|
||||||
self.assertNotIn("select-marker", app_js)
|
self.assertIn('function createSelectionSlot(pane, entry, index)', app_js)
|
||||||
|
self.assertIn('entry-select-slot', app_js)
|
||||||
|
self.assertIn('entry-select-toggle', app_js)
|
||||||
|
self.assertIn('toggleSelectionAtIndex(pane, selectedEntryFromItem(entry), index);', app_js)
|
||||||
self.assertIn('function openSearch()', app_js)
|
self.assertIn('function openSearch()', app_js)
|
||||||
self.assertIn('async function submitSearch()', app_js)
|
self.assertIn('async function submitSearch()', app_js)
|
||||||
self.assertIn('async function openInfo()', app_js)
|
self.assertIn('async function openInfo()', app_js)
|
||||||
|
|||||||
+44
-3
@@ -1237,6 +1237,45 @@ function createMediaSlot(entry) {
|
|||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSelectionSlot(pane, entry, index) {
|
||||||
|
const slot = document.createElement("span");
|
||||||
|
slot.className = "entry-select-slot";
|
||||||
|
|
||||||
|
if (entry.isParent) {
|
||||||
|
const placeholder = document.createElement("span");
|
||||||
|
placeholder.className = "entry-select-toggle is-disabled";
|
||||||
|
placeholder.setAttribute("aria-hidden", "true");
|
||||||
|
const indicator = document.createElement("span");
|
||||||
|
indicator.className = "entry-select-indicator";
|
||||||
|
placeholder.append(indicator);
|
||||||
|
slot.append(placeholder);
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = "entry-select-toggle";
|
||||||
|
const selected = selectedPaths(pane).includes(entry.path);
|
||||||
|
if (selected) {
|
||||||
|
button.classList.add("is-selected");
|
||||||
|
}
|
||||||
|
button.setAttribute("aria-label", `${selected ? "Deselect" : "Select"} ${entry.name}`);
|
||||||
|
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
||||||
|
const indicator = document.createElement("span");
|
||||||
|
indicator.className = "entry-select-indicator";
|
||||||
|
button.append(indicator);
|
||||||
|
button.onclick = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setActivePane(pane);
|
||||||
|
paneState(pane).currentRowIndex = index;
|
||||||
|
toggleSelectionAtIndex(pane, selectedEntryFromItem(entry), index);
|
||||||
|
renderPaneItems(pane);
|
||||||
|
};
|
||||||
|
slot.append(button);
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
const data = await apiRequest("GET", "/api/settings");
|
const data = await apiRequest("GET", "/api/settings");
|
||||||
settingsState.showThumbnails = !!data.show_thumbnails;
|
settingsState.showThumbnails = !!data.show_thumbnails;
|
||||||
@@ -1546,7 +1585,7 @@ function formatFileSize(bytes) {
|
|||||||
return `${(bytes / (1024 ** 4)).toFixed(1)} TB`;
|
return `${(bytes / (1024 ** 4)).toFixed(1)} TB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBrowseItem(pane, entry, kind) {
|
function createBrowseItem(pane, entry, kind, index) {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.className = "selectable";
|
li.className = "selectable";
|
||||||
li.dataset.path = entry.path;
|
li.dataset.path = entry.path;
|
||||||
@@ -1567,6 +1606,7 @@ function createBrowseItem(pane, entry, kind) {
|
|||||||
|
|
||||||
const name = document.createElement("span");
|
const name = document.createElement("span");
|
||||||
name.className = `entry-name ${kind === "directory" ? "entry-dir" : "entry-file"}`;
|
name.className = `entry-name ${kind === "directory" ? "entry-dir" : "entry-file"}`;
|
||||||
|
name.append(createSelectionSlot(pane, { ...entry, kind }, index));
|
||||||
name.append(createMediaSlot({ ...entry, kind }));
|
name.append(createMediaSlot({ ...entry, kind }));
|
||||||
|
|
||||||
if (kind === "directory") {
|
if (kind === "directory") {
|
||||||
@@ -1680,6 +1720,7 @@ function renderPaneItems(pane) {
|
|||||||
};
|
};
|
||||||
const upNameCell = document.createElement("span");
|
const upNameCell = document.createElement("span");
|
||||||
upNameCell.className = "entry-name entry-dir";
|
upNameCell.className = "entry-name entry-dir";
|
||||||
|
upNameCell.append(createSelectionSlot(pane, { ...entry, isParent: true }, index));
|
||||||
upNameCell.append(createMediaSlot({ name: "..", path: entry.path, kind: "directory" }));
|
upNameCell.append(createMediaSlot({ name: "..", path: entry.path, kind: "directory" }));
|
||||||
const upName = document.createElement("button");
|
const upName = document.createElement("button");
|
||||||
upName.type = "button";
|
upName.type = "button";
|
||||||
@@ -1705,7 +1746,7 @@ function renderPaneItems(pane) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = createBrowseItem(pane, entry, entry.kind);
|
const row = createBrowseItem(pane, entry, entry.kind, index);
|
||||||
row.dataset.rowIndex = String(index);
|
row.dataset.rowIndex = String(index);
|
||||||
if (index === model.currentRowIndex) {
|
if (index === model.currentRowIndex) {
|
||||||
row.classList.add("is-current-row");
|
row.classList.add("is-current-row");
|
||||||
@@ -1724,7 +1765,7 @@ function renderPaneItems(pane) {
|
|||||||
navigateTo(pane, entry.path);
|
navigateTo(pane, entry.path);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const fileName = row.querySelector(".entry-file span");
|
const fileName = row.querySelector(".entry-file .entry-label");
|
||||||
if (fileName) {
|
if (fileName) {
|
||||||
fileName.onclick = (ev) => {
|
fileName.onclick = (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|||||||
@@ -346,6 +346,72 @@ button:disabled {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entry-select-slot {
|
||||||
|
width: 18px;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-select-toggle {
|
||||||
|
width: 18px;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: transparent;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-select-toggle:hover {
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-select-toggle.is-disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-select-indicator {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1.5px solid color-mix(in srgb, var(--color-text-muted) 80%, transparent);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 100ms ease, border-color 100ms ease, background 100ms ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list li:hover .entry-select-indicator,
|
||||||
|
.entry-select-toggle:focus-visible .entry-select-indicator,
|
||||||
|
.entry-select-toggle.is-selected .entry-select-indicator {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-select-toggle.is-selected .entry-select-indicator {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
background: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-select-toggle.is-selected .entry-select-indicator::after {
|
||||||
|
content: "✓";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
.entry-media-slot {
|
.entry-media-slot {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
min-width: 28px;
|
min-width: 28px;
|
||||||
|
|||||||
Reference in New Issue
Block a user