Files
ComfyUI-compass-paths/js/compass_image_loader.js

210 lines
6.5 KiB
JavaScript

import { app } from "../../../scripts/app.js";
function getAllNodesOfType(type) {
const nodes = [];
for (const node of app.graph._nodes) {
if (node.type === type) nodes.push(node);
}
return nodes;
}
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 listEl = document.createElement("ul");
listEl.style.display = "none";
document.body.appendChild(listEl);
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(() => {
if (!listEl.contains(document.activeElement)) {
hideDropdown(listEl);
}
}, 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();
listEl.remove();
};
};
},
async loadedGraphNode(node) {
if (node.type !== "CompassImageLoader") return;
const directoryWidget = node.widgets?.find((w) => w.name === "directory");
const filterWidget = node.widgets?.find((w) => w.name === "directory_filter");
const inputEl = filterWidget?.inputEl;
const listEl = document.getElementById("compass_dir_list");
if (inputEl && listEl) {
inputEl._currentPrefix = directoryWidget?.value || "";
}
},
});