Add image preview functionality and fix subfolder filtering
- Add image preview display matching standard LoadImage node behavior - Fix image path handling to use ComfyUI's folder_paths.get_annotated_filepath - Enable image_upload widget capability - Implement delayed update strategy to override ComfyUI's automatic image loading - Add race condition protection for image preview updates - Maintain subfolder prefix in image names for proper ComfyUI compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,7 @@ class SubfolderImageLoader:
|
||||
}),
|
||||
"image": (default_images if default_images else [""], {
|
||||
"default": default_images[0] if default_images else "",
|
||||
"image_upload": True,
|
||||
"tooltip": "Choose an image from the selected subfolder. This list is filtered based on your subfolder selection."
|
||||
}),
|
||||
},
|
||||
@@ -106,9 +107,9 @@ class SubfolderImageLoader:
|
||||
# Root folder - show only images without subfolder (no slash)
|
||||
return [img for img in all_images if '/' not in img]
|
||||
else:
|
||||
# Specific subfolder - show only images from that subfolder, without prefix
|
||||
# Specific subfolder - show images WITH subfolder prefix for ComfyUI compatibility
|
||||
prefix = subfolder + "/"
|
||||
filtered = [img[len(prefix):] for img in all_images
|
||||
filtered = [img for img in all_images
|
||||
if img.startswith(prefix) and '/' not in img[len(prefix):]]
|
||||
return filtered
|
||||
|
||||
@@ -264,34 +265,20 @@ class SubfolderImageLoader:
|
||||
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
|
||||
# Handle the case where image might contain subfolder prefix
|
||||
clean_image = image
|
||||
actual_subfolder = subfolder
|
||||
# The image parameter now contains the full path (subfolder/filename) when applicable
|
||||
# Use it directly as the image identifier for ComfyUI
|
||||
image_identifier = image
|
||||
|
||||
# If image contains a path separator, extract the subfolder and filename
|
||||
# Extract clean filename for return value
|
||||
if '/' in image:
|
||||
parts = image.split('/')
|
||||
if len(parts) == 2:
|
||||
potential_subfolder, clean_image = parts
|
||||
# Use the subfolder from the image path if no subfolder is explicitly set
|
||||
if not actual_subfolder:
|
||||
actual_subfolder = potential_subfolder
|
||||
|
||||
# Build the full path
|
||||
if actual_subfolder:
|
||||
file_path = os.path.join(input_dir, actual_subfolder, clean_image)
|
||||
clean_image = parts[-1] # Get the filename part
|
||||
else:
|
||||
file_path = os.path.join(input_dir, clean_image)
|
||||
clean_image = image
|
||||
|
||||
# Validate path is safe
|
||||
file_path_abs = os.path.abspath(file_path)
|
||||
input_dir_abs = os.path.abspath(input_dir)
|
||||
if not file_path_abs.startswith(input_dir_abs):
|
||||
raise ValueError("Invalid file path: outside input directory")
|
||||
|
||||
# Check file exists
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"Image file not found: {file_path}")
|
||||
# Use ComfyUI's standard method to get the full file path
|
||||
# This handles the path resolution and validation automatically
|
||||
file_path = folder_paths.get_annotated_filepath(image_identifier)
|
||||
|
||||
# Load and process image
|
||||
image_tensor, mask_tensor = self.process_image(file_path, load_mask)
|
||||
|
||||
@@ -27,6 +27,74 @@ app.registerExtension({
|
||||
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);
|
||||
@@ -41,10 +109,18 @@ app.registerExtension({
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -62,12 +138,14 @@ app.registerExtension({
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -93,13 +171,19 @@ app.registerExtension({
|
||||
"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 : [""]
|
||||
values: filteredImages.length > 0 ? filteredImages : [""],
|
||||
image_upload: true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -112,10 +196,12 @@ app.registerExtension({
|
||||
// Force update the node's properties
|
||||
this.setProperty("image", newValue);
|
||||
|
||||
// Trigger the widget callback to ensure the change is registered
|
||||
if (newWidget.callback) {
|
||||
newWidget.callback(newValue);
|
||||
}
|
||||
// Use requestAnimationFrame for initial image display
|
||||
requestAnimationFrame(() => {
|
||||
if (newValue && this.showImage) {
|
||||
this.showImage(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Force UI update
|
||||
@@ -126,6 +212,9 @@ app.registerExtension({
|
||||
|
||||
} catch (error) {
|
||||
console.error("SubfolderImageLoader: Error updating image list:", error);
|
||||
} finally {
|
||||
// Always clear the flag when done
|
||||
this._updatingImageList = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user