mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-07-13 20:21:16 -03:00
fix(ui): make loras widget fixed-size with user-controlled node resize
Remove dynamic height calculation that auto-resized the node when LoRAs are added or removed. The widget now stays at the size the user sets via the node resize handle, scrolling when content overflows. - Drop updateWidgetHeight() and hardcoded entry-count height math - Set --comfy-widget-min-height once (200px) instead of recalculating - In Vue mode: add contain:layout+size to break the ResizeObserver feedback loop that forced node growth with content (CSS via .lm-loras-container.lm-vue-node scoped to vueNodesMode only) - Remove unused "Node 2.0: Maximum visible LoRA entries" setting
This commit is contained in:
@@ -120,6 +120,15 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Vue node mode: prevent content from pushing node size via ResizeObserver.
|
||||||
|
contain:size breaks the feedback loop — the container's intrinsic size
|
||||||
|
is determined solely by CSS, not by how many LoRAs are inside. */
|
||||||
|
.lm-loras-container.lm-vue-node {
|
||||||
|
height: 100%;
|
||||||
|
min-height: var(--comfy-widget-min-height, 200px);
|
||||||
|
contain: layout size;
|
||||||
|
}
|
||||||
|
|
||||||
.lm-loras-container:focus {
|
.lm-loras-container:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,14 @@ import { createToggle, createArrowButton, createDragHandle, updateEntrySelection
|
|||||||
import {
|
import {
|
||||||
parseLoraValue,
|
parseLoraValue,
|
||||||
formatLoraValue,
|
formatLoraValue,
|
||||||
updateWidgetHeight,
|
|
||||||
shouldShowClipEntry,
|
shouldShowClipEntry,
|
||||||
syncClipStrengthIfCollapsed,
|
syncClipStrengthIfCollapsed
|
||||||
LORA_ENTRY_HEIGHT,
|
|
||||||
HEADER_HEIGHT,
|
|
||||||
CONTAINER_PADDING,
|
|
||||||
EMPTY_CONTAINER_HEIGHT
|
|
||||||
} from "./loras_widget_utils.js";
|
} from "./loras_widget_utils.js";
|
||||||
import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js";
|
import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js";
|
||||||
import { forwardMiddleMouseToCanvas, forwardWheelToCanvas, enableListWheelScroll } from "./utils.js";
|
import { forwardMiddleMouseToCanvas, forwardWheelToCanvas, enableListWheelScroll } from "./utils.js";
|
||||||
import { PreviewTooltip } from "./preview_tooltip.js";
|
import { PreviewTooltip } from "./preview_tooltip.js";
|
||||||
import { ensureLmStyles } from "./lm_styles_loader.js";
|
import { ensureLmStyles } from "./lm_styles_loader.js";
|
||||||
import { getStrengthStepPreference, getLoraWidgetMaxVisibleLoras } from "./settings.js";
|
import { getStrengthStepPreference } from "./settings.js";
|
||||||
|
|
||||||
export function addLorasWidget(node, name, opts, callback) {
|
export function addLorasWidget(node, name, opts, callback) {
|
||||||
ensureLmStyles();
|
ensureLmStyles();
|
||||||
@@ -29,15 +24,13 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
// Set initial height using CSS variables approach
|
// Set initial height using CSS variables approach
|
||||||
const defaultHeight = 200;
|
const defaultHeight = 200;
|
||||||
|
|
||||||
// In Vue/node-2.0 mode, cap the widget height so it shows at most N entries.
|
// Set a fixed minimum height so the node has a reasonable starting size.
|
||||||
// This prevents content from driving the node size beyond the cap.
|
// Adding or removing LoRAs does NOT change the node size — the container
|
||||||
// canvas/legacy mode is unaffected.
|
// scrolls when content exceeds the allocated space.
|
||||||
|
container.style.setProperty('--comfy-widget-min-height', `${defaultHeight}px`);
|
||||||
|
|
||||||
if (typeof LiteGraph !== 'undefined' && LiteGraph.vueNodesMode) {
|
if (typeof LiteGraph !== 'undefined' && LiteGraph.vueNodesMode) {
|
||||||
const maxLoras = getLoraWidgetMaxVisibleLoras();
|
container.classList.add('lm-vue-node');
|
||||||
const gap = 5; // flex gap from .lm-loras-container CSS
|
|
||||||
const maxH = CONTAINER_PADDING + HEADER_HEIGHT + maxLoras * LORA_ENTRY_HEIGHT + maxLoras * gap;
|
|
||||||
container.style.maxHeight = `${maxH}px`;
|
|
||||||
container.style.setProperty('--comfy-widget-max-height', `${maxH}px`);
|
|
||||||
// Window capture-phase hook: scroll the widget instead of zooming the canvas
|
// Window capture-phase hook: scroll the widget instead of zooming the canvas
|
||||||
// when the wheel is over a scrollable loras list.
|
// when the wheel is over a scrollable loras list.
|
||||||
enableListWheelScroll(container);
|
enableListWheelScroll(container);
|
||||||
@@ -210,9 +203,6 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
emptyMessage.textContent = "No LoRAs added";
|
emptyMessage.textContent = "No LoRAs added";
|
||||||
emptyMessage.className = "lm-lora-empty-state";
|
emptyMessage.className = "lm-lora-empty-state";
|
||||||
container.appendChild(emptyMessage);
|
container.appendChild(emptyMessage);
|
||||||
|
|
||||||
// Set fixed height for empty state
|
|
||||||
updateWidgetHeight(container, EMPTY_CONTAINER_HEIGHT, defaultHeight, node);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,9 +249,6 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
// Initialize the header drag functionality
|
// Initialize the header drag functionality
|
||||||
initHeaderDrag(header, widget, renderLoras);
|
initHeaderDrag(header, widget, renderLoras);
|
||||||
|
|
||||||
// Track the total visible entries for height calculation
|
|
||||||
let totalVisibleEntries = lorasData.length;
|
|
||||||
|
|
||||||
// Render each lora entry
|
// Render each lora entry
|
||||||
lorasData.forEach((loraData) => {
|
lorasData.forEach((loraData) => {
|
||||||
const { name, strength, clipStrength, active } = loraData;
|
const { name, strength, clipStrength, active } = loraData;
|
||||||
@@ -533,7 +520,6 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
|
|
||||||
// If expanded, show the clip entry
|
// If expanded, show the clip entry
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
totalVisibleEntries++;
|
|
||||||
const clipEl = document.createElement("div");
|
const clipEl = document.createElement("div");
|
||||||
clipEl.className = "lm-lora-clip-entry";
|
clipEl.className = "lm-lora-clip-entry";
|
||||||
|
|
||||||
@@ -657,10 +643,6 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate height based on number of loras and fixed sizes
|
|
||||||
const calculatedHeight = CONTAINER_PADDING + HEADER_HEIGHT + (Math.min(totalVisibleEntries, 12) * LORA_ENTRY_HEIGHT);
|
|
||||||
updateWidgetHeight(container, calculatedHeight, defaultHeight, node);
|
|
||||||
|
|
||||||
// After all LoRA elements are created, apply selection state as the last step
|
// After all LoRA elements are created, apply selection state as the last step
|
||||||
// This ensures the selection state is not overwritten
|
// This ensures the selection state is not overwritten
|
||||||
container.querySelectorAll('.lm-lora-entry').forEach(entry => {
|
container.querySelectorAll('.lm-lora-entry').forEach(entry => {
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
|
|
||||||
// Fixed sizes for component calculations
|
|
||||||
export const LORA_ENTRY_HEIGHT = 40; // Height of a single lora entry
|
|
||||||
export const CLIP_ENTRY_HEIGHT = 40; // Height of a clip entry
|
|
||||||
export const HEADER_HEIGHT = 32; // Height of the header section
|
|
||||||
export const CONTAINER_PADDING = 12; // Top and bottom padding
|
|
||||||
export const EMPTY_CONTAINER_HEIGHT = 100; // Height when no loras are present
|
|
||||||
|
|
||||||
// Parse LoRA entries from value
|
// Parse LoRA entries from value
|
||||||
export function parseLoraValue(value) {
|
export function parseLoraValue(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
@@ -18,23 +11,6 @@ export function formatLoraValue(loras) {
|
|||||||
return loras;
|
return loras;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to update widget height consistently
|
|
||||||
export function updateWidgetHeight(container, height, defaultHeight, node) {
|
|
||||||
// Ensure minimum height
|
|
||||||
const finalHeight = Math.max(defaultHeight, height);
|
|
||||||
|
|
||||||
// Update CSS variables
|
|
||||||
container.style.setProperty('--comfy-widget-min-height', `${finalHeight}px`);
|
|
||||||
container.style.setProperty('--comfy-widget-height', `${finalHeight}px`);
|
|
||||||
|
|
||||||
// Force node to update size after a short delay to ensure DOM is updated
|
|
||||||
if (node) {
|
|
||||||
setTimeout(() => {
|
|
||||||
node.setDirtyCanvas(true, true);
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if clip entry should be shown - now based on expanded property or initial diff values
|
// Determine if clip entry should be shown - now based on expanded property or initial diff values
|
||||||
export function shouldShowClipEntry(loraData) {
|
export function shouldShowClipEntry(loraData) {
|
||||||
// If expanded property exists, use that
|
// If expanded property exists, use that
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ const NEW_TAB_ZOOM_LEVEL = 0.8;
|
|||||||
const STRENGTH_STEP_SETTING_ID = "loramanager.strength_step";
|
const STRENGTH_STEP_SETTING_ID = "loramanager.strength_step";
|
||||||
const STRENGTH_STEP_DEFAULT = 0.05;
|
const STRENGTH_STEP_DEFAULT = 0.05;
|
||||||
|
|
||||||
const LORA_WIDGET_MAX_VISIBLE_SETTING_ID = "loramanager.lora_widget_max_visible_loras";
|
|
||||||
const LORA_WIDGET_MAX_VISIBLE_DEFAULT = 12;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Helper Functions
|
// Helper Functions
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -363,32 +360,6 @@ const getStrengthStepPreference = (() => {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const getLoraWidgetMaxVisibleLoras = (() => {
|
|
||||||
let settingsUnavailableLogged = false;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const settingManager = app?.extensionManager?.setting;
|
|
||||||
if (!settingManager || typeof settingManager.get !== "function") {
|
|
||||||
if (!settingsUnavailableLogged) {
|
|
||||||
console.warn("LoRA Manager: settings API unavailable, using default max visible loras.");
|
|
||||||
settingsUnavailableLogged = true;
|
|
||||||
}
|
|
||||||
return LORA_WIDGET_MAX_VISIBLE_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const value = settingManager.get(LORA_WIDGET_MAX_VISIBLE_SETTING_ID);
|
|
||||||
return value ?? LORA_WIDGET_MAX_VISIBLE_DEFAULT;
|
|
||||||
} catch (error) {
|
|
||||||
if (!settingsUnavailableLogged) {
|
|
||||||
console.warn("LoRA Manager: unable to read max visible loras setting, using default.", error);
|
|
||||||
settingsUnavailableLogged = true;
|
|
||||||
}
|
|
||||||
return LORA_WIDGET_MAX_VISIBLE_DEFAULT;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Register Extension with All Settings
|
// Register Extension with All Settings
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -492,19 +463,6 @@ app.registerExtension({
|
|||||||
tooltip: "Step size for adjusting LoRA strength via arrow buttons or keyboard (default: 0.05)",
|
tooltip: "Step size for adjusting LoRA strength via arrow buttons or keyboard (default: 0.05)",
|
||||||
category: ["LoRA Manager", "LoRA Widget", "Strength Step"],
|
category: ["LoRA Manager", "LoRA Widget", "Strength Step"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: LORA_WIDGET_MAX_VISIBLE_SETTING_ID,
|
|
||||||
name: "Node 2.0: Maximum visible LoRA entries",
|
|
||||||
type: "slider",
|
|
||||||
attrs: {
|
|
||||||
min: 3,
|
|
||||||
max: 50,
|
|
||||||
step: 1,
|
|
||||||
},
|
|
||||||
defaultValue: LORA_WIDGET_MAX_VISIBLE_DEFAULT,
|
|
||||||
tooltip: "When using Node 2.0 rendering, limit the loras widget height to show at most this many entries (default: 12). Excess entries are accessible via scrollbar.",
|
|
||||||
category: ["LoRA Manager", "LoRA Widget", "Max Visible"],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
async setup() {
|
async setup() {
|
||||||
await loadWorkflowOptions();
|
await loadWorkflowOptions();
|
||||||
@@ -591,5 +549,4 @@ export {
|
|||||||
getUsageStatisticsPreference,
|
getUsageStatisticsPreference,
|
||||||
getNewTabTemplatePreference,
|
getNewTabTemplatePreference,
|
||||||
getStrengthStepPreference,
|
getStrengthStepPreference,
|
||||||
getLoraWidgetMaxVisibleLoras,
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user