256 lines
13 KiB
JavaScript
256 lines
13 KiB
JavaScript
import { app } from "../../../scripts/app.js";
|
|
import { api } from "../../../scripts/api.js";
|
|
|
|
// Extension for SubfolderImageLoader dynamic input filtering
|
|
app.registerExtension({
|
|
name: "comfyui.SubfolderImageLoader",
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData.name === "SubfolderImageLoader") {
|
|
const originalNodeCreated = nodeType.prototype.onNodeCreated;
|
|
|
|
nodeType.prototype.onNodeCreated = function() {
|
|
const result = originalNodeCreated?.apply(this, arguments);
|
|
|
|
// Setup widget callbacks after node is fully created
|
|
setTimeout(() => {
|
|
const subfolderWidget = this.widgets?.find(w => w.name === "subfolder");
|
|
|
|
if (subfolderWidget) {
|
|
const originalCallback = subfolderWidget.callback;
|
|
|
|
subfolderWidget.callback = (value, ...args) => {
|
|
if (originalCallback) {
|
|
originalCallback.call(subfolderWidget, value, ...args);
|
|
}
|
|
|
|
this.updateImageList(value);
|
|
};
|
|
|
|
// Flag to prevent concurrent updateImageList calls
|
|
this._updatingImageList = false;
|
|
|
|
// Track all pending image loads so we can cancel them
|
|
this._pendingImages = [];
|
|
|
|
// Disable ComfyUI's automatic image loading to prevent interference
|
|
this.imageIndex = null; // Remove automatic image loading
|
|
|
|
// Override onLoaded function to prevent automatic loading
|
|
this.onLoaded = function() {
|
|
// Do nothing - prevent ComfyUI's automatic image loading
|
|
};
|
|
|
|
// Simple image array for preview functionality
|
|
this.imgs = [];
|
|
|
|
// showImage function with delayed updates to override ComfyUI's automatic loading
|
|
this.showImage = function(name) {
|
|
// Cancel ALL previous pending image loads
|
|
this._pendingImages.forEach(pendingImg => {
|
|
pendingImg.onload = null;
|
|
pendingImg.onerror = null;
|
|
pendingImg.src = ''; // Stop loading
|
|
});
|
|
this._pendingImages = [];
|
|
|
|
const img = new Image();
|
|
|
|
// Add to pending list
|
|
this._pendingImages.push(img);
|
|
|
|
img.onload = () => {
|
|
// Only update if this image is still in the pending list (not cancelled)
|
|
if (this._pendingImages.includes(img)) {
|
|
// Apply multiple delayed updates to override ComfyUI's persistent automatic loading
|
|
[100, 200, 300].forEach(delay => {
|
|
setTimeout(() => {
|
|
// Check if this image is still the most recent one
|
|
if (this._pendingImages.includes(img)) {
|
|
this.imgs = [img];
|
|
app.graph.setDirtyCanvas(true);
|
|
}
|
|
}, delay);
|
|
});
|
|
|
|
// Remove from pending list after all delays
|
|
setTimeout(() => {
|
|
const index = this._pendingImages.indexOf(img);
|
|
if (index > -1) {
|
|
this._pendingImages.splice(index, 1);
|
|
}
|
|
}, 350);
|
|
}
|
|
};
|
|
|
|
if (name) {
|
|
let subfolder = this.widgets?.find(w => w.name === "subfolder")?.value ?? "";
|
|
// Extract filename from path if needed
|
|
const filename = name.includes('/') ? name.split('/').pop() : name;
|
|
const imageUrl = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`);
|
|
img.src = imageUrl;
|
|
} else {
|
|
this.imgs = [];
|
|
app.graph.setDirtyCanvas(true);
|
|
}
|
|
};
|
|
|
|
// Also run initial filtering based on current subfolder value
|
|
const currentSubfolder = subfolderWidget.value || "";
|
|
this.updateImageList(currentSubfolder);
|
|
|
|
} else {
|
|
console.error("SubfolderImageLoader: Could not find subfolder widget!");
|
|
}
|
|
}, 100);
|
|
|
|
return result;
|
|
};
|
|
|
|
// Method to update image list based on selected subfolder
|
|
nodeType.prototype.updateImageList = async function(selectedSubfolder) {
|
|
// Prevent concurrent updateImageList calls
|
|
if (this._updatingImageList) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Set flag to indicate we're updating the list programmatically
|
|
this._updatingImageList = true;
|
|
// Get the image widget
|
|
const imageWidget = this.widgets?.find(w => w.name === "image");
|
|
if (!imageWidget) {
|
|
this._updatingImageList = false;
|
|
return;
|
|
}
|
|
|
|
// Try to preserve the current image selection
|
|
const previousValue =
|
|
imageWidget.value ??
|
|
this.properties?.image ??
|
|
"";
|
|
|
|
// Fetch updated image list from the backend
|
|
const response = await fetch("/subfolder_loader/refresh", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
node_id: this.id,
|
|
subfolder: selectedSubfolder || ""
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error("SubfolderImageLoader: Failed to refresh image list:", response.statusText);
|
|
this._updatingImageList = false;
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
if (!data.success) {
|
|
console.error("SubfolderImageLoader: Server error:", data.error);
|
|
this._updatingImageList = false;
|
|
return;
|
|
}
|
|
|
|
// Use the pre-filtered images from the server
|
|
const filteredImages = data.filtered_images || [];
|
|
|
|
// Remove and recreate the image widget
|
|
const imageIndex = this.widgets.findIndex(w => w.name === "image");
|
|
if (imageIndex >= 0) {
|
|
// Store the old widget's callback for reference
|
|
const oldWidget = this.widgets[imageIndex];
|
|
const oldCallback = oldWidget.callback;
|
|
|
|
// Remove the old widget
|
|
this.widgets.splice(imageIndex, 1);
|
|
|
|
// Determine the new value:
|
|
// 1. If previous value is still valid, keep it
|
|
// 2. Otherwise, fall back to first filtered image
|
|
let newValue = "";
|
|
if (previousValue && filteredImages.includes(previousValue)) {
|
|
newValue = previousValue;
|
|
} else if (filteredImages.length > 0) {
|
|
newValue = filteredImages[0];
|
|
}
|
|
|
|
// Create new widget with updated options
|
|
const newWidget = this.addWidget(
|
|
"combo",
|
|
"image",
|
|
newValue,
|
|
(value) => {
|
|
// Always show image preview when widget value changes
|
|
if (this.showImage) {
|
|
this.showImage(value);
|
|
}
|
|
|
|
// Call original callback if it existed
|
|
if (oldCallback) {
|
|
oldCallback.call(newWidget, value);
|
|
}
|
|
},
|
|
{
|
|
values: filteredImages.length > 0 ? filteredImages : [""],
|
|
image_upload: true
|
|
}
|
|
);
|
|
|
|
// Ensure widget and node property reflect the preserved value
|
|
newWidget.value = newValue;
|
|
|
|
// Move the new widget to the correct position
|
|
if (imageIndex < this.widgets.length - 1) {
|
|
const widget = this.widgets.pop();
|
|
this.widgets.splice(imageIndex, 0, widget);
|
|
}
|
|
|
|
// Force update the node's properties
|
|
this.setProperty("image", newValue);
|
|
|
|
// Use requestAnimationFrame for initial image display
|
|
requestAnimationFrame(() => {
|
|
if (newValue && this.showImage) {
|
|
this.showImage(newValue);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Force UI update
|
|
// Note: ComfyUI has a quirk where combo widget visual updates sometimes require
|
|
// mouse movement to refresh the display. The functionality works correctly,
|
|
// but the visual update may be delayed until the next mouse interaction.
|
|
this.setDirtyCanvas(true, true);
|
|
|
|
} catch (error) {
|
|
console.error("SubfolderImageLoader: Error updating image list:", error);
|
|
} finally {
|
|
// Always clear the flag when done
|
|
this._updatingImageList = false;
|
|
}
|
|
};
|
|
|
|
// Override the original getExtraMenuOptions to add refresh option
|
|
const originalGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
|
nodeType.prototype.getExtraMenuOptions = function(_, options) {
|
|
const result = originalGetExtraMenuOptions?.apply(this, arguments) || [];
|
|
|
|
options.push({
|
|
content: "🔄 Refresh Files",
|
|
callback: () => {
|
|
const subfolderWidget = this.widgets?.find(w => w.name === "subfolder");
|
|
const currentSubfolder = subfolderWidget?.value || "";
|
|
this.updateImageList(currentSubfolder);
|
|
}
|
|
});
|
|
|
|
return result;
|
|
};
|
|
}
|
|
}
|
|
});
|