519 lines
22 KiB
JavaScript
519 lines
22 KiB
JavaScript
import { app } from "/scripts/app.js";
|
|
import { PromptStatus } from "../core/constants.js";
|
|
import { store } from "../core/store.js";
|
|
import { renderCardImages } from "./mediaPreview.js";
|
|
import { copyImageToClipboard, openFileOrFolder, deleteFileOnServer, findNodeIdForImage } from "./mediaActions.js";
|
|
import { centerAndSelectCanvasNode } from "../comfy/adapter.js";
|
|
import { getRunOutputs, extractWorkflowFromPng, isImageFormat } from "../utils/utils.js";
|
|
import { showFullscreenPreview } from "../utils/comparison.js";
|
|
import { stopAllAudioPlayback } from "./audioController.js";
|
|
import { deleteHistoryItem, cancelPendingTask, interruptActive } from "../queue/queueService.js";
|
|
|
|
export const cardPool = new Map();
|
|
|
|
export function resetAllCardHoverStates() {
|
|
for (const cardObj of cardPool.values()) {
|
|
if (cardObj.hoverPanel) cardObj.hoverPanel.style.display = "none";
|
|
if (cardObj.leftHoverPanel) cardObj.leftHoverPanel.style.display = "none";
|
|
}
|
|
}
|
|
|
|
export function syncCardButtonVisibility(cardObj, state) {
|
|
if (!cardObj) return;
|
|
|
|
const isPendingOrActive = state.status === PromptStatus.PENDING || state.status === PromptStatus.ACTIVE;
|
|
if (isPendingOrActive) {
|
|
if (cardObj.hoverPanel) cardObj.hoverPanel.style.setProperty("display", "none", "important");
|
|
if (cardObj.leftHoverPanel) cardObj.leftHoverPanel.style.setProperty("display", "none", "important");
|
|
return;
|
|
} else {
|
|
if (cardObj.hoverPanel) cardObj.hoverPanel.style.removeProperty("display");
|
|
if (cardObj.leftHoverPanel) cardObj.leftHoverPanel.style.removeProperty("display");
|
|
}
|
|
|
|
const isCompleted = state.status === PromptStatus.COMPLETED;
|
|
const hasRealDiskFiles = isCompleted && state.images && state.images.length > 0 &&
|
|
!state.images.every(img => img.isFallback || (img.url && img.url.startsWith("blob:")));
|
|
|
|
const hasRealImages = hasRealDiskFiles && !state.images.some(img => img.url && img.url.startsWith("blob:"));
|
|
const currentImg = hasRealImages ? state.images[cardObj.currentImageIndex || 0] : null;
|
|
const isPng = currentImg && (currentImg.filename || "").toLowerCase().endsWith(".png");
|
|
|
|
if (cardObj.btnImg) {
|
|
if (hasRealImages) {
|
|
cardObj.btnImg.style.removeProperty("display");
|
|
cardObj.btnImg.style.display = "inline-flex";
|
|
cardObj.btnImg.onclick = (ev) => {
|
|
ev.stopPropagation();
|
|
state.images.forEach(img => {
|
|
const a = document.createElement("a");
|
|
a.href = img.url ? img.url : `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}`;
|
|
a.download = img.filename || "output";
|
|
a.click();
|
|
});
|
|
};
|
|
} else {
|
|
cardObj.btnImg.style.setProperty("display", "none", "important");
|
|
}
|
|
}
|
|
|
|
if (cardObj.btnCopy) {
|
|
const isCopyableImage = hasRealImages && currentImg && isImageFormat(currentImg.filename || currentImg.url);
|
|
if (isCopyableImage) {
|
|
cardObj.btnCopy.style.removeProperty("display");
|
|
cardObj.btnCopy.style.display = "inline-flex";
|
|
cardObj.btnCopy.onclick = async (ev) => {
|
|
ev.stopPropagation();
|
|
const src = currentImg.url ? currentImg.url : `/view?filename=${encodeURIComponent(currentImg.filename)}&type=${currentImg.type || 'output'}&subfolder=${encodeURIComponent(currentImg.subfolder || '')}`;
|
|
const success = await copyImageToClipboard(src);
|
|
if (success) {
|
|
cardObj.btnCopy.className = "pi pi-check comfy-sidebar-card-action-btn";
|
|
cardObj.btnCopy.style.color = "#4ade80";
|
|
setTimeout(() => {
|
|
cardObj.btnCopy.className = "pi pi-copy comfy-sidebar-card-action-btn";
|
|
cardObj.btnCopy.style.color = "";
|
|
}, 1500);
|
|
}
|
|
};
|
|
} else {
|
|
cardObj.btnCopy.style.setProperty("display", "none", "important");
|
|
}
|
|
}
|
|
|
|
if (cardObj.btnJson) {
|
|
if (state.workflow || isPng) {
|
|
cardObj.btnJson.style.removeProperty("display");
|
|
cardObj.btnJson.style.display = "inline-flex";
|
|
cardObj.btnJson.onclick = async (ev) => {
|
|
ev.stopPropagation();
|
|
let wf = state.workflow;
|
|
if (!wf && currentImg) {
|
|
const src = currentImg.url ? currentImg.url : `/view?filename=${encodeURIComponent(currentImg.filename)}&type=${currentImg.type || 'output'}&subfolder=${encodeURIComponent(currentImg.subfolder || '')}`;
|
|
wf = await extractWorkflowFromPng(src);
|
|
if (wf) state.workflow = wf;
|
|
}
|
|
if (!wf) {
|
|
alert("No workflow metadata found in this file.");
|
|
return;
|
|
}
|
|
const blob = new Blob([JSON.stringify(wf, null, 2)], { type: "application/json" });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = `workflow_${state.pid || "comfy"}.json`;
|
|
a.click();
|
|
setTimeout(() => URL.revokeObjectURL(url), 1500);
|
|
};
|
|
} else {
|
|
cardObj.btnJson.style.setProperty("display", "none", "important");
|
|
}
|
|
}
|
|
|
|
if (cardObj.btnDel) {
|
|
// Only show delete button if we have actual verified output files on disk to delete
|
|
if (hasRealDiskFiles) {
|
|
cardObj.btnDel.style.removeProperty("display");
|
|
cardObj.btnDel.style.display = "inline-flex";
|
|
cardObj.canDeleteFromDisk = true;
|
|
if (cardObj.btnDelLabel) {
|
|
cardObj.btnDelLabel.style.display = "";
|
|
}
|
|
cardObj.btnDel.title = "Delete Card (Hold Ctrl to delete file from disk)";
|
|
} else {
|
|
cardObj.btnDel.style.setProperty("display", "none", "important");
|
|
cardObj.canDeleteFromDisk = false;
|
|
}
|
|
}
|
|
|
|
if (cardObj.btnFocus) {
|
|
const nodeId = currentImg ? findNodeIdForImage(state, currentImg) : null;
|
|
if (nodeId) {
|
|
cardObj.btnFocus.style.removeProperty("display");
|
|
cardObj.btnFocus.style.display = "inline-flex";
|
|
cardObj.btnFocus.onclick = (ev) => {
|
|
ev.stopPropagation();
|
|
centerAndSelectCanvasNode(nodeId);
|
|
};
|
|
} else {
|
|
cardObj.btnFocus.style.setProperty("display", "none", "important");
|
|
}
|
|
}
|
|
|
|
if (cardObj.leftHoverBtn) {
|
|
const outputs = getRunOutputs(state.nodeOutputs, state.workflow);
|
|
const validOutputs = outputs.filter(o => o.images && o.images.length > 0);
|
|
|
|
const primarySignatures = new Set((state.images || []).map(i => `${i.subfolder || ""}/${i.filename}`));
|
|
const distinctOutputs = [];
|
|
|
|
for (const out of validOutputs) {
|
|
const hasNewImage = out.images.some(img => !primarySignatures.has(`${img.subfolder || ""}/${img.filename}`));
|
|
if (hasNewImage) {
|
|
distinctOutputs.push(out);
|
|
}
|
|
}
|
|
|
|
const hasIntermediates = validOutputs.length > 1 && distinctOutputs.length > 0;
|
|
|
|
if (hasIntermediates) {
|
|
cardObj.leftHoverBtn.style.removeProperty("display");
|
|
cardObj.leftHoverBtn.style.display = "inline-flex";
|
|
cardObj.leftHoverBtn.onclick = (ev) => {
|
|
ev.stopPropagation();
|
|
const scrollEl = document.querySelector('.sidebar-content-container, [class*="sidebar-content-container"], [class*="overflow-y-auto"]')
|
|
|| store.ui.cardStack?.parentElement
|
|
|| store.ui.cardStack;
|
|
if (scrollEl) {
|
|
store.ui.mainQueueScrollTop = scrollEl.scrollTop;
|
|
}
|
|
store.openOutputsSubmenu(state.pid);
|
|
};
|
|
} else {
|
|
cardObj.leftHoverBtn.style.setProperty("display", "none", "important");
|
|
}
|
|
}
|
|
}
|
|
|
|
export function updateCardProgressTargeted(cardObj, progress, activeNodeName, showWorkingNode = true) {
|
|
if (!cardObj) return;
|
|
if (cardObj.progressBar) {
|
|
cardObj.progressBar.style.width = `${progress}%`;
|
|
}
|
|
if (cardObj.statusText && showWorkingNode) {
|
|
cardObj.statusText.style.display = "block";
|
|
const nodeText = activeNodeName ? (activeNodeName === "Finishing..." ? "Finishing..." : `[${activeNodeName}]`) : "Processing...";
|
|
cardObj.statusText.textContent = `${nodeText} ${progress}%`;
|
|
}
|
|
}
|
|
|
|
export function getOrCreateCard(state, callbacks = {}) {
|
|
let cardObj = cardPool.get(state.pid);
|
|
if (cardObj) return cardObj;
|
|
|
|
const card = document.createElement("div");
|
|
card.id = `card-${state.pid}`;
|
|
card.className = `comfy-sidebar-card ${state.status || "completed"}`;
|
|
card.style.position = "relative";
|
|
|
|
const timerEl = document.createElement("div");
|
|
timerEl.className = "comfy-sidebar-card-timer";
|
|
|
|
const cancelX = document.createElement("span");
|
|
cancelX.className = "pi pi-times comfy-sidebar-queue-cancel-btn";
|
|
Object.assign(cancelX.style, {
|
|
position: "absolute", top: "4px", right: "4px", display: "none", zIndex: "10"
|
|
});
|
|
|
|
const sBadge = document.createElement("div");
|
|
Object.assign(sBadge.style, {
|
|
position: "absolute", top: "6px", right: "8px", fontSize: "9px", fontWeight: "bold",
|
|
padding: "2px 6px", borderRadius: "2px", textTransform: "uppercase", display: "none",
|
|
pointerEvents: "none", zIndex: "10"
|
|
});
|
|
|
|
const dimEl = document.createElement("div");
|
|
Object.assign(dimEl.style, {
|
|
position: "absolute", top: "6px", right: "8px", fontSize: "10px",
|
|
fontFamily: "monospace", opacity: "0.7", background: "rgba(0, 0, 0, 0.6)",
|
|
padding: "2px 4px", borderRadius: "3px", pointerEvents: "none", zIndex: "5", color: "#fff",
|
|
display: "none", transform: "translateZ(0)"
|
|
});
|
|
|
|
const grid = document.createElement("div");
|
|
grid.style.display = "flex";
|
|
grid.style.flexDirection = "column";
|
|
grid.style.gap = "6px";
|
|
|
|
const p = document.createElement("div");
|
|
p.className = "comfy-sidebar-text-clamp";
|
|
|
|
const pt = document.createElement("div");
|
|
Object.assign(pt.style, {
|
|
width: "100%", height: "4px", background: "#333", borderRadius: "2px", marginTop: "8px",
|
|
overflow: "hidden", display: "none"
|
|
});
|
|
const pb = document.createElement("div");
|
|
Object.assign(pb.style, {
|
|
width: "0%", height: "100%", background: "#3b82f6", transition: "width 0.1s linear"
|
|
});
|
|
pt.appendChild(pb);
|
|
|
|
const statusText = document.createElement("div");
|
|
Object.assign(statusText.style, {
|
|
fontSize: "11px", opacity: "0.9", color: "#3b82f6", textAlign: "center", marginTop: "6px",
|
|
display: "none", fontWeight: "bold"
|
|
});
|
|
|
|
const hoverPanel = document.createElement("div");
|
|
hoverPanel.className = "comfy-sidebar-hover-panel";
|
|
Object.assign(hoverPanel.style, {
|
|
position: "absolute", bottom: "4px", right: "4px", flexDirection: "column", gap: "4px", zIndex: "20"
|
|
});
|
|
|
|
const btnImg = document.createElement("span");
|
|
btnImg.className = "pi pi-image comfy-sidebar-card-action-btn";
|
|
btnImg.title = "Download Object";
|
|
|
|
const btnCopy = document.createElement("span");
|
|
btnCopy.className = "pi pi-copy comfy-sidebar-card-action-btn";
|
|
btnCopy.title = "Copy to Clipboard";
|
|
btnCopy.style.display = "none";
|
|
|
|
const btnJson = document.createElement("span");
|
|
btnJson.className = "pi pi-file comfy-sidebar-card-action-btn";
|
|
btnJson.title = "Download JSON";
|
|
|
|
const btnDel = document.createElement("span");
|
|
btnDel.className = "pi pi-trash comfy-sidebar-card-action-btn comfy-sidebar-btn-del";
|
|
btnDel.title = "Delete Card (Hold Ctrl to delete file from disk)";
|
|
const btnDelLabel = document.createElement("span");
|
|
btnDelLabel.className = "comfy-sidebar-del-label";
|
|
btnDelLabel.textContent = "Delete File";
|
|
btnDel.appendChild(btnDelLabel);
|
|
|
|
const leftHoverPanel = document.createElement("div");
|
|
leftHoverPanel.className = "comfy-sidebar-left-hover-panel";
|
|
Object.assign(leftHoverPanel.style, {
|
|
position: "absolute", bottom: "4px", left: "4px", flexDirection: "column", gap: "4px", zIndex: "20"
|
|
});
|
|
|
|
const btnFocus = document.createElement("span");
|
|
btnFocus.className = "pi pi-eye comfy-sidebar-card-action-btn";
|
|
btnFocus.title = "Show Node";
|
|
|
|
const leftHoverBtn = document.createElement("span");
|
|
leftHoverBtn.className = "pi pi-images comfy-sidebar-card-action-btn";
|
|
leftHoverBtn.title = "View all intermediate outputs";
|
|
|
|
hoverPanel.append(btnImg, btnJson, btnDel);
|
|
leftHoverPanel.append(btnCopy, btnFocus, leftHoverBtn);
|
|
|
|
card.append(timerEl, cancelX, sBadge, dimEl, grid, p, pt, statusText, hoverPanel, leftHoverPanel);
|
|
|
|
cardObj = {
|
|
element: card,
|
|
timerEl,
|
|
statusBadge: sBadge,
|
|
dimEl,
|
|
grid,
|
|
placeholder: p,
|
|
progressContainer: pt,
|
|
progressBar: pb,
|
|
cancelBtn: cancelX,
|
|
hoverPanel,
|
|
leftHoverPanel,
|
|
btnFocus,
|
|
leftHoverBtn,
|
|
btnImg,
|
|
btnCopy,
|
|
btnJson,
|
|
btnDel,
|
|
btnDelLabel,
|
|
canDeleteFromDisk: true,
|
|
statusText,
|
|
firstImgElement: null,
|
|
lastImagesSignature: "",
|
|
currentImageIndex: 0
|
|
};
|
|
|
|
card.addEventListener("mouseenter", () => {
|
|
// Only run automatic button visibility sync on main queue cards (avoid synthetic submenu IDs)
|
|
if (store.hasPrompt(state.pid)) {
|
|
const liveState = store.getPrompt(state.pid);
|
|
syncCardButtonVisibility(cardObj, liveState);
|
|
}
|
|
});
|
|
|
|
card.addEventListener("dragstart", (e) => {
|
|
const liveState = store.getPrompt(state.pid) || state;
|
|
const isUnfinished = liveState.status && liveState.status !== PromptStatus.COMPLETED;
|
|
const hasNoImages = !liveState.images || liveState.images.length === 0;
|
|
|
|
if (liveState.workflow && (hasNoImages || isUnfinished)) {
|
|
if (cardObj.firstImgElement) {
|
|
try { e.dataTransfer.setDragImage(cardObj.firstImgElement, 15, 15); } catch(err){}
|
|
}
|
|
const jsonStr = JSON.stringify(liveState.workflow, null, 2);
|
|
const filename = `workflow_${liveState.pid}.json`;
|
|
const base64Data = btoa(unescape(encodeURIComponent(jsonStr)));
|
|
|
|
try {
|
|
e.dataTransfer.setData("DownloadURL", `application/json:${filename}:data:application/json;base64,${base64Data}`);
|
|
e.dataTransfer.setData("application/json", jsonStr);
|
|
} catch (err) {}
|
|
e.dataTransfer.effectAllowed = "copy";
|
|
}
|
|
});
|
|
|
|
let deleteTimeout = null, isDeletePending = false, isDiskDelete = false;
|
|
const resetDeleteBtn = () => {
|
|
isDeletePending = false;
|
|
isDiskDelete = false;
|
|
cardObj.btnDel.classList.remove("confirm-delete", "confirm-delete-disk");
|
|
cardObj.btnDel.title = "Delete Card (Ctrl+Click to delete from disk)";
|
|
if (deleteTimeout) { clearTimeout(deleteTimeout); deleteTimeout = null; }
|
|
};
|
|
|
|
btnDel.onclick = async (ev) => {
|
|
ev.stopPropagation();
|
|
const wantsDiskDelete = (ev.ctrlKey || ev.metaKey) && cardObj.canDeleteFromDisk !== false;
|
|
|
|
if (!isDeletePending) {
|
|
isDeletePending = true;
|
|
isDiskDelete = wantsDiskDelete;
|
|
cardObj.btnDel.classList.add(wantsDiskDelete ? "confirm-delete-disk" : "confirm-delete");
|
|
cardObj.btnDel.title = wantsDiskDelete
|
|
? "Ctrl+Click again to delete file from DISK / TRASH"
|
|
: "Click again to confirm removing card";
|
|
deleteTimeout = setTimeout(resetDeleteBtn, 2000);
|
|
} else {
|
|
const shouldDeleteFromDisk = isDiskDelete || wantsDiskDelete;
|
|
resetDeleteBtn();
|
|
|
|
const liveState = store.getPrompt(state.pid) || state;
|
|
|
|
if (shouldDeleteFromDisk) {
|
|
const filesToDelete = [];
|
|
const seen = new Set();
|
|
const addFile = (item) => {
|
|
if (item && item.filename) {
|
|
const key = `${item.type || ""}/${item.subfolder || ""}/${item.filename}`;
|
|
if (!seen.has(key)) {
|
|
seen.add(key);
|
|
filesToDelete.push(item);
|
|
}
|
|
}
|
|
};
|
|
|
|
(liveState.images || []).forEach(addFile);
|
|
const allOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
|
|
allOutputs.forEach(out => (out.images || []).forEach(addFile));
|
|
|
|
for (const fileItem of filesToDelete) {
|
|
await deleteFileOnServer(fileItem);
|
|
}
|
|
}
|
|
|
|
deleteHistoryItem(state.pid);
|
|
}
|
|
};
|
|
|
|
cardPool.set(state.pid, cardObj);
|
|
return cardObj;
|
|
}
|
|
|
|
export function updateCardDOM(cardObj, state, showPendingSummary = true, showWorkingNode = true) {
|
|
cardObj.element.className = `comfy-sidebar-card ${state.status}`;
|
|
cardObj.element.style.position = "relative";
|
|
|
|
const isUnfinished = state.status && state.status !== PromptStatus.COMPLETED;
|
|
if (state.images && state.images.length > 0 && !isUnfinished) {
|
|
cardObj.element.removeAttribute("draggable");
|
|
} else {
|
|
cardObj.element.setAttribute("draggable", "true");
|
|
}
|
|
|
|
if (state.status === PromptStatus.ACTIVE) {
|
|
cardObj.timerEl.textContent = state.startTime ? ((Date.now() - state.startTime) / 1000).toFixed(2) + "s" : "...";
|
|
cardObj.timerEl.style.display = "block";
|
|
} else if (state.duration !== undefined && state.duration !== null) {
|
|
cardObj.timerEl.textContent = state.duration.toFixed(2) + "s";
|
|
cardObj.timerEl.style.display = "block";
|
|
} else {
|
|
cardObj.timerEl.style.display = "none";
|
|
}
|
|
|
|
if (state.status === PromptStatus.CANCELLED) {
|
|
Object.assign(cardObj.statusBadge.style, { display: "block", background: "#ffc107", color: "#000" });
|
|
cardObj.statusBadge.textContent = "Cancelled";
|
|
} else if (state.status === PromptStatus.ERROR) {
|
|
Object.assign(cardObj.statusBadge.style, { display: "block", background: "#dc3545", color: "#fff" });
|
|
cardObj.statusBadge.textContent = "Error";
|
|
} else {
|
|
cardObj.statusBadge.style.display = "none";
|
|
}
|
|
|
|
if (state.status === PromptStatus.PENDING && !showPendingSummary) {
|
|
cardObj.cancelBtn.style.display = "flex";
|
|
cardObj.cancelBtn.onclick = async (ev) => {
|
|
ev.stopPropagation();
|
|
cancelPendingTask(state.pid);
|
|
};
|
|
} else if (state.status === PromptStatus.ACTIVE) {
|
|
cardObj.cancelBtn.style.display = "flex";
|
|
cardObj.cancelBtn.onclick = async (ev) => {
|
|
ev.stopPropagation();
|
|
interruptActive();
|
|
};
|
|
} else {
|
|
cardObj.cancelBtn.style.display = "none";
|
|
}
|
|
|
|
const currentImagesSignature = `${state.status || ""}:${state.images ? state.images.map(img => img.url || img.filename).join("|") : ""}`;
|
|
if (cardObj.lastImagesSignature !== currentImagesSignature) {
|
|
if (!state.images || state.images.length === 0) {
|
|
cardObj.grid.innerHTML = "";
|
|
cardObj.firstImgElement = null;
|
|
cardObj.placeholder.style.display = "block";
|
|
} else {
|
|
cardObj.placeholder.style.display = "none";
|
|
renderCardImages(cardObj, state, (st) => {
|
|
const scrollEl = document.querySelector('.sidebar-content-container, [class*="sidebar-content-container"], [class*="overflow-y-auto"]')
|
|
|| store.ui.cardStack?.parentElement
|
|
|| store.ui.cardStack;
|
|
if (scrollEl) {
|
|
store.ui.mainQueueScrollTop = scrollEl.scrollTop;
|
|
}
|
|
store.openBatchSubmenu({
|
|
pid: st.pid,
|
|
images: st.images,
|
|
workflow: st.workflow,
|
|
nodeOutputs: st.nodeOutputs
|
|
});
|
|
});
|
|
}
|
|
cardObj.lastImagesSignature = currentImagesSignature;
|
|
}
|
|
|
|
if (state.images.length === 0) {
|
|
if (state.texts && state.texts.length > 0) {
|
|
const fullText = state.texts.join("\n\n");
|
|
cardObj.placeholder.textContent = fullText;
|
|
cardObj.placeholder.title = "Click to read full text";
|
|
cardObj.placeholder.className = "comfy-sidebar-text-clamp";
|
|
cardObj.placeholder.style.cssText = "";
|
|
cardObj.placeholder.style.display = "-webkit-box";
|
|
cardObj.placeholder.onclick = (e) => {
|
|
e.stopPropagation();
|
|
stopAllAudioPlayback();
|
|
showFullscreenPreview([{ text: fullText, pid: state.pid }]);
|
|
};
|
|
} else {
|
|
cardObj.placeholder.textContent = state.progressText || "No Outputs";
|
|
cardObj.placeholder.className = "";
|
|
Object.assign(cardObj.placeholder.style, {
|
|
fontSize: "11px", opacity: "0.5", textAlign: "center", padding: "12px", marginTop: "12px",
|
|
userSelect: "none", whiteSpace: "normal", maxHeight: "none", display: "block", cursor: "default"
|
|
});
|
|
cardObj.placeholder.onclick = null;
|
|
}
|
|
}
|
|
|
|
if (state.status === PromptStatus.ACTIVE) {
|
|
if (showWorkingNode) {
|
|
cardObj.statusText.style.display = "block";
|
|
const nodeText = state.activeNodeName ? (state.activeNodeName === "Finishing..." ? "Finishing..." : `[${state.activeNodeName}]`) : "Processing...";
|
|
cardObj.statusText.textContent = `${nodeText}${state.progress ? ` ${state.progress}%` : ""}`;
|
|
} else {
|
|
cardObj.statusText.style.display = "none";
|
|
}
|
|
cardObj.progressContainer.style.display = "block";
|
|
cardObj.progressBar.style.width = `${state.progress || 0}%`;
|
|
} else {
|
|
cardObj.statusText.style.display = "none";
|
|
cardObj.progressContainer.style.display = "none";
|
|
}
|
|
|
|
syncCardButtonVisibility(cardObj, state);
|
|
return cardObj.element;
|
|
} |