feat: user customizable advanced widgets

This commit is contained in:
Rizumu Ayaka
2026-01-27 19:44:10 +08:00
parent be2fe4bd42
commit 623c56c068
8 changed files with 367 additions and 154 deletions

View File

@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import FormSearchInput from '@/renderer/extensions/vueNodes/widgets/components/form/FormSearchInput.vue'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
import { computedSectionDataList, searchWidgetsAndNodes } from '../shared'
@@ -19,6 +20,7 @@ const { nodes, mustShowNodeTitle } = defineProps<{
const { t } = useI18n()
const rightSidePanelStore = useRightSidePanelStore()
const advancedOverridesStore = useAdvancedWidgetOverridesStore()
const { searchQuery } = storeToRefs(rightSidePanelStore)
const { widgetsSectionDataList, includesAdvanced } = computedSectionDataList(
@@ -35,7 +37,8 @@ const advancedWidgetsSectionDataList = computed((): NodeWidgetsListList => {
const advancedWidgets = widgets
.filter(
(w) =>
!(w.options?.canvasOnly || w.options?.hidden) && w.options?.advanced
!(w.options?.canvasOnly || w.options?.hidden) &&
advancedOverridesStore.getAdvancedState(node, w)
)
.map((widget) => ({ node, widget }))
return { widgets: advancedWidgets, node }
@@ -110,7 +113,14 @@ const advancedLabel = computed(() => {
class="border-b border-interface-stroke"
/>
</TransitionGroup>
<template v-if="advancedWidgetsSectionDataList.length > 0 && !isSearching">
<template
v-if="
advancedWidgetsSectionDataList.length > 0 &&
!isSearching &&
!isMultipleNodesSelected &&
!mustShowNodeTitle
"
>
<SectionWidgets
v-for="{ widgets, node } in advancedWidgetsSectionDataList"
:key="`advanced-${node.id}`"

View File

@@ -12,8 +12,10 @@ import {
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useDialogService } from '@/services/dialogService'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
import { useFavoritedWidgetsStore } from '@/stores/workspace/favoritedWidgetsStore'
const {
@@ -31,7 +33,9 @@ const {
const label = defineModel<string>('label', { required: true })
const canvasStore = useCanvasStore()
const settingStore = useSettingStore()
const favoritedWidgetsStore = useFavoritedWidgetsStore()
const advancedOverridesStore = useAdvancedWidgetOverridesStore()
const dialogService = useDialogService()
const { t } = useI18n()
@@ -43,6 +47,16 @@ const isFavorited = computed(() =>
favoritedWidgetsStore.isFavorited(favoriteNode.value, widget.name)
)
const isEffectivelyAdvanced = computed(() =>
advancedOverridesStore.getAdvancedState(node, widget)
)
const showAdvancedToggle = computed(
() =>
!hasParents.value &&
!settingStore.get('Comfy.Node.AlwaysShowAdvancedWidgets')
)
async function handleRename() {
const newLabel = await dialogService.prompt({
title: t('g.rename'),
@@ -97,6 +111,15 @@ function handleToggleFavorite() {
favoritedWidgetsStore.toggleFavorite(favoriteNode.value, widget.name)
}
function handleToggleAdvanced() {
advancedOverridesStore.setAdvanced(
node,
widget.name,
!isEffectivelyAdvanced.value
)
node.expandToFitContent()
}
const buttonClasses = cn([
'border-none bg-transparent',
'w-full flex items-center gap-2 rounded px-3 py-2 text-sm',
@@ -144,6 +167,26 @@ const buttonClasses = cn([
</template>
</button>
<button
v-if="showAdvancedToggle"
:class="buttonClasses"
@click="
() => {
handleToggleAdvanced()
close()
}
"
>
<template v-if="isEffectivelyAdvanced">
<i class="icon-[lucide--eye] size-4" />
<span>{{ t('rightSidePanel.showInput') }}</span>
</template>
<template v-else>
<i class="icon-[lucide--eye-off] size-4" />
<span>{{ t('rightSidePanel.hideInput') }}</span>
</template>
</button>
<button
:class="buttonClasses"
@click="

View File

@@ -7,8 +7,9 @@ import type { Positionable } from '@/lib/litegraph/src/interfaces'
import type { LGraphGroup } from '@/lib/litegraph/src/LGraphGroup'
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { isLGraphGroup, isLGraphNode } from '@/utils/litegraphUtil'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
import { isLGraphGroup, isLGraphNode } from '@/utils/litegraphUtil'
export const GetNodeParentGroupKey: InjectionKey<
(node: LGraphNode) => LGraphGroup | null
@@ -252,6 +253,7 @@ function repeatItems<T>(items: T[]): T[] {
export function computedSectionDataList(nodes: MaybeRefOrGetter<LGraphNode[]>) {
const settingStore = useSettingStore()
const advancedOverridesStore = useAdvancedWidgetOverridesStore()
const includesAdvanced = computed(() =>
settingStore.get('Comfy.Node.AlwaysShowAdvancedWidgets')
@@ -266,7 +268,8 @@ export function computedSectionDataList(nodes: MaybeRefOrGetter<LGraphNode[]>) {
!(
w.options?.canvasOnly ||
w.options?.hidden ||
(w.options?.advanced && !includesAdvanced.value)
(advancedOverridesStore.getAdvancedState(node, w) &&
!includesAdvanced.value)
)
)
.map((widget) => ({ node, widget }))

View File

@@ -200,6 +200,7 @@ import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeS
import { app } from '@/scripts/app'
import { useExecutionStore } from '@/stores/executionStore'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
import { isTransparent } from '@/utils/colorUtil'
import {
@@ -496,6 +497,8 @@ const lgraphNode = computed(() => {
return getNodeByLocatorId(app.rootGraph, locatorId)
})
const advancedOverridesStore = useAdvancedWidgetOverridesStore()
const showAdvancedInputsButton = computed(() => {
const node = lgraphNode.value
if (!node) return false
@@ -507,12 +510,11 @@ const showAdvancedInputsButton = computed(() => {
return allInteriorWidgets.some((w) => !w.computedDisabled && !w.promoted)
}
// For regular nodes: show button if there are advanced widgets and they're currently hidden
const hasAdvancedWidgets = nodeData.widgets?.some((w) => w.options?.advanced)
// For regular nodes: show button if there are effectively advanced widgets
const alwaysShowAdvanced = settingStore.get(
'Comfy.Node.AlwaysShowAdvancedWidgets'
)
return hasAdvancedWidgets && !alwaysShowAdvanced
return advancedOverridesStore.hasAnyAdvanced(node) && !alwaysShowAdvanced
})
const showAdvancedState = customRef((track, trigger) => {
@@ -543,6 +545,9 @@ const showAdvancedState = customRef((track, trigger) => {
} else {
node.showAdvanced = value
internalState = value
nextTick(() => {
node.expandToFitContent()
})
}
trigger()
}

View File

@@ -27,7 +27,7 @@
<div
v-if="
!widget.simplified.options?.hidden &&
(!widget.simplified.options?.advanced || showAdvanced)
(!widget.simplified.advanced || showAdvanced)
"
class="lg-node-widget group col-span-full grid grid-cols-subgrid items-stretch"
>
@@ -94,6 +94,7 @@ import {
shouldRenderAsVue
} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
import { app } from '@/scripts/app'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
import {
getLocatorIdFromNodeData,
@@ -122,8 +123,9 @@ const lgraphNode = computed((): LGraphNode | null => {
return getNodeByLocatorId(graph, locatorId) ?? null
})
// Provide node to child components for accessing advanced overrides
provide<LGraphNode | null>('node', lgraphNode.value)
provide('node', lgraphNode)
const advancedOverridesStore = useAdvancedWidgetOverridesStore()
function handleWidgetPointerEvent(event: PointerEvent) {
if (shouldHandleNodePointerEvents.value) return
@@ -192,6 +194,10 @@ const processedWidgets = computed((): ProcessedWidget[] => {
? { ...options, disabled: true }
: options
const resolvedAdvanced = lgraphNode.value
? advancedOverridesStore.getAdvancedState(lgraphNode.value, widget)
: !!widget.options?.advanced
const simplified: SimplifiedWidget = {
name: widget.name,
type: widget.type,
@@ -203,7 +209,7 @@ const processedWidgets = computed((): ProcessedWidget[] => {
nodeType: widget.nodeType,
options: widgetOptions,
spec: widget.spec,
advanced: widget.options?.advanced,
advanced: resolvedAdvanced,
hidden: widget.options?.hidden
}
@@ -229,22 +235,37 @@ const processedWidgets = computed((): ProcessedWidget[] => {
})
}
// When per-node showAdvanced is on, move advanced widgets to the end.
// When global AlwaysShowAdvancedWidgets is on, keep original order.
if (
nodeData?.showAdvanced &&
!settingStore.get('Comfy.Node.AlwaysShowAdvancedWidgets')
) {
const normal = result.filter((w) => !w.simplified.advanced)
const advanced = result.filter((w) => w.simplified.advanced)
return [...normal, ...advanced]
}
return result
})
const gridTemplateRows = computed((): string => {
if (!nodeData?.widgets) return ''
const processedNames = new Set(toValue(processedWidgets).map((w) => w.name))
return nodeData.widgets
.filter(
(w) =>
processedNames.has(w.name) &&
!w.options?.hidden &&
(!w.options?.advanced || showAdvanced.value)
)
.map((w) =>
shouldExpand(w.type) || w.hasLayoutSize ? 'auto' : 'min-content'
)
const processed = toValue(processedWidgets)
const advancedByName = new Map(
processed.map((w) => [w.name, w.simplified.advanced])
)
// Use processedWidgets order (which may have advanced sorted to end)
const visible = processed.filter(
(w) =>
!w.simplified.hidden &&
(!advancedByName.get(w.name) || showAdvanced.value)
)
return visible
.map((w) => {
const raw = nodeData.widgets?.find((rw) => rw.name === w.name)
return shouldExpand(w.type) || raw?.hasLayoutSize ? 'auto' : 'min-content'
})
.join(' ')
})
</script>

View File

@@ -1,8 +1,6 @@
<script setup lang="ts">
import { computed, inject } from 'vue'
import { inject } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import { cn } from '@/utils/tailwindUtil'
@@ -14,22 +12,6 @@ const { widget } = defineProps<{
}>()
const hideLayoutField = inject<boolean>('hideLayoutField', false)
const node = inject<LGraphNode | null>('node', null)
const advancedOverridesStore = useAdvancedWidgetOverridesStore()
const isAdvanced = computed(() => {
if (!node) return !!widget.advanced
return advancedOverridesStore.getAdvancedState(node, {
name: widget.name,
options: { advanced: widget.advanced }
} as any)
})
function toggleAdvanced() {
if (!node) return
advancedOverridesStore.toggleAdvanced(node, widget.name)
}
</script>
<template>
@@ -60,15 +42,5 @@ function toggleAdvanced() {
<slot />
</div>
</div>
<button
:class="
cn(
'absolute right-1 hover:scale-150 size-2 rounded-full p-0 m-0 ring-0 border-none z-10',
isAdvanced ? 'bg-green-500' : 'bg-gray-500'
)
"
@click.stop="toggleAdvanced"
/>
</div>
</template>

View File

@@ -0,0 +1,160 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
const mockGraph = {
extra: {} as Record<string, unknown>,
nodes: [] as Array<{ id: number; widgets: Array<{ name: string }> }>
}
vi.mock('@/scripts/app', () => ({
app: {
get rootGraph() {
return mockGraph
}
}
}))
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
useWorkflowStore: () => ({
activeWorkflow: { path: 'test-workflow' },
nodeToNodeLocatorId: (node: { id: number }) => `node-${node.id}`
})
}))
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
useCanvasStore: () => ({
canvas: { setDirty: vi.fn() }
})
}))
function makeNode(
id: number,
widgets: Array<{ name: string; options?: Record<string, unknown> }>
) {
return { id, widgets } as any
}
describe('useAdvancedWidgetOverridesStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
mockGraph.extra = {}
mockGraph.nodes = []
})
it('returns backend value when no override exists', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'steps', options: { advanced: true } }])
expect(store.getAdvancedState(node, node.widgets[0])).toBe(true)
const node2 = makeNode(2, [{ name: 'cfg' }])
expect(store.getAdvancedState(node2, node2.widgets[0])).toBe(false)
})
it('override to advanced takes precedence over backend', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'cfg' }])
store.setAdvanced(node, 'cfg', true)
expect(store.getAdvancedState(node, node.widgets[0])).toBe(true)
expect(store.isOverridden(node, 'cfg')).toBe(true)
})
it('override to non-advanced takes precedence over backend', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'steps', options: { advanced: true } }])
store.setAdvanced(node, 'steps', false)
expect(store.getAdvancedState(node, node.widgets[0])).toBe(false)
expect(store.isOverridden(node, 'steps')).toBe(true)
})
it('clearOverride reverts to backend default', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'steps', options: { advanced: true } }])
store.setAdvanced(node, 'steps', false)
expect(store.getAdvancedState(node, node.widgets[0])).toBe(false)
store.clearOverride(node, 'steps')
expect(store.getAdvancedState(node, node.widgets[0])).toBe(true)
expect(store.isOverridden(node, 'steps')).toBe(false)
})
it('hasAnyAdvanced reflects overrides', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'cfg' }, { name: 'steps' }])
expect(store.hasAnyAdvanced(node)).toBe(false)
store.setAdvanced(node, 'cfg', true)
expect(store.hasAnyAdvanced(node)).toBe(true)
})
it('persists overrides to workflow.extra', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'cfg' }])
store.setAdvanced(node, 'cfg', true)
const stored = mockGraph.extra.advancedWidgetOverrides as any
expect(stored.overrides).toHaveLength(1)
expect(stored.overrides[0]).toEqual({
nodeLocatorId: 'node-1',
widgetName: 'cfg',
advanced: true
})
})
it('loads overrides from workflow.extra', () => {
mockGraph.extra = {
advancedWidgetOverrides: {
overrides: [
{ nodeLocatorId: 'node-1', widgetName: 'cfg', advanced: true },
{ nodeLocatorId: 'node-2', widgetName: 'steps', advanced: false }
]
}
}
const store = useAdvancedWidgetOverridesStore()
store.loadFromWorkflow()
const node1 = makeNode(1, [{ name: 'cfg' }])
expect(store.getAdvancedState(node1, node1.widgets[0])).toBe(true)
const node2 = makeNode(2, [{ name: 'steps', options: { advanced: true } }])
expect(store.getAdvancedState(node2, node2.widgets[0])).toBe(false)
})
it('clearAllOverrides removes everything', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'cfg' }])
store.setAdvanced(node, 'cfg', true)
expect(store.overrides.size).toBe(1)
store.clearAllOverrides()
expect(store.overrides.size).toBe(0)
})
it('pruneInvalidOverrides removes stale entries', () => {
const store = useAdvancedWidgetOverridesStore()
const node = makeNode(1, [{ name: 'cfg' }, { name: 'steps' }])
store.setAdvanced(node, 'cfg', true)
store.setAdvanced(node, 'steps', true)
expect(store.overrides.size).toBe(2)
// Simulate node having only 'cfg' widget now
mockGraph.nodes = [{ id: 1, widgets: [{ name: 'cfg' }] }] as any
store.pruneInvalidOverrides()
expect(store.overrides.size).toBe(1)
expect(store.isOverridden(node, 'cfg')).toBe(true)
expect(store.isOverridden(node, 'steps')).toBe(false)
})
})

