Code optimization and visual changes in the RS Models Loader node

This commit is contained in:
Raykosan
2026-07-22 02:07:28 +03:00
parent 52858e24a0
commit 75a2fad0fb
4 changed files with 396 additions and 95 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'}
REQUIRED_MODULES: Set[str] = set()
PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio"
__version__ = "0.30.1"
__version__ = "0.30.2"
def load_modules():
print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}")
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ComfyUI_RaykoStudio",
"version": "0.30.1",
"version": "0.30.2",
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
"author": "Raykosan",
"license": "Apache License 2.0",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI_RaykoStudio"
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
version = "0.30.1"
version = "0.30.2"
license = { file = "LICENSE" }
dependencies = ["torch", "numpy", "Pillow>=10.0.0", "freetype-py>=2.5.1", "pycairo>=1.29.0", "scipy>=1.10.0"]
+393 -92
View File
@@ -15,12 +15,19 @@ app.registerExtension({
this.loraRows = [];
this.loraOptions = [];
this.loraTree = {};
this.targetWidth = 450;
this.targetWidth = 400;
this.rowHeight = 30;
this.clickZones = [];
this.storageKey = null;
this.isRestoring = false;
this.isInitialized = false;
this.manual_size = false;
this.isAutoResizing = false;
this.MIN_WIDTH = 400;
this.scrollOffset = 0;
this.oldWheelCanvas = null;
this.oldWheelHandler = null;
this.currentFilter = "";
this.hiddenWidget = this.widgets.find(w => w.name === "lora_data");
const nodeRef = this;
@@ -77,8 +84,23 @@ app.registerExtension({
};
["unet_name", "weight_dtype", "use_clip2", "clip_name", "clip_name2", "clip_type", "clip_device", "vae_name"].forEach(k => setWidgetValue(k, data[k]));
this.loraRows = [];
this.scrollOffset = 0;
this.manual_size = false;
this.syncData();
this.updateUI();
requestAnimationFrame(() => {
const startY = this.getLoraListStartY();
const calculatedHeight = startY + this.rowHeight + 10;
this.isAutoResizing = true;
this.setSize([this.size[0], calculatedHeight]);
this.isAutoResizing = false;
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 50);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 100);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 150);
}
});
};
const collectLoraPresetData = () => ({
@@ -97,8 +119,24 @@ app.registerExtension({
strength_clip: parseFloat(row.strength_clip) || 1.0,
enabled: row.enabled !== undefined ? row.enabled : true
}));
this.scrollOffset = 0;
this.manual_size = false;
this.syncData();
this.updateUI();
requestAnimationFrame(() => {
const startY = this.getLoraListStartY();
const desiredVisible = Math.max(1, Math.min(this.loraRows.length, 10));
const calculatedHeight = startY + (desiredVisible * this.rowHeight) + 10;
this.isAutoResizing = true;
this.setSize([this.size[0], calculatedHeight]);
this.isAutoResizing = false;
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 50);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 100);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 150);
}
});
};
const presetListOverlay = document.createElement("div");
@@ -159,7 +197,6 @@ app.registerExtension({
const presetsWrapper = document.createElement("div");
presetsWrapper.style.cssText = "display:flex;flex-direction:column;gap:4px;width:100%;margin:0;padding:0;box-sizing:border-box;";
// --- MODEL PRESETS ROW ---
const presetsRoot = document.createElement("div");
presetsRoot.style.cssText = "display:flex;gap:4px;width:100%;align-items:center;height:30px;";
@@ -172,7 +209,6 @@ app.registerExtension({
selectPresetBtn.style.cssText = "flex:1;padding:4px 2px;font-size:11px;border:1px solid #99c0ee;border-radius:5px;background:#1a3a5a;color:#aadaff;cursor:pointer;height:26px;margin:0;";
presetsRoot.append(savePresetBtn, selectPresetBtn);
// --- LORA PRESETS ROW (Pale Green) ---
const loraPresetsRoot = document.createElement("div");
loraPresetsRoot.style.cssText = "display:flex;gap:4px;width:100%;align-items:center;height:30px;";
@@ -349,9 +385,87 @@ app.registerExtension({
if (this.graph) this.graph.setDirtyCanvas(true, true);
});
this.addWidget("button", "➕ Add LoRA", "", () => { this.showLoraTreeSelector(); });
this.addWidget("button", "➕ Add LoRA", "", () => {
const btnWidget = this.widgets.find(w => w.name === "➕ Add LoRA");
this.showLoraTreeSelector(btnWidget);
});
const self = this;
this.wheelHandler = function(e) {
if (app.canvas.node_over !== self) return;
const graphPos = app.canvas.graph_mouse;
if (!graphPos) return;
const relY = graphPos[1] - self.pos[1];
const startY = self.getLoraListStartY();
const rowH = self.rowHeight;
const availableHeight = self.size[1] - startY - 10;
const maxVisibleStyles = Math.max(1, Math.floor(availableHeight / rowH));
const stylesEndY = startY + maxVisibleStyles * rowH;
if (relY < startY || relY > stylesEndY) return;
if (self.loraRows.length <= maxVisibleStyles) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const delta = e.deltaY > 0 ? 1 : -1;
const maxOffset = self.loraRows.length - maxVisibleStyles;
const newOffset = Math.max(0, Math.min(self.scrollOffset + delta, maxOffset));
if (newOffset !== self.scrollOffset) {
self.scrollOffset = newOffset;
self.syncData();
self.graph.setDirtyCanvas(true, true);
}
};
const initialCanvas = app.canvas.canvas;
initialCanvas.addEventListener('wheel', this.wheelHandler, { capture: true, passive: false });
this.oldWheelCanvas = initialCanvas;
this.oldWheelHandler = this.wheelHandler;
this.visibilityHandler = function() {
if (!document.hidden) {
setTimeout(() => {
const currentCanvas = app.canvas.canvas;
if (self.oldWheelCanvas && self.oldWheelCanvas !== currentCanvas) {
self.oldWheelCanvas.removeEventListener('wheel', self.oldWheelHandler, { capture: true, passive: false });
currentCanvas.addEventListener('wheel', self.wheelHandler, { capture: true, passive: false });
self.oldWheelCanvas = currentCanvas;
self.oldWheelHandler = self.wheelHandler;
}
}, 150);
}
};
document.addEventListener("visibilitychange", this.visibilityHandler);
const onResize = this.onResize;
this.onResize = function(size) {
if (size[0] < self.MIN_WIDTH) size[0] = self.MIN_WIDTH;
const minH = self.getLoraListStartY() + self.rowHeight + 10;
if (size[1] < minH) size[1] = minH;
const availableHeight = size[1] - self.getLoraListStartY() - 10;
const maxVisibleStyles = Math.max(1, Math.floor(availableHeight / self.rowHeight));
const maxOffset = Math.max(0, self.loraRows.length - maxVisibleStyles);
if (self.scrollOffset > maxOffset) {
self.scrollOffset = maxOffset;
}
if (!self.isAutoResizing) {
self.manual_size = true;
self.syncData();
}
if (onResize) {
return onResize.apply(this, arguments);
}
};
setTimeout(() => {
if (this.id) {
this.storageKey = `rayko_lora_${this.id}`;
@@ -374,6 +488,12 @@ app.registerExtension({
if (Array.isArray(saved)) this.loraRows = saved;
} catch (e) {}
}
if (info.properties && info.properties["manual_size"] !== undefined) {
this.manual_size = info.properties["manual_size"];
}
if (info.properties && info.properties["scrollOffset"] !== undefined) {
this.scrollOffset = info.properties["scrollOffset"];
}
if (info.widgets_values && Array.isArray(info.widgets_values)) {
for (let i = 0; i < info.widgets_values.length; i++) {
const val = info.widgets_values[i];
@@ -390,7 +510,26 @@ app.registerExtension({
}
this.isRestoring = false;
const self = this;
this.loadLoraList().then(() => self.updateUI());
this.loadLoraList().then(() => {
self.updateUI();
requestAnimationFrame(() => {
if (!self.manual_size) {
const startY = self.getLoraListStartY();
const desiredVisible = Math.max(1, Math.min(self.loraRows.length, 10));
const calculatedHeight = startY + (desiredVisible * self.rowHeight) + 10;
self.isAutoResizing = true;
self.setSize([self.size[0], calculatedHeight]);
self.isAutoResizing = false;
if (self.graph) {
self.graph.setDirtyCanvas(true, true);
setTimeout(() => self.graph.setDirtyCanvas(true, true), 50);
setTimeout(() => self.graph.setDirtyCanvas(true, true), 100);
setTimeout(() => self.graph.setDirtyCanvas(true, true), 150);
}
}
});
});
const useClip2Widget = this.widgets.find(w => w.name === "use_clip2");
const clipName2Widget = this.widgets.find(w => w.name === "clip_name2");
@@ -472,29 +611,69 @@ app.registerExtension({
return tree;
};
nodeType.prototype.onDrawForeground = function(ctx, visibleRect) {
if (this.loraRows.length === 0) return;
this.clickZones = [];
nodeType.prototype.getLoraListStartY = function() {
const addButton = this.widgets.find(w => w.name === "➕ Add LoRA");
const startY = addButton ? (addButton.y + addButton.height + 15) : 40;
if (!addButton) return 40;
return addButton.y + addButton.height + 15;
};
nodeType.prototype.onDrawForeground = function(ctx, visibleRect) {
if (this.loraRows.length === 0) {
if (!this.manual_size && this.graph) {
const startY = this.getLoraListStartY();
const baseHeight = startY + this.rowHeight + 10;
if (Math.abs(this.size[1] - baseHeight) > 1) {
this.isAutoResizing = true;
this.setSize([this.size[0], baseHeight]);
this.isAutoResizing = false;
}
}
return;
}
this.clickZones = [];
const startY = this.getLoraListStartY();
const padding = 10;
const rightPanelWidth = 180;
const availableHeight = this.size[1] - startY - 10;
const maxVisibleStyles = Math.max(1, Math.floor(availableHeight / this.rowHeight));
for (let i = 0; i < this.loraRows.length; i++) {
const row = this.loraRows[i];
const maxOffset = Math.max(0, this.loraRows.length - maxVisibleStyles);
if (this.scrollOffset > maxOffset) {
this.scrollOffset = maxOffset;
}
if (!this.manual_size && this.graph) {
const desiredVisible = Math.max(1, Math.min(this.loraRows.length, 10));
const calculatedHeight = startY + (desiredVisible * this.rowHeight) + 10;
if (Math.abs(this.size[1] - calculatedHeight) > 1) {
this.isAutoResizing = true;
this.setSize([this.size[0], calculatedHeight]);
this.isAutoResizing = false;
}
}
const visibleStart = this.scrollOffset;
const visibleEnd = Math.min(visibleStart + maxVisibleStyles, this.loraRows.length);
for (let i = 0; i < visibleEnd - visibleStart; i++) {
const dataIdx = visibleStart + i;
const row = this.loraRows[dataIdx];
const y = startY + (i * this.rowHeight);
const h = this.rowHeight - 2;
const toggleY = y + h/2;
ctx.fillStyle = i % 2 === 0 ? "rgba(0,0,0,0.3)" : "rgba(0,0,0,0.15)";
ctx.fillRect(padding, y, this.size[0] - (padding * 2), h);
const toggleX = padding + 5;
const toggleY = y + h/2;
ctx.fillStyle = row.enabled ? "#4CAF50" : "#555";
ctx.beginPath();
ctx.arc(toggleX + 8, toggleY, 7, 0, Math.PI * 2);
ctx.fill();
this.clickZones.push({ type: "toggle", index: i, x: toggleX, y: y, w: 24, h: h });
this.clickZones.push({ type: "toggle", index: dataIdx, x: toggleX, y: y, w: 24, h: h });
const nameX = toggleX + 30;
const nameW = this.size[0] - (padding * 2) - 30 - rightPanelWidth - 20;
@@ -508,7 +687,7 @@ app.registerExtension({
displayName += "...";
}
ctx.fillText(displayName, nameX, toggleY + 4);
this.clickZones.push({ type: "name", index: i, x: nameX, y: y, w: nameW, h: h });
this.clickZones.push({ type: "name", index: dataIdx, x: nameX, y: y, w: nameW, h: h });
const arrowLX = this.size[0] - rightPanelWidth + 10;
ctx.fillStyle = row.enabled ? "#4CAF50" : "#555";
@@ -517,7 +696,7 @@ app.registerExtension({
ctx.lineTo(arrowLX + 8, toggleY);
ctx.lineTo(arrowLX + 18, y + 22);
ctx.fill();
this.clickZones.push({ type: "left", index: i, x: arrowLX, y: y, w: 28, h: h });
this.clickZones.push({ type: "left", index: dataIdx, x: arrowLX, y: y, w: 28, h: h });
const strInputX = arrowLX + 33;
const strInputW = 55;
@@ -529,7 +708,7 @@ app.registerExtension({
ctx.textAlign = "center";
ctx.fillText(row.strength_model.toFixed(2), strInputX + strInputW/2, toggleY + 4);
ctx.textAlign = "left";
this.clickZones.push({ type: "strength_input", index: i, x: strInputX, y: y, w: strInputW, h: h });
this.clickZones.push({ type: "strength_input", index: dataIdx, x: strInputX, y: y, w: strInputW, h: h });
const arrowRX = strInputX + strInputW + 5;
ctx.fillStyle = row.enabled ? "#4CAF50" : "#555";
@@ -538,16 +717,35 @@ app.registerExtension({
ctx.lineTo(arrowRX + 20, toggleY);
ctx.lineTo(arrowRX + 10, y + 22);
ctx.fill();
this.clickZones.push({ type: "right", index: i, x: arrowRX, y: y, w: 28, h: h });
this.clickZones.push({ type: "right", index: dataIdx, x: arrowRX, y: y, w: 28, h: h });
ctx.fillStyle = "#f44336";
ctx.fillText("❌️", arrowRX + 35, toggleY + 4);
this.clickZones.push({ type: "delete", index: i, x: arrowRX + 35, y: y, w: 30, h: h });
this.clickZones.push({ type: "delete", index: dataIdx, x: arrowRX + 35, y: y, w: 30, h: h });
}
const totalH = startY + (this.loraRows.length * this.rowHeight) + 10;
if (this.size[1] < totalH) {
this.setSize([this.targetWidth, totalH]);
if (this.loraRows.length > maxVisibleStyles) {
if (this.scrollOffset > 0) {
const indicatorY = startY - 2;
ctx.fillStyle = "rgba(255, 215, 0, 0.6)";
ctx.beginPath();
ctx.moveTo(this.size[0]/2 - 8, indicatorY);
ctx.lineTo(this.size[0]/2 + 8, indicatorY);
ctx.lineTo(this.size[0]/2, indicatorY - 8);
ctx.closePath();
ctx.fill();
}
if (visibleEnd < this.loraRows.length) {
const indicatorY = startY + (visibleEnd - visibleStart) * this.rowHeight + 2;
ctx.fillStyle = "rgba(255, 215, 0, 0.6)";
ctx.beginPath();
ctx.moveTo(this.size[0]/2 - 8, indicatorY);
ctx.lineTo(this.size[0]/2 + 8, indicatorY);
ctx.lineTo(this.size[0]/2, indicatorY + 8);
ctx.closePath();
ctx.fill();
}
}
};
@@ -583,8 +781,25 @@ app.registerExtension({
return true;
} else if (zone.type === "delete") {
this.loraRows.splice(zone.index, 1);
this.scrollOffset = 0;
this.manual_size = false;
this.syncData();
this.updateUI();
requestAnimationFrame(() => {
const startY = this.getLoraListStartY();
const desiredVisible = Math.max(1, Math.min(this.loraRows.length, 10));
const calculatedHeight = startY + (desiredVisible * this.rowHeight) + 10;
this.isAutoResizing = true;
this.setSize([this.size[0], calculatedHeight]);
this.isAutoResizing = false;
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 50);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 100);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 150);
}
});
return true;
}
}
@@ -592,49 +807,50 @@ app.registerExtension({
return false;
};
nodeType.prototype.showLoraTreeSelector = function() {
nodeType.prototype.showLoraTreeSelector = function(widget) {
const self = this;
const expandedFolders = {};
let buttonElement = null;
const widgets = this.widgets;
for (let w of widgets) {
if (w.name === "➕ Add LoRA" && w.element) {
buttonElement = w.element;
break;
}
}
if (!buttonElement) {
const allButtons = document.querySelectorAll('button, div');
for (let el of allButtons) {
if (el.innerText && el.innerText.includes("Add LoRA")) {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
buttonElement = el;
break;
}
}
}
}
let menuLeft, menuTop;
this.currentFilter = "";
const menuWidth = 450;
const menuHeight = 600;
let menuLeft = 100;
let menuTop = 100;
if (buttonElement) {
const rect = buttonElement.getBoundingClientRect();
menuLeft = rect.right + 10;
menuTop = rect.top;
} else {
menuLeft = (window.innerWidth / 2) - (menuWidth / 2);
menuTop = (window.innerHeight / 2) - (menuHeight / 2);
if (widget && app && app.canvas) {
const rect = app.canvas.canvas.getBoundingClientRect();
const scale = app.canvas.ds.scale;
const offsetX = app.canvas.ds.offset[0];
const offsetY = app.canvas.ds.offset[1];
const nodeRightX = this.pos[0] + this.size[0];
const nodeLeftX = this.pos[0];
const widgetTopY = this.pos[1] + widget.y;
let calculatedLeft = rect.left + (nodeRightX * scale) + offsetX + 10;
const calculatedTop = rect.top + (widgetTopY * scale) + offsetY;
if (calculatedLeft + menuWidth > window.innerWidth) {
calculatedLeft = rect.left + (nodeLeftX * scale) + offsetX - menuWidth - 10;
}
if (calculatedLeft < 10) calculatedLeft = 10;
let finalTop = calculatedTop;
if (finalTop + menuHeight > window.innerHeight) {
finalTop = window.innerHeight - menuHeight - 10;
}
if (finalTop < 10) finalTop = 10;
menuLeft = calculatedLeft;
menuTop = finalTop;
}
if (menuLeft + menuWidth > window.innerWidth) menuLeft = window.innerWidth - menuWidth - 10;
if (menuTop + menuHeight > window.innerHeight) menuTop = window.innerHeight - menuHeight - 10;
if (menuLeft < 10) menuLeft = 10;
if (menuTop < 10) menuTop = 10;
const existingMenu = document.getElementById("rayko-lora-selector-menu");
if (existingMenu) existingMenu.remove();
const menu = document.createElement("div");
menu.id = "rayko-lora-selector-menu";
menu.style.cssText = `position: fixed; background: #1a1a1a; border: 1px solid #444; border-radius: 6px; height: ${menuHeight}px; width: ${menuWidth}px; overflow-y: auto; overflow-x: hidden; z-index: 10000; left: ${menuLeft}px; top: ${menuTop}px; box-shadow: 0 4px 20px rgba(0,0,0,0.8); display: flex; flex-direction: column;`;
const headerContainer = document.createElement("div");
@@ -670,17 +886,29 @@ app.registerExtension({
return paths;
};
const isLoraAlreadyAdded = (loraName) => {
return self.loraRows.some(row => row.name === loraName);
};
const renderList = (filterText = "") => {
listContainer.innerHTML = "";
const lowerFilter = filterText.trim().toLowerCase();
self.currentFilter = filterText;
if (!filterText || "none".includes(lowerFilter)) {
const isAdded = isLoraAlreadyAdded("None");
const noneItem = document.createElement("div");
noneItem.textContent = " None";
noneItem.style.cssText = `padding: 10px 12px; cursor: pointer; color: #aaa; border-bottom: 1px solid #333; background: #2a2a2a; font-style: italic;`;
noneItem.textContent = (isAdded ? "✓ " : "") + "None";
noneItem.style.cssText = `padding: 10px 12px; cursor: pointer; color: ${isAdded ? '#4CAF50' : '#aaa'}; border-bottom: 1px solid #333; background: #2a2a2a; font-style: italic;`;
noneItem.onmouseenter = () => noneItem.style.background = "#3a3a3a";
noneItem.onmouseleave = () => noneItem.style.background = "#2a2a2a";
noneItem.onclick = (e) => { e.stopPropagation(); self.addLoraRow("None"); menu.remove(); };
noneItem.onclick = (e) => {
e.stopPropagation();
if (!isLoraAlreadyAdded("None")) {
self.addLoraRow("None");
renderList(filterText);
}
};
listContainer.appendChild(noneItem);
}
@@ -704,18 +932,25 @@ app.registerExtension({
listContainer.appendChild(noRes);
} else {
matches.forEach(item => {
const isAdded = isLoraAlreadyAdded(item.path);
const el = document.createElement("div");
el.textContent = ` ${item.path}`;
el.style.cssText = `padding: 8px 12px; cursor: pointer; color: #ddd; font-size: 12px; border-bottom: 1px solid #2a2a2a; background: transparent;`;
el.textContent = (isAdded ? "✓ " : " ") + item.path;
el.style.cssText = `padding: 8px 12px; cursor: pointer; color: ${isAdded ? '#4CAF50' : '#ddd'}; font-size: 12px; border-bottom: 1px solid #2a2a2a; background: transparent;`;
el.onmouseenter = () => el.style.background = "#333";
el.onmouseleave = () => el.style.background = "transparent";
el.onclick = (e) => { e.stopPropagation(); self.addLoraRow(item.path); menu.remove(); };
el.onclick = (e) => {
e.stopPropagation();
if (!isLoraAlreadyAdded(item.path)) {
self.addLoraRow(item.path);
renderList(filterText);
}
};
listContainer.appendChild(el);
});
}
} else {
listContainer.innerHTML = "";
createTreeItems("", self.loraTree, 0, listContainer, expandedFolders, self, null, null);
createTreeItems("", self.loraTree, 0, listContainer, expandedFolders, self, null, null, renderList);
}
};
@@ -730,31 +965,78 @@ app.registerExtension({
document.body.appendChild(menu);
setTimeout(() => searchInput.focus(), 50);
setTimeout(() => {
const closeHandler = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener("click", closeHandler);
let closeTimer = null;
const closeDelay = 300;
const closeMenu = () => {
if (menu && menu.parentNode) {
menu.remove();
}
document.removeEventListener("pointerdown", handleOutsideClick, true);
document.removeEventListener("keydown", handleEsc, true);
if (closeTimer) {
clearTimeout(closeTimer);
closeTimer = null;
}
};
const handleEsc = (ev) => {
if (ev.key === "Escape") closeMenu();
};
const handleOutsideClick = (ev) => {
if (menu.contains(ev.target)) {
if (closeTimer) {
clearTimeout(closeTimer);
closeTimer = null;
}
};
document.addEventListener("click", closeHandler);
}, 100);
return;
}
closeMenu();
};
menu.addEventListener("mouseleave", () => {
closeTimer = setTimeout(() => {
closeMenu();
}, closeDelay);
});
menu.addEventListener("mouseenter", () => {
if (closeTimer) {
clearTimeout(closeTimer);
closeTimer = null;
}
});
setTimeout(() => {
document.addEventListener("pointerdown", handleOutsideClick, true);
document.addEventListener("keydown", handleEsc, true);
}, 50);
};
nodeType.prototype.addLoraRow = function(loraName) {
const exists = this.loraRows.some(row => row.name === loraName);
if (exists) return;
this.loraRows.push({ name: loraName, strength_model: 1.0, strength_clip: 1.0, enabled: true });
this.scrollOffset = 0;
this.manual_size = false;
this.syncData();
if (this.graph) {
const addButton = this.widgets.find(w => w.name === "➕ Add LoRA");
const startY = addButton ? (addButton.y + addButton.height + 15) : 40;
const newHeight = startY + (this.loraRows.length * this.rowHeight) + 10;
if (this.size[1] < newHeight) {
this.setSize([this.targetWidth, newHeight]);
requestAnimationFrame(() => {
const startY = this.getLoraListStartY();
const desiredVisible = Math.max(1, Math.min(this.loraRows.length, 10));
const calculatedHeight = startY + (desiredVisible * this.rowHeight) + 10;
this.isAutoResizing = true;
this.setSize([this.size[0], calculatedHeight]);
this.isAutoResizing = false;
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 50);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 100);
setTimeout(() => this.graph.setDirtyCanvas(true, true), 150);
}
this.graph.setDirtyCanvas(true, true);
} else {
this.updateUI();
}
});
};
nodeType.prototype.updateUI = function() {
@@ -768,6 +1050,8 @@ app.registerExtension({
if (this.hiddenWidget) this.hiddenWidget.value = jsonData;
if (!this.properties) this.properties = {};
this.properties["lora_rows"] = jsonData;
this.properties["manual_size"] = this.manual_size;
this.properties["scrollOffset"] = this.scrollOffset;
this.saveToStorage();
};
@@ -775,11 +1059,17 @@ app.registerExtension({
this.syncData();
if (!o.properties) o.properties = {};
o.properties["lora_rows"] = this.properties["lora_rows"];
o.properties["manual_size"] = this.manual_size;
o.properties["scrollOffset"] = this.scrollOffset;
return originalOnSerialize ? originalOnSerialize.apply(this, arguments) : undefined;
};
nodeType.prototype.onRemoved = function() {
if (this.storageKey) localStorage.removeItem(this.storageKey);
if (this.wheelHandler && this.oldWheelCanvas) {
this.oldWheelCanvas.removeEventListener('wheel', this.wheelHandler, { capture: true, passive: false });
}
document.removeEventListener("visibilitychange", this.visibilityHandler);
return originalOnRemoved ? originalOnRemoved.apply(this, arguments) : undefined;
};
}
@@ -812,7 +1102,7 @@ window.addEventListener("focus", () => {
}
});
function createTreeItems(path, tree, level, container, expandedFolders, self, header, noneItem) {
function createTreeItems(path, tree, level, container, expandedFolders, self, header, noneItem, renderList) {
const sortedKeys = Object.keys(tree).sort((a, b) => {
const aIsFolder = tree[a] !== null;
const bIsFolder = tree[b] !== null;
@@ -821,6 +1111,10 @@ function createTreeItems(path, tree, level, container, expandedFolders, self, he
return a.toLowerCase().localeCompare(b.toLowerCase());
});
const isLoraAlreadyAdded = (loraName) => {
return self.loraRows.some(row => row.name === loraName);
};
for (const name of sortedKeys) {
const subTree = tree[name];
const isFolder = subTree !== null;
@@ -837,24 +1131,31 @@ function createTreeItems(path, tree, level, container, expandedFolders, self, he
e.stopPropagation();
expandedFolders[itemPath] = !expandedFolders[itemPath];
container.innerHTML = "";
createTreeItems("", self.loraTree, 0, container, expandedFolders, self, header, noneItem);
createTreeItems("", self.loraTree, 0, container, expandedFolders, self, header, noneItem, renderList);
};
folderContainer.appendChild(folderHeader);
container.appendChild(folderContainer);
if (expandedFolders[itemPath]) {
createTreeItems(itemPath, subTree, level + 1, container, expandedFolders, self, header, noneItem);
createTreeItems(itemPath, subTree, level + 1, container, expandedFolders, self, header, noneItem, renderList);
}
} else {
const isAdded = isLoraAlreadyAdded(itemPath);
const fileItem = document.createElement("div");
fileItem.textContent = " " + name;
fileItem.style.cssText = `padding: 8px 12px; cursor: pointer; color: #ddd; font-size: 12px;`;
fileItem.textContent = (isAdded ? "✓ " : " ") + name;
fileItem.style.cssText = `padding: 8px 12px; cursor: pointer; color: ${isAdded ? '#4CAF50' : '#ddd'}; font-size: 12px;`;
fileItem.style.paddingLeft = (12 + level * 16) + "px";
fileItem.onmouseenter = () => fileItem.style.background = "#333";
fileItem.onmouseleave = () => fileItem.style.background = "transparent";
fileItem.onclick = (e) => {
e.stopPropagation();
self.addLoraRow(itemPath);
let root = fileItem;
while (root.parentNode && root.parentNode !== document.body) root = root.parentNode;
if (root && root.parentNode === document.body) root.remove();
if (!isLoraAlreadyAdded(itemPath)) {
self.addLoraRow(itemPath);
if (renderList) {
renderList(self.currentFilter);
}
}
};
container.appendChild(fileItem);
}