Files
MajoorWaldi-ComfyUI-Majoor-…/ui/components/sidebar/utils/dom.ts
T

330 lines
11 KiB
TypeScript

import { comfyToast } from "../../../app/toast.js";
import { t } from "../../../app/i18n.js";
import { copyTextToClipboard } from "../../../utils/dom.js";
const _ALLOWED_VALUE_STYLE_PROPS = new Set([
"color",
"font-weight",
"font-style",
"opacity",
"text-decoration",
]);
const _SVG_NS = "http://www.w3.org/2000/svg";
function _buildCopyIcon() {
const svg = document.createElementNS(_SVG_NS, "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", "14");
svg.setAttribute("height", "14");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", "currentColor");
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
const rect = document.createElementNS(_SVG_NS, "rect");
rect.setAttribute("x", "9");
rect.setAttribute("y", "9");
rect.setAttribute("width", "13");
rect.setAttribute("height", "13");
rect.setAttribute("rx", "2");
rect.setAttribute("ry", "2");
svg.appendChild(rect);
const path = document.createElementNS(_SVG_NS, "path");
path.setAttribute("d", "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1");
svg.appendChild(path);
return svg;
}
function _buildCheckIcon() {
const svg = document.createElementNS(_SVG_NS, "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", "14");
svg.setAttribute("height", "14");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", "currentColor");
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
const polyline = document.createElementNS(_SVG_NS, "polyline");
polyline.setAttribute("points", "20 6 9 17 4 12");
svg.appendChild(polyline);
return svg;
}
export function createSection(title: string): HTMLElement {
const section = document.createElement("div");
section.className = "mjr-sidebar-section";
section.style.cssText = `
background: var(--comfy-menu-bg, rgba(0,0,0,0.2));
border: 1px solid var(--border-color, rgba(255,255,255,0.14));
border-radius: 8px;
padding: 12px;
min-width: 300px;
`;
const header = document.createElement("div");
header.style.cssText = `
font-size: 13px;
font-weight: 600;
color: var(--fg-color, #eaeaea);
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
`;
header.textContent = title;
section.appendChild(header);
return section;
}
export function createInfoBox(title: any, content: any, accentColor: any, options: Record<string, any> = {}) {
const showCopyButton = options?.showCopyButton !== false;
const copyOnContentClick = options?.copyOnContentClick === true;
const box = document.createElement("div");
box.style.cssText = `
background: var(--comfy-menu-bg, rgba(0,0,0,0.3));
border-left: 3px solid ${accentColor};
border: 1px solid var(--border-color, rgba(255,255,255,0.12));
border-radius: 6px;
padding: 12px;
position: relative;
`;
const header = document.createElement("div");
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
font-size: 11px;
font-weight: 600;
color: ${accentColor};
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
`;
const titleSpan = document.createElement("span");
titleSpan.textContent = title;
header.appendChild(titleSpan);
const doCopy = async () => {
const ok = await copyTextToClipboard(content);
if (ok) {
comfyToast(
t("toast.copiedToClipboardNamed", "{name} copied to clipboard!", { name: title }),
"success",
2000,
);
} else {
console.warn(t("log.clipboardCopyFailed", "Clipboard copy failed"));
comfyToast(t("toast.copyClipboardFailed", "Failed to copy to clipboard"), "error");
}
};
if (showCopyButton) {
const copyBtn = document.createElement("div");
copyBtn.title = t("action.copyToClipboard", "Copy to clipboard");
copyBtn.style.cssText = `
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s, transform 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
`;
copyBtn.replaceChildren(_buildCopyIcon());
copyBtn.onmouseenter = () => {
copyBtn.style.opacity = "1";
};
copyBtn.onmouseleave = () => {
copyBtn.style.opacity = "0.7";
};
copyBtn.onclick = async (e: any) => {
e.stopPropagation();
const originalIcon = _buildCopyIcon();
await doCopy();
copyBtn.replaceChildren(_buildCheckIcon());
copyBtn.style.color = "#4CAF50";
copyBtn.style.transform = "scale(1.1)";
setTimeout(() => {
copyBtn.replaceChildren(originalIcon);
copyBtn.style.color = "inherit";
copyBtn.style.transform = "scale(1)";
}, 1000);
};
header.appendChild(copyBtn);
}
const text = document.createElement("div");
text.textContent = content;
text.style.cssText = `
font-size: 12px;
color: var(--fg-color, rgba(255,255,255,0.9));
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
${copyOnContentClick ? "cursor: pointer;" : ""}
`;
if (copyOnContentClick) {
text.title = t("action.clickToCopy", "Click to copy");
text.addEventListener("click", async () => {
await doCopy();
try {
text.style.background = "rgba(76, 175, 80, 0.3)";
text.style.borderRadius = "4px";
setTimeout(() => {
text.style.background = "";
text.style.borderRadius = "";
}, 320);
} catch (e: any) {
console.debug?.(e);
}
});
}
box.appendChild(header);
box.appendChild(text);
return box;
}
export function createParametersBox(title: any, fields: any, accentColor: any, options: Record<string, any> = {}) {
const emphasis = options?.emphasis === true;
const box = document.createElement("div");
box.style.cssText = `
background: ${emphasis ? `linear-gradient(135deg, ${hexToRgba(accentColor, 0.16)} 0%, ${hexToRgba(accentColor, 0.08)} 100%)` : "var(--comfy-menu-bg, rgba(0,0,0,0.3))"};
border-left: 3px solid ${accentColor};
border: ${emphasis ? `1px solid ${hexToRgba(accentColor, 0.45)}` : "none"};
box-shadow: ${emphasis ? `0 0 0 1px ${hexToRgba(accentColor, 0.15)} inset` : "none"};
border-radius: 6px;
padding: 12px;
`;
const header = document.createElement("div");
header.textContent = title;
header.style.cssText = `
font-size: 11px;
font-weight: 600;
color: ${accentColor};
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 10px;
`;
box.appendChild(header);
const grid = document.createElement("div");
grid.style.cssText = `
display: grid;
grid-template-columns: auto 1fr;
gap: 8px 12px;
align-items: start;
`;
fields.forEach((field: any) => {
const label = document.createElement("div");
label.textContent = field.label + ":";
label.title = field.tooltip || field.label;
label.style.cssText = `
font-size: 11px;
color: var(--mjr-muted, rgba(127,127,127,0.9));
font-weight: 500;
`;
const value = document.createElement("div");
value.textContent = String(field.value);
value.title = t("tooltip.copyFieldValue", "{label}: {value} (click to copy)", {
label: field.label,
value: field.value,
});
value.style.cssText = `
font-size: 12px;
color: var(--fg-color, rgba(255,255,255,0.95));
word-break: break-word;
white-space: pre-wrap;
cursor: pointer;
`;
_applySafeValueStyle(value, field?.valueStyle);
value.addEventListener("click", async () => {
const ok = await copyTextToClipboard(String(field.value));
if (ok) {
value.style.background = "rgba(76, 175, 80, 0.3)";
setTimeout(() => {
value.style.background = "";
}, 300);
}
});
grid.appendChild(label);
grid.appendChild(value);
});
box.appendChild(grid);
return box;
}
function _applySafeValueStyle(el: any, rawStyle: any) {
if (!el || !rawStyle) return;
const text = String(rawStyle || "");
const declarations = text.split(";");
for (const decl of declarations) {
const idx = decl.indexOf(":");
if (idx <= 0) continue;
const prop = decl.slice(0, idx).trim().toLowerCase();
const val = decl.slice(idx + 1).trim();
if (!prop || !val) continue;
if (!_ALLOWED_VALUE_STYLE_PROPS.has(prop)) continue;
try {
el.style.setProperty(prop, val);
} catch (e: any) {
console.debug?.(e);
}
}
}
function hexToRgba(hex: any, alpha: any) {
const normalized = String(hex || "")
.trim()
.replace(/^#/, "");
if (!/^[0-9a-fA-F]{6}$/.test(normalized)) return `rgba(255,255,255,${alpha})`;
const r = parseInt(normalized.slice(0, 2), 16);
const g = parseInt(normalized.slice(2, 4), 16);
const b = parseInt(normalized.slice(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
export function createFieldRow(label: string, value: any, multiline = false): HTMLElement {
const row = document.createElement("div");
row.style.cssText = `
display: flex;
flex-direction: ${multiline ? "column" : "row"};
gap: ${multiline ? "4px" : "8px"};
margin-bottom: 8px;
font-size: 12px;
`;
const labelEl = document.createElement("div");
labelEl.textContent = label;
labelEl.style.cssText = `
color: var(--mjr-muted, rgba(255,255,255,0.65));
${multiline ? "" : "min-width: 100px;"}
font-weight: 500;
`;
const valueEl = document.createElement("div");
valueEl.textContent = String(value);
valueEl.style.cssText = `
color: var(--fg-color, #eaeaea);
${multiline ? "white-space: pre-wrap; word-break: break-word;" : ""}
flex: 1;
`;
row.appendChild(labelEl);
row.appendChild(valueEl);
return row;
}