View File

@@ -1,41 +1,33 @@
import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import type { IWidgetOptions } from '@/lib/litegraph/src/types/widgets'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import type { NodeLocatorId } from '@/types/nodeIdentification'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { app } from '@/scripts/app'
import type { NodeLocatorId } from '@/types/nodeIdentification'
/**
* Unique identifier for a widget's advanced override.
*/
interface AdvancedWidgetOverrideId {
interface AdvancedWidgetOverrideEntry {
nodeLocatorId: NodeLocatorId
widgetName: string
/** true = force advanced, false = force non-advanced */
advanced: boolean
}
/**
* Storage format for persisted advanced widget overrides.
* Stored in workflow.extra.advancedWidgetOverrides.
*/
interface AdvancedWidgetOverridesStorage {
overrides: AdvancedWidgetOverrideId[]
overrides: AdvancedWidgetOverrideEntry[]
}
/**
* Store for managing advanced widget status overrides.
* Manages per-workflow user overrides for widget advanced status.
*
* Users can manually mark/unmark widgets as advanced, and this preference
* is stored per-workflow. This allows customization of which widgets
* appear in the advanced section.
* Three-state model per widget:
* - No override: uses backend value (widget.options.advanced)
* - Override to true: widget is forced into the advanced section
* - Override to false: widget is forced out of the advanced section
*
* Design decisions:
* - Scope: Per-workflow (not global user preference)
* - Identifier: node locator ID + widget.name
* - Persistence: Stored in workflow.extra.advancedWidgetOverrides
* - Override logic: User override takes precedence over backend value
* Persisted in workflow.extra.advancedWidgetOverrides.
*/
export const useAdvancedWidgetOverridesStore = defineStore(
'advancedWidgetOverrides',
@@ -43,19 +35,19 @@ export const useAdvancedWidgetOverridesStore = defineStore(
const workflowStore = useWorkflowStore()
const canvasStore = useCanvasStore()
const overriddenIds = ref<string[]>([])
/** Map of override key → advanced boolean */
const overrides = ref<Map<string, boolean>>(new Map())
/**
* Generate a unique string key for an override ID.
*/
function getOverrideKey(id: AdvancedWidgetOverrideId): string {
return JSON.stringify([id.nodeLocatorId, id.widgetName])
function getOverrideKey(
nodeLocatorId: NodeLocatorId,
widgetName: string
): string {
return JSON.stringify([nodeLocatorId, widgetName])
}
/**
* Parse an override key back into an AdvancedWidgetOverrideId.
*/
function parseOverrideKey(key: string): AdvancedWidgetOverrideId | null {
function parseOverrideKey(
key: string
): { nodeLocatorId: NodeLocatorId; widgetName: string } | null {
try {
const [nodeLocatorId, widgetName] = JSON.parse(key) as [string, string]
if (!nodeLocatorId || !widgetName) return null
@@ -65,23 +57,14 @@ export const useAdvancedWidgetOverridesStore = defineStore(
}
}
function createOverrideId(
node: LGraphNode,
widgetName: string
): AdvancedWidgetOverrideId {
return {
nodeLocatorId: workflowStore.nodeToNodeLocatorId(node),
widgetName
}
function getNodeLocatorId(node: LGraphNode): NodeLocatorId {
return workflowStore.nodeToNodeLocatorId(node)
}
/**
* Load overrides from the current workflow's extra data.
*/
function loadFromWorkflow() {
const graph = app.rootGraph
if (!graph) {
overriddenIds.value = []
overrides.value = new Map()
return
}
@@ -90,38 +73,37 @@ export const useAdvancedWidgetOverridesStore = defineStore(
| AdvancedWidgetOverridesStorage
| undefined
const newMap = new Map<string, boolean>()
if (storedData?.overrides) {
const keys = storedData.overrides
.filter((override) => override.nodeLocatorId && override.widgetName)
.map((override) => getOverrideKey(override))
overriddenIds.value = keys
} else {
overriddenIds.value = []
for (const entry of storedData.overrides) {
if (!entry.nodeLocatorId || !entry.widgetName) continue
const key = getOverrideKey(entry.nodeLocatorId, entry.widgetName)
newMap.set(key, entry.advanced)
}
}
overrides.value = newMap
} catch (error) {
console.error(
'Failed to load advanced widget overrides from workflow:',
error
)
overriddenIds.value = []
overrides.value = new Map()
}
}
/**
* Save overrides to the current workflow's extra data.
* Marks the workflow as modified.
*/
function saveToWorkflow() {
const graph = app.rootGraph
if (!graph) return
try {
const overrides: AdvancedWidgetOverrideId[] = overriddenIds.value
.map(parseOverrideKey)
.filter((id): id is AdvancedWidgetOverrideId => id !== null)
const data: AdvancedWidgetOverridesStorage = { overrides }
const entries: AdvancedWidgetOverrideEntry[] = []
for (const [key, advanced] of overrides.value) {
const parsed = parseOverrideKey(key)
if (!parsed) continue
entries.push({ ...parsed, advanced })
}
const data: AdvancedWidgetOverridesStorage = { overrides: entries }
graph.extra ??= {}
graph.extra.advancedWidgetOverrides = data
@@ -135,77 +117,94 @@ export const useAdvancedWidgetOverridesStore = defineStore(
}
/**
* Get the resolved advanced state for a widget.
* Returns: user override if exists, otherwise backend value.
* Resolved advanced state for a widget, considering user override.
*/
function getAdvancedState(node: LGraphNode, widget: IBaseWidget): boolean {
const key = getOverrideKey(createOverrideId(node, widget.name))
const isOverridden = overriddenIds.value.includes(key)
if (isOverridden) {
return true
}
function getAdvancedState(
node: LGraphNode,
widget: { name: string; options?: IWidgetOptions<unknown> }
): boolean {
const key = getOverrideKey(getNodeLocatorId(node), widget.name)
const override = overrides.value.get(key)
if (override !== undefined) return override
return !!widget.options?.advanced
}
/**
* Toggle the advanced override for a widget.
* If the widget is already marked as advanced (by backend or override),
* toggling removes the override (falls back to backend).
* If not marked as advanced, toggling adds it to the override list.
* Set the advanced override for a widget.
* Pass the desired advanced state (true/false).
*/
function toggleAdvanced(node: LGraphNode, widgetName: string) {
const id = createOverrideId(node, widgetName)
const key = getOverrideKey(id)
if (overriddenIds.value.includes(key)) {
overriddenIds.value = overriddenIds.value.filter((k) => k !== key)
} else {
overriddenIds.value.push(key)
}
function setAdvanced(
node: LGraphNode,
widgetName: string,
advanced: boolean
) {
const key = getOverrideKey(getNodeLocatorId(node), widgetName)
overrides.value.set(key, advanced)
overrides.value = new Map(overrides.value)
saveToWorkflow()
}
/**
* Check if a widget has an active override (user marked as advanced).
* Remove the override for a widget, reverting to backend default.
*/
function clearOverride(node: LGraphNode, widgetName: string) {
const key = getOverrideKey(getNodeLocatorId(node), widgetName)
overrides.value.delete(key)
overrides.value = new Map(overrides.value)
saveToWorkflow()
}
/**
* Whether a widget has a user override.
*/
function isOverridden(node: LGraphNode, widgetName: string): boolean {
const key = getOverrideKey(createOverrideId(node, widgetName))
return overriddenIds.value.includes(key)
const key = getOverrideKey(getNodeLocatorId(node), widgetName)
return overrides.value.has(key)
}
/**
* Clear all overrides for the current workflow.
* Whether a node has any widget that is effectively advanced
* (after applying overrides).
*/
function clearOverrides() {
overriddenIds.value = []
function hasAnyAdvanced(node: LGraphNode): boolean {
const widgets = node.widgets
if (!widgets?.length) return false
return widgets.some((w) => getAdvancedState(node, w))
}
function clearAllOverrides() {
overrides.value = new Map()
saveToWorkflow()
}
/**
* Remove invalid overrides (where node or widget no longer exists).
* Remove overrides for nodes/widgets that no longer exist.
*/
function pruneInvalidOverrides() {
const graph = app.rootGraph
if (!graph) return
const validKeys: Set<string> = new Set()
const validKeys = new Set<string>()
graph.nodes?.forEach((node) => {
node.widgets?.forEach((widget) => {
const id = createOverrideId(node as LGraphNode, widget.name)
const key = getOverrideKey(id)
const key = getOverrideKey(
getNodeLocatorId(node as LGraphNode),
widget.name
)
validKeys.add(key)
})
})
const filteredIds = overriddenIds.value.filter((key) =>
validKeys.has(key)
)
let changed = false
for (const key of overrides.value.keys()) {
if (!validKeys.has(key)) {
overrides.value.delete(key)
changed = true
}
}
if (filteredIds.length !== overriddenIds.value.length) {
overriddenIds.value = filteredIds
if (changed) {
overrides.value = new Map(overrides.value)
saveToWorkflow()
}
}
@@ -219,14 +218,14 @@ export const useAdvancedWidgetOverridesStore = defineStore(
)
return {
// State
overriddenIds: computed(() => overriddenIds.value),
overrides: computed(() => overrides.value),
// Actions
getAdvancedState,
toggleAdvanced,
setAdvanced,
clearOverride,
isOverridden,
clearOverrides,
hasAnyAdvanced,
clearAllOverrides,
pruneInvalidOverrides,
loadFromWorkflow,
saveToWorkflow