import { app } from "../../../scripts/app.js"; function filterChildDirs(allDirs, parentPrefix) { const prefixNorm = parentPrefix.replace(/\/$/, "").toLowerCase(); const results = []; for (const dir of allDirs) { const parts = dir.split("/").filter(Boolean); if (parts.length < 1) continue; const parent = parts.slice(0, -1).join("/").toLowerCase(); const child = parts[parts.length - 1]; if (parent === prefixNorm) { results.push(child); } } return [...new Set(results)].sort(); } function showDropdown(listEl, items, directoryWidget, inputEl) { listEl.innerHTML = ""; if (items.length === 0) { listEl.style.display = "none"; return; } for (const item of items) { const li = document.createElement("li"); li.textContent = item; li.style.padding = "4px 8px"; li.style.cursor = "pointer"; li.style.color = "#ccc"; li.style.listStyle = "none"; li.addEventListener("mouseenter", () => { listEl.querySelectorAll("li").forEach((el) => (el.style.background = "transparent")); li.style.background = "#444"; }); li.addEventListener("click", () => { const newValue = inputEl._currentPrefix + item + "/"; inputEl.value = newValue; inputEl._currentPrefix = newValue; directoryWidget.value = newValue; hideDropdown(listEl); inputEl.focus(); }); listEl.appendChild(li); } Object.assign(listEl.style, { display: "block", background: "#222", border: "1px solid #444", position: "absolute", zIndex: "9999", width: inputEl.offsetWidth + "px", maxHeight: "200px", overflowY: "auto", }); } function hideDropdown(listEl) { listEl.innerHTML = ""; listEl.style.display = "none"; } app.registerExtension({ name: "CompassImageLoader", async beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData.name !== "CompassImageLoader") return; const origOnNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { if (origOnNodeCreated) origOnNodeCreated.apply(this, []); const directoryWidget = this.widgets?.find((w) => w.name === "directory"); if (!directoryWidget) return; const defDirs = nodeData.input?.required?.directory; directoryWidget._all_values = defDirs && Array.isArray(defDirs[0]) ? defDirs[0] : []; const filterWidget = this.widgets?.find((w) => w.name === "directory_filter"); const inputEl = filterWidget?.inputEl; if (!inputEl) return; const listId = `compass_dir_list_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`; const listEl = document.createElement("ul"); listEl.id = listId; listEl.style.display = "none"; listEl.style.position = "absolute"; listEl.style.zIndex = "9999"; document.body.appendChild(listEl); inputEl._listId = listId; inputEl._currentPrefix = directoryWidget.value || ""; inputEl.addEventListener("input", () => { const val = inputEl.value; const lastSlash = val.lastIndexOf("/"); let prefix, filter; if (lastSlash >= 0) { prefix = val.substring(0, lastSlash + 1); filter = val.substring(lastSlash + 1); } else { prefix = ""; filter = val; } if (prefix) { const cleanPrefix = prefix.replace(/\/$/, ""); const candidates = filterChildDirs(directoryWidget._all_values, cleanPrefix); const filtered = filter ? candidates.filter((c) => c.toLowerCase().startsWith(filter.toLowerCase())) : candidates; showDropdown(listEl, filtered, directoryWidget, inputEl); } else { const filtered = filter ? directoryWidget._all_values.filter((d) => d.toLowerCase().includes(filter.toLowerCase()) ) : [...new Set(directoryWidget._all_values.map((d) => d.split("/")[0]))].sort(); showDropdown(listEl, filtered, directoryWidget, inputEl); } }); inputEl.addEventListener("focus", () => { const val = inputEl.value; const lastSlash = val.lastIndexOf("/"); if (lastSlash >= 0 && val.endsWith("/")) { const prefix = val.replace(/\/$/, ""); inputEl._currentPrefix = prefix + "/"; const items = filterChildDirs(directoryWidget._all_values, prefix); showDropdown(listEl, items, directoryWidget, inputEl); } else if (lastSlash >= 0) { const prefix = val.substring(0, lastSlash + 1); inputEl._currentPrefix = prefix; inputEl.focus(); return; } else { inputEl._currentPrefix = ""; const items = filter ? directoryWidget._all_values.filter((d) => d.toLowerCase().includes(val.toLowerCase()) ) : [...new Set(directoryWidget._all_values.map((d) => d.split("/")[0]))].sort(); showDropdown(listEl, items, directoryWidget, inputEl); } }); inputEl.addEventListener("blur", () => { setTimeout(() => { const list = document.getElementById(inputEl._listId); if (list && !list.contains(document.activeElement)) { hideDropdown(list); } }, 150); }); inputEl.addEventListener("keydown", (e) => { if (e.key === "Escape") { hideDropdown(listEl); return; } if (e.key === "Enter" || e.key === "Tab") { const visible = listEl.querySelectorAll("li"); if (visible.length === 1) { visible[0].click(); e.preventDefault(); } else if (visible.length > 0) { visible[0].click(); e.preventDefault(); } hideDropdown(listEl); } }); const origSetValue = directoryWidget.callback ? directoryWidget.callback.bind(directoryWidget) : () => {}; directoryWidget.callback = (value) => { origSetValue(value); inputEl._currentPrefix = value || ""; inputEl.value = value || ""; }; const origOnRemoved = this.onRemoved ? this.onRemoved.bind(this) : () => {}; this.onRemoved = () => { origOnRemoved(); const list = document.getElementById(listId); if (list) list.remove(); }; }; }, });