mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
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:
@@ -95,29 +95,29 @@ const openModal = (modal: ModalType) => {
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
// Setup serialization
|
||||
props.widget.serializeValue = async () => {
|
||||
const config = state.buildConfig()
|
||||
console.log('[LoraPoolWidget] Serializing config:', config)
|
||||
return config
|
||||
// Setup callback for external value updates (e.g., workflow load, undo/redo)
|
||||
// ComfyUI calls this automatically after setValue() in domWidget.ts
|
||||
// NOTE: callback should NOT call refreshPreview() to avoid infinite loops:
|
||||
// watch(filters) → refreshPreview() → buildConfig() → widget.value = v → callback → refreshPreview() → ...
|
||||
props.widget.callback = (v: LoraPoolConfig | LegacyLoraPoolConfig) => {
|
||||
if (v) {
|
||||
console.log('[LoraPoolWidget] Restoring config from callback')
|
||||
state.restoreFromConfig(v)
|
||||
// Preview will refresh automatically via watch() when restoreFromConfig changes filter refs
|
||||
}
|
||||
}
|
||||
|
||||
// Handle external value updates (e.g., loading workflow, paste)
|
||||
props.widget.onSetValue = (v) => {
|
||||
state.restoreFromConfig(v as LoraPoolConfig | LegacyLoraPoolConfig)
|
||||
state.refreshPreview()
|
||||
}
|
||||
|
||||
// Restore from saved value
|
||||
// Restore from saved value if workflow was already loaded
|
||||
if (props.widget.value) {
|
||||
console.log('[LoraPoolWidget] Restoring from saved value:', props.widget.value)
|
||||
console.log('[LoraPoolWidget] Restoring from initial value')
|
||||
state.restoreFromConfig(props.widget.value as LoraPoolConfig | LegacyLoraPoolConfig)
|
||||
}
|
||||
|
||||
// Fetch filter options
|
||||
await state.fetchFilterOptions()
|
||||
|
||||
// Initial preview
|
||||
// Initial preview (only called once on mount)
|
||||
// When workflow is loaded, callback restores config, then watch triggers this
|
||||
await state.refreshPreview()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -99,8 +99,13 @@ export interface CyclerConfig {
|
||||
}
|
||||
|
||||
export interface ComponentWidget {
|
||||
/** @deprecated Use callback instead. Kept for backward compatibility with other widgets. */
|
||||
serializeValue?: () => Promise<LoraPoolConfig | RandomizerConfig | CyclerConfig>
|
||||
value?: LoraPoolConfig | LegacyLoraPoolConfig | RandomizerConfig | CyclerConfig
|
||||
/** @deprecated Use callback instead. Kept for backward compatibility with other widgets. */
|
||||
onSetValue?: (v: LoraPoolConfig | LegacyLoraPoolConfig | RandomizerConfig | CyclerConfig) => void
|
||||
/** @deprecated Directly set widget.value instead. Kept for backward compatibility with other widgets. */
|
||||
updateConfig?: (v: LoraPoolConfig | RandomizerConfig | CyclerConfig) => void
|
||||
/** Called by ComfyUI automatically after setValue() - use this for UI sync */
|
||||
callback?: (v: LoraPoolConfig | LegacyLoraPoolConfig | RandomizerConfig | CyclerConfig) => void
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,9 +90,8 @@ function createLoraPoolWidget(node) {
|
||||
},
|
||||
setValue(v: LoraPoolConfig | LegacyLoraPoolConfig) {
|
||||
internalValue = v
|
||||
if (typeof widget.onSetValue === 'function') {
|
||||
widget.onSetValue(v)
|
||||
}
|
||||
// ComfyUI automatically calls widget.callback after setValue
|
||||
// No need for custom onSetValue mechanism
|
||||
},
|
||||
serialize: true,
|
||||
// Per dev guide: providing getMinHeight via options allows the system to
|
||||
@@ -103,10 +102,6 @@ function createLoraPoolWidget(node) {
|
||||
}
|
||||
)
|
||||
|
||||
widget.updateConfig = (v: LoraPoolConfig) => {
|
||||
internalValue = v
|
||||
}
|
||||
|
||||
const vueApp = createApp(LoraPoolWidget, {
|
||||
widget,
|
||||
node
|
||||
|
||||
Reference in New Issue
Block a user