330 lines
11 KiB
TypeScript
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;
|
|
}
|