mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 12:52:10 -03:00
Ensured the toggle mask switch UI stays in sync with mask visibility when auto_refresh_after_generation hides or shows the mask. The checkbox and switch now correctly reflect the current mask state, preventing UI desynchronization and improving user experience.
250 lines
9.9 KiB
JavaScript
250 lines
9.9 KiB
JavaScript
import { createModuleLogger } from "./utils/LoggerUtils.js";
|
|
const log = createModuleLogger('BatchPreviewManager');
|
|
export class BatchPreviewManager {
|
|
constructor(canvas, initialPosition = { x: 0, y: 0 }, generationArea = null) {
|
|
this.canvas = canvas;
|
|
this.active = false;
|
|
this.layers = [];
|
|
this.currentIndex = 0;
|
|
this.element = null;
|
|
this.counterElement = null;
|
|
this.uiInitialized = false;
|
|
this.maskWasVisible = false;
|
|
this.worldX = initialPosition.x;
|
|
this.worldY = initialPosition.y;
|
|
this.isDragging = false;
|
|
this.generationArea = generationArea;
|
|
}
|
|
updateScreenPosition(viewport) {
|
|
if (!this.active || !this.element)
|
|
return;
|
|
const screenX = (this.worldX - viewport.x) * viewport.zoom;
|
|
const screenY = (this.worldY - viewport.y) * viewport.zoom;
|
|
const scale = 1;
|
|
this.element.style.transform = `translate(${screenX}px, ${screenY}px) scale(${scale})`;
|
|
}
|
|
_createUI() {
|
|
if (this.uiInitialized)
|
|
return;
|
|
this.element = document.createElement('div');
|
|
this.element.id = 'layerforge-batch-preview';
|
|
this.element.style.cssText = `
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
background-color: #333;
|
|
color: white;
|
|
padding: 8px 15px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
|
|
display: none;
|
|
align-items: center;
|
|
gap: 15px;
|
|
font-family: sans-serif;
|
|
z-index: 1001;
|
|
border: 1px solid #555;
|
|
cursor: move;
|
|
user-select: none;
|
|
`;
|
|
this.element.addEventListener('mousedown', (e) => {
|
|
if (e.target.tagName === 'BUTTON')
|
|
return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.isDragging = true;
|
|
const handleMouseMove = (moveEvent) => {
|
|
if (this.isDragging) {
|
|
const deltaX = moveEvent.movementX / this.canvas.viewport.zoom;
|
|
const deltaY = moveEvent.movementY / this.canvas.viewport.zoom;
|
|
this.worldX += deltaX;
|
|
this.worldY += deltaY;
|
|
// The render loop will handle updating the screen position, but we need to trigger it.
|
|
this.canvas.render();
|
|
}
|
|
};
|
|
const handleMouseUp = () => {
|
|
this.isDragging = false;
|
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
};
|
|
document.addEventListener('mousemove', handleMouseMove);
|
|
document.addEventListener('mouseup', handleMouseUp);
|
|
});
|
|
const prevButton = this._createButton('◀', 'Previous'); // Left arrow
|
|
const nextButton = this._createButton('▶', 'Next'); // Right arrow
|
|
const confirmButton = this._createButton('✔', 'Confirm'); // Checkmark
|
|
const cancelButton = this._createButton('✖', 'Cancel All');
|
|
const closeButton = this._createButton('➲', 'Close');
|
|
this.counterElement = document.createElement('span');
|
|
this.counterElement.style.minWidth = '40px';
|
|
this.counterElement.style.textAlign = 'center';
|
|
this.counterElement.style.fontWeight = 'bold';
|
|
prevButton.onclick = () => this.navigate(-1);
|
|
nextButton.onclick = () => this.navigate(1);
|
|
confirmButton.onclick = () => this.confirm();
|
|
cancelButton.onclick = () => this.cancelAndRemoveAll();
|
|
closeButton.onclick = () => this.hide();
|
|
this.element.append(prevButton, this.counterElement, nextButton, confirmButton, cancelButton, closeButton);
|
|
if (this.canvas.canvas.parentElement) {
|
|
this.canvas.canvas.parentElement.appendChild(this.element);
|
|
}
|
|
else {
|
|
log.error("Could not find parent node to attach batch preview UI.");
|
|
}
|
|
this.uiInitialized = true;
|
|
}
|
|
_createButton(innerHTML, title) {
|
|
const button = document.createElement('button');
|
|
button.innerHTML = innerHTML;
|
|
button.title = title;
|
|
button.style.cssText = `
|
|
background: #555;
|
|
color: white;
|
|
border: 1px solid #777;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
width: 30px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
`;
|
|
button.onmouseover = () => button.style.background = '#666';
|
|
button.onmouseout = () => button.style.background = '#555';
|
|
return button;
|
|
}
|
|
show(layers) {
|
|
if (!layers || layers.length <= 1) {
|
|
return;
|
|
}
|
|
this._createUI();
|
|
// Auto-hide mask logic
|
|
this.maskWasVisible = this.canvas.maskTool.isOverlayVisible;
|
|
if (this.maskWasVisible) {
|
|
this.canvas.maskTool.toggleOverlayVisibility();
|
|
const toggleSwitch = document.getElementById(`toggle-mask-switch-${this.canvas.node.id}`);
|
|
if (toggleSwitch) {
|
|
const checkbox = toggleSwitch.querySelector('input[type="checkbox"]');
|
|
if (checkbox) {
|
|
checkbox.checked = false;
|
|
}
|
|
toggleSwitch.classList.remove('primary');
|
|
const iconContainer = toggleSwitch.querySelector('.switch-icon');
|
|
if (iconContainer) {
|
|
iconContainer.style.opacity = '0.5';
|
|
}
|
|
}
|
|
this.canvas.render();
|
|
}
|
|
log.info(`Showing batch preview for ${layers.length} layers.`);
|
|
this.layers = layers;
|
|
this.currentIndex = 0;
|
|
if (this.element) {
|
|
this.element.style.display = 'flex';
|
|
}
|
|
this.active = true;
|
|
if (this.element) {
|
|
const menuWidthInWorld = this.element.offsetWidth / this.canvas.viewport.zoom;
|
|
const paddingInWorld = 20 / this.canvas.viewport.zoom;
|
|
this.worldX -= menuWidthInWorld / 2;
|
|
this.worldY += paddingInWorld;
|
|
}
|
|
// Hide all batch layers initially, then show only the first one
|
|
this.layers.forEach((layer) => {
|
|
layer.visible = false;
|
|
});
|
|
this._update();
|
|
}
|
|
hide() {
|
|
log.info('Hiding batch preview.');
|
|
if (this.element) {
|
|
this.element.remove();
|
|
}
|
|
this.active = false;
|
|
const index = this.canvas.batchPreviewManagers.indexOf(this);
|
|
if (index > -1) {
|
|
this.canvas.batchPreviewManagers.splice(index, 1);
|
|
}
|
|
this.canvas.render();
|
|
if (this.maskWasVisible && !this.canvas.maskTool.isOverlayVisible) {
|
|
this.canvas.maskTool.toggleOverlayVisibility();
|
|
const toggleSwitch = document.getElementById(`toggle-mask-switch-${String(this.canvas.node.id)}`);
|
|
if (toggleSwitch) {
|
|
const checkbox = toggleSwitch.querySelector('input[type="checkbox"]');
|
|
if (checkbox) {
|
|
checkbox.checked = true;
|
|
}
|
|
toggleSwitch.classList.add('primary');
|
|
const iconContainer = toggleSwitch.querySelector('.switch-icon');
|
|
if (iconContainer) {
|
|
iconContainer.style.opacity = '1';
|
|
}
|
|
}
|
|
}
|
|
this.maskWasVisible = false;
|
|
// Only make visible the layers that were part of the batch preview
|
|
this.layers.forEach((layer) => {
|
|
layer.visible = true;
|
|
});
|
|
// Update the layers panel to reflect visibility changes
|
|
if (this.canvas.canvasLayersPanel) {
|
|
this.canvas.canvasLayersPanel.onLayersChanged();
|
|
}
|
|
this.canvas.render();
|
|
}
|
|
navigate(direction) {
|
|
this.currentIndex += direction;
|
|
if (this.currentIndex < 0) {
|
|
this.currentIndex = this.layers.length - 1;
|
|
}
|
|
else if (this.currentIndex >= this.layers.length) {
|
|
this.currentIndex = 0;
|
|
}
|
|
this._update();
|
|
}
|
|
confirm() {
|
|
const layerToKeep = this.layers[this.currentIndex];
|
|
log.info(`Confirming selection: Keeping layer ${layerToKeep.id}.`);
|
|
const layersToDelete = this.layers.filter((l) => l.id !== layerToKeep.id);
|
|
const layerIdsToDelete = layersToDelete.map((l) => l.id);
|
|
this.canvas.removeLayersByIds(layerIdsToDelete);
|
|
log.info(`Deleted ${layersToDelete.length} other layers.`);
|
|
this.hide();
|
|
}
|
|
cancelAndRemoveAll() {
|
|
log.info('Cancel clicked. Removing all new layers.');
|
|
const layerIdsToDelete = this.layers.map((l) => l.id);
|
|
this.canvas.removeLayersByIds(layerIdsToDelete);
|
|
log.info(`Deleted all ${layerIdsToDelete.length} new layers.`);
|
|
this.hide();
|
|
}
|
|
_update() {
|
|
if (this.counterElement) {
|
|
this.counterElement.textContent = `${this.currentIndex + 1} / ${this.layers.length}`;
|
|
}
|
|
this._focusOnLayer(this.layers[this.currentIndex]);
|
|
}
|
|
_focusOnLayer(layer) {
|
|
if (!layer)
|
|
return;
|
|
log.debug(`Focusing on layer ${layer.id} using visibility toggle`);
|
|
// Hide all batch layers first
|
|
this.layers.forEach((l) => {
|
|
l.visible = false;
|
|
});
|
|
// Show only the current layer
|
|
layer.visible = true;
|
|
// Deselect only this layer if it is selected
|
|
const selected = this.canvas.canvasSelection.selectedLayers;
|
|
if (selected && selected.includes(layer)) {
|
|
this.canvas.updateSelection(selected.filter((l) => l !== layer));
|
|
}
|
|
// Update the layers panel to reflect visibility changes
|
|
if (this.canvas.canvasLayersPanel) {
|
|
this.canvas.canvasLayersPanel.onLayersChanged();
|
|
}
|
|
this.canvas.render();
|
|
}
|
|
}
|