Files
mercu-lore-BoxBox/web/region_selector_widget.js
2025-11-12 15:38:17 +01:00

344 lines
11 KiB
JavaScript

/**
* Region Selector Widget - Integrazione con ComfyUI
* Aggiunge un bottone al nodo per aprire il selettore di regioni
*/
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
app.registerExtension({
name: "ImageRegionSelect.RegionSelector",
/**
* Quando un nodo viene creato, aggiungi il widget personalizzato
*/
async nodeCreated(node, app) {
// Esegui solo per il nodo RegionSelectorNode
if (node.comfyClass !== "RegionSelectorNode") return;
// Trova i widget del nodo
const widthWidget = node.widgets.find(w => w.name === "image_width");
const heightWidget = node.widgets.find(w => w.name === "image_height");
const coordinatesWidget = node.widgets.find(w => w.name === "coordinates_json");
// Crea bottone per aprire il selettore
const selectorButton = document.createElement("button");
selectorButton.textContent = "🎯 Apri Selettore Regioni";
selectorButton.style.cssText = `
width: 100%;
padding: 10px;
margin: 5px 0;
background-color: #2563eb;
color: white;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
`;
selectorButton.addEventListener("mouseover", () => {
selectorButton.style.backgroundColor = "#1d4ed8";
});
selectorButton.addEventListener("mouseout", () => {
selectorButton.style.backgroundColor = "#2563eb";
});
selectorButton.addEventListener("click", () => {
openRegionSelector(node, widthWidget, heightWidget, coordinatesWidget);
});
// Aggiungi il bottone al nodo
const container = document.createElement("div");
container.style.marginBottom = "10px";
container.appendChild(selectorButton);
// Inserisci prima del primo widget
if (node.widgets.length > 0) {
node.widgets[0].element?.parentNode?.insertBefore(container, node.widgets[0].element);
}
// Aggiungi anche come custom widget per miglior tracking
node.regionSelectorButton = selectorButton;
node.regionSelectorContainer = container;
// Aggiungi etichetta informativa
const infoLabel = document.createElement("div");
infoLabel.style.cssText = `
font-size: 11px;
color: #64748b;
padding: 5px 0;
margin: 5px 0;
text-align: center;
`;
infoLabel.textContent = "Apri l'interfaccia per selezionare le coordinate della regione";
container.appendChild(infoLabel);
},
/**
* Hook che si chiama quando il nodo viene caricato
*/
async loadedGraphNode(node, app) {
if (node.comfyClass !== "RegionSelectorNode") return;
// Ripristina lo stato del bottone se necessario
const coordinatesWidget = node.widgets.find(w => w.name === "coordinates_json");
if (coordinatesWidget && coordinatesWidget.value && coordinatesWidget.value !== "{}") {
console.log("Region coordinates loaded:", coordinatesWidget.value);
}
}
});
/**
* Apre l'interfaccia selettore di regioni
*/
function openRegionSelector(node, widthWidget, heightWidget, coordinatesWidget) {
// Carica l'HTML dell'interfaccia
const htmlPath = "extensions/ImageRegionSelect/region_selector.html";
fetch(htmlPath)
.then(response => {
if (!response.ok) throw new Error(`Failed to load ${htmlPath}`);
return response.text();
})
.then(html => {
// Crea una finestra modale
const modal = createModal(html, node, widthWidget, heightWidget, coordinatesWidget);
document.body.appendChild(modal);
})
.catch(error => {
console.error("Error loading region selector:", error);
alert("Errore nel caricamento del selettore di regioni: " + error.message);
});
}
/**
* Crea una finestra modale con il selettore
*/
function createModal(html, node, widthWidget, heightWidget, coordinatesWidget) {
const modal = document.createElement("div");
modal.id = "region-selector-modal";
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(2px);
`;
// Container principale
const container = document.createElement("div");
container.style.cssText = `
width: 90vw;
height: 90vh;
background-color: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
overflow: hidden;
`;
// Inserisci l'HTML del selettore
container.innerHTML = html;
modal.appendChild(container);
// Attendi che il DOM sia pronto
setTimeout(() => {
// Inizializza il selettore con i callback
if (window.RegionSelector) {
window.RegionSelector.init(
(coordinates) => handleCoordinatesConfirmed(coordinates, modal, coordinatesWidget, node),
() => handleSelectorCancel(modal)
);
// Carica coordinate precedenti se esistono
if (coordinatesWidget && coordinatesWidget.value && coordinatesWidget.value !== "{}") {
try {
const previousCoordinates = JSON.parse(coordinatesWidget.value);
if (previousCoordinates.x !== undefined) {
window.RegionSelector.loadCoordinates(previousCoordinates);
}
} catch (e) {
console.warn("Could not load previous coordinates:", e);
}
}
}
// Aggiorna i bottoni di chiusura
const closeBtn = document.getElementById("close-btn");
const cancelBtn = document.getElementById("cancel-btn");
if (closeBtn) {
closeBtn.addEventListener("click", () => handleSelectorCancel(modal));
}
if (cancelBtn) {
cancelBtn.addEventListener("click", () => handleSelectorCancel(modal));
}
// Precarica l'immagine di sfondo (opzionale)
loadBackgroundImage(node, widthWidget, heightWidget);
}, 50);
return modal;
}
/**
* Gestisce la conferma delle coordinate
*/
function handleCoordinatesConfirmed(coordinates, modal, coordinatesWidget, node) {
// Salva le coordinate nel widget JSON
coordinatesWidget.value = JSON.stringify(coordinates);
// Forza l'aggiornamento del nodo
if (node.widgets) {
// Trigga callback se esiste
if (coordinatesWidget.callback) {
coordinatesWidget.callback(coordinatesWidget.value);
}
}
// Log per debug
console.log("Region coordinates confirmed and saved:", coordinates);
// Mostra messaggio di successo
showNotification("✅ Selezione confermata!", "success");
// Chiudi la modale
modal.remove();
}
/**
* Gestisce l'annullamento
*/
function handleSelectorCancel(modal) {
modal.remove();
console.log("Region selector cancelled");
}
/**
* Carica un'immagine come sfondo nell'interfaccia
* Ridimensiona automaticamente se > 1024px
*/
async function loadBackgroundImage(node, widthWidget, heightWidget) {
try {
const bgImg = document.getElementById("background-image");
if (!bgImg) {
console.warn("background-image element not found");
return;
}
// Cerca il widget immagine del nodo
const imageWidget = node.widgets.find(w => w.type === "image" || w.name === "image");
if (!imageWidget || !imageWidget.value) {
console.warn("No image widget found on node");
return;
}
// Estrai il filename dall'immagine
let filename = imageWidget.value;
if (typeof filename === "object" && filename.name) {
filename = filename.name;
}
console.log(`[RegionSelector] Caricamento immagine: ${filename}`);
// Chiama l'endpoint per scalare se necessario
const scaleResponse = await fetch("/region_selector/scale", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filename: filename })
});
const scaleResult = await scaleResponse.json();
console.log(`[RegionSelector] Scale result:`, scaleResult);
if (scaleResult.path) {
// Carica l'immagine (originale o scalata) nel Region Selector
bgImg.src = scaleResult.path;
// Salva i dati per il calcolo delle coordinate
if (scaleResult.scaled) {
bgImg.dataset.scaled = "true";
bgImg.dataset.scaleFactor = scaleResult.scale || 1;
console.log(`[RegionSelector] Immagine scalata caricata, scale factor: ${scaleResult.scale}`);
} else {
bgImg.dataset.scaled = "false";
bgImg.dataset.scaleFactor = 1;
console.log(`[RegionSelector] Immagine originale caricata (≤ 1024px)`);
}
} else if (scaleResult.error) {
console.warn(`[RegionSelector] Scale error: ${scaleResult.error}`);
}
} catch (e) {
console.warn("Could not auto-load image:", e);
}
}
/**
* Mostra una notifica temporanea
*/
function showNotification(message, type = "info") {
const notification = document.createElement("div");
notification.textContent = message;
notification.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
z-index: 20000;
animation: slideIn 0.3s ease;
${type === "success" ? "background-color: #16a34a; color: white;" :
type === "error" ? "background-color: #dc2626; color: white;" :
"background-color: #2563eb; color: white;"}
`;
document.body.appendChild(notification);
// Auto-remove dopo 3 secondi
setTimeout(() => {
notification.style.animation = "slideOut 0.3s ease";
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Aggiungi animazioni CSS
const style = document.createElement("style");
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
`;
document.head.appendChild(style);