416 lines
20 KiB
JavaScript
416 lines
20 KiB
JavaScript
import { browseFolder, thumbURL } from "./api.mjs";
|
|
import { readState, sortFiles, writeState } from "./state.mjs";
|
|
|
|
const FOLDER_SVG =
|
|
'<svg viewBox="0 0 64 64" aria-hidden="true"><path d="M52.291,56.817H5.626c-1.006,0-1.922-.594-2.5-1.323-.752-.949-.846-2.209-.483-3.372l7.293-23.34c.522-1.67,1.625-2.992,3.453-3.243h46.148c2.155.308,3.418,2.045,3.193,4.245l-7.097,23.693c-.491,1.64-1.523,2.993-3.343,3.341ZM50.726,14.308h-21.805c-.429-.181-.717-.689-.997-1.031l-3.967-4.843c-.559-.682-1.432-1.249-2.369-1.25H6.186c-1.185,0-2.24.531-3.095,1.272-1.098.952-1.545,2.24-1.818,3.706v31.447c1.841-5.514,3.332-10.857,5.103-16.241.459-1.396,1.126-2.594,2.154-3.621,1.355-1.054,2.862-2.056,4.685-2.057h42.426c.669-2.549-.634-7.369-4.914-7.382Z"/></svg>';
|
|
|
|
function escapeHtml(value) {
|
|
return String(value).replace(/[&<>"]/g, (char) => (
|
|
{ "&": "&", "<": "<", ">": ">", '"': """ }[char]
|
|
));
|
|
}
|
|
|
|
export function injectCSS() {
|
|
if (document.getElementById("dumas-lif-css")) return;
|
|
const style = document.createElement("style");
|
|
style.id = "dumas-lif-css";
|
|
style.textContent = `
|
|
.dlf-root { display:flex; flex-direction:column; gap:8px; padding:8px 10px; box-sizing:border-box; font-family:inherit; }
|
|
.dlf-folderrow { display:flex; gap:6px; }
|
|
.dlf-folder { flex:1; min-width:0; background:#141414; border:1px solid #3a3a3a; border-radius:5px; padding:7px 8px; color:#cfcfcf; font-size:11px; box-sizing:border-box; }
|
|
.dlf-folder:focus { outline:none; border-color:#e66a2c; }
|
|
.dlf-browse { display:flex; align-items:center; gap:5px; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.16); border-radius:5px; color:#ddd; font-size:11px; padding:0 9px; cursor:pointer; white-space:nowrap; }
|
|
.dlf-browse:hover { border-color:#e66a2c; color:#fff; }
|
|
.dlf-browse svg { width:13px; height:13px; fill:currentColor; }
|
|
.dlf-pick { background:#e66a2c; border:1px solid #e66a2c; border-radius:6px; padding:8px; font-size:12px; color:#fff; text-align:center; font-weight:500; cursor:pointer; }
|
|
.dlf-pick:hover { filter:brightness(1.08); }
|
|
.dlf-pick.empty { background:rgba(255,255,255,0.05); border-color:rgba(255,255,255,0.16); color:#9a9a9a; }
|
|
.dlf-msg { font-size:11px; color:#e0a33e; line-height:1.4; }
|
|
.dlf-msg:empty { display:none; }
|
|
.dlf-menu, .dlf-gallery, .dlf-browse-pop { position:fixed; z-index:99999; background:#191919; box-shadow:0 14px 40px rgba(0,0,0,0.6); }
|
|
.dlf-menu { border:1px solid #3a3a3a; border-radius:6px; overflow:hidden; min-width:150px; }
|
|
.dlf-menu .it { padding:7px 11px; font-size:12px; color:#cfcfcf; cursor:pointer; display:flex; justify-content:space-between; gap:14px; }
|
|
.dlf-menu .it:hover { background:#2a2a2a; }
|
|
.dlf-menu .it.on { color:#e66a2c; }
|
|
.dlf-gallery, .dlf-browse-pop { border:1px solid #e66a2c; border-radius:9px; display:flex; flex-direction:column; }
|
|
.dlf-gallery { max-height:80vh; }
|
|
.dlf-gal-head { padding:9px 12px; border-bottom:1px solid #333; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
|
|
.dlf-tbtn { background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.16); border-radius:5px; padding:5px 10px; font-size:11px; color:#ddd; cursor:pointer; user-select:none; }
|
|
.dlf-tbtn:hover { border-color:#e66a2c; color:#fff; }
|
|
.dlf-tbtn.active { border-color:#e66a2c; color:#fff; background:rgba(230,106,44,0.18); }
|
|
.dlf-firstwrap { display:flex; align-items:center; }
|
|
.dlf-firstwrap .dlf-tbtn { border-radius:5px 0 0 5px; }
|
|
.dlf-firstn { width:46px; background:#141414; border:1px solid rgba(255,255,255,0.16); border-left:none; border-radius:0 5px 5px 0; color:#e66a2c; font-size:11px; padding:5px 4px; text-align:center; box-sizing:border-box; }
|
|
.dlf-firstn:focus { outline:none; border-color:#e66a2c; }
|
|
.dlf-count { margin-left:auto; font-size:11px; color:#9a9a9a; white-space:nowrap; }
|
|
.dlf-count b { color:#e66a2c; }
|
|
.dlf-gal-body { padding:10px 12px; overflow:auto; }
|
|
.dlf-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(84px,1fr)); gap:7px; }
|
|
.dlf-thumb { position:relative; aspect-ratio:1; border-radius:5px; border:2px solid transparent; cursor:pointer; overflow:hidden; background:#0f0f0f; }
|
|
.dlf-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
|
|
.dlf-thumb .veil { position:absolute; inset:0; background:rgba(0,0,0,0.45); }
|
|
.dlf-thumb.sel { border-color:#e66a2c; }
|
|
.dlf-thumb.sel .veil { opacity:0; }
|
|
.dlf-thumb .chk { position:absolute; top:3px; right:3px; width:16px; height:16px; border-radius:50%; background:#e66a2c; color:#fff; font-size:11px; display:none; align-items:center; justify-content:center; }
|
|
.dlf-thumb.sel .chk { display:flex; }
|
|
.dlf-thumb .nm { position:absolute; bottom:0; left:0; right:0; padding:2px 4px; font-size:9px; color:#eee; background:linear-gradient(transparent, rgba(0,0,0,0.75)); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
.dlf-gal-empty { padding:30px; text-align:center; color:#888; font-size:12px; grid-column:1/-1; }
|
|
.dlf-gal-foot { padding:9px 12px; border-top:1px solid #333; display:flex; gap:10px; align-items:center; }
|
|
.dlf-subf { display:flex; align-items:center; gap:6px; font-size:11px; color:#bbb; cursor:pointer; user-select:none; }
|
|
.dlf-subf .box { width:12px; height:12px; border:1px solid #555; border-radius:3px; }
|
|
.dlf-subf.on .box { background:#e66a2c; border-color:#e66a2c; }
|
|
.dlf-done { margin-left:auto; background:#e66a2c; border:1px solid #e66a2c; border-radius:6px; padding:6px 16px; font-size:12px; color:#fff; cursor:pointer; }
|
|
.dlf-done:hover { filter:brightness(1.08); }
|
|
.dlf-bp-head { padding:9px 12px; border-bottom:1px solid #333; font-size:12px; color:#e66a2c; font-weight:600; }
|
|
.dlf-bp-crumb { padding:7px 12px 4px; font-size:11px; color:#999; word-break:break-all; }
|
|
.dlf-bp-list { padding:6px 10px 10px; overflow:auto; display:flex; flex-direction:column; gap:4px; }
|
|
.dlf-bp-item { display:flex; align-items:center; gap:8px; padding:7px 9px; background:#141414; border:1px solid #2c2c2c; border-radius:6px; cursor:pointer; font-size:12px; color:#ddd; }
|
|
.dlf-bp-item:hover { border-color:#e66a2c; background:#1c1c1c; }
|
|
.dlf-bp-item svg { width:13px; height:13px; fill:#e66a2c; flex:0 0 auto; }
|
|
.dlf-bp-item .nm { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
.dlf-bp-item .cnt { color:#777; font-size:11px; white-space:nowrap; }
|
|
.dlf-bp-item.up { color:#9a9a9a; }
|
|
.dlf-bp-empty { padding:14px; text-align:center; color:#777; font-size:12px; }
|
|
.dlf-bp-foot { padding:9px 12px; border-top:1px solid #333; display:flex; gap:8px; }
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
function positionBelow(popup, anchorEl, width) {
|
|
const rect = anchorEl.getBoundingClientRect();
|
|
popup.style.width = `${width}px`;
|
|
popup.style.left = `${Math.max(8, Math.min(rect.left, window.innerWidth - width - 8))}px`;
|
|
popup.style.top = `${rect.bottom + 4}px`;
|
|
requestAnimationFrame(() => {
|
|
const popupRect = popup.getBoundingClientRect();
|
|
if (popupRect.bottom > window.innerHeight - 8) {
|
|
popup.style.top = `${Math.max(8, window.innerHeight - 8 - popupRect.height)}px`;
|
|
}
|
|
});
|
|
}
|
|
|
|
function attachClosePopup(popup, onClose, ignoreSelector) {
|
|
const close = () => {
|
|
if (popup._dlfClosed) return;
|
|
popup._dlfClosed = true;
|
|
document.removeEventListener("mousedown", onDown, true);
|
|
document.removeEventListener("pointerdown", onDown, true);
|
|
document.removeEventListener("wheel", onWheel, true);
|
|
document.removeEventListener("keydown", onKey, true);
|
|
popup.remove();
|
|
onClose?.();
|
|
};
|
|
const inside = (target) =>
|
|
popup.contains(target) || (ignoreSelector && target.closest && target.closest(ignoreSelector));
|
|
const onDown = (event) => { if (!inside(event.target)) close(); };
|
|
const onWheel = (event) => { if (!inside(event.target)) close(); };
|
|
const onKey = (event) => { if (event.key === "Escape") close(); };
|
|
popup._dlfClose = close;
|
|
setTimeout(() => {
|
|
if (popup._dlfClosed) return;
|
|
document.addEventListener("mousedown", onDown, true);
|
|
document.addEventListener("pointerdown", onDown, true);
|
|
document.addEventListener("wheel", onWheel, true);
|
|
document.addEventListener("keydown", onKey, true);
|
|
}, 0);
|
|
return close;
|
|
}
|
|
|
|
export function openMiniMenu(anchorEl, items, currentValue, onPick) {
|
|
document.querySelectorAll(".dlf-menu").forEach((menu) => menu._dlfClose?.());
|
|
const menu = document.createElement("div");
|
|
menu.className = "dlf-menu";
|
|
for (const item of items) {
|
|
const row = document.createElement("div");
|
|
row.className = `it${item.value === currentValue ? " on" : ""}`;
|
|
row.innerHTML = `<span>${escapeHtml(item.label)}</span>`;
|
|
row.addEventListener("click", () => {
|
|
menu._dlfClose?.();
|
|
onPick(item.value);
|
|
});
|
|
menu.appendChild(row);
|
|
}
|
|
document.body.appendChild(menu);
|
|
positionBelow(menu, anchorEl, Math.max(150, anchorEl.getBoundingClientRect().width));
|
|
attachClosePopup(menu);
|
|
}
|
|
|
|
export function buildRoot() {
|
|
const root = document.createElement("div");
|
|
root.className = "dlf-root";
|
|
root.innerHTML =
|
|
`<div class="dlf-folderrow">` +
|
|
`<input class="dlf-folder" type="text" spellcheck="false" placeholder="Folder path - type, paste, or Browse">` +
|
|
`<button class="dlf-browse" type="button" title="Browse for a folder">${FOLDER_SVG}<span class="dlf-browse-lbl">Browse</span></button>` +
|
|
`</div>` +
|
|
`<button class="dlf-pick empty" type="button" title="Choose which images to load">Pick images · 0 / 0</button>` +
|
|
`<div class="dlf-msg"></div>`;
|
|
return {
|
|
root,
|
|
folderInput: root.querySelector(".dlf-folder"),
|
|
browseBtn: root.querySelector(".dlf-browse"),
|
|
browseLbl: root.querySelector(".dlf-browse-lbl"),
|
|
pickBtn: root.querySelector(".dlf-pick"),
|
|
msgEl: root.querySelector(".dlf-msg"),
|
|
};
|
|
}
|
|
|
|
const SORTS = [
|
|
{ value: "name|asc", label: "Name ↑" },
|
|
{ value: "name|desc", label: "Name ↓" },
|
|
{ value: "date|asc", label: "Date ↑" },
|
|
{ value: "date|desc", label: "Date ↓" },
|
|
];
|
|
|
|
function randomPreviewFile(files) {
|
|
if (!files.length) return "";
|
|
return files[Math.floor(Math.random() * files.length)]?.file || "";
|
|
}
|
|
|
|
export function openPickGallery(node, anchorEl, ctx) {
|
|
document.querySelectorAll(".dlf-gallery").forEach((gallery) => gallery._dlfClose?.());
|
|
const gallery = document.createElement("div");
|
|
gallery.className = "dlf-gallery";
|
|
gallery.innerHTML =
|
|
`<div class="dlf-gal-head">` +
|
|
`<div class="dlf-tbtn" data-act="all" title="Select every image in this folder">Select all</div>` +
|
|
`<div class="dlf-tbtn" data-act="none" title="Deselect all">None</div>` +
|
|
`<div class="dlf-firstwrap"><div class="dlf-tbtn" data-act="first" title="Select the first N images">First</div>` +
|
|
`<input class="dlf-firstn" type="number" min="1" value="5" title="How many images First selects"></div>` +
|
|
`<div class="dlf-tbtn" data-act="random" title="Pick one random image each run">Select random</div>` +
|
|
`<div class="dlf-count"><b class="dlf-cn">0</b> / <span class="dlf-ct">0</span> active</div>` +
|
|
`</div>` +
|
|
`<div class="dlf-gal-body"><div class="dlf-grid"></div></div>` +
|
|
`<div class="dlf-gal-foot">` +
|
|
`<div class="dlf-subf" title="Also include images inside sub-folders"><span class="box"></span> Include subfolders</div>` +
|
|
`<div class="dlf-tbtn" data-act="sort" title="Change the sort order">Sort: Name ↑</div>` +
|
|
`<div class="dlf-done" data-act="done" title="Apply this selection and close">Done</div>` +
|
|
`</div>`;
|
|
document.body.appendChild(gallery);
|
|
|
|
const grid = gallery.querySelector(".dlf-grid");
|
|
const countCurrent = gallery.querySelector(".dlf-cn");
|
|
const countTotal = gallery.querySelector(".dlf-ct");
|
|
const firstInput = gallery.querySelector(".dlf-firstn");
|
|
const recursiveToggle = gallery.querySelector(".dlf-subf");
|
|
const sortButton = gallery.querySelector('[data-act="sort"]');
|
|
const randomButton = gallery.querySelector('[data-act="random"]');
|
|
const allButton = gallery.querySelector('[data-act="all"]');
|
|
const firstButton = gallery.querySelector('[data-act="first"]');
|
|
|
|
let state = readState(node);
|
|
const manualSelection = new Set(state.selected || []);
|
|
let randomPreview = "";
|
|
|
|
function activeFiles() {
|
|
const files = sortFiles(node._dlfFiles || [], state.sort, state.sort_dir);
|
|
if (state.selection_mode === "all") return new Set(files.map((file) => file.file));
|
|
if (state.selection_mode === "first_n") {
|
|
const count = Math.max(0, Math.min(parseInt(state.first_n, 10) || 0, files.length));
|
|
return new Set(files.slice(0, count).map((file) => file.file));
|
|
}
|
|
if (state.selection_mode === "random") {
|
|
if (!randomPreview || !files.some((file) => file.file === randomPreview)) {
|
|
randomPreview = randomPreviewFile(files);
|
|
}
|
|
return randomPreview ? new Set([randomPreview]) : new Set();
|
|
}
|
|
return manualSelection;
|
|
}
|
|
|
|
function commit() {
|
|
const fresh = readState(node);
|
|
fresh.selected = [...manualSelection];
|
|
fresh.sort = state.sort;
|
|
fresh.sort_dir = state.sort_dir;
|
|
fresh.recursive = state.recursive;
|
|
fresh.selection_mode = state.selection_mode;
|
|
fresh.first_n = Math.max(1, parseInt(firstInput.value, 10) || 1);
|
|
writeState(node, fresh);
|
|
state = fresh;
|
|
ctx.onChange?.(node);
|
|
}
|
|
|
|
function renderGrid() {
|
|
grid.innerHTML = "";
|
|
const files = sortFiles(node._dlfFiles || [], state.sort, state.sort_dir);
|
|
const active = activeFiles();
|
|
sortButton.textContent = `Sort: ${SORTS.find((item) => item.value === `${state.sort}|${state.sort_dir}`)?.label || "Name ↑"}`;
|
|
recursiveToggle.classList.toggle("on", !!state.recursive);
|
|
allButton.classList.toggle("active", state.selection_mode === "all");
|
|
firstButton.classList.toggle("active", state.selection_mode === "first_n");
|
|
randomButton.classList.toggle("active", state.selection_mode === "random");
|
|
countCurrent.textContent = active.size;
|
|
countTotal.textContent = files.length;
|
|
firstInput.value = String(Math.max(1, parseInt(state.first_n, 10) || 1));
|
|
firstInput.max = String(files.length || 1);
|
|
|
|
if (!files.length) {
|
|
const empty = document.createElement("div");
|
|
empty.className = "dlf-gal-empty";
|
|
empty.textContent = node._dlfListError || "No images in this folder.";
|
|
grid.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
for (const file of files) {
|
|
const cell = document.createElement("div");
|
|
cell.className = `dlf-thumb${active.has(file.file) ? " sel" : ""}`;
|
|
cell.innerHTML =
|
|
`<img loading="lazy" src="${thumbURL(state.folder, file.file, file.mtime)}" onerror="this.style.display='none'">` +
|
|
`<div class="veil"></div><div class="chk">✓</div>` +
|
|
`<div class="nm">${escapeHtml(file.name)}</div>`;
|
|
cell.addEventListener("click", () => {
|
|
state.selection_mode = "selected";
|
|
randomPreview = "";
|
|
if (manualSelection.has(file.file)) manualSelection.delete(file.file);
|
|
else manualSelection.add(file.file);
|
|
commit();
|
|
renderGrid();
|
|
});
|
|
grid.appendChild(cell);
|
|
}
|
|
}
|
|
|
|
gallery.querySelector('[data-act="all"]').addEventListener("click", () => {
|
|
state.selection_mode = "all";
|
|
randomPreview = "";
|
|
commit();
|
|
renderGrid();
|
|
});
|
|
gallery.querySelector('[data-act="none"]').addEventListener("click", () => {
|
|
state.selection_mode = "selected";
|
|
manualSelection.clear();
|
|
randomPreview = "";
|
|
commit();
|
|
renderGrid();
|
|
});
|
|
gallery.querySelector('[data-act="first"]').addEventListener("click", () => {
|
|
state.selection_mode = "first_n";
|
|
randomPreview = "";
|
|
commit();
|
|
renderGrid();
|
|
});
|
|
gallery.querySelector('[data-act="random"]').addEventListener("click", () => {
|
|
state.selection_mode = "random";
|
|
randomPreview = randomPreviewFile(sortFiles(node._dlfFiles || [], state.sort, state.sort_dir));
|
|
commit();
|
|
renderGrid();
|
|
});
|
|
firstInput.addEventListener("input", () => {
|
|
if (state.selection_mode !== "first_n") return;
|
|
commit();
|
|
renderGrid();
|
|
});
|
|
firstInput.addEventListener("keydown", (event) => {
|
|
event.stopImmediatePropagation();
|
|
if (event.key === "Enter") {
|
|
event.preventDefault();
|
|
commit();
|
|
renderGrid();
|
|
}
|
|
});
|
|
sortButton.addEventListener("click", () => {
|
|
openMiniMenu(sortButton, SORTS, `${state.sort}|${state.sort_dir}`, (value) => {
|
|
const [sort, sortDir] = value.split("|");
|
|
state.sort = sort;
|
|
state.sort_dir = sortDir;
|
|
if (state.selection_mode === "random") {
|
|
randomPreview = randomPreviewFile(sortFiles(node._dlfFiles || [], state.sort, state.sort_dir));
|
|
}
|
|
commit();
|
|
renderGrid();
|
|
});
|
|
});
|
|
recursiveToggle.addEventListener("click", async () => {
|
|
state.recursive = !state.recursive;
|
|
writeState(node, state);
|
|
grid.innerHTML = '<div class="dlf-gal-empty">Loading…</div>';
|
|
await ctx.refreshListing(node);
|
|
if (gallery._dlfClosed) return;
|
|
state = readState(node);
|
|
manualSelection.clear();
|
|
(state.selected || []).forEach((file) => manualSelection.add(file));
|
|
randomPreview = "";
|
|
renderGrid();
|
|
});
|
|
gallery.querySelector('[data-act="done"]').addEventListener("click", () => gallery._dlfClose?.());
|
|
|
|
node._dlfGallery = gallery;
|
|
attachClosePopup(gallery, () => {
|
|
document.querySelectorAll(".dlf-menu").forEach((menu) => menu._dlfClose?.());
|
|
if (node._dlfGallery === gallery) node._dlfGallery = null;
|
|
}, ".dlf-menu");
|
|
positionBelow(gallery, anchorEl, Math.min(560, window.innerWidth - 16));
|
|
renderGrid();
|
|
}
|
|
|
|
export function openBrowsePopup(node, anchorEl, ctx) {
|
|
document.querySelectorAll(".dlf-browse-pop").forEach((popup) => popup._dlfClose?.());
|
|
const popup = document.createElement("div");
|
|
popup.className = "dlf-browse-pop";
|
|
popup.innerHTML =
|
|
`<div class="dlf-bp-head">Choose a folder</div>` +
|
|
`<div class="dlf-bp-crumb"></div>` +
|
|
`<div class="dlf-bp-list"></div>` +
|
|
`<div class="dlf-bp-foot">` +
|
|
`<div class="dlf-tbtn" data-act="cancel">Cancel</div>` +
|
|
`<div class="dlf-done" data-act="use">Use this folder</div>` +
|
|
`</div>`;
|
|
document.body.appendChild(popup);
|
|
|
|
const crumb = popup.querySelector(".dlf-bp-crumb");
|
|
const list = popup.querySelector(".dlf-bp-list");
|
|
const useButton = popup.querySelector('[data-act="use"]');
|
|
let current = ctx.startPath || "";
|
|
|
|
async function nav(path) {
|
|
list.innerHTML = '<div class="dlf-bp-empty">Loading…</div>';
|
|
const response = await browseFolder(path);
|
|
if (popup._dlfClosed) return;
|
|
if (!response.ok) {
|
|
list.innerHTML = `<div class="dlf-bp-empty">${escapeHtml(response.message || "Could not open this folder.")}</div>`;
|
|
return;
|
|
}
|
|
current = response.path || "";
|
|
useButton.style.opacity = current ? "" : "0.4";
|
|
useButton.style.pointerEvents = current ? "" : "none";
|
|
crumb.innerHTML = current ? `Location: <b style="color:#ddd">${escapeHtml(current)}</b>` : "This PC";
|
|
list.innerHTML = "";
|
|
if (response.parent !== null && response.parent !== undefined) {
|
|
const up = document.createElement("div");
|
|
up.className = "dlf-bp-item up";
|
|
up.innerHTML = '<span style="width:13px;text-align:center;flex:0 0 auto">↰</span> <span class="nm">.. (up one level)</span>';
|
|
up.addEventListener("click", () => nav(response.parent || ""));
|
|
list.appendChild(up);
|
|
}
|
|
if (!response.dirs.length) {
|
|
const empty = document.createElement("div");
|
|
empty.className = "dlf-bp-empty";
|
|
empty.textContent = current ? "No sub-folders here. Use this folder." : "No drives found.";
|
|
list.appendChild(empty);
|
|
}
|
|
for (const dir of response.dirs) {
|
|
const item = document.createElement("div");
|
|
item.className = "dlf-bp-item";
|
|
const count = dir.images >= 0 ? `<span class="cnt">${dir.images} image${dir.images === 1 ? "" : "s"}</span>` : "";
|
|
item.innerHTML = `${FOLDER_SVG}<span class="nm">${escapeHtml(dir.name)}</span>${count}`;
|
|
item.addEventListener("click", () => nav(dir.path));
|
|
list.appendChild(item);
|
|
}
|
|
}
|
|
|
|
popup.querySelector('[data-act="cancel"]').addEventListener("click", () => popup._dlfClose?.());
|
|
popup.querySelector('[data-act="use"]').addEventListener("click", () => {
|
|
if (current) ctx.onPick(current);
|
|
popup._dlfClose?.();
|
|
});
|
|
|
|
node._dlfBrowsePopup = popup;
|
|
attachClosePopup(popup, () => {
|
|
if (node._dlfBrowsePopup === popup) node._dlfBrowsePopup = null;
|
|
});
|
|
positionBelow(popup, anchorEl, Math.min(440, window.innerWidth - 16));
|
|
nav(current);
|
|
}
|