Skip to content

Commit e0b9095

Browse files
emit when widget linked
1 parent 9318f6f commit e0b9095

File tree

5 files changed

+149
-72
lines changed

5 files changed

+149
-72
lines changed

src/composables/graph/useGraphNodeManager.ts

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@ import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
1313
import { useNodeDefStore } from '@/stores/nodeDefStore'
1414
import type { WidgetValue } from '@/types/simplifiedWidget'
1515

16-
import type { LGraph, LGraphNode } from '../../lib/litegraph/src/litegraph'
16+
import type {
17+
LGraph,
18+
LGraphNode,
19+
LGraphTriggerAction,
20+
LGraphTriggerParam
21+
} from '../../lib/litegraph/src/litegraph'
22+
import { NodeSlotType } from '../../lib/litegraph/src/types/globalEnums'
23+
24+
export interface WidgetSlotMetadata {
25+
index: number
26+
linked: boolean
27+
}
1728

1829
export interface SafeWidgetData {
1930
name: string
@@ -23,6 +34,7 @@ export interface SafeWidgetData {
2334
options?: Record<string, unknown>
2435
callback?: ((value: unknown) => void) | undefined
2536
spec?: InputSpec
37+
slotMetadata?: WidgetSlotMetadata
2638
}
2739

2840
export interface VueNodeData {
@@ -66,6 +78,22 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
6678
// Non-reactive storage for original LiteGraph nodes
6779
const nodeRefs = new Map<string, LGraphNode>()
6880

81+
const refreshNodeSlots = (nodeId: string) => {
82+
const nodeRef = nodeRefs.get(nodeId)
83+
const currentData = vueNodeData.get(nodeId)
84+
85+
if (!nodeRef || !currentData) return
86+
87+
const refreshedData = extractVueNodeData(nodeRef)
88+
89+
vueNodeData.set(nodeId, {
90+
...currentData,
91+
widgets: refreshedData.widgets,
92+
inputs: refreshedData.inputs,
93+
outputs: refreshedData.outputs
94+
})
95+
}
96+
6997
// Extract safe data from LiteGraph node for Vue consumption
7098
const extractVueNodeData = (node: LGraphNode): VueNodeData => {
7199
// Determine subgraph ID - null for root graph, string for subgraphs
@@ -74,6 +102,16 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
74102
? String(node.graph.id)
75103
: null
76104
// Extract safe widget data
105+
const slotMetadata = new Map<string, WidgetSlotMetadata>()
106+
107+
node.inputs?.forEach((input, index) => {
108+
if (!input?.widget?.name) return
109+
slotMetadata.set(input.widget.name, {
110+
index,
111+
linked: input.link != null
112+
})
113+
})
114+
77115
const safeWidgets = node.widgets?.map((widget) => {
78116
try {
79117
// TODO: Use widget.getReactiveData() once TypeScript types are updated
@@ -90,6 +128,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
90128
value = widget.options.values[0]
91129
}
92130
const spec = nodeDefStore.getInputSpecForWidget(node, widget.name)
131+
const slotInfo = slotMetadata.get(widget.name)
93132

94133
return {
95134
name: widget.name,
@@ -98,7 +137,8 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
98137
label: widget.label,
99138
options: widget.options ? { ...widget.options } : undefined,
100139
callback: widget.callback,
101-
spec
140+
spec,
141+
slotMetadata: slotInfo
102142
}
103143
} catch (error) {
104144
return {
@@ -405,37 +445,27 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
405445
handleNodeRemoved(node, originalOnNodeRemoved)
406446
}
407447

408-
// Listen for property change events from instrumented nodes
409-
graph.onTrigger = (action: string, param: unknown) => {
410-
if (
411-
action === 'node:property:changed' &&
412-
param &&
413-
typeof param === 'object'
414-
) {
415-
const event = param as {
416-
nodeId: string | number
417-
property: string
418-
oldValue: unknown
419-
newValue: unknown
420-
}
421-
422-
const nodeId = String(event.nodeId)
448+
const triggerHandlers: {
449+
[K in LGraphTriggerAction]: (event: LGraphTriggerParam<K>) => void
450+
} = {
451+
'node:property:changed': (propertyEvent) => {
452+
const nodeId = String(propertyEvent.nodeId)
423453
const currentData = vueNodeData.get(nodeId)
424454

425455
if (currentData) {
426-
switch (event.property) {
456+
switch (propertyEvent.property) {
427457
case 'title':
428458
vueNodeData.set(nodeId, {
429459
...currentData,
430-
title: String(event.newValue)
460+
title: String(propertyEvent.newValue)
431461
})
432462
break
433463
case 'flags.collapsed':
434464
vueNodeData.set(nodeId, {
435465
...currentData,
436466
flags: {
437467
...currentData.flags,
438-
collapsed: Boolean(event.newValue)
468+
collapsed: Boolean(propertyEvent.newValue)
439469
}
440470
})
441471
break
@@ -444,63 +474,59 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
444474
...currentData,
445475
flags: {
446476
...currentData.flags,
447-
pinned: Boolean(event.newValue)
477+
pinned: Boolean(propertyEvent.newValue)
448478
}
449479
})
450480
break
451481
case 'mode':
452482
vueNodeData.set(nodeId, {
453483
...currentData,
454-
mode: typeof event.newValue === 'number' ? event.newValue : 0
484+
mode:
485+
typeof propertyEvent.newValue === 'number'
486+
? propertyEvent.newValue
487+
: 0
455488
})
456489
break
457490
case 'color':
458491
vueNodeData.set(nodeId, {
459492
...currentData,
460493
color:
461-
typeof event.newValue === 'string'
462-
? event.newValue
494+
typeof propertyEvent.newValue === 'string'
495+
? propertyEvent.newValue
463496
: undefined
464497
})
465498
break
466499
case 'bgcolor':
467500
vueNodeData.set(nodeId, {
468501
...currentData,
469502
bgcolor:
470-
typeof event.newValue === 'string'
471-
? event.newValue
503+
typeof propertyEvent.newValue === 'string'
504+
? propertyEvent.newValue
472505
: undefined
473506
})
474507
}
475508
}
476-
} else if (
477-
action === 'node:slot-errors:changed' &&
478-
param &&
479-
typeof param === 'object'
480-
) {
481-
const event = param as { nodeId: string | number }
482-
const nodeId = String(event.nodeId)
483-
const litegraphNode = nodeRefs.get(nodeId)
484-
const currentData = vueNodeData.get(nodeId)
485-
486-
if (litegraphNode && currentData) {
487-
// Re-extract slot data with updated hasErrors properties
488-
vueNodeData.set(nodeId, {
489-
...currentData,
490-
inputs: litegraphNode.inputs
491-
? [...litegraphNode.inputs]
492-
: undefined,
493-
outputs: litegraphNode.outputs
494-
? [...litegraphNode.outputs]
495-
: undefined
496-
})
509+
},
510+
'node:slot-errors:changed': (slotErrorsEvent) => {
511+
refreshNodeSlots(String(slotErrorsEvent.nodeId))
512+
},
513+
'node:slot-links:changed': (slotLinksEvent) => {
514+
if (slotLinksEvent.slotType === NodeSlotType.INPUT) {
515+
refreshNodeSlots(String(slotLinksEvent.nodeId))
497516
}
498517
}
518+
}
499519

500-
// Call original trigger handler if it exists
501-
if (originalOnTrigger) {
502-
originalOnTrigger(action, param)
520+
const isTriggerAction = (value: string): value is LGraphTriggerAction =>
521+
Object.prototype.hasOwnProperty.call(triggerHandlers, value)
522+
523+
graph.onTrigger = (action: string, event: unknown) => {
524+
if (isTriggerAction(action)) {
525+
const handler = triggerHandlers[action] as (payload: unknown) => void
526+
handler(event)
503527
}
528+
529+
originalOnTrigger?.(action, event)
504530
}
505531

506532
// Initialize state

src/lib/litegraph/src/LGraph.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ import {
5454
splitPositionables
5555
} from './subgraph/subgraphUtils'
5656
import { Alignment, LGraphEventMode } from './types/globalEnums'
57+
import type {
58+
LGraphTriggerAction,
59+
LGraphTriggerHandler,
60+
LGraphTriggerParam
61+
} from './types/graphTriggers'
5762
import type {
5863
ExportedSubgraph,
5964
ExposedWidget,
@@ -65,6 +70,11 @@ import type {
6570
} from './types/serialisation'
6671
import { getAllNestedItems } from './utils/collections'
6772

73+
export type {
74+
LGraphTriggerAction,
75+
LGraphTriggerParam
76+
} from './types/graphTriggers'
77+
6878
export interface LGraphState {
6979
lastGroupId: number
7080
lastNodeId: number
@@ -254,7 +264,7 @@ export class LGraph
254264
onExecuteStep?(): void
255265
onNodeAdded?(node: LGraphNode): void
256266
onNodeRemoved?(node: LGraphNode): void
257-
onTrigger?(action: string, param: unknown): void
267+
onTrigger?: LGraphTriggerHandler
258268
onBeforeChange?(graph: LGraph, info?: LGraphNode): void
259269
onAfterChange?(graph: LGraph, info?: LGraphNode | null): void
260270
onConnectionChange?(node: LGraphNode): void
@@ -1180,6 +1190,11 @@ export class LGraph
11801190
}
11811191

11821192
// ********** GLOBALS *****************
1193+
trigger<A extends LGraphTriggerAction>(
1194+
action: A,
1195+
param: LGraphTriggerParam<A>
1196+
): void
1197+
trigger(action: string, param: unknown): void
11831198
trigger(action: string, param: unknown) {
11841199
this.onTrigger?.(action, param)
11851200
}

src/lib/litegraph/src/litegraph.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ export type {
9898
Positionable,
9999
Size
100100
} from './interfaces'
101-
export { LGraph } from './LGraph'
101+
export {
102+
LGraph,
103+
type LGraphTriggerAction,
104+
type LGraphTriggerParam
105+
} from './LGraph'
102106
export { BadgePosition, LGraphBadge } from './LGraphBadge'
103107
export { LGraphCanvas } from './LGraphCanvas'
104108
export { LGraphGroup } from './LGraphGroup'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { NodeSlotType } from './globalEnums'
2+
3+
interface NodePropertyChangedEvent {
4+
nodeId: string | number
5+
property: string
6+
oldValue: unknown
7+
newValue: unknown
8+
}
9+
10+
interface NodeSlotErrorsChangedEvent {
11+
nodeId: string | number
12+
}
13+
14+
interface NodeSlotLinksChangedEvent {
15+
nodeId: string | number
16+
slotType: NodeSlotType
17+
slotIndex: number
18+
connected: boolean
19+
linkId: number
20+
}
21+
22+
type LGraphTriggerEventMap = {
23+
'node:property:changed': NodePropertyChangedEvent
24+
'node:slot-errors:changed': NodeSlotErrorsChangedEvent
25+
'node:slot-links:changed': NodeSlotLinksChangedEvent
26+
}
27+
28+
export type LGraphTriggerAction = keyof LGraphTriggerEventMap
29+
30+
export type LGraphTriggerParam<A extends LGraphTriggerAction> =
31+
A extends keyof LGraphTriggerEventMap ? LGraphTriggerEventMap[A] : unknown
32+
33+
export type LGraphTriggerHandler = {
34+
<A extends LGraphTriggerAction>(action: A, param: LGraphTriggerParam<A>): void
35+
(action: string, param: unknown): void
36+
}

src/renderer/extensions/vueNodes/components/NodeWidgets.vue

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
boundingRect: [0, 0, 0, 0]
3434
}"
3535
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
36-
:index="getWidgetInputIndex(widget)"
36+
:index="widget.slotMetadata?.index ?? 0"
3737
:dot-only="true"
3838
/>
3939
</div>
@@ -56,12 +56,12 @@ import { type Ref, computed, inject, onErrorCaptured, ref } from 'vue'
5656
5757
import type {
5858
SafeWidgetData,
59-
VueNodeData
59+
VueNodeData,
60+
WidgetSlotMetadata
6061
} from '@/composables/graph/useGraphNodeManager'
6162
import { useErrorHandling } from '@/composables/useErrorHandling'
6263
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
6364
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
64-
// Import widget components directly
6565
import WidgetInputText from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue'
6666
import {
6767
getComponent,
@@ -113,6 +113,7 @@ interface ProcessedWidget {
113113
value: WidgetValue
114114
updateHandler: (value: unknown) => void
115115
tooltipConfig: any
116+
slotMetadata?: WidgetSlotMetadata
116117
}
117118
118119
const processedWidgets = computed((): ProcessedWidget[] => {
@@ -129,12 +130,21 @@ const processedWidgets = computed((): ProcessedWidget[] => {
129130
130131
const vueComponent = getComponent(widget.type) || WidgetInputText
131132
133+
const slotMetadata = widget.slotMetadata
134+
135+
let widgetOptions = widget.options
136+
if (slotMetadata?.linked) {
137+
widgetOptions = widget.options
138+
? { ...widget.options, disabled: true }
139+
: { disabled: true }
140+
}
141+
132142
const simplified: SimplifiedWidget = {
133143
name: widget.name,
134144
type: widget.type,
135145
value: widget.value,
136146
label: widget.label,
137-
options: widget.options,
147+
options: widgetOptions,
138148
callback: widget.callback,
139149
spec: widget.spec
140150
}
@@ -155,25 +165,11 @@ const processedWidgets = computed((): ProcessedWidget[] => {
155165
simplified,
156166
value: widget.value,
157167
updateHandler,
158-
tooltipConfig
168+
tooltipConfig,
169+
slotMetadata
159170
})
160171
}
161172
162173
return result
163174
})
164-
165-
// TODO: Refactor to avoid O(n) lookup - consider storing input index on widget creation
166-
// or restructuring data model to unify widgets and inputs
167-
// Map a widget to its corresponding input slot index
168-
const getWidgetInputIndex = (widget: ProcessedWidget): number => {
169-
const inputs = nodeData?.inputs
170-
if (!inputs) return 0
171-
172-
const idx = inputs.findIndex((input: any) => {
173-
if (!input || typeof input !== 'object') return false
174-
if (!('name' in input && 'type' in input)) return false
175-
return 'widget' in input && input.widget?.name === widget.name
176-
})
177-
return idx >= 0 ? idx : 0
178-
}
179175
</script>

0 commit comments

Comments
 (0)