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:
Will Miao
2025-10-21 17:42:32 +08:00
parent 49b9b7a5ea
commit f4eb916914
8 changed files with 584 additions and 569 deletions

View File

@@ -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
View 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;
}

View 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;
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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++) {

View File

@@ -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);