Initial release of ComfyUI Subfolder Image Loader
Features: - Subfolder navigation and dynamic image filtering - Support for all common image formats - Transparency mask extraction - Security validation and error handling - JavaScript frontend for real-time UI updates
This commit is contained in:
150
web/js/subfolder_image_loader.js
Normal file
150
web/js/subfolder_image_loader.js
Normal file
@@ -0,0 +1,150 @@
|
||||
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);
|
||||
};
|
||||
|
||||
// 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) {
|
||||
try {
|
||||
// Get the image widget
|
||||
const imageWidget = this.widgets?.find(w => w.name === "image");
|
||||
if (!imageWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.success) {
|
||||
console.error("SubfolderImageLoader: Server error:", data.error);
|
||||
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 - always use first image from filtered list
|
||||
const newValue = filteredImages.length > 0 ? filteredImages[0] : "";
|
||||
|
||||
// Create new widget with updated options
|
||||
const newWidget = this.addWidget(
|
||||
"combo",
|
||||
"image",
|
||||
newValue,
|
||||
(value) => {
|
||||
// Call original callback if it existed
|
||||
if (oldCallback) {
|
||||
oldCallback.call(newWidget, value);
|
||||
}
|
||||
},
|
||||
{
|
||||
values: filteredImages.length > 0 ? filteredImages : [""]
|
||||
}
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
// Trigger the widget callback to ensure the change is registered
|
||||
if (newWidget.callback) {
|
||||
newWidget.callback(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);
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user