Files
HAL-XP 4ece0021ae Added run monitoring and fixed some focus settings
Added a way to monitor success or error and run a custom script
2025-12-03 12:10:47 +01:00

154 lines
6.5 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
app.registerExtension({
name: "Comfy.HALXP.Focus",
async setup() {
// --- CONSTANTS ---
const SMOOTHNESS = 0.15;
// Clean IDs
const SETTING_DISPLAY_ID = "HALXP.Focus.DisplayButton";
const SETTING_AUTO_ID = "HALXP.Focus.AutoEnable";
// --- STATE ---
let isFocusEnabled = false;
let isWorkflowRunning = false;
let activeNode = null;
let animationFrameId = null;
let focusBtn = null;
let dot = null;
// --- 1. REGISTER SETTINGS ---
app.ui.settings.addSetting({
id: SETTING_DISPLAY_ID,
name: "Display Focus Button",
type: "boolean",
defaultValue: true,
// 3rd element 'Display' ensures uniqueness
category: ['HALXP-Comfy', 'Focus', 'Display'],
tooltip: "Show or hide the HALXP Focus button in the top menu.",
onChange: (value) => {
if (value) injectNextToManager();
else {
if (focusBtn) focusBtn.remove();
if (isFocusEnabled) setFocusState(false);
}
}
});
app.ui.settings.addSetting({
id: SETTING_AUTO_ID,
name: "Auto-Focus on Run",
type: "boolean",
defaultValue: true,
// 3rd element 'Auto' ensures uniqueness
category: ['HALXP-Comfy', 'Focus', 'Auto'],
tooltip: "Automatically enable Focus mode when a workflow starts executing.",
});
// --- 2. BUTTON LOGIC ---
function createButton() {
const btn = document.createElement("button");
btn.textContent = "Focus";
btn.id = "halxp-focus-btn";
btn.style.cssText = `
position: relative; display: inline-flex; align-items: center; justify-content: center;
padding: 0 10px; margin: 0 4px; background-color: var(--comfy-input-bg, #333);
color: var(--fg-color, #fff); border: 1px solid var(--border-color, #555);
border-radius: 4px; cursor: pointer; font-family: sans-serif; font-size: 13px;
font-weight: 600; flex-shrink: 0; white-space: nowrap; z-index: 9999; pointer-events: auto;
border: 1px solid #d3c1c1ff;
`;
const d = document.createElement("span");
d.style.cssText = `width: 8px; height: 8px; border-radius: 50%; background-color: #666; margin-right: 8px; display: inline-block; flex-shrink: 0;`;
btn.prepend(d);
btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); toggleFocus(); };
dot = d;
return btn;
}
function injectNextToManager() {
const shouldDisplay = app.ui.settings.getSettingValue(SETTING_DISPLAY_ID, true);
if (!shouldDisplay || document.getElementById("halxp-focus-btn")) return;
const allButtons = Array.from(document.querySelectorAll("button"));
const managerBtn = allButtons.find(b => {
if (!b.offsetParent) return false;
const text = b.innerText.trim();
return text === "Manager" || text === "ComfyUI Manager";
});
if (managerBtn && managerBtn.parentNode) {
if (!focusBtn) focusBtn = createButton();
const h = managerBtn.offsetHeight;
focusBtn.style.height = h > 0 ? `${h}px` : "28px";
managerBtn.parentNode.insertBefore(focusBtn, managerBtn);
if (isFocusEnabled) updateButtonStyle(true);
}
}
setInterval(injectNextToManager, 500);
// --- 3. FOCUS CORE LOGIC ---
function toggleFocus() { setFocusState(!isFocusEnabled); }
function setFocusState(enabled) {
isFocusEnabled = enabled;
if (focusBtn && dot) updateButtonStyle(enabled);
if (enabled && activeNode) requestAnimationFrame(smoothFollow);
else if (animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; }
}
function updateButtonStyle(enabled) {
if (!dot || !focusBtn) return;
if (enabled) {
dot.style.backgroundColor = "#4CAF50"; dot.style.boxShadow = "0 0 6px #4CAF50";
focusBtn.style.color = "#fff"; focusBtn.style.borderColor = "#4CAF50";
} else {
dot.style.backgroundColor = "#666"; dot.style.boxShadow = "none";
focusBtn.style.color = "var(--fg-color, #ccc)"; focusBtn.style.borderColor = "#d3c1c1ff";
}
}
// Disable on manual drag
const waitForCanvas = setInterval(() => {
if (app.canvas && app.canvas.canvas) {
clearInterval(waitForCanvas);
app.canvas.canvas.addEventListener("pointerdown", () => {
if (isFocusEnabled) setFocusState(false);
}, { capture: true });
}
}, 500);
// --- 4. EXECUTION LOOP ---
api.addEventListener("executing", (e) => {
if (e.detail) {
if (!isWorkflowRunning) {
isWorkflowRunning = true;
if (app.ui.settings.getSettingValue(SETTING_AUTO_ID, true)) setFocusState(true);
}
activeNode = app.graph.getNodeById(e.detail);
if (isFocusEnabled && activeNode) {
app.canvas.setDirty(true, true);
if (!animationFrameId) requestAnimationFrame(smoothFollow);
}
} else {
isWorkflowRunning = false;
activeNode = null;
}
});
function smoothFollow() {
if (isFocusEnabled && activeNode) {
const canvas = app.canvas;
const ds = canvas.ds;
const center = [canvas.canvas.clientWidth / 2, canvas.canvas.clientHeight / 2];
const nodeCenter = [activeNode.pos[0] + activeNode.size[0] / 2, activeNode.pos[1] + activeNode.size[1] / 2];
const target = [(center[0] / ds.scale) - nodeCenter[0], (center[1] / ds.scale) - nodeCenter[1]];
ds.offset[0] += (target[0] - ds.offset[0]) * SMOOTHNESS;
ds.offset[1] += (target[1] - ds.offset[1]) * SMOOTHNESS;
canvas.setDirty(true, true);
animationFrameId = requestAnimationFrame(smoothFollow);
}
}
}
});