feat: enhance node settings management and auto-enhancement functionality
- 🔧 Introduced settings synchronization for enabling/disabling the extension. - 🚀 Improved auto-enhancement logic to respect the global enable setting. - 🛠️ Added methods for handling setting changes and updating UI accordingly.
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
|
||||
## Dev workflow
|
||||
- Frontend: `cd frontend`, `bun install`, `bun run dev` for hot reload, `bun run build` (outputs to `../web`), `bun run type-check` for strict TS.
|
||||
- Quick shortcut: use `bun run --cwd ./frontend <script>` to run install/build/type-check commands without changing directories.
|
||||
- Backend: no build step; ComfyUI imports Python directly. Optional deps (e.g., `aiohttp`) are listed in `requirements.txt` for CivitAI support.
|
||||
- Release: `.\release.ps1 -Version "x.y.z"` builds assets and packages the distribution ZIP.
|
||||
|
||||
|
||||
@@ -88,15 +88,15 @@ const nodeEnhancerExtension: ComfyExtension = {
|
||||
*/
|
||||
nodeCreated(node: any): void {
|
||||
// Auto-enhance nodes if setting is enabled
|
||||
const autoEnhanceAll = app.ui.settings.getSettingValue('nodeEnhancer.autoEnhanceAll', false);
|
||||
const enabled = app.ui.settings.getSettingValue('nodeEnhancer.enabled', true);
|
||||
const autoEnhanceAll = enabled && app.ui.settings.getSettingValue('nodeEnhancer.autoEnhanceAll', false);
|
||||
|
||||
if (autoEnhanceAll && node.type) {
|
||||
// Find the enhancement config for this node type
|
||||
if (autoEnhanceAll && node?.type) {
|
||||
const availableEnhancements = NodeEnhancerExtension.getAvailableEnhancements();
|
||||
const config = availableEnhancements.find(e => e.nodeType === node.type);
|
||||
const hasEnhancement = availableEnhancements.some(e => e.nodeType === node.type);
|
||||
|
||||
if (config) {
|
||||
NodeEnhancerExtension.enableEnhancement(config.nodeType);
|
||||
if (hasEnhancement) {
|
||||
NodeEnhancerExtension.enableEnhancementsForNode(node);
|
||||
console.log(`Node Enhancer: Auto-enhanced ${node.type}`);
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,7 @@ const nodeEnhancerExtension: ComfyExtension = {
|
||||
*/
|
||||
afterConfigureGraph(_graphData: any): void {
|
||||
console.log('Node Enhancer: Graph configured');
|
||||
NodeEnhancerExtension.onGraphConfigured();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -105,6 +105,15 @@ export class NodeEnhancerExtension {
|
||||
private static readonly NODE_FLAG_KEY = 'ndSuperSelectorEnabled';
|
||||
private static readonly LEGACY_NODE_FLAG_KEY = 'ndPowerEnabled';
|
||||
private static readonly OVERLAY_WIDGET_TYPE = 'ndSuperSelectorOverlay';
|
||||
private static readonly SETTINGS = {
|
||||
enabled: 'nodeEnhancer.enabled',
|
||||
autoEnhanceAll: 'nodeEnhancer.autoEnhanceAll',
|
||||
contextToggle: 'nodeEnhancer.enableContextToggle'
|
||||
} as const;
|
||||
|
||||
private static settingsHookInitialized = false;
|
||||
private static suppressSettingSideEffects = false;
|
||||
private static settingsStyleInjected = false;
|
||||
|
||||
private static debugLog(...args: any[]): void {
|
||||
if (this.DEBUG) {
|
||||
@@ -112,6 +121,25 @@ export class NodeEnhancerExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private static isExtensionEnabled(): boolean {
|
||||
try {
|
||||
return app?.ui?.settings?.getSettingValue?.(this.SETTINGS.enabled, true) !== false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static isAutoEnhanceEnabled(): boolean {
|
||||
try {
|
||||
if (!this.isExtensionEnabled()) {
|
||||
return false;
|
||||
}
|
||||
return app?.ui?.settings?.getSettingValue?.(this.SETTINGS.autoEnhanceAll, false) === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static normalizeValueForWidget(widget: any, value: string): string {
|
||||
if (!widget || typeof value !== 'string') {
|
||||
return value;
|
||||
@@ -144,7 +172,10 @@ export class NodeEnhancerExtension {
|
||||
}
|
||||
private static isGloballyEnabled(): boolean {
|
||||
try {
|
||||
return app?.ui?.settings?.getSettingValue?.('nodeEnhancer.enableContextToggle', true) !== false;
|
||||
if (!this.isExtensionEnabled()) {
|
||||
return false;
|
||||
}
|
||||
return app?.ui?.settings?.getSettingValue?.(this.SETTINGS.contextToggle, true) !== false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
@@ -522,6 +553,9 @@ export class NodeEnhancerExtension {
|
||||
// Initialize file picker service
|
||||
await this.filePickerService;
|
||||
|
||||
this.setupSettingsSync();
|
||||
this.refreshSettingState();
|
||||
|
||||
console.log('Node Enhancer Extension: Initialized successfully');
|
||||
}
|
||||
|
||||
@@ -553,12 +587,20 @@ export class NodeEnhancerExtension {
|
||||
return originalMenu ? originalMenu.call(this, canvas, optionsArr) : optionsArr;
|
||||
}
|
||||
|
||||
let options = Array.isArray(optionsArr) ? optionsArr : [];
|
||||
const maybe = originalMenu?.call(this, canvas, options);
|
||||
if (Array.isArray(maybe)) {
|
||||
options = maybe;
|
||||
} else if (!Array.isArray(options)) {
|
||||
options = [];
|
||||
const providedOptions = Array.isArray(optionsArr) ? optionsArr : [];
|
||||
const originalResult = originalMenu ? originalMenu.call(this, canvas, providedOptions) : providedOptions;
|
||||
const targetOptions = Array.isArray(originalResult)
|
||||
? originalResult
|
||||
: Array.isArray(optionsArr)
|
||||
? optionsArr
|
||||
: providedOptions;
|
||||
|
||||
const options = Array.isArray(targetOptions) ? targetOptions : [];
|
||||
|
||||
for (let i = options.length - 1; i >= 0; i--) {
|
||||
if (options[i]?.__ndSuperSelectorToggle) {
|
||||
options.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const toggleOption: any = {
|
||||
@@ -569,13 +611,11 @@ export class NodeEnhancerExtension {
|
||||
configs.forEach(cfg => NodeEnhancerExtension.disableForNode(this, cfg));
|
||||
this.__ndPowerEnabled = false;
|
||||
NodeEnhancerExtension.restoreTitle(this);
|
||||
NodeEnhancerExtension.setNodeFlag(this, false);
|
||||
this.setDirtyCanvas?.(true, true);
|
||||
} else {
|
||||
configs.forEach(cfg => NodeEnhancerExtension.enableForNode(this, cfg));
|
||||
this.__ndPowerEnabled = true;
|
||||
NodeEnhancerExtension.applyTitleBadge(this);
|
||||
NodeEnhancerExtension.enableConfigsForNode(this, configs);
|
||||
}
|
||||
NodeEnhancerExtension.setNodeFlag(this, !!this.__ndPowerEnabled);
|
||||
this.setDirtyCanvas?.(true, true);
|
||||
} catch (error) {
|
||||
console.warn('Node Enhancer: toggle failed', error);
|
||||
}
|
||||
@@ -583,22 +623,11 @@ export class NodeEnhancerExtension {
|
||||
};
|
||||
toggleOption.__ndSuperSelectorToggle = true;
|
||||
|
||||
const baseOptions = Array.isArray(options) ? options.slice() : [];
|
||||
const filtered = baseOptions.filter((opt: any) => !opt?.__ndSuperSelectorToggle);
|
||||
|
||||
if (!Array.isArray(options)) {
|
||||
options = Array.isArray(optionsArr) ? optionsArr : [];
|
||||
if (options.length && options[0] !== null) {
|
||||
options.unshift(null);
|
||||
}
|
||||
|
||||
options.length = 0;
|
||||
options.push(toggleOption);
|
||||
|
||||
if (filtered.length) {
|
||||
if (filtered[0] !== null) {
|
||||
options.push(null);
|
||||
}
|
||||
options.push(...filtered);
|
||||
}
|
||||
options.unshift(toggleOption);
|
||||
|
||||
if (Array.isArray(optionsArr) && optionsArr !== options) {
|
||||
optionsArr.length = 0;
|
||||
@@ -726,6 +755,47 @@ export class NodeEnhancerExtension {
|
||||
this.setNodeFlag(node, true);
|
||||
}
|
||||
|
||||
private static enableConfigsForNode(node: any, configs: EnhancedNodeConfig[]): void {
|
||||
if (!node || !Array.isArray(configs) || !configs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let anyEnabled = false;
|
||||
|
||||
for (const config of configs) {
|
||||
try {
|
||||
this.enableForNode(node, config);
|
||||
anyEnabled = true;
|
||||
} catch (error) {
|
||||
console.warn('Node Enhancer: failed to enable config', config, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (anyEnabled) {
|
||||
node.__ndPowerEnabled = true;
|
||||
this.applyTitleBadge(node);
|
||||
this.setNodeFlag(node, true);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
static enableEnhancementsForNode(node: any): void {
|
||||
if (!this.isExtensionEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node?.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const configs = this.ENHANCED_NODES.filter(cfg => cfg.nodeType === node.type);
|
||||
if (!configs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enableConfigsForNode(node, configs);
|
||||
}
|
||||
|
||||
/** Disable enhancement for a specific node instance */
|
||||
private static disableForNode(node: any, config: EnhancedNodeConfig): void {
|
||||
if (!node.__ndEnhancedWidgets || !node.__ndEnhancedWidgets[config.widgetName]) {
|
||||
@@ -868,15 +938,15 @@ export class NodeEnhancerExtension {
|
||||
static getAvailableEnhancements(): EnhancedNodeConfig[] { return this.ENHANCED_NODES; }
|
||||
|
||||
static enableEnhancement(nodeTypeName: string): void {
|
||||
const config = this.ENHANCED_NODES.find(cfg => cfg.nodeType === nodeTypeName);
|
||||
if (!config) return;
|
||||
if (!this.isExtensionEnabled()) return;
|
||||
const configs = this.ENHANCED_NODES.filter(cfg => cfg.nodeType === nodeTypeName);
|
||||
if (!configs.length) return;
|
||||
const graph = (window as any).app?.graph;
|
||||
if (!graph) return;
|
||||
(graph._nodes || []).forEach((node: any) => {
|
||||
if (node.type === nodeTypeName) {
|
||||
if (node?.type === nodeTypeName) {
|
||||
try {
|
||||
this.enableForNode(node, config);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
this.enableConfigsForNode(node, configs);
|
||||
} catch (error) {
|
||||
console.warn('Node Enhancer: failed to enable node', node, error);
|
||||
}
|
||||
@@ -884,6 +954,291 @@ export class NodeEnhancerExtension {
|
||||
});
|
||||
}
|
||||
|
||||
private static setupSettingsSync(attempt = 0): void {
|
||||
if (this.settingsHookInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = app?.ui?.settings;
|
||||
if (!settings) {
|
||||
if (attempt > 20) {
|
||||
return;
|
||||
}
|
||||
const delay = Math.min(1000, 100 * Math.max(1, attempt + 1));
|
||||
setTimeout(() => this.setupSettingsSync(attempt + 1), delay);
|
||||
return;
|
||||
}
|
||||
|
||||
this.settingsHookInitialized = true;
|
||||
|
||||
try {
|
||||
const originalSet = settings.setSettingValue?.bind(settings);
|
||||
if (typeof originalSet === 'function') {
|
||||
const self = this;
|
||||
settings.setSettingValue = function(key: string, value: any) {
|
||||
const result = originalSet(key, value);
|
||||
self.handleSettingChanged(key, value);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Node Enhancer: failed to hook settings.setSettingValue', error);
|
||||
}
|
||||
|
||||
try {
|
||||
const originalLoad = settings.loadSettings?.bind(settings);
|
||||
if (typeof originalLoad === 'function') {
|
||||
const self = this;
|
||||
settings.loadSettings = async function(...args: any[]) {
|
||||
const result = await originalLoad(...args);
|
||||
self.refreshSettingState();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Node Enhancer: failed to hook settings.loadSettings', error);
|
||||
}
|
||||
|
||||
this.refreshSettingState();
|
||||
}
|
||||
|
||||
private static handleSettingChanged(key: string, value: any): void {
|
||||
if (this.suppressSettingSideEffects) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === this.SETTINGS.enabled) {
|
||||
this.handleGlobalEnabledChange(value !== false);
|
||||
}
|
||||
|
||||
if (key === this.SETTINGS.autoEnhanceAll) {
|
||||
if (value) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (key === this.SETTINGS.contextToggle || key === this.SETTINGS.autoEnhanceAll || key === this.SETTINGS.enabled) {
|
||||
this.updateSettingVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private static refreshSettingState(): void {
|
||||
this.updateSettingVisibility();
|
||||
|
||||
if (!this.isExtensionEnabled()) {
|
||||
this.disableAllEnhancements();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isAutoEnhanceEnabled()) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static handleGlobalEnabledChange(enabled: boolean): void {
|
||||
if (!enabled) {
|
||||
this.disableAllEnhancements();
|
||||
} else if (this.isAutoEnhanceEnabled()) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static updateSettingVisibility(): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ensureSettingsStyles();
|
||||
|
||||
const enabled = this.isExtensionEnabled();
|
||||
const targets = [this.SETTINGS.autoEnhanceAll, this.SETTINGS.contextToggle];
|
||||
|
||||
for (const id of targets) {
|
||||
const element = this.findSettingElement(id);
|
||||
this.setSettingDisabled(element, !enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private static ensureSettingsStyles(): void {
|
||||
if (this.settingsStyleInjected || typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'nd-super-nodes-settings-styles';
|
||||
style.textContent = `
|
||||
.nd-super-nodes-setting-disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.nd-super-nodes-setting-disabled input,
|
||||
.nd-super-nodes-setting-disabled button,
|
||||
.nd-super-nodes-setting-disabled select {
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
document.head?.appendChild(style);
|
||||
this.settingsStyleInjected = true;
|
||||
} catch (error) {
|
||||
console.warn('Node Enhancer: failed to inject settings styles', error);
|
||||
}
|
||||
}
|
||||
|
||||
private static findSettingElement(id: string): HTMLElement | null {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const settings = app?.ui?.settings;
|
||||
const candidates: Array<HTMLElement | null | undefined> = [];
|
||||
|
||||
try {
|
||||
const stored = settings?.settings?.get?.(id)?.element ?? settings?.settings?.[id]?.element;
|
||||
if (stored instanceof HTMLElement) {
|
||||
candidates.push(stored);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const selectorCandidates = [
|
||||
`[data-setting-id="${id}"]`,
|
||||
`[data-id="${id}"]`,
|
||||
`[data-setting="${id}"]`,
|
||||
`[data-settingname="${id}"]`,
|
||||
(() => {
|
||||
try {
|
||||
if (typeof (window as any).CSS?.escape === 'function') {
|
||||
return `#${(window as any).CSS.escape(id)}`;
|
||||
}
|
||||
} catch {}
|
||||
return null;
|
||||
})()
|
||||
].filter(Boolean) as string[];
|
||||
|
||||
for (const selector of selectorCandidates) {
|
||||
try {
|
||||
const el = document.querySelector(selector);
|
||||
if (el instanceof HTMLElement) {
|
||||
candidates.push(el);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (candidate instanceof HTMLElement) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const label = Array.from(document.querySelectorAll('label')).find((node) => node.textContent?.includes(id));
|
||||
if (label && label instanceof HTMLElement) {
|
||||
return label.closest('.setting-item') as HTMLElement;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static setSettingDisabled(element: HTMLElement | null, disabled: boolean): void {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const className = 'nd-super-nodes-setting-disabled';
|
||||
if (disabled) {
|
||||
element.classList.add(className);
|
||||
element.setAttribute('aria-disabled', 'true');
|
||||
} else {
|
||||
element.classList.remove(className);
|
||||
element.removeAttribute('aria-disabled');
|
||||
}
|
||||
|
||||
const inputs = element.querySelectorAll('input, button, select');
|
||||
inputs.forEach((input) => {
|
||||
if (input instanceof HTMLInputElement || input instanceof HTMLButtonElement || input instanceof HTMLSelectElement) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static applyAutoEnhanceAll(): void {
|
||||
if (!this.isAutoEnhanceEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const graph = (window as any).app?.graph;
|
||||
if (!graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = Array.isArray(graph._nodes) ? graph._nodes : [];
|
||||
nodes.forEach((node: any) => {
|
||||
if (!node?.type) {
|
||||
return;
|
||||
}
|
||||
const configs = this.ENHANCED_NODES.filter(cfg => cfg.nodeType === node.type);
|
||||
if (!configs.length) {
|
||||
return;
|
||||
}
|
||||
this.enableConfigsForNode(node, configs);
|
||||
});
|
||||
}
|
||||
|
||||
private static disableAllEnhancements(): void {
|
||||
const graph = (window as any).app?.graph;
|
||||
if (!graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = Array.isArray(graph._nodes) ? graph._nodes : [];
|
||||
nodes.forEach((node: any) => {
|
||||
if (!node?.type) {
|
||||
return;
|
||||
}
|
||||
const configs = this.ENHANCED_NODES.filter(cfg => cfg.nodeType === node.type);
|
||||
if (!configs.length) {
|
||||
return;
|
||||
}
|
||||
this.disableConfigsForNode(node, configs);
|
||||
});
|
||||
}
|
||||
|
||||
private static disableConfigsForNode(node: any, configs: EnhancedNodeConfig[]): void {
|
||||
if (!node || !Array.isArray(configs) || !configs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
|
||||
for (const config of configs) {
|
||||
if (node.__ndEnhancedWidgets?.[config.widgetName]) {
|
||||
try {
|
||||
this.disableForNode(node, config);
|
||||
changed = true;
|
||||
} catch (error) {
|
||||
console.warn('Node Enhancer: failed to disable config', config, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.setNodeFlag(node, false);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
static onGraphConfigured(): void {
|
||||
if (!this.isExtensionEnabled()) {
|
||||
this.disableAllEnhancements();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isAutoEnhanceEnabled()) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static loadSet(key: string, sessionFirst = false): Set<string> {
|
||||
try {
|
||||
if (sessionFirst) {
|
||||
|
||||
+338
-35
@@ -1718,7 +1718,7 @@ const _FilePickerService = class _FilePickerService {
|
||||
this.triggerLinkedServiceRefresh();
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent("nd-super-nodes:files-refreshed", { detail: { trigger } }));
|
||||
} catch {
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
triggerLinkedServiceRefresh() {
|
||||
@@ -4277,6 +4277,23 @@ const _NodeEnhancerExtension = class _NodeEnhancerExtension {
|
||||
console.debug("[NodeEnhancer]", ...args);
|
||||
}
|
||||
}
|
||||
static isExtensionEnabled() {
|
||||
try {
|
||||
return app$1?.ui?.settings?.getSettingValue?.(this.SETTINGS.enabled, true) !== false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
static isAutoEnhanceEnabled() {
|
||||
try {
|
||||
if (!this.isExtensionEnabled()) {
|
||||
return false;
|
||||
}
|
||||
return app$1?.ui?.settings?.getSettingValue?.(this.SETTINGS.autoEnhanceAll, false) === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static normalizeValueForWidget(widget, value) {
|
||||
if (!widget || typeof value !== "string") {
|
||||
return value;
|
||||
@@ -4305,7 +4322,10 @@ const _NodeEnhancerExtension = class _NodeEnhancerExtension {
|
||||
}
|
||||
static isGloballyEnabled() {
|
||||
try {
|
||||
return app$1?.ui?.settings?.getSettingValue?.("nodeEnhancer.enableContextToggle", true) !== false;
|
||||
if (!this.isExtensionEnabled()) {
|
||||
return false;
|
||||
}
|
||||
return app$1?.ui?.settings?.getSettingValue?.(this.SETTINGS.contextToggle, true) !== false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
@@ -4629,6 +4649,8 @@ const _NodeEnhancerExtension = class _NodeEnhancerExtension {
|
||||
console.log("Node Enhancer Extension: Initializing...");
|
||||
this.loadUserPreferences();
|
||||
await this.filePickerService;
|
||||
this.setupSettingsSync();
|
||||
this.refreshSettingState();
|
||||
console.log("Node Enhancer Extension: Initialized successfully");
|
||||
}
|
||||
/**
|
||||
@@ -4656,12 +4678,14 @@ const _NodeEnhancerExtension = class _NodeEnhancerExtension {
|
||||
if (!_NodeEnhancerExtension.isGloballyEnabled()) {
|
||||
return originalMenu ? originalMenu.call(this, canvas, optionsArr) : optionsArr;
|
||||
}
|
||||
let options = Array.isArray(optionsArr) ? optionsArr : [];
|
||||
const maybe = originalMenu?.call(this, canvas, options);
|
||||
if (Array.isArray(maybe)) {
|
||||
options = maybe;
|
||||
} else if (!Array.isArray(options)) {
|
||||
options = [];
|
||||
const providedOptions = Array.isArray(optionsArr) ? optionsArr : [];
|
||||
const originalResult = originalMenu ? originalMenu.call(this, canvas, providedOptions) : providedOptions;
|
||||
const targetOptions = Array.isArray(originalResult) ? originalResult : Array.isArray(optionsArr) ? optionsArr : providedOptions;
|
||||
const options = Array.isArray(targetOptions) ? targetOptions : [];
|
||||
for (let i = options.length - 1; i >= 0; i--) {
|
||||
if (options[i]?.__ndSuperSelectorToggle) {
|
||||
options.splice(i, 1);
|
||||
}
|
||||
}
|
||||
const toggleOption = {
|
||||
content: this.__ndPowerEnabled ? "➖ Disable ND Super Selector" : "⚡ Enable ND Super Selector",
|
||||
@@ -4671,32 +4695,21 @@ const _NodeEnhancerExtension = class _NodeEnhancerExtension {
|
||||
configs.forEach((cfg) => _NodeEnhancerExtension.disableForNode(this, cfg));
|
||||
this.__ndPowerEnabled = false;
|
||||
_NodeEnhancerExtension.restoreTitle(this);
|
||||
_NodeEnhancerExtension.setNodeFlag(this, false);
|
||||
this.setDirtyCanvas?.(true, true);
|
||||
} else {
|
||||
configs.forEach((cfg) => _NodeEnhancerExtension.enableForNode(this, cfg));
|
||||
this.__ndPowerEnabled = true;
|
||||
_NodeEnhancerExtension.applyTitleBadge(this);
|
||||
_NodeEnhancerExtension.enableConfigsForNode(this, configs);
|
||||
}
|
||||
_NodeEnhancerExtension.setNodeFlag(this, !!this.__ndPowerEnabled);
|
||||
this.setDirtyCanvas?.(true, true);
|
||||
} catch (error) {
|
||||
console.warn("Node Enhancer: toggle failed", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
toggleOption.__ndSuperSelectorToggle = true;
|
||||
const baseOptions = Array.isArray(options) ? options.slice() : [];
|
||||
const filtered = baseOptions.filter((opt) => !opt?.__ndSuperSelectorToggle);
|
||||
if (!Array.isArray(options)) {
|
||||
options = Array.isArray(optionsArr) ? optionsArr : [];
|
||||
}
|
||||
options.length = 0;
|
||||
options.push(toggleOption);
|
||||
if (filtered.length) {
|
||||
if (filtered[0] !== null) {
|
||||
options.push(null);
|
||||
}
|
||||
options.push(...filtered);
|
||||
if (options.length && options[0] !== null) {
|
||||
options.unshift(null);
|
||||
}
|
||||
options.unshift(toggleOption);
|
||||
if (Array.isArray(optionsArr) && optionsArr !== options) {
|
||||
optionsArr.length = 0;
|
||||
optionsArr.push(...options);
|
||||
@@ -4808,6 +4821,39 @@ const _NodeEnhancerExtension = class _NodeEnhancerExtension {
|
||||
this.setupEnhancedNode(node, config);
|
||||
this.setNodeFlag(node, true);
|
||||
}
|
||||
static enableConfigsForNode(node, configs) {
|
||||
if (!node || !Array.isArray(configs) || !configs.length) {
|
||||
return;
|
||||
}
|
||||
let anyEnabled = false;
|
||||
for (const config of configs) {
|
||||
try {
|
||||
this.enableForNode(node, config);
|
||||
anyEnabled = true;
|
||||
} catch (error) {
|
||||
console.warn("Node Enhancer: failed to enable config", config, error);
|
||||
}
|
||||
}
|
||||
if (anyEnabled) {
|
||||
node.__ndPowerEnabled = true;
|
||||
this.applyTitleBadge(node);
|
||||
this.setNodeFlag(node, true);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
}
|
||||
}
|
||||
static enableEnhancementsForNode(node) {
|
||||
if (!this.isExtensionEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (!node?.type) {
|
||||
return;
|
||||
}
|
||||
const configs = this.ENHANCED_NODES.filter((cfg) => cfg.nodeType === node.type);
|
||||
if (!configs.length) {
|
||||
return;
|
||||
}
|
||||
this.enableConfigsForNode(node, configs);
|
||||
}
|
||||
/** Disable enhancement for a specific node instance */
|
||||
static disableForNode(node, config) {
|
||||
if (!node.__ndEnhancedWidgets || !node.__ndEnhancedWidgets[config.widgetName]) {
|
||||
@@ -4936,21 +4982,268 @@ const _NodeEnhancerExtension = class _NodeEnhancerExtension {
|
||||
return this.ENHANCED_NODES;
|
||||
}
|
||||
static enableEnhancement(nodeTypeName) {
|
||||
const config = this.ENHANCED_NODES.find((cfg) => cfg.nodeType === nodeTypeName);
|
||||
if (!config) return;
|
||||
if (!this.isExtensionEnabled()) return;
|
||||
const configs = this.ENHANCED_NODES.filter((cfg) => cfg.nodeType === nodeTypeName);
|
||||
if (!configs.length) return;
|
||||
const graph = window.app?.graph;
|
||||
if (!graph) return;
|
||||
(graph._nodes || []).forEach((node) => {
|
||||
if (node.type === nodeTypeName) {
|
||||
if (node?.type === nodeTypeName) {
|
||||
try {
|
||||
this.enableForNode(node, config);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
this.enableConfigsForNode(node, configs);
|
||||
} catch (error) {
|
||||
console.warn("Node Enhancer: failed to enable node", node, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
static setupSettingsSync(attempt = 0) {
|
||||
if (this.settingsHookInitialized) {
|
||||
return;
|
||||
}
|
||||
const settings = app$1?.ui?.settings;
|
||||
if (!settings) {
|
||||
if (attempt > 20) {
|
||||
return;
|
||||
}
|
||||
const delay = Math.min(1e3, 100 * Math.max(1, attempt + 1));
|
||||
setTimeout(() => this.setupSettingsSync(attempt + 1), delay);
|
||||
return;
|
||||
}
|
||||
this.settingsHookInitialized = true;
|
||||
try {
|
||||
const originalSet = settings.setSettingValue?.bind(settings);
|
||||
if (typeof originalSet === "function") {
|
||||
const self = this;
|
||||
settings.setSettingValue = function(key, value) {
|
||||
const result = originalSet(key, value);
|
||||
self.handleSettingChanged(key, value);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Node Enhancer: failed to hook settings.setSettingValue", error);
|
||||
}
|
||||
try {
|
||||
const originalLoad = settings.loadSettings?.bind(settings);
|
||||
if (typeof originalLoad === "function") {
|
||||
const self = this;
|
||||
settings.loadSettings = async function(...args) {
|
||||
const result = await originalLoad(...args);
|
||||
self.refreshSettingState();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Node Enhancer: failed to hook settings.loadSettings", error);
|
||||
}
|
||||
this.refreshSettingState();
|
||||
}
|
||||
static handleSettingChanged(key, value) {
|
||||
if (this.suppressSettingSideEffects) {
|
||||
return;
|
||||
}
|
||||
if (key === this.SETTINGS.enabled) {
|
||||
this.handleGlobalEnabledChange(value !== false);
|
||||
}
|
||||
if (key === this.SETTINGS.autoEnhanceAll) {
|
||||
if (value) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
if (key === this.SETTINGS.contextToggle || key === this.SETTINGS.autoEnhanceAll || key === this.SETTINGS.enabled) {
|
||||
this.updateSettingVisibility();
|
||||
}
|
||||
}
|
||||
static refreshSettingState() {
|
||||
this.updateSettingVisibility();
|
||||
if (!this.isExtensionEnabled()) {
|
||||
this.disableAllEnhancements();
|
||||
return;
|
||||
}
|
||||
if (this.isAutoEnhanceEnabled()) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
static handleGlobalEnabledChange(enabled) {
|
||||
if (!enabled) {
|
||||
this.disableAllEnhancements();
|
||||
} else if (this.isAutoEnhanceEnabled()) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
static updateSettingVisibility() {
|
||||
if (typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
this.ensureSettingsStyles();
|
||||
const enabled = this.isExtensionEnabled();
|
||||
const targets = [this.SETTINGS.autoEnhanceAll, this.SETTINGS.contextToggle];
|
||||
for (const id of targets) {
|
||||
const element = this.findSettingElement(id);
|
||||
this.setSettingDisabled(element, !enabled);
|
||||
}
|
||||
}
|
||||
static ensureSettingsStyles() {
|
||||
if (this.settingsStyleInjected || typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const style = document.createElement("style");
|
||||
style.id = "nd-super-nodes-settings-styles";
|
||||
style.textContent = `
|
||||
.nd-super-nodes-setting-disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.nd-super-nodes-setting-disabled input,
|
||||
.nd-super-nodes-setting-disabled button,
|
||||
.nd-super-nodes-setting-disabled select {
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
document.head?.appendChild(style);
|
||||
this.settingsStyleInjected = true;
|
||||
} catch (error) {
|
||||
console.warn("Node Enhancer: failed to inject settings styles", error);
|
||||
}
|
||||
}
|
||||
static findSettingElement(id) {
|
||||
if (typeof document === "undefined") {
|
||||
return null;
|
||||
}
|
||||
const settings = app$1?.ui?.settings;
|
||||
const candidates = [];
|
||||
try {
|
||||
const stored = settings?.settings?.get?.(id)?.element ?? settings?.settings?.[id]?.element;
|
||||
if (stored instanceof HTMLElement) {
|
||||
candidates.push(stored);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
const selectorCandidates = [
|
||||
`[data-setting-id="${id}"]`,
|
||||
`[data-id="${id}"]`,
|
||||
`[data-setting="${id}"]`,
|
||||
`[data-settingname="${id}"]`,
|
||||
(() => {
|
||||
try {
|
||||
if (typeof window.CSS?.escape === "function") {
|
||||
return `#${window.CSS.escape(id)}`;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
return null;
|
||||
})()
|
||||
].filter(Boolean);
|
||||
for (const selector of selectorCandidates) {
|
||||
try {
|
||||
const el = document.querySelector(selector);
|
||||
if (el instanceof HTMLElement) {
|
||||
candidates.push(el);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
for (const candidate of candidates) {
|
||||
if (candidate instanceof HTMLElement) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const label = Array.from(document.querySelectorAll("label")).find((node) => node.textContent?.includes(id));
|
||||
if (label && label instanceof HTMLElement) {
|
||||
return label.closest(".setting-item");
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static setSettingDisabled(element, disabled) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const className = "nd-super-nodes-setting-disabled";
|
||||
if (disabled) {
|
||||
element.classList.add(className);
|
||||
element.setAttribute("aria-disabled", "true");
|
||||
} else {
|
||||
element.classList.remove(className);
|
||||
element.removeAttribute("aria-disabled");
|
||||
}
|
||||
const inputs = element.querySelectorAll("input, button, select");
|
||||
inputs.forEach((input) => {
|
||||
if (input instanceof HTMLInputElement || input instanceof HTMLButtonElement || input instanceof HTMLSelectElement) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
static applyAutoEnhanceAll() {
|
||||
if (!this.isAutoEnhanceEnabled()) {
|
||||
return;
|
||||
}
|
||||
const graph = window.app?.graph;
|
||||
if (!graph) {
|
||||
return;
|
||||
}
|
||||
const nodes = Array.isArray(graph._nodes) ? graph._nodes : [];
|
||||
nodes.forEach((node) => {
|
||||
if (!node?.type) {
|
||||
return;
|
||||
}
|
||||
const configs = this.ENHANCED_NODES.filter((cfg) => cfg.nodeType === node.type);
|
||||
if (!configs.length) {
|
||||
return;
|
||||
}
|
||||
this.enableConfigsForNode(node, configs);
|
||||
});
|
||||
}
|
||||
static disableAllEnhancements() {
|
||||
const graph = window.app?.graph;
|
||||
if (!graph) {
|
||||
return;
|
||||
}
|
||||
const nodes = Array.isArray(graph._nodes) ? graph._nodes : [];
|
||||
nodes.forEach((node) => {
|
||||
if (!node?.type) {
|
||||
return;
|
||||
}
|
||||
const configs = this.ENHANCED_NODES.filter((cfg) => cfg.nodeType === node.type);
|
||||
if (!configs.length) {
|
||||
return;
|
||||
}
|
||||
this.disableConfigsForNode(node, configs);
|
||||
});
|
||||
}
|
||||
static disableConfigsForNode(node, configs) {
|
||||
if (!node || !Array.isArray(configs) || !configs.length) {
|
||||
return;
|
||||
}
|
||||
let changed = false;
|
||||
for (const config of configs) {
|
||||
if (node.__ndEnhancedWidgets?.[config.widgetName]) {
|
||||
try {
|
||||
this.disableForNode(node, config);
|
||||
changed = true;
|
||||
} catch (error) {
|
||||
console.warn("Node Enhancer: failed to disable config", config, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
this.setNodeFlag(node, false);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
}
|
||||
}
|
||||
static onGraphConfigured() {
|
||||
if (!this.isExtensionEnabled()) {
|
||||
this.disableAllEnhancements();
|
||||
return;
|
||||
}
|
||||
if (this.isAutoEnhanceEnabled()) {
|
||||
this.applyAutoEnhanceAll();
|
||||
}
|
||||
}
|
||||
static loadSet(key, sessionFirst = false) {
|
||||
try {
|
||||
if (sessionFirst) {
|
||||
@@ -5085,6 +5378,14 @@ _NodeEnhancerExtension.DEBUG = true;
|
||||
_NodeEnhancerExtension.NODE_FLAG_KEY = "ndSuperSelectorEnabled";
|
||||
_NodeEnhancerExtension.LEGACY_NODE_FLAG_KEY = "ndPowerEnabled";
|
||||
_NodeEnhancerExtension.OVERLAY_WIDGET_TYPE = "ndSuperSelectorOverlay";
|
||||
_NodeEnhancerExtension.SETTINGS = {
|
||||
enabled: "nodeEnhancer.enabled",
|
||||
autoEnhanceAll: "nodeEnhancer.autoEnhanceAll",
|
||||
contextToggle: "nodeEnhancer.enableContextToggle"
|
||||
};
|
||||
_NodeEnhancerExtension.settingsHookInitialized = false;
|
||||
_NodeEnhancerExtension.suppressSettingSideEffects = false;
|
||||
_NodeEnhancerExtension.settingsStyleInjected = false;
|
||||
let NodeEnhancerExtension = _NodeEnhancerExtension;
|
||||
const EXTENSION_NAME$1 = "NodeEnhancer";
|
||||
const EXTENSION_VERSION = "1.0.0";
|
||||
@@ -5153,12 +5454,13 @@ const nodeEnhancerExtension = {
|
||||
* Called when a node is created
|
||||
*/
|
||||
nodeCreated(node) {
|
||||
const autoEnhanceAll = app$1.ui.settings.getSettingValue("nodeEnhancer.autoEnhanceAll", false);
|
||||
if (autoEnhanceAll && node.type) {
|
||||
const enabled = app$1.ui.settings.getSettingValue("nodeEnhancer.enabled", true);
|
||||
const autoEnhanceAll = enabled && app$1.ui.settings.getSettingValue("nodeEnhancer.autoEnhanceAll", false);
|
||||
if (autoEnhanceAll && node?.type) {
|
||||
const availableEnhancements = NodeEnhancerExtension.getAvailableEnhancements();
|
||||
const config = availableEnhancements.find((e) => e.nodeType === node.type);
|
||||
if (config) {
|
||||
NodeEnhancerExtension.enableEnhancement(config.nodeType);
|
||||
const hasEnhancement = availableEnhancements.some((e) => e.nodeType === node.type);
|
||||
if (hasEnhancement) {
|
||||
NodeEnhancerExtension.enableEnhancementsForNode(node);
|
||||
console.log(`Node Enhancer: Auto-enhanced ${node.type}`);
|
||||
}
|
||||
}
|
||||
@@ -5174,6 +5476,7 @@ const nodeEnhancerExtension = {
|
||||
*/
|
||||
afterConfigureGraph(_graphData) {
|
||||
console.log("Node Enhancer: Graph configured");
|
||||
NodeEnhancerExtension.onGraphConfigured();
|
||||
}
|
||||
};
|
||||
console.log(`${EXTENSION_NAME$1}: Registering extension with ComfyUI`);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user