Files
JaredTherriault-ComfyUI-JNodes/web/js/ImageDrawer/Contexts.js
T

887 lines
29 KiB
JavaScript

import { $el } from "/scripts/ui.js";
import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";
import * as ExtraNetworks from "./ImageListChildElements/ExtraNetworks.js";
import * as ImageElements from "./ImageListChildElements/ImageElements.js";
import * as SortTypes from "../common/SortTypes.js"
import { utilitiesInstance } from "../common/Utilities.js"
import {
createLabeledCheckboxToggle, createLabeledSliderRange, createVideoPlaybackOptionsFlyout,
options_LabeledCheckboxToggle, options_LabeledSliderRange, setting_ModelCardAspectRatio,
setting_FavouritesDirectory
} from "../common/SettingsManager.js";
import { SearchableDropDown } from "../common/SearchableDropDown.js";
export function initializeContexts(imageDrawerInstance) {
return {
feed: new ContextFeed(imageDrawerInstance),
temp: new ContextTemp(imageDrawerInstance),
favoourites: new ContextFavourites(imageDrawerInstance),
input: new ContextInput(imageDrawerInstance),
output: new ContextOutput(imageDrawerInstance),
lora: new ContextLora(imageDrawerInstance),
embeddings: new ContextEmbeddings(imageDrawerInstance),
//savedPrompts: new ContextSavedPrompts(imageDrawerInstance),
// metadata: new ContextMetadataReader(imageDrawerInstance),
// compare: new ContextCompare(imageDrawerInstance),
};
};
export class ImageDrawerContextCache {
constructor(scrollLevel, searchBarText, imageListElements, sortType, customContextCacheData = null) {
this.scrollLevel = scrollLevel;
this.searchBarText = searchBarText;
this.imageListElements = imageListElements;
this.sortType = sortType;
this.customContextCacheData = customContextCacheData;
}
};
export class ImageDrawerContext {
constructor(name, tooltip, imageDrawerInstance) {
this.name = name;
this.tooltip = tooltip;
this.imageDrawerInstance = imageDrawerInstance;
this.cache = null;
}
hasCache() {
return this.cache != null;
}
setCache(newCache) {
if (!(newCache instanceof ImageDrawerContextCache)) {
console.error("Invalid cache type. Expected ImageDrawerContextCache.");
return;
}
this.cache = newCache;
}
makeCache() {
const imageDrawerSearchInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerSearch");
const imageDrawerListSortingInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerListSorting");
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
const childNodesArray = Array.from(imageDrawerListInstance.getImageListChildren());
const newCache =
new ImageDrawerContextCache(
imageDrawerListInstance.getImageListScrollLevel(), imageDrawerSearchInstance.getSearchText(),
childNodesArray, imageDrawerListSortingInstance.getCurrentSortTypeName());
this.setCache(newCache);
}
async switchToContext(bSkipRestore = false) {
const imageDrawerListSortingInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerListSorting");
imageDrawerListSortingInstance.stopAutomaticShuffle();
const bSuccessfulRestore = bSkipRestore || await this.checkAndRestoreContextCache();
if (!bSuccessfulRestore) {
const imageDrawerSearchInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerSearch");
imageDrawerSearchInstance.clearAndExecuteSearch(); // Reset search if no cache
}
const imageDrawerMainInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerMain");
imageDrawerMainInstance.setContextToolbarWidget(await this.makeToolbar());
return bSuccessfulRestore;
}
async makeToolbar() {
const container = $el("div", { //Inner container so it can maintain 'flex' display attribute
style: {
alignItems: 'normal',
display: 'flex',
gap: '.5rem',
flex: '0 1 100%',
justifyContent: 'flex-end',
}
});
const scrollToTopButton = $el("button.JNodes-image-drawer-btn", {
textContent: "⏫",
title: "Jump to top of list",
onclick: async () => {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
imageDrawerListInstance.getImageListElement().scrollTop = 0;
scrollToTopButton.blur();
},
style: {
width: "fit-content",
padding: '3px',
},
});
container.appendChild(scrollToTopButton);
return container;
}
async checkAndRestoreContextCache() {
if (this.hasCache()) {
if (this.cache.imageListElements.length > 0) {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
const imageDrawerSearchInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerSearch");
const imageDrawerListSortingInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerListSorting");
// Replace children
imageDrawerListInstance.replaceImageListChildren(this.cache.imageListElements);
// Execute Search
imageDrawerSearchInstance.setSearchTextAndExecute(this.cache.searchBarText);
// Restore sort type
imageDrawerListSortingInstance.setOptionSelectedFromOptionName(this.cache.sortType);
// Restore scroll level
imageDrawerListInstance.setImageListScrollLevel(this.cache.scrollLevel);
return true;
}
}
return false;
}
onRequestShowInFileManager(item) {
if (item && item.showInFileManager) {
item.showInFileManager();
}
}
onRequestSingleDeletion(item) {
if (item && item.deleteItem) {
item.deleteItem();
}
}
onRequestBatchDeletion() {
const batchSelectionManagerInstance = this.imageDrawerInstance.getComponentByName("BatchSelectionManager");
for (const child of batchSelectionManagerInstance.getValidSelectedItems()) {
this.onRequestSingleDeletion(child);
}
}
onRequestSingleRemoval(item) {
if (item && item.removeItemFromImageList) {
item.removeItemFromImageList();
}
}
onRequestBatchRemoval() {
const batchSelectionManagerInstance = this.imageDrawerInstance.getComponentByName("BatchSelectionManager");
for (const child of batchSelectionManagerInstance.getValidSelectedItems()) {
this.onRequestSingleRemoval(child);
}
}
onRequestSingleFavourite(item) {
if (item && item.copyItem) {
item.copyItem(setting_FavouritesDirectory.value);
}
}
onRequestBatchFavourite() {
const batchSelectionManagerInstance = this.imageDrawerInstance.getComponentByName("BatchSelectionManager");
for (const child of batchSelectionManagerInstance.getValidSelectedItems()) {
this.onRequestSingleFavourite(child);
}
}
getSupportedSortTypes() {
return [SortTypes.SortTypeFilename, SortTypes.SortTypeDate, SortTypes.SortTypeShuffle];
}
getDesiredSortType() {
return this.cache?.sortType || this.getDefaultSortType();
}
getDefaultSortType() {
return { type: SortTypes.SortTypeFilename, bIsAscending: true };
}
shouldCancelAsyncOperation() {
const imageDrawerContextSelectorInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerContextSelector");
return imageDrawerContextSelectorInstance.getCurrentContextObject() != this; // By default, cancel async operations if the selected context has changed
}
}
export class ContextClearable extends ImageDrawerContext {
async onClearClicked() { }
async makeToolbar() {
// Remove all images from the list
let clearButton = $el("button.JNodes-image-drawer-btn", {
textContent: "Clear",
onclick: async () => {
await this.onClearClicked();
clearButton.blur();
},
style: {
width: "fit-content",
padding: '3px',
},
});
const finalWidget = await super.makeToolbar();
finalWidget.insertBefore(clearButton, finalWidget.firstChild);
const flyoutHandle = createVideoPlaybackOptionsFlyout().handle;
finalWidget.insertBefore(flyoutHandle, finalWidget.firstChild);
return finalWidget;
}
getSupportedSortTypes() {
const NewSortTypes = [
SortTypes.SortTypeFileSize, SortTypes.SortTypeImageWidth,
SortTypes.SortTypeImageHeight, SortTypes.SortTypeImageAspectRatio, SortTypes.SortTypeFileType
];
return super.getSupportedSortTypes().concat(NewSortTypes);
}
}
export class ContextRefreshable extends ImageDrawerContext {
async onRefreshClicked() {
const imageDrawerListSortingInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerListSorting");
imageDrawerListSortingInstance.stopAutomaticShuffle();
imageDrawerListSortingInstance.sortWithCurrentType();
}
async makeToolbar() {
// Refresh button
let refreshButton = $el("button.JNodes-image-drawer-btn", {
textContent: "Refresh",
onclick: async () => {
await this.onRefreshClicked();
refreshButton.blur();
},
style: {
width: "fit-content",
padding: '3px',
},
});
const finalWidget = await super.makeToolbar();
finalWidget.insertBefore(refreshButton, finalWidget.firstChild);
return finalWidget;
}
}
export class ContextModel extends ContextRefreshable {
constructor(name, description, imageDrawerInstance, type) {
super(name, description, imageDrawerInstance);
this.type = type;
}
async getModels(bForceRefresh = false) { }
async loadModels(bForceRefresh = false) {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
imageDrawerListInstance.clearImageListChildren();
await imageDrawerListInstance.addElementToImageList($el("label", { textContent: `Loading ${this.name}...` }));
let modelDicts = await this.getModels(bForceRefresh);
if (this.shouldCancelAsyncOperation()) { return; }
imageDrawerListInstance.clearImageListChildren(); // Remove loading indicator
//console.log("modelDicts: " + JSON.stringify(loraDicts));
const modelKeys = Object.keys(modelDicts);
if (modelKeys.length > 0) {
let count = 0;
let maxCount = 0;
imageDrawerListInstance.notifyStartChangingImageList();
for (const modelKey of modelKeys) {
if (this.shouldCancelAsyncOperation()) { break; }
if (maxCount > 0 && count > maxCount) { break; }
let element = await ExtraNetworks.createExtraNetworkCard(modelKey, modelDicts[modelKey], this.type, this.imageDrawerInstance);
if (element == undefined) {
console.log("Attempting to add undefined element for model named: " + modelKey + " with dict: " + JSON.stringify(modelDicts[modelKey]));
}
await imageDrawerListInstance.addElementToImageList(element);
count++;
}
imageDrawerListInstance.notifyFinishChangingImageList();
}
else {
imageDrawerListInstance.notifyStartChangingImageList();
await imageDrawerListInstance.addElementToImageList($el("label", { textContent: "No models were found." }));
imageDrawerListInstance.notifyFinishChangingImageList();
}
}
async switchToContext() {
if (!await super.switchToContext()) {
await this.loadModels();
}
}
async makeToolbar() {
const container = await super.makeToolbar();
let options = new options_LabeledSliderRange();
options.labelTextContent = 'Card Aspect Ratio:'
options.id = 'ModelCardAspectRatioSlider';
options.value = setting_ModelCardAspectRatio.value;
options.min = 0.1;
options.max = 2;
options.step = 0.01;
options.bIncludeValueLabel = true;
options.valueLabelFractionalDigits = 2;
options.oninput = async (e) => {
setting_ModelCardAspectRatio.value = e.target.valueAsNumber;
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
for (let element of imageDrawerListInstance.getImageListChildren()) {
if (element.classList.contains('extraNetworksCard')) {
element.style.aspectRatio = setting_ModelCardAspectRatio.value;
}
}
};
container.insertBefore(createLabeledSliderRange(options), container.firstChild);
return container;
}
async onRefreshClicked() {
await this.loadModels(true);
super.onRefreshClicked();
}
onRequestSingleDeletion(item) {
// Unimplemented
}
onRequestBatchDeletion() {
// Unimplemented
}
onRequestSingleRemoval(item) {
// Unimplemented
}
onRequestBatchRemoval() {
// Unimplemented
}
getSupportedSortTypes() {
return super.getSupportedSortTypes().concat([SortTypes.SortTypeFriendlyName]);
}
getDefaultSortType() {
return { type: SortTypes.SortTypeFriendlyName, bIsAscending: true };
}
}
export class ContextSubdirectoryExplorer extends ContextRefreshable {
constructor(name, description, imageDrawerInstance, directoryName, bShouldForceLoad = false) {
super(name, description, imageDrawerInstance);
this.rootDirectoryName = directoryName;
this.rootDirectoryDisplayName = '/root';
this.bIncludeSubdirectories = false;
this.fileList = null;
this.subdirectorySelector = null;
this.bShouldForceLoad = bShouldForceLoad; // Whether or not to lazy load. Lazy load = !bShouldForceLoad
}
async updateSubdirectorySelectorOptions() {
// Fill out combo box options based on paths
const lastSelectedValue = this.subdirectorySelector.data.getSelectedOptionName(); // Cache last selection
this.subdirectorySelector.data.clearOptions();
// Indicate loading
const loadingIndicatorText = "Loading...";
this.subdirectorySelector.data.addOptionUnique(loadingIndicatorText, loadingIndicatorText);
this.subdirectorySelector.data.setOptionSelected(loadingIndicatorText);
const subdirectoriesResponse = await api.fetchApi(
`/jnodes_list_comfyui_subdirectories?root_directory=${this.rootDirectoryName}`, { method: "GET", cache: "no-store" });
let subdirectories;
try {
// Decode into a string
let decodedString = await utilitiesInstance.decodeReadableStream(subdirectoriesResponse.body);
decodedString = utilitiesInstance.sanitizeMetadataForJson(decodedString);
const asJson = JSON.parse(decodedString);
if (asJson.success && asJson.payload) {
subdirectories = asJson.payload;
}
} catch (e) {
console.error(`Could not get list of files when loading "${this.rootDirectoryName}": ${e}`)
}
this.subdirectorySelector.data.clearOptions();
for (let directoryIndex = 0; directoryIndex < subdirectories.length; directoryIndex++) {
const bIsRoot = directoryIndex == 0;
const result = subdirectories[directoryIndex];
const path = bIsRoot ? this.rootDirectoryDisplayName : result;
this.subdirectorySelector.data.addOptionUnique(path, bIsRoot ? '' : path);
}
// Restore last selected value if it exists in the new combo options
if (this.subdirectorySelector.data.hasOption(lastSelectedValue)) {
this.subdirectorySelector.data.setOptionSelected(lastSelectedValue);
} else {
// Otherwise just set it to the first option
const newOptionNames = this.subdirectorySelector.data.getOptionNames();
if (newOptionNames.length > 0) {
this.subdirectorySelector.data.setOptionSelected(newOptionNames[0]);
}
}
}
// Get the image paths in the folder or directory specified at this.folderName
// as well as all subdirectories then load the images in a given subdirectory
async fetchFolderItems(selectedSubdirectory = "") {
const imageDrawerListSortingInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerListSorting");
imageDrawerListSortingInstance.stopAutomaticShuffle();
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
imageDrawerListInstance.clearImageListChildren();
const withOrWithout = this.bIncludeSubdirectories ? "with" : "without";
// todo: Python cancellation needs some work
// const cancelButton = $el("button.JNodes-image-drawer-btn", {
// textContent: 'Cancel',
// onclick: async () => {
// await api.fetchApi(
// '/jnodes_request_task_cancellation', { method: "POST" }); // Cancel any outstanding python task
// cancelButton.textContent = 'Canceling...';
// },
// style: {
// width: "fit-content",
// padding: '3px',
// },
// });
imageDrawerListInstance.addElementToImageList(
$el('div', [
$el("label", {
textContent:
`Loading directory '${selectedSubdirectory || this.rootDirectoryName}' ${withOrWithout} subdirectories...`
}),
//cancelButton
])
);
const allItems = await api.fetchApi(
'/jnodes_get_comfyui_subdirectory_images' +
`?root_directory=${this.rootDirectoryName}` +
`&selected_subdirectory=${selectedSubdirectory}` +
`&recursive=${this.bIncludeSubdirectories}`, { cache: "no-store" });
imageDrawerListInstance.clearImageListChildren();
let decodedString;
try {
// Decode into a string
decodedString = await utilitiesInstance.decodeReadableStream(allItems.body);
decodedString = utilitiesInstance.sanitizeMetadataForJson(decodedString);
const asJson = JSON.parse(decodedString);
this.fileList = asJson.payload;
} catch (e) {
console.error(`Could not get list of images when loading "${this.rootDirectoryName}": ${e}`)
this.fileList = []; // Set an empty list on failure. This allows the function to complete without further failures.
}
// Load root folder if no path is specified (even if there are no images within)
await this.loadImagesInFolder(selectedSubdirectory);
this.updateSubdirectorySelectorOptions();
}
async loadImagesInFolder(selectedSubdirectory) {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
if (!this.fileList || this.fileList.length == 0) {
await imageDrawerListInstance.addElementToImageList(
$el('div', [
$el("label", {
textContent:
`No images or videos found in '${selectedSubdirectory == "" ? this.rootDirectoryDisplayName : selectedSubdirectory}'`
}),
//cancelButton
])
);
return;
}
if (this.shouldCancelAsyncOperation()) { return; }
const createElementFromFile = async (file) => {
let element = await ImageElements.createImageElementFromFileInfo({
filename: file.item,
file: file,
type: this.rootDirectoryName,
subdirectory: file.subdirectory ? file.subdirectory : selectedSubdirectory,
bShouldForceLoad: this.bShouldForceLoad,
bShouldSort: false,
bShouldApplySearch: false,
}, this.imageDrawerInstance);
if (element !== undefined) {
imageDrawerListInstance.addElementToImageList(element, false);
} else {
console.log(`Attempted to add undefined image element in ${this.name}`);
}
};
imageDrawerListInstance.notifyStartChangingImageList();
const promises = [];
for (let fileIndex = 0; fileIndex < this.fileList.length; fileIndex++) {
if (this.shouldCancelAsyncOperation()) { break; }
const file = this.fileList[fileIndex];
// Push the promise to the array
promises.push(createElementFromFile(file));
}
// Wait for all promises to resolve
Promise.all(promises).then(() => {
const imageDrawerListSortingInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerListSorting");
imageDrawerListSortingInstance.sortWithCurrentType();
const imageDrawerSearchInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerSearch");
imageDrawerSearchInstance.executeSearchWithEnteredSearchText();
imageDrawerListInstance.notifyFinishChangingImageList();
});
}
async makeToolbar() {
const container = await super.makeToolbar();
const flyoutHandle = createVideoPlaybackOptionsFlyout().handle;
container.insertBefore(flyoutHandle, container.firstChild);
flyoutHandle.determineTransformLayout(); // Call immediately after parenting to avoid first caling being from the center
let includeSubdirectoriesToggleOptions = new options_LabeledCheckboxToggle();
includeSubdirectoriesToggleOptions.labelTextContent = 'Include Subdirectories';
includeSubdirectoriesToggleOptions.id = 'IncludeSubdirectoriesToggle';
includeSubdirectoriesToggleOptions.checked = this.bIncludeSubdirectories;
includeSubdirectoriesToggleOptions.oninput = async (e) => {
this.bIncludeSubdirectories = e.target.checked;
const selectedOption = this.subdirectorySelector.data.getSelectedOptionElement();
const selectedValue = selectedOption.value;
await this.fetchFolderItems(selectedValue);
}
const IncludeSubdirectoriesToggle = createLabeledCheckboxToggle(includeSubdirectoriesToggleOptions);
container.insertBefore(IncludeSubdirectoriesToggle, container.firstChild);
const searchableDropDown = new SearchableDropDown();
this.subdirectorySelector = searchableDropDown.createSearchableDropDown();
this.subdirectorySelector.style.width = "100%";
this.subdirectorySelector.addEventListener("selectoption", async () => {
// Force subdirectory inclusion off to avoid OOM - user must opt-in explicitly each time
this.bIncludeSubdirectories = false;
IncludeSubdirectoriesToggle.getMainElement().checked = false;
const selectedOption = this.subdirectorySelector.data.getSelectedOptionElement();
const selectedValue = selectedOption.value;
// await api.fetchApi(
// '/jnodes_request_task_cancellation', { method: "POST" }); // Cancel any outstanding python task
await this.fetchFolderItems(selectedValue);
});
container.insertBefore(this.subdirectorySelector, container.firstChild);
return container;
}
makeCache() {
super.makeCache();
this.cache.customContextCacheData = {
selectedSubdirectory: this.subdirectorySelector?.data?.getSelectedOptionName(), subdirectorySearchToken: this.subdirectorySelector?.data?.getFilterText()
};
}
async switchToContext() {
if (!await super.switchToContext()) {
await this.fetchFolderItems(); // updateSubdirectorySelector is called in fetchFolderItems
return;
}
await this.updateSubdirectorySelectorOptions(); // If we're not calling fetchFolderItems because we're restoring a cache, call updateSubdirectorySelector
if (this.cache?.customContextCacheData?.selectedSubdirectory) { // Restore subdirectory selection from custom cache data
this.subdirectorySelector.data.setOptionSelected(this.cache.customContextCacheData.selectedSubdirectory);
}
if (this.cache?.customContextCacheData?.subdirectorySearchToken) { // Restore subdirectory selection from custom cache data
this.subdirectorySelector.data.setFilterTextAndExecuteSearch(this.cache.customContextCacheData.subdirectorySearchToken);
}
}
async onRefreshClicked() {
await this.fetchFolderItems(this.subdirectorySelector.data.getSelectedOptionElement().value);
await super.onRefreshClicked();
}
getSupportedSortTypes() {
const NewSortTypes = [
SortTypes.SortTypeFileSize, SortTypes.SortTypeImageWidth,
SortTypes.SortTypeImageHeight, SortTypes.SortTypeImageAspectRatio, SortTypes.SortTypeFileType
];
return super.getSupportedSortTypes().concat(NewSortTypes);
}
}
export class ContextFeed extends ContextClearable {
constructor(imageDrawerInstance) {
super("Feed", "The latest generations from this web session (cleared on page refresh)", imageDrawerInstance);
this.feedImages = [];
// Automatically update feed if it's the active context
api.addEventListener("executed", async ({ detail }) => {
const outImages = detail?.output?.images;
if (outImages) {
const node = app.graph.getNodeById(detail.node);
if (node.type == "PreviewImage") { return; } // todo: Make this configurable
for (const src of outImages) {
// Always add feed images to the record, but only add thumbs to the imageList if
// we're currently in feed mode. Otherwise they'll be added when switching to feed.
src.file_age = Date.now(); // Get time of creation since the last epoch, in milliseconds. For sorting.
this.feedImages.push(src);
}
const imageDrawerContextSelectorInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerContextSelector");
if (imageDrawerContextSelectorInstance.getCurrentContextName() == this.name) {
await this.addNewUncachedFeedImages();
}
// utilitiesInstance.tryFreeMemory(false, true, false);
}
});
}
async addNewUncachedFeedImages(bShouldSort = true, bShouldApplySearch = true) {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
const imageListLength = imageDrawerListInstance.getImageListChildren().length;
if (imageListLength < this.feedImages.length) {
imageDrawerListInstance.notifyStartChangingImageList();
for (let imageIndex = imageListLength; imageIndex < this.feedImages.length; imageIndex++) {
if (this.shouldCancelAsyncOperation()) { break; }
let fileInfo = this.feedImages[imageIndex];
fileInfo.file = fileInfo;
fileInfo.bShouldForceLoad = true; // Don't lazy load
const element = await ImageElements.createImageElementFromFileInfo(fileInfo, this.imageDrawerInstance);
if (element == undefined) { console.log(`Attempting to add undefined image element in ${this.name}`); }
const bHandleSearch = false;
await imageDrawerListInstance.addElementToImageList(element, bHandleSearch);
}
if (bShouldSort) {
const imageDrawerListSortingInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerListSorting");
imageDrawerListSortingInstance.sortWithCurrentType();
}
if (bShouldApplySearch) {
const imageDrawerSearchInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerSearch");
imageDrawerSearchInstance.executeSearchWithEnteredSearchText();
}
imageDrawerListInstance.notifyFinishChangingImageList();
}
}
async switchToContext() {
if (!await super.switchToContext()) {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
imageDrawerListInstance.clearImageListChildren();
}
await this.addNewUncachedFeedImages();
}
async onClearClicked() {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
imageDrawerListInstance.clearImageListChildren();
this.feedImages = [];
}
onRequestSingleDeletion(item) {
this.removeItemFromFeed(item);
if (item && item.deleteItem) {
const bRemoveFromImageList = false; // Don't do it again, that was handled above
item.deleteItem(bRemoveFromImageList);
}
}
onRequestBatchDeletion() {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
for (const child of imageDrawerListInstance.getImageListChildren()) {
this.onRequestSingleDeletion(child);
}
}
onRequestSingleRemoval(item) {
this.removeItemFromFeed(item);
}
onRequestBatchRemoval() {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
for (const child of imageDrawerListInstance.getImageListChildren()) {
this.onRequestSingleRemoval(child);
}
}
getDefaultSortType() {
return { type: SortTypes.SortTypeDate, bIsAscending: false };
}
removeItemFromFeed(item) {
super.onRequestSingleRemoval(item);
if (item.img?.src) {
this.feedImages = this.feedImages.filter(src => src !== item.img.src); // Remove matching feed sources
}
}
}
export class ContextTemp extends ContextSubdirectoryExplorer {
constructor(imageDrawerInstance) {
const bShouldForceLoad = true; // These need to be searchable by meta data
super("Temp / History", "The generations you've created since the last comfyUI server restart", imageDrawerInstance, "temp", bShouldForceLoad);
}
getDefaultSortType() {
return { type: SortTypes.SortTypeDate, bIsAscending: false };
}
}
export class ContextFavourites extends ContextSubdirectoryExplorer {
constructor(imageDrawerInstance) {
const bShouldForceLoad = true; // These need to be searchable by meta data
// Update rootDirectoryName when this setting changes
setting_FavouritesDirectory.setOnChange((newValue) => { this.rootDirectoryName = newValue; })
super("Favourites", "The generations you've copied to the Favourites directory defined in Settings > JNodes Settings", imageDrawerInstance, setting_FavouritesDirectory.value, bShouldForceLoad);
}
}
export class ContextInput extends ContextSubdirectoryExplorer {
constructor(imageDrawerInstance) {
super("Input", "Images and videos found in your input folder", imageDrawerInstance, "input");
}
}
export class ContextOutput extends ContextSubdirectoryExplorer {
constructor(imageDrawerInstance) {
const bShouldForceLoad = true; // These need to be searchable by meta data
super("Output", "Images and videos found in your output folder", imageDrawerInstance, "output", bShouldForceLoad);
}
}
export class ContextLora extends ContextModel {
constructor(imageDrawerInstance) {
super("Lora / Lycoris", "Lora and Lycoris models found in your Lora directory", imageDrawerInstance, "loras");
}
async getModels(bForceRefresh = false) {
return await ExtraNetworks.getLoras(bForceRefresh);
}
}
export class ContextEmbeddings extends ContextModel {
constructor(imageDrawerInstance) {
super("Embeddings / Textual Inversions", "Embedding/textual inversion models found in your embeddings directory", imageDrawerInstance, "embeddings");
}
async getModels(bForceRefresh = false) {
return await ExtraNetworks.getEmbeddings(bForceRefresh);
}
}
export class ContextSavedPrompts extends ContextSubdirectoryExplorer {
constructor(imageDrawerInstance) {
super(
"Saved Prompts",
"Images and videos found in the JNodes/saved_prompts folder and its subdirectories. Title comes from filename",
imageDrawerInstance,
"JNodes/saved_prompts");
}
}
export class ContextMetadataReader extends ImageDrawerContext {
constructor(imageDrawerInstance) {
super(
"Metadata Reader",
"Read and display metadata from a generation",
imageDrawerInstance);
}
getSupportedSortTypes() {
return [];
}
}
export class ContextCompare extends ContextClearable {
constructor(imageDrawerInstance) {
super(
"Compare",
"Compare generations sent to this context via menu. Does not persist on refresh.",
imageDrawerInstance
);
}
async switchToContext() {
if (!await super.switchToContext()) {
const imageDrawerListInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerList");
imageDrawerListInstance.clearImageListChildren();
}
//await this.addNewUncachedFeedImages();
}
async onClearClicked() {
}
getSupportedSortNames() {
return [];
}
}