mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
Refactor: Let LGraphNode handle more events itself (#5709)
## Summary Don't route events up through GraphCanvas if the component itself can handle the changes ## Changes - **What**: Reduce the indirect access or action dispatch to composables/stores. ## Review Focus The behavior should be either equivalent or a little snappier than before. Also, the local state in LGraphNode has (almost) all been removed in favor of reacting to the nodeData prop. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5709-Refactor-Let-LGraphNode-handle-more-events-itself-2756d73d365081e6a88ce6241bceecc0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -77,6 +77,12 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/prefer-as-const': 'off',
|
'@typescript-eslint/prefer-as-const': 'off',
|
||||||
'@typescript-eslint/consistent-type-imports': 'error',
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||||||
|
'@typescript-eslint/no-empty-object-type': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allowInterfaces: 'always'
|
||||||
|
}
|
||||||
|
],
|
||||||
'unused-imports/no-unused-imports': 'error',
|
'unused-imports/no-unused-imports': 'error',
|
||||||
'vue/no-v-html': 'off',
|
'vue/no-v-html': 'off',
|
||||||
// Enforce dark-theme: instead of dark: prefix
|
// Enforce dark-theme: instead of dark: prefix
|
||||||
|
|||||||
@@ -53,9 +53,6 @@
|
|||||||
"
|
"
|
||||||
:zoom-level="canvasStore.canvas?.ds?.scale || 1"
|
:zoom-level="canvasStore.canvas?.ds?.scale || 1"
|
||||||
:data-node-id="nodeData.id"
|
:data-node-id="nodeData.id"
|
||||||
@node-click="handleNodeSelect"
|
|
||||||
@update:collapsed="handleNodeCollapse"
|
|
||||||
@update:title="handleNodeTitleUpdate"
|
|
||||||
/>
|
/>
|
||||||
</TransformPane>
|
</TransformPane>
|
||||||
|
|
||||||
@@ -121,8 +118,6 @@ import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteracti
|
|||||||
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
|
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
|
||||||
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
|
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
|
||||||
import VueGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
import VueGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
||||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
|
||||||
import { useExecutionStateProvider } from '@/renderer/extensions/vueNodes/execution/useExecutionStateProvider'
|
|
||||||
import { UnauthorizedError, api } from '@/scripts/api'
|
import { UnauthorizedError, api } from '@/scripts/api'
|
||||||
import { app as comfyApp } from '@/scripts/app'
|
import { app as comfyApp } from '@/scripts/app'
|
||||||
import { ChangeTracker } from '@/scripts/changeTracker'
|
import { ChangeTracker } from '@/scripts/changeTracker'
|
||||||
@@ -173,7 +168,6 @@ const { shouldRenderVueNodes } = useVueFeatureFlags()
|
|||||||
// Vue node system
|
// Vue node system
|
||||||
const vueNodeLifecycle = useVueNodeLifecycle()
|
const vueNodeLifecycle = useVueNodeLifecycle()
|
||||||
const viewportCulling = useViewportCulling()
|
const viewportCulling = useViewportCulling()
|
||||||
const nodeEventHandlers = useNodeEventHandlers()
|
|
||||||
|
|
||||||
const handleVueNodeLifecycleReset = async () => {
|
const handleVueNodeLifecycleReset = async () => {
|
||||||
if (shouldRenderVueNodes.value) {
|
if (shouldRenderVueNodes.value) {
|
||||||
@@ -204,12 +198,6 @@ const handleTransformUpdate = () => {
|
|||||||
// TODO: Fix paste position sync in separate PR
|
// TODO: Fix paste position sync in separate PR
|
||||||
vueNodeLifecycle.detectChangesInRAF.value()
|
vueNodeLifecycle.detectChangesInRAF.value()
|
||||||
}
|
}
|
||||||
const handleNodeSelect = nodeEventHandlers.handleNodeSelect
|
|
||||||
const handleNodeCollapse = nodeEventHandlers.handleNodeCollapse
|
|
||||||
const handleNodeTitleUpdate = nodeEventHandlers.handleNodeTitleUpdate
|
|
||||||
|
|
||||||
// Provide execution state to all Vue nodes
|
|
||||||
useExecutionStateProvider()
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated')
|
nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated')
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
:style="`backgroundColor: ${containerStyles.backgroundColor};`"
|
:style="`backgroundColor: ${containerStyles.backgroundColor};`"
|
||||||
:pt="{
|
:pt="{
|
||||||
header: 'hidden',
|
header: 'hidden',
|
||||||
content: 'px-1 py-1 h-10 px-1 flex flex-row gap-1'
|
content: 'p-1 h-10 flex flex-row gap-1'
|
||||||
}"
|
}"
|
||||||
@wheel="canvasInteractions.handleWheel"
|
@wheel="canvasInteractions.handleWheel"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export interface LinkReleaseContextExtended {
|
|||||||
links: ConnectingLink[]
|
links: ConnectingLink[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
||||||
export interface LiteGraphCanvasEvent extends CustomEvent<CanvasEventDetail> {}
|
export interface LiteGraphCanvasEvent extends CustomEvent<CanvasEventDetail> {}
|
||||||
|
|
||||||
export interface LGraphNodeConstructor<T extends LGraphNode = LGraphNode> {
|
export interface LGraphNodeConstructor<T extends LGraphNode = LGraphNode> {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { InjectionKey, Ref } from 'vue'
|
|
||||||
|
|
||||||
import type { NodeProgressState } from '@/schemas/apiSchema'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection key for providing executing node IDs to Vue node components.
|
|
||||||
* Contains a reactive Set of currently executing node IDs (as strings).
|
|
||||||
*/
|
|
||||||
export const ExecutingNodeIdsKey: InjectionKey<Ref<Set<string>>> =
|
|
||||||
Symbol('executingNodeIds')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection key for providing node progress states to Vue node components.
|
|
||||||
* Contains a reactive Record of node IDs to their current progress state.
|
|
||||||
*/
|
|
||||||
export const NodeProgressStatesKey: InjectionKey<
|
|
||||||
Ref<Record<string, NodeProgressState>>
|
|
||||||
> = Symbol('nodeProgressStates')
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { InjectionKey } from 'vue'
|
import type { InjectionKey } from 'vue'
|
||||||
|
|
||||||
import type { Point } from '@/renderer/core/layout/types'
|
import type { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lightweight, injectable transform state used by layout-aware components.
|
* Lightweight, injectable transform state used by layout-aware components.
|
||||||
@@ -21,29 +21,11 @@ import type { Point } from '@/renderer/core/layout/types'
|
|||||||
* const state = inject(TransformStateKey)!
|
* const state = inject(TransformStateKey)!
|
||||||
* const screen = state.canvasToScreen({ x: 100, y: 50 })
|
* const screen = state.canvasToScreen({ x: 100, y: 50 })
|
||||||
*/
|
*/
|
||||||
interface TransformState {
|
interface TransformState
|
||||||
/** Convert a screen-space point (CSS pixels) to canvas space. */
|
extends Pick<
|
||||||
screenToCanvas: (p: Point) => Point
|
ReturnType<typeof useTransformState>,
|
||||||
/** Convert a canvas-space point to screen space (CSS pixels). */
|
'screenToCanvas' | 'canvasToScreen' | 'camera' | 'isNodeInViewport'
|
||||||
canvasToScreen: (p: Point) => Point
|
> {}
|
||||||
/** Current pan/zoom; `x`/`y` are offsets, `z` is scale. */
|
|
||||||
camera?: { x: number; y: number; z: number }
|
|
||||||
/**
|
|
||||||
* Test whether a node's rectangle intersects the (expanded) viewport.
|
|
||||||
* Handy for viewport culling and lazy work.
|
|
||||||
*
|
|
||||||
* @param nodePos Top-left in canvas space `[x, y]`
|
|
||||||
* @param nodeSize Size in canvas units `[width, height]`
|
|
||||||
* @param viewport Screen-space viewport `{ width, height }`
|
|
||||||
* @param margin Optional fractional margin (e.g. `0.2` = 20%)
|
|
||||||
*/
|
|
||||||
isNodeInViewport?: (
|
|
||||||
nodePos: ArrayLike<number>,
|
|
||||||
nodeSize: ArrayLike<number>,
|
|
||||||
viewport: { width: number; height: number },
|
|
||||||
margin?: number
|
|
||||||
) => boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TransformStateKey: InjectionKey<TransformState> =
|
export const TransformStateKey: InjectionKey<TransformState> =
|
||||||
Symbol('transformState')
|
Symbol('transformState')
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
:lod-level="lodLevel"
|
:lod-level="lodLevel"
|
||||||
:collapsed="isCollapsed"
|
:collapsed="isCollapsed"
|
||||||
@collapse="handleCollapse"
|
@collapse="handleCollapse"
|
||||||
@update:title="handleTitleUpdate"
|
@update:title="handleHeaderTitleUpdate"
|
||||||
@enter-subgraph="handleEnterSubgraph"
|
@enter-subgraph="handleEnterSubgraph"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,7 +101,6 @@
|
|||||||
:node-data="nodeData"
|
:node-data="nodeData"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:lod-level="lodLevel"
|
:lod-level="lodLevel"
|
||||||
@slot-click="handleSlotClick"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Widgets rendered at reduced+ detail -->
|
<!-- Widgets rendered at reduced+ detail -->
|
||||||
@@ -140,15 +139,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import {
|
import { computed, inject, onErrorCaptured, onMounted, provide, ref } from 'vue'
|
||||||
computed,
|
|
||||||
inject,
|
|
||||||
onErrorCaptured,
|
|
||||||
onMounted,
|
|
||||||
ref,
|
|
||||||
toRef,
|
|
||||||
watch
|
|
||||||
} from 'vue'
|
|
||||||
|
|
||||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
@@ -156,13 +147,13 @@ import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
|||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||||
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
||||||
|
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||||
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
|
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
|
||||||
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||||
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
|
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
|
||||||
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
|
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
|
||||||
import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||||
import { useNodePreviewState } from '@/renderer/extensions/vueNodes/preview/useNodePreviewState'
|
import { useNodePreviewState } from '@/renderer/extensions/vueNodes/preview/useNodePreviewState'
|
||||||
import type { ExecutedWsMessage } from '@/schemas/apiSchema'
|
|
||||||
import { app } from '@/scripts/app'
|
import { app } from '@/scripts/app'
|
||||||
import { useExecutionStore } from '@/stores/executionStore'
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||||
@@ -197,23 +188,10 @@ const {
|
|||||||
zoomLevel = 1
|
zoomLevel = 1
|
||||||
} = defineProps<LGraphNodeProps>()
|
} = defineProps<LGraphNodeProps>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const { handleNodeCollapse, handleNodeTitleUpdate, handleNodeSelect } =
|
||||||
'node-click': [
|
useNodeEventHandlers()
|
||||||
event: PointerEvent,
|
|
||||||
nodeData: VueNodeData,
|
|
||||||
wasDragging: boolean
|
|
||||||
]
|
|
||||||
'slot-click': [
|
|
||||||
event: PointerEvent,
|
|
||||||
nodeData: VueNodeData,
|
|
||||||
slotIndex: number,
|
|
||||||
isInput: boolean
|
|
||||||
]
|
|
||||||
'update:collapsed': [nodeId: string, collapsed: boolean]
|
|
||||||
'update:title': [nodeId: string, newTitle: string]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
useVueElementTracking(nodeData.id, 'node')
|
useVueElementTracking(() => nodeData.id, 'node')
|
||||||
|
|
||||||
const { selectedNodeIds } = storeToRefs(useCanvasStore())
|
const { selectedNodeIds } = storeToRefs(useCanvasStore())
|
||||||
|
|
||||||
@@ -226,7 +204,7 @@ const isSelected = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Use execution state composable
|
// Use execution state composable
|
||||||
const { executing, progress } = useNodeExecutionState(nodeData.id)
|
const { executing, progress } = useNodeExecutionState(() => nodeData.id)
|
||||||
|
|
||||||
// Direct access to execution store for error state
|
// Direct access to execution store for error state
|
||||||
const executionStore = useExecutionStore()
|
const executionStore = useExecutionStore()
|
||||||
@@ -245,14 +223,13 @@ const bypassed = computed((): boolean => nodeData.mode === 4)
|
|||||||
const { handleWheel, shouldHandleNodePointerEvents } = useCanvasInteractions()
|
const { handleWheel, shouldHandleNodePointerEvents } = useCanvasInteractions()
|
||||||
|
|
||||||
// LOD (Level of Detail) system based on zoom level
|
// LOD (Level of Detail) system based on zoom level
|
||||||
const zoomRef = toRef(() => zoomLevel)
|
|
||||||
const {
|
const {
|
||||||
lodLevel,
|
lodLevel,
|
||||||
shouldRenderWidgets,
|
shouldRenderWidgets,
|
||||||
shouldRenderSlots,
|
shouldRenderSlots,
|
||||||
shouldRenderContent,
|
shouldRenderContent,
|
||||||
lodCssClass
|
lodCssClass
|
||||||
} = useLOD(zoomRef)
|
} = useLOD(() => zoomLevel)
|
||||||
|
|
||||||
// Computed properties for template usage
|
// Computed properties for template usage
|
||||||
const isMinimalLOD = computed(() => lodLevel.value === LODLevel.MINIMAL)
|
const isMinimalLOD = computed(() => lodLevel.value === LODLevel.MINIMAL)
|
||||||
@@ -268,16 +245,18 @@ onErrorCaptured((error) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Use layout system for node position and dragging
|
// Use layout system for node position and dragging
|
||||||
const { position: layoutPosition, zIndex, resize } = useNodeLayout(nodeData.id)
|
const {
|
||||||
|
position: layoutPosition,
|
||||||
|
zIndex,
|
||||||
|
resize
|
||||||
|
} = useNodeLayout(() => nodeData.id)
|
||||||
const {
|
const {
|
||||||
handlePointerDown,
|
handlePointerDown,
|
||||||
handlePointerUp,
|
handlePointerUp,
|
||||||
handlePointerMove,
|
handlePointerMove,
|
||||||
isDragging,
|
isDragging,
|
||||||
dragStyle
|
dragStyle
|
||||||
} = useNodePointerInteractions(nodeData, (event, nodeData, wasDragging) => {
|
} = useNodePointerInteractions(() => nodeData, handleNodeSelect)
|
||||||
emit('node-click', event, nodeData, wasDragging)
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (size && transformState?.camera) {
|
if (size && transformState?.camera) {
|
||||||
@@ -291,17 +270,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Track collapsed state
|
// Track collapsed state
|
||||||
const isCollapsed = ref(nodeData.flags?.collapsed ?? false)
|
const isCollapsed = computed(() => nodeData.flags?.collapsed ?? false)
|
||||||
|
|
||||||
// Watch for external changes to the collapsed state
|
|
||||||
watch(
|
|
||||||
() => nodeData.flags?.collapsed,
|
|
||||||
(newCollapsed: boolean | undefined) => {
|
|
||||||
if (newCollapsed !== undefined && newCollapsed !== isCollapsed.value) {
|
|
||||||
isCollapsed.value = newCollapsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if node has custom content (like image outputs)
|
// Check if node has custom content (like image outputs)
|
||||||
const hasCustomContent = computed(() => {
|
const hasCustomContent = computed(() => {
|
||||||
@@ -310,12 +279,13 @@ const hasCustomContent = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Computed classes and conditions for better reusability
|
// Computed classes and conditions for better reusability
|
||||||
const separatorClasses =
|
const separatorClasses = cn(
|
||||||
'bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full'
|
'bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full'
|
||||||
const progressClasses = 'h-2 bg-primary-500 transition-all duration-300'
|
)
|
||||||
|
const progressClasses = cn('h-2 bg-primary-500 transition-all duration-300')
|
||||||
|
|
||||||
const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
|
const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
|
||||||
nodeData.id,
|
() => nodeData.id,
|
||||||
{
|
{
|
||||||
isMinimalLOD,
|
isMinimalLOD,
|
||||||
isCollapsed
|
isCollapsed
|
||||||
@@ -356,31 +326,11 @@ const outlineClass = computed(() => {
|
|||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
const handleCollapse = () => {
|
const handleCollapse = () => {
|
||||||
isCollapsed.value = !isCollapsed.value
|
handleNodeCollapse(nodeData.id, !isCollapsed.value)
|
||||||
// Emit event so parent can sync with LiteGraph if needed
|
|
||||||
emit('update:collapsed', nodeData.id, isCollapsed.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSlotClick = (
|
const handleHeaderTitleUpdate = (newTitle: string) => {
|
||||||
event: PointerEvent,
|
handleNodeTitleUpdate(nodeData.id, newTitle)
|
||||||
slotIndex: number,
|
|
||||||
isInput: boolean
|
|
||||||
) => {
|
|
||||||
if (!nodeData) {
|
|
||||||
console.warn('LGraphNode: nodeData is null/undefined in handleSlotClick')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't handle slot clicks when canvas is in panning mode
|
|
||||||
if (!shouldHandleNodePointerEvents.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
emit('slot-click', event, nodeData, slotIndex, isInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTitleUpdate = (newTitle: string) => {
|
|
||||||
emit('update:title', nodeData.id, newTitle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEnterSubgraph = () => {
|
const handleEnterSubgraph = () => {
|
||||||
@@ -410,15 +360,17 @@ const handleEnterSubgraph = () => {
|
|||||||
|
|
||||||
const nodeOutputs = useNodeOutputStore()
|
const nodeOutputs = useNodeOutputStore()
|
||||||
|
|
||||||
const nodeImageUrls = ref<string[]>([])
|
const nodeOutputLocatorId = computed(() =>
|
||||||
const onNodeOutputsUpdate = (newOutputs: ExecutedWsMessage['output']) => {
|
nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id
|
||||||
|
)
|
||||||
|
const nodeImageUrls = computed(() => {
|
||||||
|
const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value]
|
||||||
const locatorId = getLocatorIdFromNodeData(nodeData)
|
const locatorId = getLocatorIdFromNodeData(nodeData)
|
||||||
|
|
||||||
// Use root graph for getNodeByLocatorId since it needs to traverse from root
|
// Use root graph for getNodeByLocatorId since it needs to traverse from root
|
||||||
const rootGraph = app.graph?.rootGraph || app.graph
|
const rootGraph = app.graph?.rootGraph || app.graph
|
||||||
if (!rootGraph) {
|
if (!rootGraph) {
|
||||||
nodeImageUrls.value = []
|
return []
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = getNodeByLocatorId(rootGraph, locatorId)
|
const node = getNodeByLocatorId(rootGraph, locatorId)
|
||||||
@@ -426,23 +378,13 @@ const onNodeOutputsUpdate = (newOutputs: ExecutedWsMessage['output']) => {
|
|||||||
if (node && newOutputs?.images?.length) {
|
if (node && newOutputs?.images?.length) {
|
||||||
const urls = nodeOutputs.getNodeImageUrls(node)
|
const urls = nodeOutputs.getNodeImageUrls(node)
|
||||||
if (urls) {
|
if (urls) {
|
||||||
nodeImageUrls.value = urls
|
return urls
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Clear URLs if no outputs or no images
|
|
||||||
nodeImageUrls.value = []
|
|
||||||
}
|
}
|
||||||
}
|
// Clear URLs if no outputs or no images
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
const nodeOutputLocatorId = computed(() =>
|
const nodeContainerRef = ref()
|
||||||
nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id
|
provide('tooltipContainer', nodeContainerRef)
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => nodeOutputs.nodeOutputs[nodeOutputLocatorId.value],
|
|
||||||
(newOutputs) => {
|
|
||||||
onNodeOutputsUpdate(newOutputs)
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ import EditableText from '@/components/common/EditableText.vue'
|
|||||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
|
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
|
||||||
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
|
||||||
import { app } from '@/scripts/app'
|
import { app } from '@/scripts/app'
|
||||||
import {
|
import {
|
||||||
getLocatorIdFromNodeData,
|
getLocatorIdFromNodeData,
|
||||||
@@ -73,7 +72,6 @@ import {
|
|||||||
interface NodeHeaderProps {
|
interface NodeHeaderProps {
|
||||||
nodeData?: VueNodeData
|
nodeData?: VueNodeData
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
lodLevel?: LODLevel
|
|
||||||
collapsed?: boolean
|
collapsed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import { computed, onErrorCaptured, ref } from 'vue'
|
|||||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
|
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
|
||||||
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
|
||||||
import { isSlotObject } from '@/utils/typeGuardUtil'
|
import { isSlotObject } from '@/utils/typeGuardUtil'
|
||||||
|
|
||||||
import InputSlot from './InputSlot.vue'
|
import InputSlot from './InputSlot.vue'
|
||||||
@@ -44,7 +43,6 @@ import OutputSlot from './OutputSlot.vue'
|
|||||||
interface NodeSlotsProps {
|
interface NodeSlotsProps {
|
||||||
nodeData?: VueNodeData
|
nodeData?: VueNodeData
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
lodLevel?: LODLevel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { nodeData = null, readonly } = defineProps<NodeSlotsProps>()
|
const { nodeData = null, readonly } = defineProps<NodeSlotsProps>()
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ function useNodeEventHandlersIndividual() {
|
|||||||
const currentCollapsed = node.flags?.collapsed ?? false
|
const currentCollapsed = node.flags?.collapsed ?? false
|
||||||
if (currentCollapsed !== collapsed) {
|
if (currentCollapsed !== collapsed) {
|
||||||
node.collapse()
|
node.collapse()
|
||||||
|
nodeManager.value.scheduleUpdate(nodeId, 'critical')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
* Supports different element types (nodes, slots, widgets, etc.) with
|
* Supports different element types (nodes, slots, widgets, etc.) with
|
||||||
* customizable data attributes and update handlers.
|
* customizable data attributes and update handlers.
|
||||||
*/
|
*/
|
||||||
import { getCurrentInstance, onMounted, onUnmounted } from 'vue'
|
import {
|
||||||
|
type MaybeRefOrGetter,
|
||||||
|
getCurrentInstance,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
toValue
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
|
import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
|
||||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
@@ -154,9 +160,10 @@ const resizeObserver = new ResizeObserver((entries) => {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function useVueElementTracking(
|
export function useVueElementTracking(
|
||||||
appIdentifier: string,
|
appIdentifierMaybe: MaybeRefOrGetter<string>,
|
||||||
trackingType: string
|
trackingType: string
|
||||||
) {
|
) {
|
||||||
|
const appIdentifier = toValue(appIdentifierMaybe)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const element = getCurrentInstance()?.proxy?.$el
|
const element = getCurrentInstance()?.proxy?.$el
|
||||||
if (!(element instanceof HTMLElement) || !appIdentifier) return
|
if (!(element instanceof HTMLElement) || !appIdentifier) return
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { storeToRefs } from 'pinia'
|
|
||||||
import { computed, provide } from 'vue'
|
|
||||||
|
|
||||||
import {
|
|
||||||
ExecutingNodeIdsKey,
|
|
||||||
NodeProgressStatesKey
|
|
||||||
} from '@/renderer/core/canvas/injectionKeys'
|
|
||||||
import { useExecutionStore } from '@/stores/executionStore'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Composable for providing execution state to Vue node children
|
|
||||||
*
|
|
||||||
* This composable sets up the execution state providers that can be injected
|
|
||||||
* by child Vue nodes using useNodeExecutionState.
|
|
||||||
*
|
|
||||||
* Should be used in the parent component that manages Vue nodes (e.g., GraphCanvas).
|
|
||||||
*/
|
|
||||||
export const useExecutionStateProvider = () => {
|
|
||||||
const executionStore = useExecutionStore()
|
|
||||||
const { executingNodeIds: storeExecutingNodeIds, nodeProgressStates } =
|
|
||||||
storeToRefs(executionStore)
|
|
||||||
|
|
||||||
// Convert execution store data to the format expected by Vue nodes
|
|
||||||
const executingNodeIds = computed(
|
|
||||||
() => new Set(storeExecutingNodeIds.value.map(String))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Provide the execution state to all child Vue nodes
|
|
||||||
provide(ExecutingNodeIdsKey, executingNodeIds)
|
|
||||||
provide(NodeProgressStatesKey, nodeProgressStates)
|
|
||||||
|
|
||||||
return {
|
|
||||||
executingNodeIds,
|
|
||||||
nodeProgressStates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { computed, inject, ref } from 'vue'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { type MaybeRefOrGetter, computed, toValue } from 'vue'
|
||||||
|
|
||||||
import {
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
ExecutingNodeIdsKey,
|
|
||||||
NodeProgressStatesKey
|
|
||||||
} from '@/renderer/core/canvas/injectionKeys'
|
|
||||||
import type { NodeProgressState } from '@/schemas/apiSchema'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable for managing execution state of Vue-based nodes
|
* Composable for managing execution state of Vue-based nodes
|
||||||
@@ -12,18 +9,18 @@ import type { NodeProgressState } from '@/schemas/apiSchema'
|
|||||||
* Provides reactive access to execution state and progress for a specific node
|
* Provides reactive access to execution state and progress for a specific node
|
||||||
* by injecting execution data from the parent GraphCanvas provider.
|
* by injecting execution data from the parent GraphCanvas provider.
|
||||||
*
|
*
|
||||||
* @param nodeId - The ID of the node to track execution state for
|
* @param nodeIdMaybe - The ID of the node to track execution state for
|
||||||
* @returns Object containing reactive execution state and progress
|
* @returns Object containing reactive execution state and progress
|
||||||
*/
|
*/
|
||||||
export const useNodeExecutionState = (nodeId: string) => {
|
export const useNodeExecutionState = (
|
||||||
const executingNodeIds = inject(ExecutingNodeIdsKey, ref(new Set<string>()))
|
nodeIdMaybe: MaybeRefOrGetter<string>
|
||||||
const nodeProgressStates = inject(
|
) => {
|
||||||
NodeProgressStatesKey,
|
const nodeId = toValue(nodeIdMaybe)
|
||||||
ref<Record<string, NodeProgressState>>({})
|
const { uniqueExecutingNodeIdStrings, nodeProgressStates } =
|
||||||
)
|
storeToRefs(useExecutionStore())
|
||||||
|
|
||||||
const executing = computed(() => {
|
const executing = computed(() => {
|
||||||
return executingNodeIds.value.has(nodeId)
|
return uniqueExecutingNodeIdStrings.value.has(nodeId)
|
||||||
})
|
})
|
||||||
|
|
||||||
const progress = computed(() => {
|
const progress = computed(() => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { storeToRefs } from 'pinia'
|
|||||||
* Uses customRef for shared write access with Canvas renderer.
|
* Uses customRef for shared write access with Canvas renderer.
|
||||||
* Provides dragging functionality and reactive layout state.
|
* Provides dragging functionality and reactive layout state.
|
||||||
*/
|
*/
|
||||||
import { computed, inject } from 'vue'
|
import { type MaybeRefOrGetter, computed, inject, toValue } from 'vue'
|
||||||
|
|
||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
||||||
@@ -17,7 +17,8 @@ import { LayoutSource, type Point } from '@/renderer/core/layout/types'
|
|||||||
* Composable for individual Vue node components
|
* Composable for individual Vue node components
|
||||||
* Uses customRef for shared write access with Canvas renderer
|
* Uses customRef for shared write access with Canvas renderer
|
||||||
*/
|
*/
|
||||||
export function useNodeLayout(nodeId: string) {
|
export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
|
||||||
|
const nodeId = toValue(nodeIdMaybe)
|
||||||
const mutations = useLayoutMutations()
|
const mutations = useLayoutMutations()
|
||||||
const { selectedNodeIds } = storeToRefs(useCanvasStore())
|
const { selectedNodeIds } = storeToRefs(useCanvasStore())
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
* <NodeSlots v-if="shouldRenderSlots" />
|
* <NodeSlots v-if="shouldRenderSlots" />
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
import { type Ref, computed, readonly } from 'vue'
|
import { type MaybeRefOrGetter, computed, readonly, toRef } from 'vue'
|
||||||
|
|
||||||
export enum LODLevel {
|
export enum LODLevel {
|
||||||
MINIMAL = 'minimal', // zoom <= 0.4
|
MINIMAL = 'minimal', // zoom <= 0.4
|
||||||
@@ -78,7 +78,8 @@ const LOD_CONFIGS: Record<LODLevel, LODConfig> = {
|
|||||||
* @param zoomRef - Reactive reference to current zoom level (camera.z)
|
* @param zoomRef - Reactive reference to current zoom level (camera.z)
|
||||||
* @returns LOD state and configuration
|
* @returns LOD state and configuration
|
||||||
*/
|
*/
|
||||||
export function useLOD(zoomRef: Ref<number>) {
|
export function useLOD(zoomRefMaybe: MaybeRefOrGetter<number>) {
|
||||||
|
const zoomRef = toRef(zoomRefMaybe)
|
||||||
// Continuous LOD score (0-1) for smooth transitions
|
// Continuous LOD score (0-1) for smooth transitions
|
||||||
const lodScore = computed(() => {
|
const lodScore = computed(() => {
|
||||||
const zoom = zoomRef.value
|
const zoom = zoomRef.value
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { type Ref, computed } from 'vue'
|
import { type MaybeRefOrGetter, type Ref, computed, toValue } from 'vue'
|
||||||
|
|
||||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||||
|
|
||||||
export const useNodePreviewState = (
|
export const useNodePreviewState = (
|
||||||
nodeId: string,
|
nodeIdMaybe: MaybeRefOrGetter<string>,
|
||||||
options?: {
|
options?: {
|
||||||
isMinimalLOD?: Ref<boolean>
|
isMinimalLOD?: Ref<boolean>
|
||||||
isCollapsed?: Ref<boolean>
|
isCollapsed?: Ref<boolean>
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
|
const nodeId = toValue(nodeIdMaybe)
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const { nodePreviewImages } = storeToRefs(useNodeOutputStore())
|
const { nodePreviewImages } = storeToRefs(useNodeOutputStore())
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,57 @@ interface QueuedPrompt {
|
|||||||
workflow?: ComfyWorkflow
|
workflow?: ComfyWorkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subgraphNodeIdToSubgraph = (id: string, graph: LGraph | Subgraph) => {
|
||||||
|
const node = graph.getNodeById(id)
|
||||||
|
if (node?.isSubgraphNode()) return node.subgraph
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively get the subgraph objects for the given subgraph instance IDs
|
||||||
|
* @param currentGraph The current graph
|
||||||
|
* @param subgraphNodeIds The instance IDs
|
||||||
|
* @param subgraphs The subgraphs
|
||||||
|
* @returns The subgraphs that correspond to each of the instance IDs.
|
||||||
|
*/
|
||||||
|
function getSubgraphsFromInstanceIds(
|
||||||
|
currentGraph: LGraph | Subgraph,
|
||||||
|
subgraphNodeIds: string[],
|
||||||
|
subgraphs: Subgraph[] = []
|
||||||
|
): Subgraph[] {
|
||||||
|
// Last segment is the node portion; nothing to do.
|
||||||
|
if (subgraphNodeIds.length === 1) return subgraphs
|
||||||
|
|
||||||
|
const currentPart = subgraphNodeIds.shift()
|
||||||
|
if (currentPart === undefined) return subgraphs
|
||||||
|
|
||||||
|
const subgraph = subgraphNodeIdToSubgraph(currentPart, currentGraph)
|
||||||
|
if (!subgraph) throw new Error(`Subgraph not found: ${currentPart}`)
|
||||||
|
|
||||||
|
subgraphs.push(subgraph)
|
||||||
|
return getSubgraphsFromInstanceIds(subgraph, subgraphNodeIds, subgraphs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert execution context node IDs to NodeLocatorIds
|
||||||
|
* @param nodeId The node ID from execution context (could be execution ID)
|
||||||
|
* @returns The NodeLocatorId
|
||||||
|
*/
|
||||||
|
function executionIdToNodeLocatorId(nodeId: string | number): NodeLocatorId {
|
||||||
|
const nodeIdStr = String(nodeId)
|
||||||
|
|
||||||
|
if (!nodeIdStr.includes(':')) {
|
||||||
|
// It's a top-level node ID
|
||||||
|
return nodeIdStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's an execution node ID
|
||||||
|
const parts = nodeIdStr.split(':')
|
||||||
|
const localNodeId = parts[parts.length - 1]
|
||||||
|
const subgraphs = getSubgraphsFromInstanceIds(app.graph, parts)
|
||||||
|
const nodeLocatorId = createNodeLocatorId(subgraphs.at(-1)!.id, localNodeId)
|
||||||
|
return nodeLocatorId
|
||||||
|
}
|
||||||
|
|
||||||
export const useExecutionStore = defineStore('execution', () => {
|
export const useExecutionStore = defineStore('execution', () => {
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const canvasStore = useCanvasStore()
|
const canvasStore = useCanvasStore()
|
||||||
@@ -55,29 +106,6 @@ export const useExecutionStore = defineStore('execution', () => {
|
|||||||
// This is the progress of all nodes in the currently executing workflow
|
// This is the progress of all nodes in the currently executing workflow
|
||||||
const nodeProgressStates = ref<Record<string, NodeProgressState>>({})
|
const nodeProgressStates = ref<Record<string, NodeProgressState>>({})
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert execution context node IDs to NodeLocatorIds
|
|
||||||
* @param nodeId The node ID from execution context (could be execution ID)
|
|
||||||
* @returns The NodeLocatorId
|
|
||||||
*/
|
|
||||||
const executionIdToNodeLocatorId = (
|
|
||||||
nodeId: string | number
|
|
||||||
): NodeLocatorId => {
|
|
||||||
const nodeIdStr = String(nodeId)
|
|
||||||
|
|
||||||
if (!nodeIdStr.includes(':')) {
|
|
||||||
// It's a top-level node ID
|
|
||||||
return nodeIdStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's an execution node ID
|
|
||||||
const parts = nodeIdStr.split(':')
|
|
||||||
const localNodeId = parts[parts.length - 1]
|
|
||||||
const subgraphs = getSubgraphsFromInstanceIds(app.graph, parts)
|
|
||||||
const nodeLocatorId = createNodeLocatorId(subgraphs.at(-1)!.id, localNodeId)
|
|
||||||
return nodeLocatorId
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergeExecutionProgressStates = (
|
const mergeExecutionProgressStates = (
|
||||||
currentState: NodeProgressState | undefined,
|
currentState: NodeProgressState | undefined,
|
||||||
newState: NodeProgressState
|
newState: NodeProgressState
|
||||||
@@ -139,9 +167,13 @@ export const useExecutionStore = defineStore('execution', () => {
|
|||||||
|
|
||||||
// @deprecated For backward compatibility - stores the primary executing node ID
|
// @deprecated For backward compatibility - stores the primary executing node ID
|
||||||
const executingNodeId = computed<NodeId | null>(() => {
|
const executingNodeId = computed<NodeId | null>(() => {
|
||||||
return executingNodeIds.value.length > 0 ? executingNodeIds.value[0] : null
|
return executingNodeIds.value[0] ?? null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const uniqueExecutingNodeIdStrings = computed(
|
||||||
|
() => new Set(executingNodeIds.value.map(String))
|
||||||
|
)
|
||||||
|
|
||||||
// For backward compatibility - returns the primary executing node
|
// For backward compatibility - returns the primary executing node
|
||||||
const executingNode = computed<ComfyNode | null>(() => {
|
const executingNode = computed<ComfyNode | null>(() => {
|
||||||
if (!executingNodeId.value) return null
|
if (!executingNodeId.value) return null
|
||||||
@@ -159,36 +191,6 @@ export const useExecutionStore = defineStore('execution', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const subgraphNodeIdToSubgraph = (id: string, graph: LGraph | Subgraph) => {
|
|
||||||
const node = graph.getNodeById(id)
|
|
||||||
if (node?.isSubgraphNode()) return node.subgraph
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively get the subgraph objects for the given subgraph instance IDs
|
|
||||||
* @param currentGraph The current graph
|
|
||||||
* @param subgraphNodeIds The instance IDs
|
|
||||||
* @param subgraphs The subgraphs
|
|
||||||
* @returns The subgraphs that correspond to each of the instance IDs.
|
|
||||||
*/
|
|
||||||
const getSubgraphsFromInstanceIds = (
|
|
||||||
currentGraph: LGraph | Subgraph,
|
|
||||||
subgraphNodeIds: string[],
|
|
||||||
subgraphs: Subgraph[] = []
|
|
||||||
): Subgraph[] => {
|
|
||||||
// Last segment is the node portion; nothing to do.
|
|
||||||
if (subgraphNodeIds.length === 1) return subgraphs
|
|
||||||
|
|
||||||
const currentPart = subgraphNodeIds.shift()
|
|
||||||
if (currentPart === undefined) return subgraphs
|
|
||||||
|
|
||||||
const subgraph = subgraphNodeIdToSubgraph(currentPart, currentGraph)
|
|
||||||
if (!subgraph) throw new Error(`Subgraph not found: ${currentPart}`)
|
|
||||||
|
|
||||||
subgraphs.push(subgraph)
|
|
||||||
return getSubgraphsFromInstanceIds(subgraph, subgraphNodeIds, subgraphs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the progress of the currently executing node (for backward compatibility)
|
// This is the progress of the currently executing node (for backward compatibility)
|
||||||
const _executingNodeProgress = ref<ProgressWsMessage | null>(null)
|
const _executingNodeProgress = ref<ProgressWsMessage | null>(null)
|
||||||
const executingNodeProgress = computed(() =>
|
const executingNodeProgress = computed(() =>
|
||||||
@@ -423,66 +425,25 @@ export const useExecutionStore = defineStore('execution', () => {
|
|||||||
return {
|
return {
|
||||||
isIdle,
|
isIdle,
|
||||||
clientId,
|
clientId,
|
||||||
/**
|
|
||||||
* The id of the prompt that is currently being executed
|
|
||||||
*/
|
|
||||||
activePromptId,
|
activePromptId,
|
||||||
/**
|
|
||||||
* The queued prompts
|
|
||||||
*/
|
|
||||||
queuedPrompts,
|
queuedPrompts,
|
||||||
/**
|
|
||||||
* The node errors from the previous execution.
|
|
||||||
*/
|
|
||||||
lastNodeErrors,
|
lastNodeErrors,
|
||||||
/**
|
|
||||||
* The error from the previous execution.
|
|
||||||
*/
|
|
||||||
lastExecutionError,
|
lastExecutionError,
|
||||||
/**
|
|
||||||
* Local node ID for the most recent execution error.
|
|
||||||
*/
|
|
||||||
lastExecutionErrorNodeId,
|
lastExecutionErrorNodeId,
|
||||||
/**
|
|
||||||
* The id of the node that is currently being executed (backward compatibility)
|
|
||||||
*/
|
|
||||||
executingNodeId,
|
executingNodeId,
|
||||||
/**
|
|
||||||
* The list of all nodes that are currently executing
|
|
||||||
*/
|
|
||||||
executingNodeIds,
|
executingNodeIds,
|
||||||
/**
|
|
||||||
* The prompt that is currently being executed
|
|
||||||
*/
|
|
||||||
activePrompt,
|
activePrompt,
|
||||||
/**
|
|
||||||
* The total number of nodes to execute
|
|
||||||
*/
|
|
||||||
totalNodesToExecute,
|
totalNodesToExecute,
|
||||||
/**
|
|
||||||
* The number of nodes that have been executed
|
|
||||||
*/
|
|
||||||
nodesExecuted,
|
nodesExecuted,
|
||||||
/**
|
|
||||||
* The progress of the execution
|
|
||||||
*/
|
|
||||||
executionProgress,
|
executionProgress,
|
||||||
/**
|
|
||||||
* The node that is currently being executed (backward compatibility)
|
|
||||||
*/
|
|
||||||
executingNode,
|
executingNode,
|
||||||
/**
|
|
||||||
* The progress of the executing node (backward compatibility)
|
|
||||||
*/
|
|
||||||
executingNodeProgress,
|
executingNodeProgress,
|
||||||
/**
|
|
||||||
* All node progress states from progress_state events
|
|
||||||
*/
|
|
||||||
nodeProgressStates,
|
nodeProgressStates,
|
||||||
nodeLocationProgressStates,
|
nodeLocationProgressStates,
|
||||||
bindExecutionEvents,
|
bindExecutionEvents,
|
||||||
unbindExecutionEvents,
|
unbindExecutionEvents,
|
||||||
storePrompt,
|
storePrompt,
|
||||||
|
uniqueExecutingNodeIdStrings,
|
||||||
// Raw executing progress data for backward compatibility in ComfyApp.
|
// Raw executing progress data for backward compatibility in ComfyApp.
|
||||||
_executingNodeProgress,
|
_executingNodeProgress,
|
||||||
// NodeLocatorId conversion helpers
|
// NodeLocatorId conversion helpers
|
||||||
|
|||||||
2
src/types/litegraph-augmentation.d.ts
vendored
2
src/types/litegraph-augmentation.d.ts
vendored
@@ -82,7 +82,7 @@ declare module '@/lib/litegraph/src/litegraph' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add interface augmentations into the class itself
|
// Add interface augmentations into the class itself
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
||||||
interface BaseWidget extends IBaseWidget {}
|
interface BaseWidget extends IBaseWidget {}
|
||||||
|
|
||||||
interface LGraphNode {
|
interface LGraphNode {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { createTestingPinia } from '@pinia/testing'
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { computed } from 'vue'
|
import { computed, toValue } from 'vue'
|
||||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||||
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
||||||
|
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||||
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||||
|
|
||||||
const mockData = vi.hoisted(() => ({
|
const mockData = vi.hoisted(() => ({
|
||||||
@@ -25,6 +26,14 @@ vi.mock('@/renderer/core/canvas/canvasStore', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
vi.mock(
|
||||||
|
'@/renderer/extensions/vueNodes/composables/useNodeEventHandlers',
|
||||||
|
() => {
|
||||||
|
const handleNodeSelect = vi.fn()
|
||||||
|
return { useNodeEventHandlers: () => ({ handleNodeSelect }) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
vi.mock(
|
vi.mock(
|
||||||
'@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking',
|
'@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking',
|
||||||
() => ({
|
() => ({
|
||||||
@@ -130,7 +139,13 @@ describe('LGraphNode', () => {
|
|||||||
it('should call resize tracking composable with node ID', () => {
|
it('should call resize tracking composable with node ID', () => {
|
||||||
mountLGraphNode({ nodeData: mockNodeData })
|
mountLGraphNode({ nodeData: mockNodeData })
|
||||||
|
|
||||||
expect(useVueElementTracking).toHaveBeenCalledWith('test-node-123', 'node')
|
expect(useVueElementTracking).toHaveBeenCalledWith(
|
||||||
|
expect.any(Function),
|
||||||
|
'node'
|
||||||
|
)
|
||||||
|
const idArg = vi.mocked(useVueElementTracking).mock.calls[0]?.[0]
|
||||||
|
const id = toValue(idArg)
|
||||||
|
expect(id).toEqual('test-node-123')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render with data-node-id attribute', () => {
|
it('should render with data-node-id attribute', () => {
|
||||||
@@ -179,12 +194,16 @@ describe('LGraphNode', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should emit node-click event on pointer up', async () => {
|
it('should emit node-click event on pointer up', async () => {
|
||||||
|
const { handleNodeSelect } = useNodeEventHandlers()
|
||||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||||
|
|
||||||
await wrapper.trigger('pointerup')
|
await wrapper.trigger('pointerup')
|
||||||
|
|
||||||
expect(wrapper.emitted('node-click')).toHaveLength(1)
|
expect(handleNodeSelect).toHaveBeenCalledOnce()
|
||||||
expect(wrapper.emitted('node-click')?.[0]).toHaveLength(3)
|
expect(handleNodeSelect).toHaveBeenCalledWith(
|
||||||
expect(wrapper.emitted('node-click')?.[0][1]).toEqual(mockNodeData)
|
expect.any(PointerEvent),
|
||||||
|
mockNodeData,
|
||||||
|
expect.any(Boolean)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user