refactor(lora-pool-widget): adopt DOM widget value persistence best practices

- Replace custom onSetValue with ComfyUI's built-in widget.callback
- Remove widget.updateConfig, set widget.value directly
- Add isRestoring flag to break callback → watch → refreshPreview loop
- Update ComponentWidget types with callback and deprecate old methods

Refs: docs/dom-widgets/value-persistence-best-practices.md
This commit is contained in:
Will Miao
2026-01-27 23:49:44 +08:00
parent 822ac046e0
commit ad4574e02f
6 changed files with 123 additions and 116 deletions

View File

@@ -13,6 +13,10 @@ import { useLoraPoolApi } from './useLoraPoolApi'
export function useLoraPoolState(widget: ComponentWidget) {
const api = useLoraPoolApi()
// Flag to prevent infinite loops during config restoration
// callback → restoreFromConfig → watch → refreshPreview → buildConfig → widget.value = config → callback → ...
let isRestoring = false
// Filter state
const selectedBaseModels = ref<string[]>([])
const includeTags = ref<string[]>([])
@@ -57,10 +61,10 @@ export function useLoraPoolState(widget: ComponentWidget) {
}
}
// Update widget value
if (widget.updateConfig) {
widget.updateConfig(config)
} else {
// Update widget value (this triggers callback for UI sync)
// Skip during restoration to prevent infinite loops:
// callback → restoreFromConfig → watch → refreshPreview → buildConfig → widget.value = config → callback → ...
if (!isRestoring) {
widget.value = config
}
return config
@@ -91,32 +95,40 @@ export function useLoraPoolState(widget: ComponentWidget) {
// Restore state from config
const restoreFromConfig = (rawConfig: LoraPoolConfig | LegacyLoraPoolConfig) => {
// Migrate if needed
const config = rawConfig.version === 1
? migrateConfig(rawConfig as LegacyLoraPoolConfig)
: rawConfig as LoraPoolConfig
// Set flag to prevent buildConfig from triggering widget.value updates during restoration
// This breaks the infinite loop: callback → restoreFromConfig → watch → refreshPreview → buildConfig → widget.value = config → callback
isRestoring = true
if (!config?.filters) return
try {
// Migrate if needed
const config = rawConfig.version === 1
? migrateConfig(rawConfig as LegacyLoraPoolConfig)
: rawConfig as LoraPoolConfig
const { filters, preview } = config
if (!config?.filters) return
// Helper to update ref only if value changed
const updateIfChanged = <T>(refValue: { value: T }, newValue: T) => {
if (JSON.stringify(refValue.value) !== JSON.stringify(newValue)) {
refValue.value = newValue
const { filters, preview } = config
// Helper to update ref only if value changed
const updateIfChanged = <T>(refValue: { value: T }, newValue: T) => {
if (JSON.stringify(refValue.value) !== JSON.stringify(newValue)) {
refValue.value = newValue
}
}
}
updateIfChanged(selectedBaseModels, filters.baseModels || [])
updateIfChanged(includeTags, filters.tags?.include || [])
updateIfChanged(excludeTags, filters.tags?.exclude || [])
updateIfChanged(includeFolders, filters.folders?.include || [])
updateIfChanged(excludeFolders, filters.folders?.exclude || [])
updateIfChanged(noCreditRequired, filters.license?.noCreditRequired ?? false)
updateIfChanged(allowSelling, filters.license?.allowSelling ?? false)
// matchCount doesn't trigger watchers, so direct assignment is fine
matchCount.value = preview?.matchCount || 0
updateIfChanged(selectedBaseModels, filters.baseModels || [])
updateIfChanged(includeTags, filters.tags?.include || [])
updateIfChanged(excludeTags, filters.tags?.exclude || [])
updateIfChanged(includeFolders, filters.folders?.include || [])
updateIfChanged(excludeFolders, filters.folders?.exclude || [])
updateIfChanged(noCreditRequired, filters.license?.noCreditRequired ?? false)
updateIfChanged(allowSelling, filters.license?.allowSelling ?? false)
// matchCount doesn't trigger watchers, so direct assignment is fine
matchCount.value = preview?.matchCount || 0
} finally {
isRestoring = false
}
}
// Fetch filter options from API