mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 13:12:12 -03:00
feat: standardize LoRA Manager frontend with CSS classes and improved styles
- Replace inline styles with CSS classes for better maintainability - Update class names to use consistent 'lm-' prefix across components - Add comprehensive CSS stylesheet with tooltip system and responsive layouts - Improve accessibility with proper focus states and keyboard navigation - Implement hover and active state transitions for enhanced UX - Refactor expand button to use CSS classes instead of inline styles - Update test files to reflect new class naming convention
This commit is contained in:
@@ -28,14 +28,14 @@ vi.mock(COMPONENTS_MODULE, () => ({
|
||||
describe('LoRA widget drag interactions', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
const dragStyle = document.getElementById('comfy-lora-drag-style');
|
||||
const dragStyle = document.getElementById('lm-lora-shared-styles');
|
||||
if (dragStyle) {
|
||||
dragStyle.remove();
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.classList.remove('comfy-lora-dragging');
|
||||
document.body.classList.remove('lm-lora-strength-dragging');
|
||||
});
|
||||
|
||||
it('adjusts a single LoRA strength while syncing collapsed clip strength', async () => {
|
||||
@@ -84,7 +84,7 @@ describe('LoRA widget drag interactions', () => {
|
||||
const previewSpy = { hide: vi.fn() };
|
||||
|
||||
const dragEl = document.createElement('div');
|
||||
dragEl.className = 'comfy-lora-entry';
|
||||
dragEl.className = 'lm-lora-entry';
|
||||
document.body.append(dragEl);
|
||||
|
||||
const widget = {
|
||||
@@ -95,7 +95,7 @@ describe('LoRA widget drag interactions', () => {
|
||||
module.initDrag(dragEl, 'Test', widget, false, previewSpy, renderSpy);
|
||||
|
||||
dragEl.dispatchEvent(new MouseEvent('mousedown', { clientX: 50, bubbles: true }));
|
||||
expect(document.body.classList.contains('comfy-lora-dragging')).toBe(true);
|
||||
expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(true);
|
||||
|
||||
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 70, bubbles: true }));
|
||||
expect(renderSpy).toHaveBeenCalledWith(widget.value, widget);
|
||||
@@ -103,6 +103,6 @@ describe('LoRA widget drag interactions', () => {
|
||||
expect(widget.value[0].strength).not.toBe(0.5);
|
||||
|
||||
document.dispatchEvent(new MouseEvent('mouseup'));
|
||||
expect(document.body.classList.contains('comfy-lora-dragging')).toBe(false);
|
||||
expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
449
web/comfyui/lm_styles.css
Normal file
449
web/comfyui/lm_styles.css
Normal file
@@ -0,0 +1,449 @@
|
||||
/* Shared styling for the LoRA Manager frontend widgets */
|
||||
.lm-tooltip {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
max-width: 300px;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.lm-tooltip__media-container {
|
||||
position: relative;
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.lm-tooltip__media {
|
||||
max-width: 100%;
|
||||
max-height: 300px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lm-tooltip__label {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 8px;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.lm-loras-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
padding: 6px;
|
||||
background-color: rgba(40, 44, 52, 0.6);
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.lm-loras-container:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.lm-lora-empty-state {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
color: rgba(226, 232, 240, 0.8);
|
||||
font-style: italic;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lm-loras-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.2);
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.lm-toggle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lm-toggle-label,
|
||||
.lm-strength-label {
|
||||
color: rgba(226, 232, 240, 0.8);
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.lm-toggle-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.lm-strength-label {
|
||||
margin-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lm-drag-hint {
|
||||
margin-left: 5px;
|
||||
font-size: 11px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.lm-loras-header:hover .lm-drag-hint {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lm-lora-entry {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 4px;
|
||||
border: 1px solid transparent;
|
||||
background-color: rgba(45, 55, 72, 0.7);
|
||||
}
|
||||
|
||||
.lm-lora-entry[data-active="false"] {
|
||||
background-color: rgba(35, 40, 50, 0.5);
|
||||
}
|
||||
|
||||
.lm-lora-entry:hover {
|
||||
background-color: rgba(50, 60, 80, 0.8);
|
||||
}
|
||||
|
||||
.lm-lora-entry[data-active="false"]:hover {
|
||||
background-color: rgba(40, 45, 55, 0.6);
|
||||
}
|
||||
|
||||
.lm-lora-entry[data-selected="true"] {
|
||||
background-color: rgba(66, 153, 225, 0.3) !important;
|
||||
border: 1px solid rgba(66, 153, 225, 0.6) !important;
|
||||
box-shadow: 0 0 0 1px rgba(66, 153, 225, 0.3) !important;
|
||||
}
|
||||
|
||||
.lm-lora-name {
|
||||
margin-left: 4px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: rgba(226, 232, 240, 0.9);
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.lm-lora-entry[data-active="false"] .lm-lora-name {
|
||||
color: rgba(226, 232, 240, 0.6);
|
||||
}
|
||||
|
||||
.lm-lora-strength-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lm-lora-strength-input {
|
||||
width: 50px;
|
||||
min-width: 50px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(226, 232, 240, 0.2);
|
||||
background: rgba(26, 32, 44, 0.9);
|
||||
color: rgba(226, 232, 240, 0.9);
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.lm-lora-strength-input:focus {
|
||||
outline: none;
|
||||
border-color: rgba(66, 153, 225, 0.9);
|
||||
box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.3);
|
||||
}
|
||||
|
||||
.lm-lora-strength-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.lm-lora-entry[data-active="false"] .lm-lora-strength-input {
|
||||
color: rgba(226, 232, 240, 0.6);
|
||||
}
|
||||
|
||||
.lm-lora-arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 12px;
|
||||
color: rgba(226, 232, 240, 0.8);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.lm-lora-arrow:hover {
|
||||
color: white;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.lm-lora-expand-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 10px;
|
||||
color: rgba(226, 232, 240, 0.7);
|
||||
background-color: rgba(45, 55, 72, 0.3);
|
||||
border: 1px solid rgba(226, 232, 240, 0.2);
|
||||
border-radius: 3px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.lm-lora-expand-button:hover {
|
||||
background-color: rgba(66, 153, 225, 0.2);
|
||||
border-color: rgba(66, 153, 225, 0.4);
|
||||
color: rgba(226, 232, 240, 0.9);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.lm-lora-expand-button:active {
|
||||
transform: scale(0.95);
|
||||
background-color: rgba(66, 153, 225, 0.3);
|
||||
}
|
||||
|
||||
.lm-lora-expand-button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.lm-lora-expand-button:focus-visible {
|
||||
box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.5);
|
||||
}
|
||||
|
||||
.lm-lora-toggle {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background-color: rgba(45, 55, 72, 0.7);
|
||||
border: 1px solid rgba(226, 232, 240, 0.2);
|
||||
}
|
||||
|
||||
.lm-lora-toggle--active {
|
||||
background-color: rgba(66, 153, 225, 0.9);
|
||||
border-color: rgba(66, 153, 225, 0.9);
|
||||
}
|
||||
|
||||
.lm-lora-toggle:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.lm-lora-drag-handle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
font-size: 14px;
|
||||
color: rgba(226, 232, 240, 0.6);
|
||||
transition: all 0.2s ease;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.lm-lora-drag-handle:hover {
|
||||
color: rgba(226, 232, 240, 0.9);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.lm-lora-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
body.lm-lora-strength-dragging,
|
||||
body.lm-lora-strength-dragging * {
|
||||
cursor: ew-resize !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
body.lm-lora-reordering,
|
||||
body.lm-lora-reordering * {
|
||||
cursor: grabbing !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
.lm-lora-entry--dragging {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.lm-lora-drop-indicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background-color: rgba(66, 153, 225, 0.9);
|
||||
border-radius: 2px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
box-shadow: 0 0 6px rgba(66, 153, 225, 0.8);
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lm-lora-strength-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.lm-lora-entry-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry {
|
||||
margin-left: 10px;
|
||||
margin-bottom: 4px;
|
||||
padding: 6px;
|
||||
padding-left: 20px;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(65, 70, 90, 0.6);
|
||||
border: 1px solid rgba(66, 153, 225, 0.2);
|
||||
border-left: 2px solid rgba(72, 118, 255, 0.6);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry[data-active="false"] {
|
||||
background-color: rgba(50, 55, 65, 0.5);
|
||||
border-color: rgba(66, 153, 225, 0.1);
|
||||
border-left-color: rgba(66, 153, 225, 0.3);
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry:hover {
|
||||
background-color: rgba(70, 75, 95, 0.7);
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry[data-active="false"]:hover {
|
||||
background-color: rgba(55, 60, 70, 0.6);
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry .lm-lora-name {
|
||||
font-size: 13px;
|
||||
color: rgba(200, 215, 240, 0.9);
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry[data-active="false"] .lm-lora-name {
|
||||
color: rgba(200, 215, 240, 0.6);
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry .lm-lora-strength-input {
|
||||
color: rgba(200, 215, 240, 0.9);
|
||||
}
|
||||
|
||||
.lm-lora-clip-entry[data-active="false"] .lm-lora-strength-input {
|
||||
color: rgba(200, 215, 240, 0.6);
|
||||
}
|
||||
|
||||
.lm-lora-badge {
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
background: rgba(66, 153, 225, 0.2);
|
||||
color: rgba(226, 232, 240, 0.85);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.lm-lora-context-menu {
|
||||
position: fixed;
|
||||
background: rgba(26, 32, 44, 0.95);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.35);
|
||||
padding: 8px 0;
|
||||
z-index: 10000;
|
||||
min-width: 180px;
|
||||
border: 1px solid rgba(66, 153, 225, 0.3);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.lm-lora-menu-item {
|
||||
padding: 6px 20px;
|
||||
cursor: pointer;
|
||||
color: rgba(226, 232, 240, 0.9);
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lm-lora-menu-item:hover {
|
||||
background-color: rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.lm-lora-menu-item-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lm-lora-context-menu hr {
|
||||
border: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
margin: 6px 0;
|
||||
}
|
||||
37
web/comfyui/lm_styles_loader.js
Normal file
37
web/comfyui/lm_styles_loader.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const STYLE_ID = "lm-lora-shared-styles";
|
||||
let stylePromise = null;
|
||||
|
||||
function injectStyles(cssText) {
|
||||
let styleEl = document.getElementById(STYLE_ID);
|
||||
if (!styleEl) {
|
||||
styleEl = document.createElement("style");
|
||||
styleEl.id = STYLE_ID;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
styleEl.textContent = cssText;
|
||||
}
|
||||
|
||||
async function loadCssText() {
|
||||
const cssUrl = new URL("./lm_styles.css", import.meta.url);
|
||||
const response = await fetch(cssUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load ${cssUrl}`);
|
||||
}
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
export function ensureLmStyles() {
|
||||
if (!stylePromise) {
|
||||
stylePromise = loadCssText()
|
||||
.then((cssText) => {
|
||||
injectStyles(cssText);
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn("Failed to load LoRA Manager styles", error);
|
||||
stylePromise = null;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return stylePromise;
|
||||
}
|
||||
@@ -13,28 +13,19 @@ import {
|
||||
import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js";
|
||||
import { forwardMiddleMouseToCanvas } from "./utils.js";
|
||||
import { PreviewTooltip } from "./preview_tooltip.js";
|
||||
import { ensureLmStyles } from "./lm_styles_loader.js";
|
||||
|
||||
export function addLorasWidget(node, name, opts, callback) {
|
||||
ensureLmStyles();
|
||||
|
||||
// Create container for loras
|
||||
const container = document.createElement("div");
|
||||
container.className = "comfy-loras-container";
|
||||
container.className = "lm-loras-container";
|
||||
|
||||
forwardMiddleMouseToCanvas(container);
|
||||
|
||||
// Set initial height using CSS variables approach
|
||||
const defaultHeight = 200;
|
||||
|
||||
Object.assign(container.style, {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
padding: "6px",
|
||||
backgroundColor: "rgba(40, 44, 52, 0.6)",
|
||||
borderRadius: "6px",
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
overflow: "auto"
|
||||
});
|
||||
|
||||
// Initialize default value
|
||||
const defaultValue = opts?.defaultVal || [];
|
||||
@@ -50,7 +41,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
const selectLora = (loraName) => {
|
||||
selectedLora = loraName;
|
||||
// Update visual feedback for all entries
|
||||
container.querySelectorAll('.comfy-lora-entry').forEach(entry => {
|
||||
container.querySelectorAll('.lm-lora-entry').forEach(entry => {
|
||||
const entryLoraName = entry.dataset.loraName;
|
||||
updateEntrySelection(entry, entryLoraName === selectedLora);
|
||||
});
|
||||
@@ -65,7 +56,6 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Make container focusable for keyboard events
|
||||
container.tabIndex = 0;
|
||||
container.style.outline = 'none';
|
||||
|
||||
// Function to render loras from data
|
||||
const renderLoras = (value, widget) => {
|
||||
@@ -125,17 +115,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
// Show message when no loras are added
|
||||
const emptyMessage = document.createElement("div");
|
||||
emptyMessage.textContent = "No LoRAs added";
|
||||
Object.assign(emptyMessage.style, {
|
||||
textAlign: "center",
|
||||
padding: "20px 0",
|
||||
color: "rgba(226, 232, 240, 0.8)",
|
||||
fontStyle: "italic",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
width: "100%"
|
||||
});
|
||||
emptyMessage.className = "lm-lora-empty-state";
|
||||
container.appendChild(emptyMessage);
|
||||
|
||||
// Set fixed height for empty state
|
||||
@@ -145,16 +125,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Create header
|
||||
const header = document.createElement("div");
|
||||
header.className = "comfy-loras-header";
|
||||
Object.assign(header.style, {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "4px 8px",
|
||||
borderBottom: "1px solid rgba(226, 232, 240, 0.2)",
|
||||
marginBottom: "5px",
|
||||
position: "relative" // Added for positioning the drag hint
|
||||
});
|
||||
header.className = "lm-loras-header";
|
||||
|
||||
// Add toggle all control
|
||||
const allActive = lorasData.every(lora => lora.active);
|
||||
@@ -170,58 +141,23 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
// Add label to toggle all
|
||||
const toggleLabel = document.createElement("div");
|
||||
toggleLabel.textContent = "Toggle All";
|
||||
Object.assign(toggleLabel.style, {
|
||||
color: "rgba(226, 232, 240, 0.8)",
|
||||
fontSize: "13px",
|
||||
marginLeft: "8px",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
});
|
||||
toggleLabel.className = "lm-toggle-label";
|
||||
|
||||
const toggleContainer = document.createElement("div");
|
||||
Object.assign(toggleContainer.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
});
|
||||
toggleContainer.className = "lm-toggle-container";
|
||||
toggleContainer.appendChild(toggleAll);
|
||||
toggleContainer.appendChild(toggleLabel);
|
||||
|
||||
// Strength label with drag hint
|
||||
const strengthLabel = document.createElement("div");
|
||||
strengthLabel.textContent = "Strength";
|
||||
Object.assign(strengthLabel.style, {
|
||||
color: "rgba(226, 232, 240, 0.8)",
|
||||
fontSize: "13px",
|
||||
marginRight: "8px",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
});
|
||||
|
||||
strengthLabel.className = "lm-strength-label";
|
||||
|
||||
// Add drag hint icon next to strength label
|
||||
const dragHint = document.createElement("span");
|
||||
dragHint.innerHTML = "↔"; // Simple left-right arrow as drag indicator
|
||||
Object.assign(dragHint.style, {
|
||||
marginLeft: "5px",
|
||||
fontSize: "11px",
|
||||
opacity: "0.6",
|
||||
transition: "opacity 0.2s ease"
|
||||
});
|
||||
dragHint.className = "lm-drag-hint";
|
||||
strengthLabel.appendChild(dragHint);
|
||||
|
||||
// Add hover effect to improve discoverability
|
||||
header.addEventListener("mouseenter", () => {
|
||||
dragHint.style.opacity = "1";
|
||||
});
|
||||
|
||||
header.addEventListener("mouseleave", () => {
|
||||
dragHint.style.opacity = "0.6";
|
||||
});
|
||||
|
||||
header.appendChild(toggleContainer);
|
||||
header.appendChild(strengthLabel);
|
||||
@@ -243,30 +179,20 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Create the main LoRA entry
|
||||
const loraEl = document.createElement("div");
|
||||
loraEl.className = "comfy-lora-entry";
|
||||
Object.assign(loraEl.style, {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "6px",
|
||||
borderRadius: "6px",
|
||||
backgroundColor: active ? "rgba(45, 55, 72, 0.7)" : "rgba(35, 40, 50, 0.5)",
|
||||
transition: "all 0.2s ease",
|
||||
marginBottom: "4px",
|
||||
});
|
||||
loraEl.className = "lm-lora-entry";
|
||||
|
||||
// Store lora name and active state in dataset for selection
|
||||
loraEl.dataset.loraName = name;
|
||||
loraEl.dataset.active = active;
|
||||
loraEl.dataset.active = active ? "true" : "false";
|
||||
|
||||
// Add click handler for selection
|
||||
loraEl.addEventListener('click', (e) => {
|
||||
// Skip if clicking on interactive elements
|
||||
if (e.target.closest('.comfy-lora-toggle') ||
|
||||
if (e.target.closest('.lm-lora-toggle') ||
|
||||
e.target.closest('input') ||
|
||||
e.target.closest('.comfy-lora-arrow') ||
|
||||
e.target.closest('.comfy-lora-drag-handle') ||
|
||||
e.target.closest('.comfy-lora-expand-button')) {
|
||||
e.target.closest('.lm-lora-arrow') ||
|
||||
e.target.closest('.lm-lora-drag-handle') ||
|
||||
e.target.closest('.lm-lora-expand-button')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -322,19 +248,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
// Create name display
|
||||
const nameEl = document.createElement("div");
|
||||
nameEl.textContent = name;
|
||||
Object.assign(nameEl.style, {
|
||||
marginLeft: "4px",
|
||||
flex: "1",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
color: active ? "rgba(226, 232, 240, 0.9)" : "rgba(226, 232, 240, 0.6)",
|
||||
fontSize: "13px",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
});
|
||||
nameEl.className = "lm-lora-name";
|
||||
|
||||
// Move preview tooltip events to nameEl instead of loraEl
|
||||
let previewTimer; // Timer for delayed preview
|
||||
@@ -355,15 +269,6 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
// Initialize drag functionality for strength adjustment
|
||||
initDrag(loraEl, name, widget, false, previewTooltip, renderLoras);
|
||||
|
||||
// Remove the preview tooltip events from loraEl
|
||||
loraEl.onmouseenter = () => {
|
||||
loraEl.style.backgroundColor = active ? "rgba(50, 60, 80, 0.8)" : "rgba(40, 45, 55, 0.6)";
|
||||
};
|
||||
|
||||
loraEl.onmouseleave = () => {
|
||||
loraEl.style.backgroundColor = active ? "rgba(45, 55, 72, 0.7)" : "rgba(35, 40, 50, 0.5)";
|
||||
};
|
||||
|
||||
// Add context menu event
|
||||
loraEl.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
@@ -373,11 +278,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Create strength control
|
||||
const strengthControl = document.createElement("div");
|
||||
Object.assign(strengthControl.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
});
|
||||
strengthControl.className = "lm-lora-strength-control";
|
||||
|
||||
// Left arrow
|
||||
const leftArrow = createArrowButton("left", () => {
|
||||
@@ -397,51 +298,21 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Strength display
|
||||
const strengthEl = document.createElement("input");
|
||||
strengthEl.classList.add("comfy-lora-strength-input");
|
||||
strengthEl.classList.add("lm-lora-strength-input");
|
||||
strengthEl.type = "text";
|
||||
strengthEl.value = typeof strength === 'number' ? strength.toFixed(2) : Number(strength).toFixed(2);
|
||||
strengthEl.addEventListener('pointerdown', () => {
|
||||
pendingFocusTarget = { name, type: "strength" };
|
||||
});
|
||||
Object.assign(strengthEl.style, {
|
||||
minWidth: "50px",
|
||||
width: "50px",
|
||||
textAlign: "center",
|
||||
color: active ? "rgba(226, 232, 240, 0.9)" : "rgba(226, 232, 240, 0.6)",
|
||||
fontSize: "13px",
|
||||
background: "none",
|
||||
border: "1px solid transparent",
|
||||
padding: "2px 4px",
|
||||
borderRadius: "3px",
|
||||
outline: "none",
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
strengthEl.addEventListener('mouseenter', () => {
|
||||
strengthEl.style.border = "1px solid rgba(226, 232, 240, 0.2)";
|
||||
});
|
||||
|
||||
strengthEl.addEventListener('mouseleave', () => {
|
||||
if (document.activeElement !== strengthEl) {
|
||||
strengthEl.style.border = "1px solid transparent";
|
||||
}
|
||||
});
|
||||
|
||||
// Handle focus
|
||||
strengthEl.addEventListener('focus', () => {
|
||||
pendingFocusTarget = null;
|
||||
strengthEl.style.border = "1px solid rgba(66, 153, 225, 0.6)";
|
||||
strengthEl.style.background = "rgba(0, 0, 0, 0.2)";
|
||||
// Auto-select all content
|
||||
strengthEl.select();
|
||||
selectLora(name);
|
||||
});
|
||||
|
||||
strengthEl.addEventListener('blur', () => {
|
||||
strengthEl.style.border = "1px solid transparent";
|
||||
strengthEl.style.background = "none";
|
||||
});
|
||||
|
||||
// Handle input changes
|
||||
const commitStrengthValue = () => {
|
||||
let parsedValue = parseFloat(strengthEl.value);
|
||||
@@ -503,12 +374,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Assemble entry
|
||||
const leftSection = document.createElement("div");
|
||||
Object.assign(leftSection.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flex: "1",
|
||||
minWidth: "0", // Allow shrinking
|
||||
});
|
||||
leftSection.className = "lm-lora-entry-left";
|
||||
|
||||
leftSection.appendChild(dragHandle); // Add drag handle first
|
||||
leftSection.appendChild(toggle);
|
||||
@@ -524,49 +390,20 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
if (isExpanded) {
|
||||
totalVisibleEntries++;
|
||||
const clipEl = document.createElement("div");
|
||||
clipEl.className = "comfy-lora-clip-entry";
|
||||
Object.assign(clipEl.style, {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "6px",
|
||||
paddingLeft: "20px", // Indent to align with parent name
|
||||
borderRadius: "6px",
|
||||
backgroundColor: active ? "rgba(65, 70, 90, 0.6)" : "rgba(50, 55, 65, 0.5)",
|
||||
borderLeft: "2px solid rgba(72, 118, 255, 0.6)",
|
||||
transition: "all 0.2s ease",
|
||||
marginBottom: "4px",
|
||||
marginLeft: "10px",
|
||||
marginTop: "-2px"
|
||||
});
|
||||
clipEl.className = "lm-lora-clip-entry";
|
||||
|
||||
// Store the same lora name in clip entry dataset
|
||||
clipEl.dataset.loraName = name;
|
||||
clipEl.dataset.active = active;
|
||||
clipEl.dataset.active = active ? "true" : "false";
|
||||
|
||||
// Create clip name display
|
||||
const clipNameEl = document.createElement("div");
|
||||
clipNameEl.textContent = "[clip] " + name;
|
||||
Object.assign(clipNameEl.style, {
|
||||
flex: "1",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
color: active ? "rgba(200, 215, 240, 0.9)" : "rgba(200, 215, 240, 0.6)",
|
||||
fontSize: "13px",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
});
|
||||
clipNameEl.className = "lm-lora-name";
|
||||
|
||||
// Create clip strength control
|
||||
const clipStrengthControl = document.createElement("div");
|
||||
Object.assign(clipStrengthControl.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
});
|
||||
clipStrengthControl.className = "lm-lora-strength-control";
|
||||
|
||||
// Left arrow for clip
|
||||
const clipLeftArrow = createArrowButton("left", () => {
|
||||
@@ -584,51 +421,21 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Clip strength display
|
||||
const clipStrengthEl = document.createElement("input");
|
||||
clipStrengthEl.classList.add("comfy-lora-strength-input", "comfy-lora-clip-strength-input");
|
||||
clipStrengthEl.classList.add("lm-lora-strength-input", "lm-lora-clip-strength-input");
|
||||
clipStrengthEl.type = "text";
|
||||
clipStrengthEl.value = typeof clipStrength === 'number' ? clipStrength.toFixed(2) : Number(clipStrength).toFixed(2);
|
||||
clipStrengthEl.addEventListener('pointerdown', () => {
|
||||
pendingFocusTarget = { name, type: "clip" };
|
||||
});
|
||||
Object.assign(clipStrengthEl.style, {
|
||||
minWidth: "50px",
|
||||
width: "50px",
|
||||
textAlign: "center",
|
||||
color: active ? "rgba(200, 215, 240, 0.9)" : "rgba(200, 215, 240, 0.6)",
|
||||
fontSize: "13px",
|
||||
background: "none",
|
||||
border: "1px solid transparent",
|
||||
padding: "2px 4px",
|
||||
borderRadius: "3px",
|
||||
outline: "none",
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
clipStrengthEl.addEventListener('mouseenter', () => {
|
||||
clipStrengthEl.style.border = "1px solid rgba(226, 232, 240, 0.2)";
|
||||
});
|
||||
|
||||
clipStrengthEl.addEventListener('mouseleave', () => {
|
||||
if (document.activeElement !== clipStrengthEl) {
|
||||
clipStrengthEl.style.border = "1px solid transparent";
|
||||
}
|
||||
});
|
||||
|
||||
// Handle focus
|
||||
clipStrengthEl.addEventListener('focus', () => {
|
||||
pendingFocusTarget = null;
|
||||
clipStrengthEl.style.border = "1px solid rgba(72, 118, 255, 0.6)";
|
||||
clipStrengthEl.style.background = "rgba(0, 0, 0, 0.2)";
|
||||
// Auto-select all content
|
||||
clipStrengthEl.select();
|
||||
selectLora(name);
|
||||
});
|
||||
|
||||
clipStrengthEl.addEventListener('blur', () => {
|
||||
clipStrengthEl.style.border = "1px solid transparent";
|
||||
clipStrengthEl.style.background = "none";
|
||||
});
|
||||
|
||||
// Handle input changes
|
||||
const clipFocusEntry = createFocusEntry(name, "clip");
|
||||
|
||||
@@ -688,27 +495,13 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Assemble clip entry
|
||||
const clipLeftSection = document.createElement("div");
|
||||
Object.assign(clipLeftSection.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flex: "1",
|
||||
minWidth: "0", // Allow shrinking
|
||||
});
|
||||
|
||||
clipLeftSection.className = "lm-lora-entry-left";
|
||||
|
||||
clipLeftSection.appendChild(clipNameEl);
|
||||
|
||||
|
||||
clipEl.appendChild(clipLeftSection);
|
||||
clipEl.appendChild(clipStrengthControl);
|
||||
|
||||
// Hover effects for clip entry
|
||||
clipEl.onmouseenter = () => {
|
||||
clipEl.style.backgroundColor = active ? "rgba(70, 75, 95, 0.7)" : "rgba(55, 60, 70, 0.6)";
|
||||
};
|
||||
|
||||
clipEl.onmouseleave = () => {
|
||||
clipEl.style.backgroundColor = active ? "rgba(65, 70, 90, 0.6)" : "rgba(50, 55, 65, 0.5)";
|
||||
};
|
||||
|
||||
// Add drag functionality to clip entry
|
||||
initDrag(clipEl, name, widget, true, previewTooltip, renderLoras);
|
||||
|
||||
@@ -722,7 +515,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// After all LoRA elements are created, apply selection state as the last step
|
||||
// This ensures the selection state is not overwritten
|
||||
container.querySelectorAll('.comfy-lora-entry').forEach(entry => {
|
||||
container.querySelectorAll('.lm-lora-entry').forEach(entry => {
|
||||
const entryLoraName = entry.dataset.loraName;
|
||||
updateEntrySelection(entry, entryLoraName === selectedLora);
|
||||
});
|
||||
@@ -733,9 +526,9 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
let selector = "";
|
||||
|
||||
if (focusTarget.type === "strength") {
|
||||
selector = `.comfy-lora-entry[data-lora-name="${safeName}"] .comfy-lora-strength-input`;
|
||||
selector = `.lm-lora-entry[data-lora-name="${safeName}"] .lm-lora-strength-input`;
|
||||
} else if (focusTarget.type === "clip") {
|
||||
selector = `.comfy-lora-clip-entry[data-lora-name="${safeName}"] .comfy-lora-clip-strength-input`;
|
||||
selector = `.lm-lora-clip-entry[data-lora-name="${safeName}"] .lm-lora-clip-strength-input`;
|
||||
}
|
||||
|
||||
if (selector) {
|
||||
|
||||
@@ -1,215 +1,80 @@
|
||||
// Function to create toggle element
|
||||
export function createToggle(active, onChange) {
|
||||
const toggle = document.createElement("div");
|
||||
toggle.className = "comfy-lora-toggle";
|
||||
|
||||
toggle.className = "lm-lora-toggle";
|
||||
|
||||
updateToggleStyle(toggle, active);
|
||||
|
||||
|
||||
toggle.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
onChange(!active);
|
||||
});
|
||||
|
||||
|
||||
return toggle;
|
||||
}
|
||||
|
||||
// Helper function to update toggle style
|
||||
export function updateToggleStyle(toggleEl, active) {
|
||||
Object.assign(toggleEl.style, {
|
||||
width: "18px",
|
||||
height: "18px",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s ease",
|
||||
backgroundColor: active ? "rgba(66, 153, 225, 0.9)" : "rgba(45, 55, 72, 0.7)",
|
||||
border: `1px solid ${active ? "rgba(66, 153, 225, 0.9)" : "rgba(226, 232, 240, 0.2)"}`,
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
toggleEl.onmouseenter = () => {
|
||||
toggleEl.style.transform = "scale(1.05)";
|
||||
toggleEl.style.boxShadow = "0 2px 4px rgba(0,0,0,0.15)";
|
||||
};
|
||||
|
||||
toggleEl.onmouseleave = () => {
|
||||
toggleEl.style.transform = "scale(1)";
|
||||
toggleEl.style.boxShadow = "none";
|
||||
};
|
||||
toggleEl.classList.toggle("lm-lora-toggle--active", active);
|
||||
}
|
||||
|
||||
// Create arrow button for strength adjustment
|
||||
export function createArrowButton(direction, onClick) {
|
||||
const button = document.createElement("div");
|
||||
button.className = `comfy-lora-arrow comfy-lora-arrow-${direction}`;
|
||||
|
||||
Object.assign(button.style, {
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
fontSize: "12px",
|
||||
color: "rgba(226, 232, 240, 0.8)",
|
||||
transition: "all 0.2s ease",
|
||||
});
|
||||
|
||||
button.className = `lm-lora-arrow lm-lora-arrow-${direction}`;
|
||||
button.textContent = direction === "left" ? "◀" : "▶";
|
||||
|
||||
|
||||
button.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
button.onmouseenter = () => {
|
||||
button.style.color = "white";
|
||||
button.style.transform = "scale(1.2)";
|
||||
};
|
||||
|
||||
button.onmouseleave = () => {
|
||||
button.style.color = "rgba(226, 232, 240, 0.8)";
|
||||
button.style.transform = "scale(1)";
|
||||
};
|
||||
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
// Function to create drag handle
|
||||
export function createDragHandle() {
|
||||
const handle = document.createElement("div");
|
||||
handle.className = "comfy-lora-drag-handle";
|
||||
handle.className = "lm-lora-drag-handle";
|
||||
handle.innerHTML = "≡";
|
||||
handle.title = "Drag to reorder LoRA";
|
||||
|
||||
Object.assign(handle.style, {
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
cursor: "grab",
|
||||
userSelect: "none",
|
||||
fontSize: "14px",
|
||||
color: "rgba(226, 232, 240, 0.6)",
|
||||
transition: "all 0.2s ease",
|
||||
marginRight: "8px",
|
||||
flexShrink: "0"
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
handle.onmouseenter = () => {
|
||||
handle.style.color = "rgba(226, 232, 240, 0.9)";
|
||||
handle.style.transform = "scale(1.1)";
|
||||
};
|
||||
|
||||
handle.onmouseleave = () => {
|
||||
handle.style.color = "rgba(226, 232, 240, 0.6)";
|
||||
handle.style.transform = "scale(1)";
|
||||
};
|
||||
|
||||
// Change cursor when dragging
|
||||
handle.onmousedown = () => {
|
||||
handle.style.cursor = "grabbing";
|
||||
};
|
||||
|
||||
handle.onmouseup = () => {
|
||||
handle.style.cursor = "grab";
|
||||
};
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Function to create drop indicator
|
||||
export function createDropIndicator() {
|
||||
const indicator = document.createElement("div");
|
||||
indicator.className = "comfy-lora-drop-indicator";
|
||||
|
||||
Object.assign(indicator.style, {
|
||||
position: "absolute",
|
||||
left: "0",
|
||||
right: "0",
|
||||
height: "3px",
|
||||
backgroundColor: "rgba(66, 153, 225, 0.9)",
|
||||
borderRadius: "2px",
|
||||
opacity: "0",
|
||||
transition: "opacity 0.2s ease",
|
||||
boxShadow: "0 0 6px rgba(66, 153, 225, 0.8)",
|
||||
zIndex: "10",
|
||||
pointerEvents: "none"
|
||||
});
|
||||
|
||||
indicator.className = "lm-lora-drop-indicator";
|
||||
return indicator;
|
||||
}
|
||||
|
||||
// Function to update entry selection state
|
||||
export function updateEntrySelection(entryEl, isSelected) {
|
||||
// Remove any conflicting styles first
|
||||
entryEl.style.removeProperty('border');
|
||||
entryEl.style.removeProperty('box-shadow');
|
||||
|
||||
const baseColor = entryEl.dataset.active === 'true' ?
|
||||
"rgba(45, 55, 72, 0.7)" : "rgba(35, 40, 50, 0.5)";
|
||||
const selectedColor = entryEl.dataset.active === 'true' ?
|
||||
"rgba(66, 153, 225, 0.3)" : "rgba(66, 153, 225, 0.2)";
|
||||
|
||||
// Update data attribute to track selection state
|
||||
entryEl.dataset.selected = isSelected ? 'true' : 'false';
|
||||
|
||||
if (isSelected) {
|
||||
entryEl.style.setProperty('backgroundColor', selectedColor, 'important');
|
||||
entryEl.style.setProperty('border', "1px solid rgba(66, 153, 225, 0.6)", 'important');
|
||||
entryEl.style.setProperty('box-shadow', "0 0 0 1px rgba(66, 153, 225, 0.3)", 'important');
|
||||
} else {
|
||||
entryEl.style.backgroundColor = baseColor;
|
||||
entryEl.style.border = "1px solid transparent";
|
||||
entryEl.style.boxShadow = "none";
|
||||
entryEl.dataset.selected = isSelected ? "true" : "false";
|
||||
if (!isSelected) {
|
||||
entryEl.style.removeProperty("background-color");
|
||||
entryEl.style.removeProperty("border");
|
||||
entryEl.style.removeProperty("box-shadow");
|
||||
}
|
||||
}
|
||||
|
||||
// Function to create menu item
|
||||
export function createMenuItem(text, icon, onClick) {
|
||||
const menuItem = document.createElement('div');
|
||||
Object.assign(menuItem.style, {
|
||||
padding: '6px 20px',
|
||||
cursor: 'pointer',
|
||||
color: 'rgba(226, 232, 240, 0.9)',
|
||||
fontSize: '13px',
|
||||
userSelect: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
});
|
||||
const menuItem = document.createElement("div");
|
||||
menuItem.className = "lm-lora-menu-item";
|
||||
|
||||
// Create icon element
|
||||
const iconEl = document.createElement('div');
|
||||
const iconEl = document.createElement("div");
|
||||
iconEl.className = "lm-lora-menu-item-icon";
|
||||
iconEl.innerHTML = icon;
|
||||
Object.assign(iconEl.style, {
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
// Create text element
|
||||
const textEl = document.createElement('span');
|
||||
const textEl = document.createElement("span");
|
||||
textEl.textContent = text;
|
||||
|
||||
menuItem.appendChild(iconEl);
|
||||
menuItem.appendChild(textEl);
|
||||
|
||||
menuItem.addEventListener('mouseenter', () => {
|
||||
menuItem.style.backgroundColor = 'rgba(66, 153, 225, 0.2)';
|
||||
});
|
||||
|
||||
menuItem.addEventListener('mouseleave', () => {
|
||||
menuItem.style.backgroundColor = 'transparent';
|
||||
});
|
||||
|
||||
if (onClick) {
|
||||
menuItem.addEventListener('click', onClick);
|
||||
menuItem.addEventListener("click", onClick);
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
@@ -218,73 +83,19 @@ export function createMenuItem(text, icon, onClick) {
|
||||
// Function to create expand/collapse button
|
||||
export function createExpandButton(isExpanded, onClick) {
|
||||
const button = document.createElement("button");
|
||||
button.className = "comfy-lora-expand-button";
|
||||
button.className = "lm-lora-expand-button";
|
||||
button.type = "button";
|
||||
button.tabIndex = -1;
|
||||
|
||||
Object.assign(button.style, {
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
fontSize: "10px",
|
||||
color: "rgba(226, 232, 240, 0.7)",
|
||||
backgroundColor: "rgba(45, 55, 72, 0.3)",
|
||||
border: "1px solid rgba(226, 232, 240, 0.2)",
|
||||
borderRadius: "3px",
|
||||
transition: "all 0.2s ease",
|
||||
marginLeft: "6px",
|
||||
marginRight: "4px",
|
||||
flexShrink: "0",
|
||||
outline: "none"
|
||||
});
|
||||
|
||||
|
||||
// Set icon based on expanded state
|
||||
updateExpandButtonState(button, isExpanded);
|
||||
|
||||
|
||||
button.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClick(!isExpanded);
|
||||
});
|
||||
|
||||
// Add hover effects
|
||||
button.addEventListener("mouseenter", () => {
|
||||
button.style.backgroundColor = "rgba(66, 153, 225, 0.2)";
|
||||
button.style.borderColor = "rgba(66, 153, 225, 0.4)";
|
||||
button.style.color = "rgba(226, 232, 240, 0.9)";
|
||||
button.style.transform = "scale(1.05)";
|
||||
});
|
||||
|
||||
button.addEventListener("mouseleave", () => {
|
||||
button.style.backgroundColor = "rgba(45, 55, 72, 0.3)";
|
||||
button.style.borderColor = "rgba(226, 232, 240, 0.2)";
|
||||
button.style.color = "rgba(226, 232, 240, 0.7)";
|
||||
button.style.transform = "scale(1)";
|
||||
});
|
||||
|
||||
// Add active (pressed) state
|
||||
button.addEventListener("mousedown", () => {
|
||||
button.style.transform = "scale(0.95)";
|
||||
button.style.backgroundColor = "rgba(66, 153, 225, 0.3)";
|
||||
});
|
||||
|
||||
button.addEventListener("mouseup", () => {
|
||||
button.style.transform = "scale(1.05)"; // Return to hover state
|
||||
});
|
||||
|
||||
// Add focus state for keyboard accessibility
|
||||
button.addEventListener("focus", () => {
|
||||
button.style.boxShadow = "0 0 0 2px rgba(66, 153, 225, 0.5)";
|
||||
});
|
||||
|
||||
button.addEventListener("blur", () => {
|
||||
button.style.boxShadow = "none";
|
||||
});
|
||||
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,27 +102,14 @@ export function initDrag(dragEl, name, widget, isClipStrength = false, previewTo
|
||||
let initialX = 0;
|
||||
let initialStrength = 0;
|
||||
|
||||
// Create a style element for drag cursor override if it doesn't exist
|
||||
if (!document.getElementById('comfy-lora-drag-style')) {
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = 'comfy-lora-drag-style';
|
||||
styleEl.textContent = `
|
||||
body.comfy-lora-dragging,
|
||||
body.comfy-lora-dragging * {
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
// Create a drag handler
|
||||
dragEl.addEventListener('mousedown', (e) => {
|
||||
// Skip if clicking on toggle or strength control areas
|
||||
if (e.target.closest('.comfy-lora-toggle') ||
|
||||
e.target.closest('input') ||
|
||||
e.target.closest('.comfy-lora-arrow') ||
|
||||
e.target.closest('.comfy-lora-drag-handle') ||
|
||||
e.target.closest('.comfy-lora-expand-button')) {
|
||||
if (e.target.closest('.lm-lora-toggle') ||
|
||||
e.target.closest('input') ||
|
||||
e.target.closest('.lm-lora-arrow') ||
|
||||
e.target.closest('.lm-lora-drag-handle') ||
|
||||
e.target.closest('.lm-lora-expand-button')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,7 +124,7 @@ export function initDrag(dragEl, name, widget, isClipStrength = false, previewTo
|
||||
isDragging = true;
|
||||
|
||||
// Add class to body to enforce cursor style globally
|
||||
document.body.classList.add('comfy-lora-dragging');
|
||||
document.body.classList.add('lm-lora-strength-dragging');
|
||||
|
||||
// Prevent text selection during drag
|
||||
e.preventDefault();
|
||||
@@ -166,7 +153,7 @@ export function initDrag(dragEl, name, widget, isClipStrength = false, previewTo
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
// Remove the class to restore normal cursor behavior
|
||||
document.body.classList.remove('comfy-lora-dragging');
|
||||
document.body.classList.remove('lm-lora-strength-dragging');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -178,12 +165,10 @@ export function initHeaderDrag(headerEl, widget, renderFunction) {
|
||||
let initialStrengths = [];
|
||||
|
||||
// Add cursor style to indicate draggable
|
||||
headerEl.style.cursor = 'ew-resize';
|
||||
|
||||
// Create a drag handler
|
||||
headerEl.addEventListener('mousedown', (e) => {
|
||||
// Skip if clicking on toggle or other interactive elements
|
||||
if (e.target.closest('.comfy-lora-toggle') ||
|
||||
if (e.target.closest('.lm-lora-toggle') ||
|
||||
e.target.closest('input')) {
|
||||
return;
|
||||
}
|
||||
@@ -201,7 +186,7 @@ export function initHeaderDrag(headerEl, widget, renderFunction) {
|
||||
isDragging = true;
|
||||
|
||||
// Add class to body to enforce cursor style globally
|
||||
document.body.classList.add('comfy-lora-dragging');
|
||||
document.body.classList.add('lm-lora-strength-dragging');
|
||||
|
||||
// Prevent text selection during drag
|
||||
e.preventDefault();
|
||||
@@ -225,7 +210,7 @@ export function initHeaderDrag(headerEl, widget, renderFunction) {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
// Remove the class to restore normal cursor behavior
|
||||
document.body.classList.remove('comfy-lora-dragging');
|
||||
document.body.classList.remove('lm-lora-strength-dragging');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -243,14 +228,12 @@ export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
|
||||
e.stopPropagation();
|
||||
|
||||
isDragging = true;
|
||||
draggedElement = dragHandle.closest('.comfy-lora-entry');
|
||||
draggedElement = dragHandle.closest('.lm-lora-entry');
|
||||
container = draggedElement.parentElement;
|
||||
|
||||
// Add dragging class and visual feedback
|
||||
draggedElement.classList.add('comfy-lora-dragging');
|
||||
draggedElement.style.opacity = '0.5';
|
||||
draggedElement.style.transform = 'scale(0.98)';
|
||||
|
||||
draggedElement.classList.add('lm-lora-entry--dragging');
|
||||
|
||||
// Create single drop indicator with absolute positioning
|
||||
dropIndicator = createDropIndicator();
|
||||
|
||||
@@ -263,7 +246,7 @@ export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
|
||||
container._originalPosition = originalPosition;
|
||||
|
||||
// Add global cursor style
|
||||
document.body.style.cursor = 'grabbing';
|
||||
document.body.classList.add('lm-lora-reordering');
|
||||
|
||||
// Store workflow scale for accurate positioning
|
||||
scale = app.canvas.ds.scale;
|
||||
@@ -273,7 +256,7 @@ export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
|
||||
if (!isDragging || !draggedElement || !dropIndicator) return;
|
||||
|
||||
const targetIndex = getDropTargetIndex(container, e.clientY);
|
||||
const entries = container.querySelectorAll('.comfy-lora-entry, .comfy-lora-clip-entry');
|
||||
const entries = container.querySelectorAll('.lm-lora-entry, .lm-lora-clip-entry');
|
||||
|
||||
if (targetIndex === 0) {
|
||||
// Show at top
|
||||
@@ -307,7 +290,7 @@ export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
|
||||
|
||||
document.addEventListener('mouseup', (e) => {
|
||||
// Always reset cursor regardless of isDragging state
|
||||
document.body.style.cursor = '';
|
||||
document.body.classList.remove('lm-lora-reordering');
|
||||
|
||||
if (!isDragging || !draggedElement) return;
|
||||
|
||||
@@ -319,7 +302,7 @@ export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
|
||||
|
||||
if (currentIndex !== -1 && currentIndex !== targetIndex) {
|
||||
// Calculate actual target index (excluding clip entries from count)
|
||||
const loraEntries = container.querySelectorAll('.comfy-lora-entry');
|
||||
const loraEntries = container.querySelectorAll('.lm-lora-entry');
|
||||
let actualTargetIndex = targetIndex;
|
||||
|
||||
// Adjust target index if it's beyond the number of actual LoRA entries
|
||||
@@ -347,9 +330,7 @@ export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
|
||||
// Cleanup
|
||||
isDragging = false;
|
||||
if (draggedElement) {
|
||||
draggedElement.classList.remove('comfy-lora-dragging');
|
||||
draggedElement.style.opacity = '';
|
||||
draggedElement.style.transform = '';
|
||||
draggedElement.classList.remove('lm-lora-entry--dragging');
|
||||
draggedElement = null;
|
||||
}
|
||||
|
||||
@@ -366,7 +347,7 @@ export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
|
||||
|
||||
// Also reset cursor when mouse leaves the document
|
||||
document.addEventListener('mouseleave', () => {
|
||||
document.body.style.cursor = '';
|
||||
document.body.classList.remove('lm-lora-reordering');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -461,25 +442,15 @@ export function createContextMenu(x, y, loraName, widget, previewTooltip, render
|
||||
}
|
||||
|
||||
// Remove existing context menu if any
|
||||
const existingMenu = document.querySelector('.comfy-lora-context-menu');
|
||||
const existingMenu = document.querySelector('.lm-lora-context-menu');
|
||||
if (existingMenu) {
|
||||
existingMenu.remove();
|
||||
}
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'comfy-lora-context-menu';
|
||||
Object.assign(menu.style, {
|
||||
position: 'fixed',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
backgroundColor: 'rgba(30, 30, 30, 0.95)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 0',
|
||||
zIndex: 1000,
|
||||
boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
|
||||
minWidth: '180px',
|
||||
});
|
||||
menu.className = 'lm-lora-context-menu';
|
||||
menu.style.left = `${x}px`;
|
||||
menu.style.top = `${y}px`;
|
||||
|
||||
// View on Civitai option with globe icon
|
||||
const viewOnCivitaiOption = createMenuItem(
|
||||
@@ -713,25 +684,13 @@ export function createContextMenu(x, y, loraName, widget, previewTooltip, render
|
||||
);
|
||||
|
||||
// Add separator
|
||||
const separator1 = document.createElement('div');
|
||||
Object.assign(separator1.style, {
|
||||
margin: '4px 0',
|
||||
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
});
|
||||
|
||||
const separator1 = document.createElement('hr');
|
||||
|
||||
// Add second separator
|
||||
const separator2 = document.createElement('div');
|
||||
Object.assign(separator2.style, {
|
||||
margin: '4px 0',
|
||||
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
});
|
||||
const separator2 = document.createElement('hr');
|
||||
|
||||
// Add separator for order options
|
||||
const orderSeparator = document.createElement('div');
|
||||
Object.assign(orderSeparator.style, {
|
||||
margin: '4px 0',
|
||||
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
});
|
||||
const orderSeparator = document.createElement('hr');
|
||||
|
||||
menu.appendChild(viewOnCivitaiOption);
|
||||
menu.appendChild(deleteOption);
|
||||
|
||||
@@ -219,7 +219,7 @@ export function moveLoraByDirection(loras, loraName, direction) {
|
||||
* @returns {number} - Target index for dropping
|
||||
*/
|
||||
export function getDropTargetIndex(container, clientY) {
|
||||
const entries = container.querySelectorAll('.comfy-lora-entry');
|
||||
const entries = container.querySelectorAll('.lm-lora-entry');
|
||||
let targetIndex = entries.length;
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { ensureLmStyles } from "./lm_styles_loader.js";
|
||||
|
||||
/**
|
||||
* Lightweight preview tooltip that can display images or videos for different model types.
|
||||
@@ -21,18 +22,10 @@ export class PreviewTooltip {
|
||||
? displayNameFormatter
|
||||
: (name) => name;
|
||||
|
||||
ensureLmStyles();
|
||||
|
||||
this.element = document.createElement("div");
|
||||
Object.assign(this.element.style, {
|
||||
position: "fixed",
|
||||
zIndex: 9999,
|
||||
background: "rgba(0, 0, 0, 0.85)",
|
||||
borderRadius: "6px",
|
||||
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)",
|
||||
display: "none",
|
||||
overflow: "hidden",
|
||||
maxWidth: "300px",
|
||||
pointerEvents: "none",
|
||||
});
|
||||
this.element.className = "lm-tooltip";
|
||||
document.body.appendChild(this.element);
|
||||
this.hideTimeout = null;
|
||||
this.isFromAutocomplete = false;
|
||||
@@ -119,23 +112,13 @@ export class PreviewTooltip {
|
||||
}
|
||||
|
||||
const mediaContainer = document.createElement("div");
|
||||
Object.assign(mediaContainer.style, {
|
||||
position: "relative",
|
||||
maxWidth: "300px",
|
||||
maxHeight: "300px",
|
||||
});
|
||||
mediaContainer.className = "lm-tooltip__media-container";
|
||||
|
||||
const isVideo = previewUrl.endsWith(".mp4");
|
||||
const mediaElement = isVideo
|
||||
? document.createElement("video")
|
||||
: document.createElement("img");
|
||||
|
||||
Object.assign(mediaElement.style, {
|
||||
maxWidth: "300px",
|
||||
maxHeight: "300px",
|
||||
objectFit: "contain",
|
||||
display: "block",
|
||||
});
|
||||
mediaElement.classList.add("lm-tooltip__media");
|
||||
|
||||
if (isVideo) {
|
||||
mediaElement.autoplay = true;
|
||||
@@ -146,24 +129,7 @@ export class PreviewTooltip {
|
||||
|
||||
const nameLabel = document.createElement("div");
|
||||
nameLabel.textContent = displayName;
|
||||
Object.assign(nameLabel.style, {
|
||||
position: "absolute",
|
||||
bottom: "0",
|
||||
left: "0",
|
||||
right: "0",
|
||||
padding: "8px",
|
||||
color: "white",
|
||||
fontSize: "13px",
|
||||
fontFamily:
|
||||
"'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif",
|
||||
background: "linear-gradient(transparent, rgba(0, 0, 0, 0.8))",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
textAlign: "center",
|
||||
backdropFilter: "blur(4px)",
|
||||
WebkitBackdropFilter: "blur(4px)",
|
||||
});
|
||||
nameLabel.className = "lm-tooltip__label";
|
||||
|
||||
mediaContainer.appendChild(mediaElement);
|
||||
mediaContainer.appendChild(nameLabel);
|
||||
|
||||
Reference in New Issue
Block a user