mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-06 08:26:45 -03:00
fix(widgets): isolate autocomplete text cleanup
This commit is contained in:
@@ -5,6 +5,7 @@ import LoraRandomizerWidget from '@/components/LoraRandomizerWidget.vue'
|
|||||||
import LoraCyclerWidget from '@/components/LoraCyclerWidget.vue'
|
import LoraCyclerWidget from '@/components/LoraCyclerWidget.vue'
|
||||||
import JsonDisplayWidget from '@/components/JsonDisplayWidget.vue'
|
import JsonDisplayWidget from '@/components/JsonDisplayWidget.vue'
|
||||||
import AutocompleteTextWidget from '@/components/AutocompleteTextWidget.vue'
|
import AutocompleteTextWidget from '@/components/AutocompleteTextWidget.vue'
|
||||||
|
import { createVueWidgetCleanup } from './vue-widget-cleanup'
|
||||||
import type { LoraPoolConfig, RandomizerConfig, CyclerConfig } from './composables/types'
|
import type { LoraPoolConfig, RandomizerConfig, CyclerConfig } from './composables/types'
|
||||||
import {
|
import {
|
||||||
setupModeChangeHandler,
|
setupModeChangeHandler,
|
||||||
@@ -66,6 +67,12 @@ function forwardMiddleMouseToCanvas(container: HTMLElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const vueApps = new Map<number, VueApp>()
|
const vueApps = new Map<number, VueApp>()
|
||||||
|
let autocompleteTextWidgetInstanceId = 0
|
||||||
|
|
||||||
|
export function createAutocompleteTextWidgetInstanceId() {
|
||||||
|
autocompleteTextWidgetInstanceId += 1
|
||||||
|
return autocompleteTextWidgetInstanceId
|
||||||
|
}
|
||||||
|
|
||||||
// Cache for dynamically loaded addLorasWidget module
|
// Cache for dynamically loaded addLorasWidget module
|
||||||
let addLorasWidgetCache: any = null
|
let addLorasWidgetCache: any = null
|
||||||
@@ -562,8 +569,9 @@ function createAutocompleteTextWidgetFactory(
|
|||||||
inputOptions: { placeholder?: string } = {}
|
inputOptions: { placeholder?: string } = {}
|
||||||
) {
|
) {
|
||||||
const metadataWidgetName = `__lm_autocomplete_meta_${widgetName}`
|
const metadataWidgetName = `__lm_autocomplete_meta_${widgetName}`
|
||||||
|
const instanceId = createAutocompleteTextWidgetInstanceId()
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
container.id = `autocomplete-text-widget-${node.id}-${widgetName}`
|
container.id = `autocomplete-text-widget-${instanceId}`
|
||||||
container.style.width = '100%'
|
container.style.width = '100%'
|
||||||
container.style.height = '100%'
|
container.style.height = '100%'
|
||||||
container.style.display = 'flex'
|
container.style.display = 'flex'
|
||||||
@@ -644,17 +652,12 @@ function createAutocompleteTextWidgetFactory(
|
|||||||
})
|
})
|
||||||
|
|
||||||
vueApp.mount(container)
|
vueApp.mount(container)
|
||||||
// Use a unique key combining node.id and widget name to avoid collisions
|
const appKey = instanceId
|
||||||
const appKey = node.id * 100000 + widgetName.charCodeAt(0)
|
|
||||||
vueApps.set(appKey, vueApp)
|
vueApps.set(appKey, vueApp)
|
||||||
|
|
||||||
widget.onRemove = () => {
|
widget.onRemove = createVueWidgetCleanup(vueApp, () => {
|
||||||
const vueApp = vueApps.get(appKey)
|
vueApps.delete(appKey)
|
||||||
if (vueApp) {
|
})
|
||||||
vueApp.unmount()
|
|
||||||
vueApps.delete(appKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { widget }
|
return { widget }
|
||||||
}
|
}
|
||||||
|
|||||||
15
vue-widgets/src/vue-widget-cleanup.ts
Normal file
15
vue-widgets/src/vue-widget-cleanup.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { App as VueApp } from 'vue'
|
||||||
|
|
||||||
|
export function createVueWidgetCleanup(vueApp: VueApp, onCleanup?: () => void) {
|
||||||
|
let didUnmount = false
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (didUnmount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vueApp.unmount()
|
||||||
|
didUnmount = true
|
||||||
|
onCleanup?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
38
vue-widgets/tests/unit/vueWidgetCleanup.test.ts
Normal file
38
vue-widgets/tests/unit/vueWidgetCleanup.test.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { createVueWidgetCleanup } from '@/vue-widget-cleanup'
|
||||||
|
|
||||||
|
describe('createVueWidgetCleanup', () => {
|
||||||
|
it('cleans up only the Vue app bound to the widget remove handler', () => {
|
||||||
|
const firstCleanup = vi.fn()
|
||||||
|
const secondCleanup = vi.fn()
|
||||||
|
const firstApp = { unmount: vi.fn() }
|
||||||
|
const secondApp = { unmount: vi.fn() }
|
||||||
|
|
||||||
|
const removeFirst = createVueWidgetCleanup(firstApp as any, firstCleanup)
|
||||||
|
const removeSecond = createVueWidgetCleanup(secondApp as any, secondCleanup)
|
||||||
|
|
||||||
|
removeFirst()
|
||||||
|
|
||||||
|
expect(firstApp.unmount).toHaveBeenCalledTimes(1)
|
||||||
|
expect(firstCleanup).toHaveBeenCalledTimes(1)
|
||||||
|
expect(secondApp.unmount).not.toHaveBeenCalled()
|
||||||
|
expect(secondCleanup).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
removeSecond()
|
||||||
|
|
||||||
|
expect(secondApp.unmount).toHaveBeenCalledTimes(1)
|
||||||
|
expect(secondCleanup).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is idempotent when ComfyUI calls the remove handler more than once', () => {
|
||||||
|
const cleanup = vi.fn()
|
||||||
|
const app = { unmount: vi.fn() }
|
||||||
|
const remove = createVueWidgetCleanup(app as any, cleanup)
|
||||||
|
|
||||||
|
remove()
|
||||||
|
remove()
|
||||||
|
|
||||||
|
expect(app.unmount).toHaveBeenCalledTimes(1)
|
||||||
|
expect(cleanup).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -14933,6 +14933,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-76ce0f19"]]);
|
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-76ce0f19"]]);
|
||||||
|
function createVueWidgetCleanup(vueApp, onCleanup) {
|
||||||
|
let didUnmount = false;
|
||||||
|
return () => {
|
||||||
|
if (didUnmount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vueApp.unmount();
|
||||||
|
didUnmount = true;
|
||||||
|
onCleanup == null ? void 0 : onCleanup();
|
||||||
|
};
|
||||||
|
}
|
||||||
const LORA_PROVIDER_NODE_TYPES$1 = [
|
const LORA_PROVIDER_NODE_TYPES$1 = [
|
||||||
"Lora Stacker (LoraManager)",
|
"Lora Stacker (LoraManager)",
|
||||||
"Lora Randomizer (LoraManager)",
|
"Lora Randomizer (LoraManager)",
|
||||||
@@ -15274,6 +15285,11 @@ function forwardMiddleMouseToCanvas(container) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const vueApps = /* @__PURE__ */ new Map();
|
const vueApps = /* @__PURE__ */ new Map();
|
||||||
|
let autocompleteTextWidgetInstanceId = 0;
|
||||||
|
function createAutocompleteTextWidgetInstanceId() {
|
||||||
|
autocompleteTextWidgetInstanceId += 1;
|
||||||
|
return autocompleteTextWidgetInstanceId;
|
||||||
|
}
|
||||||
let addLorasWidgetCache = null;
|
let addLorasWidgetCache = null;
|
||||||
function createLoraPoolWidget(node) {
|
function createLoraPoolWidget(node) {
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
@@ -15653,8 +15669,9 @@ if ((_a = app$1.ui) == null ? void 0 : _a.settings) {
|
|||||||
function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputOptions = {}) {
|
function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputOptions = {}) {
|
||||||
var _a2, _b, _c;
|
var _a2, _b, _c;
|
||||||
const metadataWidgetName = `__lm_autocomplete_meta_${widgetName}`;
|
const metadataWidgetName = `__lm_autocomplete_meta_${widgetName}`;
|
||||||
|
const instanceId = createAutocompleteTextWidgetInstanceId();
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
container.id = `autocomplete-text-widget-${node.id}-${widgetName}`;
|
container.id = `autocomplete-text-widget-${instanceId}`;
|
||||||
container.style.width = "100%";
|
container.style.width = "100%";
|
||||||
container.style.height = "100%";
|
container.style.height = "100%";
|
||||||
container.style.display = "flex";
|
container.style.display = "flex";
|
||||||
@@ -15721,15 +15738,11 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
|
|||||||
ripple: false
|
ripple: false
|
||||||
});
|
});
|
||||||
vueApp.mount(container);
|
vueApp.mount(container);
|
||||||
const appKey = node.id * 1e5 + widgetName.charCodeAt(0);
|
const appKey = instanceId;
|
||||||
vueApps.set(appKey, vueApp);
|
vueApps.set(appKey, vueApp);
|
||||||
widget.onRemove = () => {
|
widget.onRemove = createVueWidgetCleanup(vueApp, () => {
|
||||||
const vueApp2 = vueApps.get(appKey);
|
vueApps.delete(appKey);
|
||||||
if (vueApp2) {
|
});
|
||||||
vueApp2.unmount();
|
|
||||||
vueApps.delete(appKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return { widget };
|
return { widget };
|
||||||
}
|
}
|
||||||
app$1.registerExtension({
|
app$1.registerExtension({
|
||||||
@@ -15834,4 +15847,7 @@ app$1.registerExtension({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
export {
|
||||||
|
createAutocompleteTextWidgetInstanceId
|
||||||
|
};
|
||||||
//# sourceMappingURL=lora-manager-widgets.js.map
|
//# sourceMappingURL=lora-manager-widgets.js.map
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user