Improve preview dialog layout and update to v1.2.0
Enhanced the image grid layout in the preview dialog for better ComfyUI-style appearance and responsive sizing.
This commit is contained in:
+1
-1
@@ -8,4 +8,4 @@ from .nf_preview_selector import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
pass # NF Preview Selector loaded
|
||||
pass
|
||||
|
||||
@@ -72,12 +72,32 @@
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* ComfyUI-style layout with optimized calculations */
|
||||
.nf-image-grid.comfyui-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--max-columns, auto-fit), var(--min-column-width, 150px));
|
||||
grid-template-rows: repeat(var(--max-rows, auto-fit), var(--cell-height, auto));
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.nf-image-container {
|
||||
position: relative;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ComfyUI-style image containers with fixed dimensions */
|
||||
.nf-image-grid.comfyui-layout .nf-image-container {
|
||||
width: var(--min-column-width, 150px);
|
||||
height: var(--cell-height, 150px);
|
||||
}
|
||||
|
||||
/* Responsive image containers */
|
||||
@@ -102,6 +122,13 @@
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* ComfyUI-style images with fixed dimensions */
|
||||
.nf-image-grid.comfyui-layout .nf-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.nf-image-overlay {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
|
||||
@@ -10,7 +10,6 @@ export class NFFloatingWindow extends HTMLDivElement {
|
||||
this.moveCallback = moveCallback;
|
||||
this.resizeCallback = resizeCallback;
|
||||
|
||||
// Setup window structure
|
||||
this.classList.add('nf-float');
|
||||
|
||||
// Header
|
||||
@@ -24,7 +23,6 @@ export class NFFloatingWindow extends HTMLDivElement {
|
||||
this.body.classList.add('nf-float-body');
|
||||
this.appendChild(this.body);
|
||||
|
||||
// Setup dragging
|
||||
this.dragging = false;
|
||||
this.position = { x: x, y: y };
|
||||
|
||||
@@ -33,17 +31,14 @@ export class NFFloatingWindow extends HTMLDivElement {
|
||||
document.addEventListener('mousemove', this.drag.bind(this));
|
||||
document.addEventListener('mouseleave', this.stopDrag.bind(this));
|
||||
|
||||
// Position window
|
||||
this.moveTo(x, y);
|
||||
|
||||
// Add to parent or body
|
||||
if (parent) {
|
||||
parent.appendChild(this);
|
||||
} else {
|
||||
document.body.appendChild(this);
|
||||
}
|
||||
|
||||
// Setup resize observer for size changes
|
||||
this.setupResizeObserver();
|
||||
}
|
||||
|
||||
@@ -110,5 +105,4 @@ export class NFFloatingWindow extends HTMLDivElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Register custom element
|
||||
customElements.define('nf-floating-window', NFFloatingWindow, { extends: 'div' });
|
||||
@@ -8,28 +8,20 @@ import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { nfPreviewDialog } from './preview_dialog.js';
|
||||
|
||||
// NF Preview Selector loading
|
||||
|
||||
app.registerExtension({
|
||||
name: "nf.preview_selector",
|
||||
|
||||
async setup() {
|
||||
// Setting up extension
|
||||
|
||||
// Register message handler using ComfyUI's system
|
||||
if (api.addEventListener) {
|
||||
// Register for our custom event type
|
||||
api.addEventListener("nf_preview_request", (event) => {
|
||||
// Received review request
|
||||
nfPreviewDialog.show(event.detail);
|
||||
});
|
||||
|
||||
// Event listener registered
|
||||
}
|
||||
|
||||
// Event system is working correctly
|
||||
|
||||
// Add settings
|
||||
app.ui.settings.addSetting({
|
||||
id: "NFPreviewSelector.UI.WindowPosition",
|
||||
name: "Remember window position and size",
|
||||
@@ -46,41 +38,31 @@ app.registerExtension({
|
||||
tooltip: "Maximum width of the preview dialog in pixels (400-1800)"
|
||||
});
|
||||
|
||||
// Auto-select first image setting removed - this is available in node UI
|
||||
|
||||
// Setup complete
|
||||
},
|
||||
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass === "NFPreviewSelector") {
|
||||
// Node created
|
||||
|
||||
// Set node title
|
||||
node.title = "NF Preview Selector";
|
||||
|
||||
// Add any node-specific setup here
|
||||
node.onResize = function() {
|
||||
// Handle node resize if needed
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "NFPreviewSelector") {
|
||||
// Registering node definition
|
||||
|
||||
// Add any node definition modifications here
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
if (onNodeCreated) {
|
||||
onNodeCreated.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Node-specific initialization
|
||||
this.setProperty("review_position", { x: 100, y: 100 });
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Extension loaded successfully
|
||||
+90
-87
@@ -22,7 +22,6 @@ export class NFPreviewDialog {
|
||||
// Load CSS
|
||||
this.loadCSS();
|
||||
|
||||
// Apply max width setting
|
||||
this.applyMaxWidthSetting();
|
||||
|
||||
// Setup Queue Prompt keybinding integration
|
||||
@@ -37,14 +36,11 @@ export class NFPreviewDialog {
|
||||
}
|
||||
|
||||
applyMaxWidthSetting() {
|
||||
// Get max width setting from ComfyUI settings (try new ID first, fallback to old)
|
||||
let maxWidth = app.ui.settings.getSettingValue("NFPreviewSelector.UI.MaxWidth.v2",
|
||||
app.ui.settings.getSettingValue("NFPreviewSelector.UI.MaxWidth", 800));
|
||||
|
||||
// Validate and clamp the value between 400 and 1800
|
||||
maxWidth = Math.max(400, Math.min(1800, maxWidth));
|
||||
|
||||
// Create or update CSS rule for max width
|
||||
const style = document.createElement('style');
|
||||
style.id = 'nf-preview-max-width-style';
|
||||
style.textContent = `
|
||||
@@ -53,7 +49,6 @@ export class NFPreviewDialog {
|
||||
}
|
||||
`;
|
||||
|
||||
// Remove existing style if it exists
|
||||
const existingStyle = document.getElementById('nf-preview-max-width-style');
|
||||
if (existingStyle) {
|
||||
existingStyle.remove();
|
||||
@@ -65,29 +60,19 @@ export class NFPreviewDialog {
|
||||
setupQueuePromptIntegration() {
|
||||
// Listen for Queue Prompt keybinding events
|
||||
document.addEventListener('keydown', (event) => {
|
||||
// Check if it's Ctrl+Enter (Queue Prompt) or Ctrl+Shift+Enter (Queue Prompt to Front)
|
||||
if (event.ctrlKey && event.key === 'Enter') {
|
||||
// Only handle if preview dialog is visible
|
||||
if (this.isVisible()) {
|
||||
// Queue Prompt keybinding detected
|
||||
|
||||
// Stop the dialog processing immediately
|
||||
this.stopDialogForQueue();
|
||||
|
||||
// Allow the event to propagate to ComfyUI's queue system
|
||||
// Do not prevent default - let ComfyUI handle the queue
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also listen for programmatic queue events via API
|
||||
api.addEventListener("status", (event) => {
|
||||
const status = event.detail;
|
||||
|
||||
// If queue is starting and dialog is visible, stop the dialog
|
||||
if (status && status.exec_info && status.exec_info.queue_remaining > 0) {
|
||||
if (this.isVisible()) {
|
||||
// Queue started programmatically
|
||||
this.stopDialogForQueue();
|
||||
}
|
||||
}
|
||||
@@ -95,15 +80,10 @@ export class NFPreviewDialog {
|
||||
}
|
||||
|
||||
stopDialogForQueue() {
|
||||
// Stop countdown immediately
|
||||
this.stopCountdown();
|
||||
|
||||
// Stopping dialog processing for queue
|
||||
|
||||
// Send cancellation response to backend (empty selection, cancelled=true)
|
||||
this.sendSelection([], true);
|
||||
|
||||
// If dialog is pinned, don't hide it - just reset for next use
|
||||
if (this.isPinned) {
|
||||
this.prepareForNextRequest();
|
||||
} else {
|
||||
@@ -116,24 +96,18 @@ export class NFPreviewDialog {
|
||||
}
|
||||
|
||||
show(data) {
|
||||
// Showing review dialog
|
||||
|
||||
// Apply latest max width setting
|
||||
this.applyMaxWidthSetting();
|
||||
|
||||
// If dialog is already open for this review, don't create another
|
||||
if (this.reviewId === data.review_id && this.window) {
|
||||
// Dialog already open
|
||||
return;
|
||||
}
|
||||
|
||||
// If dialog is pinned and visible, update with new data
|
||||
if (this.isPinned && this.isVisible()) {
|
||||
this.updateWithNewData(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any existing countdown
|
||||
this.stopCountdown();
|
||||
|
||||
this.images = data.images || [];
|
||||
@@ -153,19 +127,16 @@ export class NFPreviewDialog {
|
||||
this.window.destroy();
|
||||
}
|
||||
|
||||
// Get saved position or calculate center position
|
||||
const rememberPosition = app.ui.settings.getSettingValue("NFPreviewSelector.UI.WindowPosition", true);
|
||||
const savedPosition = rememberPosition ? this.getSavedPosition() : null;
|
||||
let x, y;
|
||||
|
||||
if (savedPosition) {
|
||||
// Use saved position from previous session
|
||||
x = savedPosition.x;
|
||||
y = savedPosition.y;
|
||||
} else {
|
||||
// First time or position memory disabled: calculate center position
|
||||
x = Math.max(0, (window.innerWidth - 600) / 2); // 600px is approximate dialog width
|
||||
y = Math.max(0, (window.innerHeight - 400) / 2); // 400px is approximate dialog height
|
||||
x = Math.max(0, (window.innerWidth - 600) / 2);
|
||||
y = Math.max(0, (window.innerHeight - 400) / 2);
|
||||
}
|
||||
|
||||
this.window = new NFFloatingWindow(
|
||||
@@ -180,7 +151,6 @@ export class NFPreviewDialog {
|
||||
this.setupWindowContent();
|
||||
this.updateWindowTitle();
|
||||
|
||||
// Apply saved size if available and position memory is enabled
|
||||
if (rememberPosition) {
|
||||
const savedSize = this.getSavedSize();
|
||||
if (savedSize) {
|
||||
@@ -189,7 +159,6 @@ export class NFPreviewDialog {
|
||||
}
|
||||
}
|
||||
|
||||
// Center the dialog only if no saved position (first time)
|
||||
if (!savedPosition) {
|
||||
this.centerDialog();
|
||||
}
|
||||
@@ -205,12 +174,10 @@ export class NFPreviewDialog {
|
||||
this.controls = document.createElement('div');
|
||||
this.controls.className = 'nf-controls';
|
||||
|
||||
// Selection info
|
||||
this.selectionInfo = document.createElement('div');
|
||||
this.selectionInfo.className = 'nf-selection-info';
|
||||
this.updateSelectionInfo();
|
||||
|
||||
// Countdown
|
||||
this.countdown = document.createElement('div');
|
||||
this.countdown.className = 'nf-countdown';
|
||||
|
||||
@@ -229,7 +196,6 @@ export class NFPreviewDialog {
|
||||
this.cancelButton.textContent = 'Cancel';
|
||||
this.cancelButton.onclick = this.cancelSelection.bind(this);
|
||||
|
||||
// Pin button
|
||||
this.pinButton = document.createElement('button');
|
||||
this.pinButton.className = 'nf-button pin';
|
||||
this.pinButton.innerHTML = '📌';
|
||||
@@ -251,7 +217,6 @@ export class NFPreviewDialog {
|
||||
renderImages() {
|
||||
this.imageGrid.innerHTML = '';
|
||||
|
||||
// Counter for loaded images to determine layout
|
||||
let loadedImages = 0;
|
||||
let landscapeCount = 0;
|
||||
let portraitCount = 0;
|
||||
@@ -267,18 +232,16 @@ export class NFPreviewDialog {
|
||||
img.src = this.getImageUrl(imageInfo);
|
||||
img.alt = `Image ${index + 1}`;
|
||||
|
||||
// Check image aspect ratio when loaded
|
||||
img.onload = () => {
|
||||
loadedImages++;
|
||||
|
||||
const aspectRatio = img.naturalWidth / img.naturalHeight;
|
||||
if (aspectRatio > 1.1) { // Landscape (wider than tall)
|
||||
if (aspectRatio > 1.1) {
|
||||
landscapeCount++;
|
||||
} else if (aspectRatio < 0.9) { // Portrait (taller than wide)
|
||||
} else if (aspectRatio < 0.9) {
|
||||
portraitCount++;
|
||||
}
|
||||
|
||||
// When all images are loaded, determine layout
|
||||
if (loadedImages === this.images.length) {
|
||||
this.applyImageLayout(landscapeCount, portraitCount);
|
||||
}
|
||||
@@ -295,24 +258,94 @@ export class NFPreviewDialog {
|
||||
}
|
||||
|
||||
applyImageLayout(landscapeCount, portraitCount) {
|
||||
// Always use smart grid layout that adapts to window size
|
||||
this.imageGrid.className = 'nf-image-grid smart-grid-layout';
|
||||
this.imageGrid.className = 'nf-image-grid comfyui-layout';
|
||||
|
||||
// Set CSS custom properties for dynamic grid sizing
|
||||
const dialogWidth = this.window.offsetWidth - 48; // Account for padding
|
||||
const dialogWidth = this.window.offsetWidth - 48;
|
||||
const dialogHeight = this.window.offsetHeight - 140;
|
||||
|
||||
const layoutResult = this.calculateComfyUILayout(this.images, dialogWidth, dialogHeight);
|
||||
|
||||
this.imageGrid.style.setProperty('--min-column-width', `${layoutResult.cellWidth}px`);
|
||||
this.imageGrid.style.setProperty('--cell-height', `${layoutResult.cellHeight}px`);
|
||||
this.imageGrid.style.setProperty('--max-columns', layoutResult.cols);
|
||||
this.imageGrid.style.setProperty('--max-rows', layoutResult.rows);
|
||||
|
||||
}
|
||||
|
||||
calculateComfyUILayout(images, availableWidth, availableHeight) {
|
||||
if (!images || images.length === 0) {
|
||||
return { cellWidth: 200, cellHeight: 200, cols: 1, rows: 1 };
|
||||
}
|
||||
|
||||
let imageWidth = 512;
|
||||
let imageHeight = 512;
|
||||
|
||||
const firstImageContainer = this.imageGrid.querySelector('.nf-image-container img');
|
||||
if (firstImageContainer && firstImageContainer.naturalWidth > 0) {
|
||||
imageWidth = firstImageContainer.naturalWidth;
|
||||
imageHeight = firstImageContainer.naturalHeight;
|
||||
}
|
||||
|
||||
const numImages = images.length;
|
||||
let bestArea = 0;
|
||||
let bestResult = { cellWidth: 200, cellHeight: 200, cols: 1, rows: 1 };
|
||||
|
||||
for (let cols = 1; cols <= numImages; cols++) {
|
||||
const rows = Math.ceil(numImages / cols);
|
||||
const cellWidth_available = availableWidth / cols;
|
||||
const cellHeight_available = availableHeight / rows;
|
||||
|
||||
const scaleX = cellWidth_available / imageWidth;
|
||||
const scaleY = cellHeight_available / imageHeight;
|
||||
const scale = Math.min(scaleX, scaleY, 1);
|
||||
|
||||
const cellWidth = imageWidth * scale;
|
||||
const cellHeight = imageHeight * scale;
|
||||
|
||||
const totalDisplayArea = cellWidth * cellHeight * numImages;
|
||||
|
||||
if (totalDisplayArea > bestArea) {
|
||||
bestArea = totalDisplayArea;
|
||||
bestResult = {
|
||||
cellWidth: Math.round(cellWidth),
|
||||
cellHeight: Math.round(cellHeight),
|
||||
cols,
|
||||
rows
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return bestResult;
|
||||
}
|
||||
|
||||
calculateResponsiveColumnWidth(landscapeCount, portraitCount) {
|
||||
const dialogWidth = this.window.offsetWidth - 48;
|
||||
const isLandscapeDominant = landscapeCount > portraitCount;
|
||||
|
||||
if (isLandscapeDominant) {
|
||||
// For landscape images, use slightly wider columns but still allow multiple columns
|
||||
const minColumnWidth = Math.max(250, Math.min(350, dialogWidth / 2)); // 200-300px, max 2 columns typically
|
||||
this.imageGrid.style.setProperty('--min-column-width', `${minColumnWidth}px`);
|
||||
// Using landscape image layout
|
||||
} else {
|
||||
// For portrait/square images, use standard grid sizing
|
||||
const minColumnWidth = Math.max(120, Math.min(250, dialogWidth / 3)); // 200-250px, allow up to 3-4 columns
|
||||
this.imageGrid.style.setProperty('--min-column-width', `${minColumnWidth}px`);
|
||||
// Using portrait/square image layout
|
||||
}
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
const dpiMultiplier = Math.min(Math.max(devicePixelRatio, 1), 2);
|
||||
|
||||
const baseRatio = isLandscapeDominant ? 0.45 : 0.30; // 45% for landscape, 30% for portrait
|
||||
const maxColumns = isLandscapeDominant ? 2 : 4;
|
||||
|
||||
const baseColumnWidth = dialogWidth * baseRatio;
|
||||
|
||||
const dpiAwareColumnWidth = baseColumnWidth * dpiMultiplier;
|
||||
|
||||
const minWidthForColumns = dialogWidth / maxColumns;
|
||||
|
||||
const absoluteMin = isLandscapeDominant ? 200 : 120;
|
||||
const absoluteMax = isLandscapeDominant ? 600 : 400;
|
||||
|
||||
const finalColumnWidth = Math.max(
|
||||
absoluteMin,
|
||||
Math.min(
|
||||
absoluteMax,
|
||||
Math.max(minWidthForColumns, dpiAwareColumnWidth)
|
||||
)
|
||||
);
|
||||
|
||||
return Math.round(finalColumnWidth);
|
||||
}
|
||||
|
||||
getImageUrl(imageInfo) {
|
||||
@@ -333,7 +366,6 @@ export class NFPreviewDialog {
|
||||
this.updateSelectionInfo();
|
||||
this.updateConfirmButton();
|
||||
|
||||
// Selected indices updated
|
||||
}
|
||||
|
||||
updateSelectionInfo() {
|
||||
@@ -357,7 +389,6 @@ export class NFPreviewDialog {
|
||||
this.updateCountdown();
|
||||
|
||||
if (this.remainingTime <= 0) {
|
||||
// Timeout reached
|
||||
this.timeoutSelection();
|
||||
}
|
||||
}, 1000);
|
||||
@@ -380,11 +411,9 @@ export class NFPreviewDialog {
|
||||
this.stopCountdown();
|
||||
|
||||
const selection = Array.from(this.selectedIndices).sort((a, b) => a - b);
|
||||
// Confirming selection
|
||||
|
||||
this.sendSelection(selection, false);
|
||||
|
||||
// If pinned, don't hide dialog - just reset for next use
|
||||
if (this.isPinned) {
|
||||
this.prepareForNextRequest();
|
||||
} else {
|
||||
@@ -395,10 +424,8 @@ export class NFPreviewDialog {
|
||||
cancelSelection() {
|
||||
this.stopCountdown();
|
||||
|
||||
// Cancelling selection
|
||||
this.sendSelection([], true);
|
||||
|
||||
// If pinned, don't hide dialog - just reset for next use
|
||||
if (this.isPinned) {
|
||||
this.prepareForNextRequest();
|
||||
} else {
|
||||
@@ -407,19 +434,16 @@ export class NFPreviewDialog {
|
||||
}
|
||||
|
||||
prepareForNextRequest() {
|
||||
// Reset dialog state for next request while keeping it open
|
||||
this.selectedIndices.clear();
|
||||
this.clearImageSelection();
|
||||
this.updateSelectionInfo();
|
||||
this.updateConfirmButton();
|
||||
this.stopCountdown();
|
||||
|
||||
// Clear images and show waiting message
|
||||
this.images = [];
|
||||
this.reviewId = null;
|
||||
this.uniqueId = null;
|
||||
|
||||
// Show waiting message
|
||||
this.showWaitingMessage();
|
||||
}
|
||||
|
||||
@@ -435,13 +459,10 @@ export class NFPreviewDialog {
|
||||
timeoutSelection() {
|
||||
this.stopCountdown();
|
||||
|
||||
// On timeout, select all images or none based on setting
|
||||
const selection = []; // Default to empty selection on timeout
|
||||
// Timeout - returning empty selection
|
||||
const selection = [];
|
||||
|
||||
this.sendSelection(selection, false);
|
||||
|
||||
// If pinned, don't hide dialog - just reset for next use
|
||||
if (this.isPinned) {
|
||||
this.prepareForNextRequest();
|
||||
} else {
|
||||
@@ -457,7 +478,6 @@ export class NFPreviewDialog {
|
||||
cancelled: cancelled
|
||||
};
|
||||
|
||||
// Send response to backend
|
||||
fetch('/nf_preview_response', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -465,7 +485,6 @@ export class NFPreviewDialog {
|
||||
},
|
||||
body: JSON.stringify(response)
|
||||
}).catch(error => {
|
||||
console.error("NF Preview Dialog: Error sending response:", error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -483,7 +502,6 @@ export class NFPreviewDialog {
|
||||
centerDialog() {
|
||||
if (!this.window) return;
|
||||
|
||||
// Wait for the DOM to update and get actual dimensions
|
||||
setTimeout(() => {
|
||||
const dialogRect = this.window.getBoundingClientRect();
|
||||
const centerX = Math.max(0, (window.innerWidth - dialogRect.width) / 2);
|
||||
@@ -498,14 +516,12 @@ export class NFPreviewDialog {
|
||||
const saved = localStorage.getItem('nf-preview-dialog-position');
|
||||
if (saved) {
|
||||
const position = JSON.parse(saved);
|
||||
// Validate position is within screen bounds
|
||||
if (position.x >= 0 && position.y >= 0 &&
|
||||
position.x < window.innerWidth && position.y < window.innerHeight) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("NF Preview Dialog: Error loading saved position:", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -514,7 +530,6 @@ export class NFPreviewDialog {
|
||||
try {
|
||||
localStorage.setItem('nf-preview-dialog-position', JSON.stringify({ x, y }));
|
||||
} catch (e) {
|
||||
console.warn("NF Preview Dialog: Error saving position:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,14 +538,12 @@ export class NFPreviewDialog {
|
||||
const saved = localStorage.getItem('nf-preview-dialog-size');
|
||||
if (saved) {
|
||||
const size = JSON.parse(saved);
|
||||
// Validate size is reasonable
|
||||
if (size.width > 200 && size.height > 150 &&
|
||||
size.width < window.innerWidth + 100 && size.height < window.innerHeight + 100) {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("NF Preview Dialog: Error loading saved size:", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -539,29 +552,23 @@ export class NFPreviewDialog {
|
||||
try {
|
||||
localStorage.setItem('nf-preview-dialog-size', JSON.stringify({ width, height }));
|
||||
} catch (e) {
|
||||
console.warn("NF Preview Dialog: Error saving size:", e);
|
||||
}
|
||||
}
|
||||
|
||||
onWindowMove(x, y) {
|
||||
// Save position when user moves the dialog (only if setting is enabled)
|
||||
const rememberPosition = app.ui.settings.getSettingValue("NFPreviewSelector.UI.WindowPosition", true);
|
||||
if (rememberPosition) {
|
||||
this.savePosition(x, y);
|
||||
}
|
||||
// Window moved
|
||||
}
|
||||
|
||||
onWindowResize(width, height) {
|
||||
// Save size when user resizes the dialog (only if setting is enabled)
|
||||
const rememberPosition = app.ui.settings.getSettingValue("NFPreviewSelector.UI.WindowPosition", true);
|
||||
if (rememberPosition) {
|
||||
this.saveSize(width, height);
|
||||
}
|
||||
|
||||
// Recalculate grid layout for new window size
|
||||
if (this.imageGrid && this.imageGrid.children.length > 0) {
|
||||
// Get current aspect ratio distribution
|
||||
let landscapeCount = 0;
|
||||
let portraitCount = 0;
|
||||
|
||||
@@ -580,7 +587,6 @@ export class NFPreviewDialog {
|
||||
this.applyImageLayout(landscapeCount, portraitCount);
|
||||
}
|
||||
|
||||
// Window resized
|
||||
}
|
||||
|
||||
togglePin() {
|
||||
@@ -606,10 +612,8 @@ export class NFPreviewDialog {
|
||||
}
|
||||
|
||||
updateWithNewData(data) {
|
||||
// Stop current countdown
|
||||
this.stopCountdown();
|
||||
|
||||
// Update data
|
||||
this.reviewId = data.review_id;
|
||||
this.uniqueId = data.unique_id;
|
||||
this.images = data.images || [];
|
||||
@@ -617,7 +621,6 @@ export class NFPreviewDialog {
|
||||
this.remainingTime = this.timeout;
|
||||
this.selectedIndices.clear();
|
||||
|
||||
// Re-render images and restart countdown
|
||||
this.renderImages();
|
||||
this.updateSelectionInfo();
|
||||
this.updateConfirmButton();
|
||||
|
||||
+14
-14
@@ -26,7 +26,7 @@ async def handle_review_response(request):
|
||||
selection = data.get('selection', [])
|
||||
cancelled = data.get('cancelled', False)
|
||||
|
||||
pass # Response received
|
||||
pass
|
||||
|
||||
# Store response
|
||||
review_responses[review_id] = {
|
||||
@@ -36,7 +36,7 @@ async def handle_review_response(request):
|
||||
|
||||
return web.json_response({'status': 'success'})
|
||||
except Exception as e:
|
||||
pass # Error handled
|
||||
pass
|
||||
return web.json_response({'status': 'error', 'message': str(e)}, status=500)
|
||||
|
||||
# Register API endpoint
|
||||
@@ -78,7 +78,7 @@ class NFPreviewSelector(PreviewImage):
|
||||
def preview_images(self, images, mode, timeout, unique_id=None, latents=None, selection_indices="", prompt=None, extra_pnginfo=None):
|
||||
"""Main function for image preview and selection"""
|
||||
|
||||
pass # Processing images
|
||||
pass
|
||||
|
||||
# No flags needed anymore
|
||||
|
||||
@@ -115,9 +115,9 @@ class NFPreviewSelector(PreviewImage):
|
||||
# Use ComfyUI's message dispatch system
|
||||
try:
|
||||
PromptServer.instance.send_sync("nf_preview_request", message_data)
|
||||
pass # Request sent
|
||||
pass
|
||||
except Exception as e:
|
||||
pass # Fallback to direct WebSocket
|
||||
pass
|
||||
# Fallback to direct WebSocket
|
||||
message = {
|
||||
"type": "nf_preview_request",
|
||||
@@ -129,20 +129,20 @@ class NFPreviewSelector(PreviewImage):
|
||||
try:
|
||||
await ws.send_str(json.dumps(message))
|
||||
except Exception as e:
|
||||
pass # Send failed
|
||||
pass
|
||||
|
||||
if PromptServer.instance.loop:
|
||||
asyncio.run_coroutine_threadsafe(send_message(), PromptServer.instance.loop)
|
||||
|
||||
pass # Request sent
|
||||
pass
|
||||
|
||||
# Wait for response from frontend
|
||||
result = self.wait_for_response(review_id, timeout)
|
||||
if result == "CANCELLED":
|
||||
pass # Cancelled
|
||||
pass
|
||||
raise InterruptProcessingException()
|
||||
elif result == "TIMEOUT":
|
||||
pass # Timed out
|
||||
pass
|
||||
raise InterruptProcessingException()
|
||||
else:
|
||||
selected_indices = result
|
||||
@@ -154,7 +154,7 @@ class NFPreviewSelector(PreviewImage):
|
||||
# Process selection
|
||||
if not selected_indices:
|
||||
# For timeout or other cases, return empty tensors
|
||||
pass # No selection
|
||||
pass
|
||||
empty_image = torch.zeros((1, images.shape[1], images.shape[2], images.shape[3]))
|
||||
empty_latent = {"samples": torch.zeros((1, 4, 64, 64))} if latents is None else {"samples": torch.zeros((1, latents["samples"].shape[1], latents["samples"].shape[2], latents["samples"].shape[3]))}
|
||||
return (empty_image, empty_latent, "")
|
||||
@@ -172,7 +172,7 @@ class NFPreviewSelector(PreviewImage):
|
||||
|
||||
indices_str = ",".join(str(i) for i in selected_indices)
|
||||
|
||||
pass # Selection made
|
||||
pass
|
||||
|
||||
return {
|
||||
"result": (selected_images, selected_latents, indices_str),
|
||||
@@ -190,16 +190,16 @@ class NFPreviewSelector(PreviewImage):
|
||||
response = review_responses.pop(review_id)
|
||||
|
||||
if response['cancelled']:
|
||||
pass # Review cancelled
|
||||
pass
|
||||
return "CANCELLED"
|
||||
else:
|
||||
selection = response['selection']
|
||||
pass # Review completed
|
||||
pass
|
||||
return selection
|
||||
|
||||
time.sleep(0.1) # Poll every 100ms
|
||||
|
||||
pass # Review timed out
|
||||
pass
|
||||
return "TIMEOUT" # Indicate timeout occurred
|
||||
|
||||
# Node registration
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "Preview_Selector"
|
||||
description = "A floating dialog-based image preview and selection system for ComfyUI."
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user