Files
JaredTherriault-ComfyUI-JNodes/web/js/ImageDrawer/Core/ImageDrawerModule.js
T
JaredTherriault f0338b0c5f Batch selection, batch deletion, classifying some scripts, module manager
-Added new batch selection and batch deletion features. All imageElements now have a checkbox and each selected element is tracked within a context or folder. When you've selected all you want to delete, click the recycle button on the toolbar, then again to confirm. All checked items will be recylced/deleted. You can also click on the selection widget next to the batch delete button to toggle select/deselect all.
-Renamed ImageDrawer.js to ImageDrawerMain.js
-Separated ImageListAndSearch out into separate scripts and classes named ImageDrawerList and ImageDrawerSearch
-Changed order of some initialized functions and variables in imageElements to better accommodate batch selection
-
-Moved some scripts over to a classy architecture. More scripts will be moved over time. This will eventually allow for multiple image drawer instances in the same window.
-Added a new module script that manages all class instances. See ImageDrawerModule.js for an explanation of the new architecture. Basically, it makes cyclical dependencies impossible.
-Updated imports
-Updated various scripts to use class instances where applicable
-Updated some outdated comments
-Added option to prepend or append labels to Labeled widgets
-Parameter optimizations
-
2024-05-07 20:39:40 -07:00

153 lines
6.1 KiB
JavaScript

import { app } from "/scripts/app.js";
/*
The idea is to have a central place for all classes to be able to access other classes without circular dependency issues.
Each participating class will need to inherit from ImageDrawerComponent. Its owning script will need to keep an instance of a ClassInstanceFactory,
one for each participating class. For example, in ImageDrawer.js with 'import { ImageDrawerComponent, ClassInstanceFactory } from "./Core/ImageDrawerModule.js";':
class ImageDrawerMain extends ImageDrawerComponent {
constructor(args) {
super(args);
// Other ImageDrawer class stuff
}
}
...
const factoryInstance = new ClassInstanceFactory(ImageDrawer);
The ClassInstanceFactory instance registers itself with the Module automatically, but does not create an instance of the component class it governs yet.
When the user wants to access a component, they just 'import { imageDrawerComponentManagerInstance } from "./Core/ImageDrawerModule.js";' and call, for example:
const imageDrawerMainInstance = imageDrawerComponentManagerInstance.getComponentByName("ImageDrawerMain", this.drawerInstanceIndex);
This returns an instance of the ImageDrawer class for the given caller's same drawerInstanceIndex, as there may be many instances of the drawer widget.
This assumes the calling class is also a ImageDrawerComponent. If not, 'this.drawerInstanceIndex' may be omitted from the call, assuming there is only on drawer widget instance.
It is best to call getComponentByName() inside of a function, similar to functional programming paradigms. This ensures the instance is not created if not necessary.
getComponentByName() will check drawerInstanceToComponentMap at the given drawerInstanceIndex for an existing instance of the component with the given name, i.e. "ImageDrawerMain".
If it doesn't find an instance, it will lookup the registered ClassInstanceFactory with the given name and call makeInstance() on it, then the instance will automatically
register itself with the module so next time getComponentByName() is called, it will just return the instance that was created earlier.
This ensures not only that script instances are created on-demand and don't take up memory when never used, but also that any class can reference any other class without a hard
reference to that class, thus pre-empting the possibility of circular dependency.
Pros of this approach include:
-Circular dependency issues become impossible
-Imports are much cleaner
-Scripts are only instantiated when needed, thus saving on memory
-Easily extensible for end-user plugin creation
Cons of this approach include:
-Possibility of a component class's name being changed, thus breaking all getComponentByName() calls to that component unless manually updated
-Participating classes need some boilerplate to create and to reference
-Startup may take longer as we wait for ClassInstanceFactory instances to register themselves
*/
class ImageDrawerComponentManager {
constructor() {
this.classInstanceFactories = [];
this.drawerInstanceToComponentMap = {}; // Drawer instance index mapped to a set of registered components
}
registerClassInstanceFactory(factory) {
this.classInstanceFactories.push(factory);
}
_getClassInstanceFactoryByName(name) {
const foundFactory = this.classInstanceFactories.find(
(factory) => factory.name === name);
if (foundFactory) {
return foundFactory;
}
return null;
}
_getOrInitComponentArrayByDrawerIndex(drawerInstanceIndex) {
if (!this.drawerInstanceToComponentMap[drawerInstanceIndex]) {
this.drawerInstanceToComponentMap[drawerInstanceIndex] = [];
}
return this.drawerInstanceToComponentMap[drawerInstanceIndex];
}
registerComponent(component, drawerInstanceIndex = 0) {
this._getOrInitComponentArrayByDrawerIndex(drawerInstanceIndex).push(component);
}
getComponentByName(name, drawerInstanceIndex = 0) {
let foundComponent = this._getOrInitComponentArrayByDrawerIndex(drawerInstanceIndex).find(
(component) => component.name === name);
if (!foundComponent) {
const foundFactory = this._getClassInstanceFactoryByName(name);
if (foundFactory) {
foundComponent = foundFactory.makeInstance(drawerInstanceIndex);
}
}
return foundComponent;
}
async setup() {
const imageDrawerMainInstance = this.getComponentByName("ImageDrawerMain");
if (imageDrawerMainInstance) {
await imageDrawerMainInstance.setup();
} else {
throw ("imageDrawerMainInstance never registered!");
}
}
};
export const imageDrawerComponentManagerInstance = new ImageDrawerComponentManager();
export class ImageDrawerComponent {
constructor(args) {
this.name = args.name;
this.drawerInstanceIndex = args.drawerInstanceIndex || 0;
imageDrawerComponentManagerInstance.registerComponent(this, this.drawerInstanceIndex);
}
}
export class ClassInstanceFactory {
constructor(managedComponentClass, args = {}) {
if (!managedComponentClass) {
throw("ClassInstanceFactory initialized without a managedComponentClass!");
}
this.managedComponentClass = managedComponentClass;
this.name = managedComponentClass.name;
this.scriptArgs = args;
this.scriptArgs.name = this.name;
imageDrawerComponentManagerInstance.registerClassInstanceFactory(this);
}
makeInstance(drawerInstanceIndex = 0) {
this.scriptArgs.drawerInstanceIndex = drawerInstanceIndex;
const newComponentInstance = new this.managedComponentClass(this.scriptArgs); // Will be automatically registered
return newComponentInstance;
}
}
app.registerExtension({
name: "JNodes.ImageDrawer",
async setup() {
await imageDrawerComponentManagerInstance.setup();
}
});