Skip to content

Commit 5c21b1f

Browse files
committed
[refactor] use composable to pass slot error states
1 parent 4eed21a commit 5c21b1f

File tree

9 files changed

+545
-322
lines changed

9 files changed

+545
-322
lines changed

src/components/graph/GraphCanvas.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteracti
118118
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
119119
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
120120
import VueGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
121+
import { useSlotErrorState } from '@/renderer/extensions/vueNodes/composables/useSlotErrorState'
121122
import { UnauthorizedError, api } from '@/scripts/api'
122123
import { app as comfyApp } from '@/scripts/app'
123124
import { ChangeTracker } from '@/scripts/changeTracker'
@@ -143,6 +144,7 @@ const nodeDefStore = useNodeDefStore()
143144
const workspaceStore = useWorkspaceStore()
144145
const canvasStore = useCanvasStore()
145146
const executionStore = useExecutionStore()
147+
const { setSlotError, clearNodeErrors } = useSlotErrorState()
146148
const toastStore = useToastStore()
147149
const canvasInteractions = useCanvasInteractions()
148150
@@ -298,6 +300,9 @@ watch(
298300
() => executionStore.lastNodeErrors,
299301
(lastNodeErrors) => {
300302
const removeSlotError = (node: LGraphNode) => {
303+
// Clear reactive state
304+
clearNodeErrors(String(node.id))
305+
// Clear LiteGraph object properties
301306
for (const slot of node.inputs) {
302307
delete slot.hasErrors
303308
}
@@ -314,17 +319,20 @@ watch(
314319
const validErrors = nodeErrors.errors.filter(
315320
(error) => error.extra_info?.input_name !== undefined
316321
)
317-
const slotErrorsChanged =
318-
validErrors.length > 0 &&
319-
validErrors.some((error) => {
322+
const slotErrorsChanged = validErrors
323+
.map((error) => {
320324
const inputName = error.extra_info!.input_name!
321325
const inputIndex = node.findInputSlot(inputName)
322326
if (inputIndex !== -1) {
327+
// Update LiteGraph object
323328
node.inputs[inputIndex].hasErrors = true
329+
// Update reactive state (coupled update)
330+
setSlotError(String(node.id), inputIndex, 'input', true)
324331
return true
325332
}
326333
return false
327334
})
335+
.some(Boolean)
328336
329337
// Trigger Vue node data update if slot errors changed
330338
if (slotErrorsChanged && comfyApp.graph.onTrigger) {

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<SlotConnectionDot
66
ref="connectionDotRef"
77
:color="slotColor"
8-
:class="errorClassesDot"
8+
:class="cn('-translate-x-1/2', errorClassesDot)"
99
v-on="readonly ? {} : { pointerdown: onPointerDown }"
1010
/>
1111

@@ -40,8 +40,8 @@ import { getSlotColor } from '@/constants/slotColors'
4040
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
4141
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
4242
import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking'
43+
import { useSlotErrorState } from '@/renderer/extensions/vueNodes/composables/useSlotErrorState'
4344
import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction'
44-
import type { NodeErrorContext } from '@/renderer/extensions/vueNodes/types/errorContext'
4545
import { cn } from '@/utils/tailwindUtil'
4646
4747
import LODFallback from './LODFallback.vue'
@@ -60,19 +60,17 @@ interface InputSlotProps {
6060
6161
const props = defineProps<InputSlotProps>()
6262
63-
const nodeErrorContext = inject<NodeErrorContext>('nodeErrorContext')
63+
const { hasSlotError: hasSlotErrorFromState } = useSlotErrorState()
6464
6565
const hasSlotError = computed(() => {
66-
const slotName = props.slotData?.name
67-
if (!slotName || !nodeErrorContext) return false
68-
return nodeErrorContext.hasInputSlotError(slotName)
66+
// Use reactive state instead of direct LiteGraph property
67+
return hasSlotErrorFromState(props.nodeId ?? '', props.index, 'input')
6968
})
7069
7170
const errorClassesDot = computed(() => {
72-
return cn('-translate-x-1/2', {
73-
'ring-2 ring-error dark-theme:ring-error ring-offset-0 rounded-full':
74-
hasSlotError.value
75-
})
71+
return hasSlotError.value
72+
? 'ring-2 ring-error dark-theme:ring-error ring-offset-0 rounded-full'
73+
: ''
7674
})
7775
7876
const labelClasses = computed(() =>

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

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@
9494
>
9595
<!-- Slots only rendered at full detail -->
9696
<NodeSlots
97-
v-memo="[nodeData.inputs?.length, nodeData.outputs?.length]"
97+
v-memo="[
98+
nodeData.inputs?.length,
99+
nodeData.outputs?.length,
100+
slotErrorState
101+
]"
98102
:node-data="nodeData"
99103
:readonly="readonly"
100104
/>
@@ -133,15 +137,7 @@
133137

134138
<script setup lang="ts">
135139
import { storeToRefs } from 'pinia'
136-
import {
137-
computed,
138-
inject,
139-
onErrorCaptured,
140-
onMounted,
141-
provide,
142-
ref,
143-
readonly as vueReadonly
144-
} from 'vue'
140+
import { computed, inject, onErrorCaptured, onMounted, provide, ref } from 'vue'
145141
146142
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
147143
import { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'
@@ -152,11 +148,11 @@ import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteracti
152148
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
153149
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
154150
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
151+
import { useSlotErrorState } from '@/renderer/extensions/vueNodes/composables/useSlotErrorState'
155152
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
156153
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
157154
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
158155
import { useNodePreviewState } from '@/renderer/extensions/vueNodes/preview/useNodePreviewState'
159-
import type { NodeErrorContext } from '@/renderer/extensions/vueNodes/types/errorContext'
160156
import { app } from '@/scripts/app'
161157
import { useExecutionStore } from '@/stores/executionStore'
162158
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
@@ -208,23 +204,15 @@ const isSelected = computed(() => {
208204
// Use execution state composable
209205
const { executing, progress } = useNodeExecutionState(() => nodeData.id)
210206
207+
// Get slot error state for v-memo dependency
208+
const { slotErrorState } = useSlotErrorState()
209+
211210
// Direct access to execution store for error state
212211
const executionStore = useExecutionStore()
213212
const hasExecutionError = computed(
214213
() => executionStore.lastExecutionErrorNodeId === nodeData.id
215214
)
216215
217-
const hasInputSlotError = (inputName: string): boolean => {
218-
const nodeValidationErrors = executionStore.lastNodeErrors?.[nodeData.id]
219-
if (!nodeValidationErrors?.errors) return false
220-
221-
return nodeValidationErrors.errors.some(
222-
(err) =>
223-
err.type === 'required_input_missing' &&
224-
err.extra_info?.input_name === inputName
225-
)
226-
}
227-
228216
const hasAnyError = computed((): boolean => {
229217
return !!(
230218
hasExecutionError.value ||
@@ -391,9 +379,4 @@ const nodeImageUrls = computed(() => {
391379
392380
const nodeContainerRef = ref()
393381
provide('tooltipContainer', nodeContainerRef)
394-
395-
const nodeErrorContext: NodeErrorContext = {
396-
hasInputSlotError
397-
}
398-
provide('nodeErrorContext', vueReadonly(nodeErrorContext))
399382
</script>

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@
55
<!-- Slot Name -->
66
<span
77
v-if="!dotOnly"
8-
:class="
9-
cn(
10-
'whitespace-nowrap text-sm font-normal lod-toggle',
11-
'dark-theme:text-slate-200 text-stone-200'
12-
)
13-
"
8+
class="whitespace-nowrap text-sm font-normal dark-theme:text-slate-200 text-stone-200 lod-toggle"
149
>
1510
{{ slotData.localized_name || slotData.name || `Output ${index}` }}
1611
</span>
@@ -20,7 +15,7 @@
2015
<SlotConnectionDot
2116
ref="connectionDotRef"
2217
:color="slotColor"
23-
:class="errorClasses"
18+
class="translate-x-1/2"
2419
v-on="readonly ? {} : { pointerdown: onPointerDown }"
2520
/>
2621
</div>
@@ -61,11 +56,6 @@ interface OutputSlotProps {
6156
6257
const props = defineProps<OutputSlotProps>()
6358
64-
// Apply styling classes to slot connection dot
65-
const errorClasses = computed(() => {
66-
return cn('translate-x-1/2')
67-
})
68-
6959
// Error boundary implementation
7060
const renderError = ref<string | null>(null)
7161
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* @fileoverview Reactive state management for slot error indicators
3+
* @module composables/useSlotErrorState
4+
*/
5+
import { reactive, readonly } from 'vue'
6+
7+
import type { NodeId } from '@/lib/litegraph/src/litegraph'
8+
9+
/**
10+
* Slot type indicators
11+
*/
12+
type SlotType = 'input' | 'output'
13+
14+
/**
15+
* Generates a consistent key for slot error state tracking
16+
* @param nodeId - The node identifier
17+
* @param slotIndex - The slot index
18+
* @param slotType - The slot type ('input' or 'output')
19+
* @returns Formatted key string
20+
*/
21+
function generateSlotKey(
22+
nodeId: NodeId,
23+
slotIndex: number,
24+
slotType: SlotType
25+
): string {
26+
return `${String(nodeId)}-${slotType}-${slotIndex}`
27+
}
28+
29+
/**
30+
* Global reactive state for slot error indicators
31+
* Provides Vue-reactive tracking for slot errors without making LiteGraph objects reactive
32+
*/
33+
const slotErrorState = reactive(new Map<string, boolean>())
34+
35+
/**
36+
* Maps nodeId to Set of slot keys for easy lookup
37+
*/
38+
const nodeToSlotsIndex = reactive(new Map<string, Set<string>>())
39+
40+
/**
41+
* Composable for managing slot error state reactively
42+
* Bridges LiteGraph object mutations with Vue reactivity
43+
*/
44+
export function useSlotErrorState() {
45+
/**
46+
* Set error state for a specific slot
47+
* @param nodeId - The node identifier (string or number from LiteGraph)
48+
* @param slotIndex - The slot index
49+
* @param slotType - The slot type ('input' or 'output')
50+
* @param hasError - Whether the slot has an error
51+
*/
52+
function setSlotError(
53+
nodeId: NodeId,
54+
slotIndex: number,
55+
slotType: SlotType,
56+
hasError: boolean
57+
) {
58+
const key = generateSlotKey(nodeId, slotIndex, slotType)
59+
const nodeIdStr = String(nodeId)
60+
61+
if (!hasError) {
62+
slotErrorState.delete(key)
63+
const nodeSlots = nodeToSlotsIndex.get(nodeIdStr)
64+
if (!nodeSlots) return
65+
66+
nodeSlots.delete(key)
67+
if (nodeSlots.size === 0) {
68+
nodeToSlotsIndex.delete(nodeIdStr)
69+
}
70+
return
71+
}
72+
73+
slotErrorState.set(key, true)
74+
if (!nodeToSlotsIndex.has(nodeIdStr)) {
75+
nodeToSlotsIndex.set(nodeIdStr, new Set())
76+
}
77+
nodeToSlotsIndex.get(nodeIdStr)!.add(key)
78+
}
79+
80+
/**
81+
* Check if a specific slot has an error
82+
* @param nodeId - The node identifier (string or number from LiteGraph)
83+
* @param slotIndex - The slot index
84+
* @param slotType - The slot type ('input' or 'output')
85+
* @returns True if the slot has an error
86+
*/
87+
function hasSlotError(
88+
nodeId: NodeId,
89+
slotIndex: number,
90+
slotType: SlotType
91+
): boolean {
92+
const key = generateSlotKey(nodeId, slotIndex, slotType)
93+
return slotErrorState.get(key) ?? false
94+
}
95+
96+
/**
97+
* Clear all error states for a specific node
98+
* @param nodeId - The node identifier (string or number from LiteGraph)
99+
*/
100+
function clearNodeErrors(nodeId: NodeId) {
101+
const nodeIdStr = String(nodeId)
102+
const nodeSlots = nodeToSlotsIndex.get(nodeIdStr)
103+
104+
if (!nodeSlots) return
105+
106+
for (const key of nodeSlots) {
107+
slotErrorState.delete(key)
108+
}
109+
nodeToSlotsIndex.delete(nodeIdStr)
110+
}
111+
112+
/**
113+
* Clear all error states
114+
*/
115+
function clearAllErrors() {
116+
slotErrorState.clear()
117+
nodeToSlotsIndex.clear()
118+
}
119+
120+
return {
121+
setSlotError,
122+
hasSlotError,
123+
clearNodeErrors,
124+
clearAllErrors,
125+
// Expose readonly version for reactive consumption
126+
slotErrorState: readonly(slotErrorState)
127+
}
128+
}

src/renderer/extensions/vueNodes/types/errorContext.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)