mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
feat: add useWidget composable and WidgetItemByKey component
- useWidget composable at src/composables/graph/useWidget.ts - Returns reactive widget, value, isHidden, isDisabled, isAdvanced, isPromoted, label - WidgetItemByKey.vue wrapper at src/components/rightSidePanel/parameters/ - 7 new tests for useWidget composable Amp-Thread-ID: https://ampcode.com/threads/T-019c2639-ebe0-7088-858d-853102e1873c Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
53
src/components/rightSidePanel/parameters/WidgetItemByKey.vue
Normal file
53
src/components/rightSidePanel/parameters/WidgetItemByKey.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
|
||||
import WidgetItem from './WidgetItem.vue'
|
||||
|
||||
const {
|
||||
nodeId,
|
||||
widgetName,
|
||||
isDraggable = false,
|
||||
hiddenFavoriteIndicator = false,
|
||||
showNodeName = false,
|
||||
parents = [],
|
||||
isShownOnParents = false
|
||||
} = defineProps<{
|
||||
nodeId: NodeId
|
||||
widgetName: string
|
||||
isDraggable?: boolean
|
||||
hiddenFavoriteIndicator?: boolean
|
||||
showNodeName?: boolean
|
||||
parents?: SubgraphNode[]
|
||||
isShownOnParents?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:widgetValue': [value: string | number | boolean | object]
|
||||
}>()
|
||||
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const node = computed(() => canvasStore.canvas?.graph?.getNodeById(nodeId))
|
||||
|
||||
const widget = computed(() =>
|
||||
node.value?.widgets?.find((w) => w.name === widgetName)
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WidgetItem
|
||||
v-if="node && widget"
|
||||
:node="node"
|
||||
:widget="widget"
|
||||
:is-draggable="isDraggable"
|
||||
:hidden-favorite-indicator="hiddenFavoriteIndicator"
|
||||
:show-node-name="showNodeName"
|
||||
:parents="parents"
|
||||
:is-shown-on-parents="isShownOnParents"
|
||||
@update:widget-value="emit('update:widgetValue', $event)"
|
||||
/>
|
||||
</template>
|
||||
117
src/composables/graph/useWidget.test.ts
Normal file
117
src/composables/graph/useWidget.test.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
|
||||
import { useWidget } from './useWidget'
|
||||
|
||||
describe('useWidget', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('returns undefined widget when not registered', () => {
|
||||
const { widget, value, isHidden, isDisabled, isAdvanced, isPromoted } =
|
||||
useWidget(1, 'test_widget')
|
||||
|
||||
expect(widget.value).toBeUndefined()
|
||||
expect(value.value).toBeUndefined()
|
||||
expect(isHidden.value).toBe(false)
|
||||
expect(isDisabled.value).toBe(false)
|
||||
expect(isAdvanced.value).toBe(false)
|
||||
expect(isPromoted.value).toBe(false)
|
||||
})
|
||||
|
||||
it('returns widget state when registered', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget(1, 'test_widget', 'text', 'initial_value', {
|
||||
hidden: true,
|
||||
advanced: true,
|
||||
label: 'Custom Label'
|
||||
})
|
||||
|
||||
const { widget, value, isHidden, isAdvanced, label } = useWidget(
|
||||
1,
|
||||
'test_widget'
|
||||
)
|
||||
|
||||
expect(widget.value).toBeDefined()
|
||||
expect(value.value).toBe('initial_value')
|
||||
expect(isHidden.value).toBe(true)
|
||||
expect(isAdvanced.value).toBe(true)
|
||||
expect(label.value).toBe('Custom Label')
|
||||
})
|
||||
|
||||
it('allows setting value through writable computed', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget(1, 'test_widget', 'number', 42)
|
||||
|
||||
const { value } = useWidget(1, 'test_widget')
|
||||
|
||||
expect(value.value).toBe(42)
|
||||
|
||||
value.value = 100
|
||||
expect(store.get(1, 'test_widget')).toBe(100)
|
||||
expect(store.getWidget(1, 'test_widget')?.value).toBe(100)
|
||||
})
|
||||
|
||||
it('reacts to nodeId ref changes', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget(1, 'shared_widget', 'text', 'node1_value')
|
||||
store.registerWidget(2, 'shared_widget', 'text', 'node2_value')
|
||||
|
||||
const nodeId = ref<NodeId>(1)
|
||||
const { value } = useWidget(nodeId, 'shared_widget')
|
||||
|
||||
expect(value.value).toBe('node1_value')
|
||||
|
||||
nodeId.value = 2
|
||||
expect(value.value).toBe('node2_value')
|
||||
})
|
||||
|
||||
it('reacts to widgetName ref changes', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget(1, 'widget_a', 'text', 'value_a')
|
||||
store.registerWidget(1, 'widget_b', 'text', 'value_b')
|
||||
|
||||
const widgetName = ref('widget_a')
|
||||
const { value } = useWidget(1, widgetName)
|
||||
|
||||
expect(value.value).toBe('value_a')
|
||||
|
||||
widgetName.value = 'widget_b'
|
||||
expect(value.value).toBe('value_b')
|
||||
})
|
||||
|
||||
it('reacts to store state changes', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget(1, 'test_widget', 'toggle', false)
|
||||
|
||||
const { value, isHidden } = useWidget(1, 'test_widget')
|
||||
|
||||
expect(value.value).toBe(false)
|
||||
expect(isHidden.value).toBe(false)
|
||||
|
||||
store.set(1, 'test_widget', true)
|
||||
expect(value.value).toBe(true)
|
||||
|
||||
store.setHidden(1, 'test_widget', true)
|
||||
expect(isHidden.value).toBe(true)
|
||||
})
|
||||
|
||||
it('returns correct disabled and promoted states', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget(1, 'test_widget', 'text', 'value', {
|
||||
disabled: true,
|
||||
promoted: true
|
||||
})
|
||||
|
||||
const { isDisabled, isPromoted } = useWidget(1, 'test_widget')
|
||||
|
||||
expect(isDisabled.value).toBe(true)
|
||||
expect(isPromoted.value).toBe(true)
|
||||
})
|
||||
})
|
||||
48
src/composables/graph/useWidget.ts
Normal file
48
src/composables/graph/useWidget.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { MaybeRefOrGetter } from 'vue'
|
||||
|
||||
import { computed, toValue } from 'vue'
|
||||
|
||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { WidgetState } from '@/stores/widgetValueStore'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
|
||||
/**
|
||||
* Composable for reactive access to widget state by (nodeId, widgetName).
|
||||
*
|
||||
* Provides computed accessors for widget value and metadata flags.
|
||||
* The value computed is writable, allowing direct assignment.
|
||||
*
|
||||
* @param nodeId - Node ID (can be ref or getter)
|
||||
* @param widgetName - Widget name (can be ref or getter)
|
||||
*/
|
||||
export function useWidget(
|
||||
nodeId: MaybeRefOrGetter<NodeId>,
|
||||
widgetName: MaybeRefOrGetter<string>
|
||||
) {
|
||||
const store = useWidgetValueStore()
|
||||
|
||||
const widget = computed<WidgetState | undefined>(() =>
|
||||
store.getWidget(toValue(nodeId), toValue(widgetName))
|
||||
)
|
||||
|
||||
const value = computed({
|
||||
get: () => widget.value?.value,
|
||||
set: (v: unknown) => store.set(toValue(nodeId), toValue(widgetName), v)
|
||||
})
|
||||
|
||||
const isHidden = computed(() => widget.value?.hidden ?? false)
|
||||
const isDisabled = computed(() => widget.value?.disabled ?? false)
|
||||
const isAdvanced = computed(() => widget.value?.advanced ?? false)
|
||||
const isPromoted = computed(() => widget.value?.promoted ?? false)
|
||||
const label = computed(() => widget.value?.label)
|
||||
|
||||
return {
|
||||
widget,
|
||||
value,
|
||||
isHidden,
|
||||
isDisabled,
|
||||
isAdvanced,
|
||||
isPromoted,
|
||||
label
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user