Files
hermankwan-ComfyUI-Batch-Im…/web/ui_renderer.js
T
Herman Kwan 82336a25be feat(core): release v0.2.0 major architecture refactor and UI overhaul
- Migrate frontend to modular ES modules (timer, progress, renderer)
- Rewrite batch tracking logic to support Sequential Image Loaders and native batches
- Introduce robust queue-empty fallback debounce to prevent infinite timers
- Decouple timer interactions from progress counters for absolute independence
- Redesign UI layout, fix node resizing conflicts, and add dynamic status colors
- Update README with new usage instructions and v0.2.0 changelog
2026-10-22 23:47:42 -07:00

151 lines
5.9 KiB
JavaScript

const COLOR_PALETTE = ["#FFFFFF", "#00BFFF", "#00FF88", "#FFCC00", "#FF007F"];
const INACTIVE_COLOR_MAP = {
"#FFFFFF": "#2A2A2A", "#00BFFF": "#002B36", "#00FF88": "#003311",
"#FFCC00": "#332600", "#FF007F": "#33001A"
};
const SEGMENT_MAP = {
'0': [1,1,1,1,1,1,0], '1': [0,1,1,0,0,0,0], '2': [1,1,0,1,1,0,1],
'3': [1,1,1,1,0,0,1], '4': [0,1,1,0,0,1,1], '5': [1,0,1,1,0,1,1],
'6': [1,0,1,1,1,1,1], '7': [1,1,1,0,0,0,0], '8': [1,1,1,1,1,1,1],
'9': [1,1,1,1,0,1,1]
};
export function renderTimerUI(ctx, node, timerMs, displayText, activeColor, isCompleted = false) {
const nodeW = node.size[0];
const nodeH = node.size[1];
let headerOffset = 30;
if (node.widgets && node.widgets.length > 0) {
let maxY = 0;
for (const w of node.widgets) {
if (w.last_y !== undefined && w.type !== "hidden_seq_index") {
const h = (typeof w.computeSize === "function") ? w.computeSize()[1] : 20;
const bottom = w.last_y + h;
if (bottom > maxY) maxY = bottom;
}
}
headerOffset = maxY > 0 ? maxY + 15 : 65;
}
const margin = 10;
const rectW = nodeW - margin * 2;
const rectH = Math.max(80, nodeH - headerOffset - margin);
ctx.save();
ctx.fillStyle = "#000000";
ctx.fillRect(margin, headerOffset, rectW, rectH);
ctx.strokeStyle = "#222222";
ctx.lineWidth = 1;
ctx.strokeRect(margin, headerOffset, rectW, rectH);
const scale = Math.max(0.5, Math.min(rectW / 240, rectH / 90));
const centerY = headerOffset + rectH * 0.42;
const totalSec = Math.floor(Math.max(0, timerMs) / 1000);
const timeStr = `${String(Math.floor(totalSec/3600)).padStart(2,'0')}:${String(Math.floor((totalSec%3600)/60)).padStart(2,'0')}:${String(totalSec%60).padStart(2,'0')}`;
ctx.fillStyle = "#666666";
ctx.font = `bold ${Math.max(9, Math.round(10 * scale))}px monospace`;
ctx.textAlign = "center";
const digitHeight = 26 * scale;
const digitStartY = centerY - (digitHeight / 2);
ctx.fillText("TOTAL ELAPSED", nodeW / 2, digitStartY - 8 * scale);
const digitWidth = 14 * scale;
const digitGap = 5 * scale;
const colonWidth = 8 * scale;
const totalDisplayWidth = (6 * digitWidth) + (7 * digitGap) + (2 * colonWidth);
let startX = (nodeW - totalDisplayWidth) / 2;
const inactiveColor = INACTIVE_COLOR_MAP[activeColor] || "#2A2A2A";
node._hitDigit = { x: startX - 4, y: digitStartY - 4, w: totalDisplayWidth + 8, h: digitHeight + 8 };
for (let i = 0; i < timeStr.length; i++) {
const char = timeStr[i];
if (char === ':') {
const dotSize = Math.max(3, colonWidth * 0.35);
const cx = startX + colonWidth / 2 - dotSize / 2;
ctx.fillStyle = activeColor;
ctx.fillRect(cx, digitStartY + digitHeight * 0.3 - dotSize / 2, dotSize, dotSize);
ctx.fillRect(cx, digitStartY + digitHeight * 0.7 - dotSize / 2, dotSize, dotSize);
startX += colonWidth + digitGap;
} else {
drawDigit(ctx, startX, digitStartY, digitWidth, digitHeight, char, activeColor, inactiveColor);
startX += digitWidth + digitGap;
}
}
const swatchSize = Math.max(5, Math.round(6 * scale));
const swatchGap = 6 * scale;
const totalSwatchesW = COLOR_PALETTE.length * swatchSize + (COLOR_PALETTE.length - 1) * swatchGap;
let swatchStartX = (nodeW - totalSwatchesW) / 2;
const swatchY = digitStartY + digitHeight + 6 * scale;
node._hitSwatches = [];
COLOR_PALETTE.forEach(color => {
ctx.fillStyle = color;
ctx.fillRect(swatchStartX, swatchY, swatchSize, swatchSize);
if (color === activeColor) {
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)";
ctx.lineWidth = 1;
ctx.strokeRect(swatchStartX - 1.5, swatchY - 1.5, swatchSize + 3, swatchSize + 3);
}
node._hitSwatches.push({ x: swatchStartX - 3, y: swatchY - 3, w: swatchSize + 6, h: swatchSize + 6, color });
swatchStartX += swatchSize + swatchGap;
});
const statusFontSize = Math.max(10, Math.round(11 * scale));
const statusY = swatchY + swatchSize + 14 * scale;
ctx.font = `bold ${statusFontSize}px monospace`;
const bracketW = ctx.measureText("[").width;
const innerW = ctx.measureText(` ${displayText} `).width;
let textX = (nodeW - (bracketW * 2 + innerW)) / 2;
ctx.fillStyle = "#666666";
ctx.textAlign = "left";
ctx.fillText("[", textX, statusY);
textX += bracketW;
let textColor = "#00B5F1";
if (displayText === "READY") {
textColor = "#B0BEC5";
} else if (displayText === "INTERRUPT") {
textColor = "#DEB200";
} else if (displayText === "ERROR") {
textColor = "#E60373";
} else if (isCompleted) {
textColor = "#00DF77";
}
ctx.fillStyle = textColor;
ctx.fillText(` ${displayText} `, textX, statusY);
textX += innerW;
ctx.fillStyle = "#666666";
ctx.fillText("]", textX, statusY);
ctx.restore();
}
function drawDigit(ctx, x, y, width, height, char, activeColor, inactiveColor) {
const segments = SEGMENT_MAP[char] || [0,0,0,0,0,0,0];
const t = Math.max(2, width * 0.14);
const gap = width * 0.08;
const hH = height / 2;
const segs = [
() => ctx.fillRect(x + t + gap, y, width - 2 * t - 2 * gap, t),
() => ctx.fillRect(x + width - t, y + t + gap, t, hH - t - 2 * gap),
() => ctx.fillRect(x + width - t, y + hH + gap, t, hH - t - 2 * gap),
() => ctx.fillRect(x + t + gap, y + height - t, width - 2 * t - 2 * gap, t),
() => ctx.fillRect(x, y + hH + gap, t, hH - t - 2 * gap),
() => ctx.fillRect(x, y + t + gap, t, hH - t - 2 * gap),
() => ctx.fillRect(x + t + gap, y + hH - t / 2, width - 2 * t - 2 * gap, t)
];
for (let i = 0; i < 7; i++) {
ctx.fillStyle = segments[i] ? activeColor : inactiveColor;
segs[i]();
}
}