Refactor: Composable disentangling (#5695)

## Summary

Prerequisite refactor/cleanup to use a global store instead of having
nodes throw up events to a parent component that stores a reference to a
singleton service that itself bootstraps and synchronizes with a
separate service to maintain a partially reactive but not fully reactive
set of states that describe some but not all aspects of the nodes on
either the litegraph, the vue side, or both.

## Changes

- **What**: Refactoring, the behavior should not change.
- **Dependencies**: A type utility to help with Vue component props

## Review Focus

Is there something about the current structure that this could affect
that would not be caught by our tests or using the application?

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5695-Refactor-Composable-disentangling-2746d73d365081e6938ce656932f3e36)
by [Unito](https://www.unito.io)
This commit is contained in:
Alexander Brown
2025-09-20 13:06:42 -07:00
committed by GitHub
parent fd12591756
commit 8133bd4b7b
21 changed files with 339 additions and 400 deletions

View File

@@ -99,6 +99,16 @@ export const useCanvasStore = defineStore('canvas', () => {
const currentGraph = shallowRef<LGraph | null>(null)
const isInSubgraph = ref(false)
// Provide selection state to all Vue nodes
const selectedNodeIds = computed(
() =>
new Set(
selectedItems.value
.filter((item) => item.id !== undefined)
.map((item) => String(item.id))
)
)
whenever(
() => canvas.value,
(newCanvas) => {
@@ -122,6 +132,7 @@ export const useCanvasStore = defineStore('canvas', () => {
return {
canvas,
selectedItems,
selectedNodeIds,
nodeSelected,
groupSelected,
rerouteSelected,

View File

@@ -2,13 +2,6 @@ import type { InjectionKey, Ref } from 'vue'
import type { NodeProgressState } from '@/schemas/apiSchema'
/**
* Injection key for providing selected node IDs to Vue node components.
* Contains a reactive Set of selected node IDs (as strings).
*/
export const SelectedNodeIdsKey: InjectionKey<Ref<Set<string>>> =
Symbol('selectedNodeIds')
/**
* Injection key for providing executing node IDs to Vue node components.
* Contains a reactive Set of currently executing node IDs (as strings).

View File

@@ -139,12 +139,12 @@
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import {
computed,
inject,
onErrorCaptured,
onMounted,
provide,
ref,
toRef,
watch
@@ -153,10 +153,11 @@ import {
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
@@ -171,7 +172,6 @@ import {
} from '@/utils/graphTraversalUtil'
import { cn } from '@/utils/tailwindUtil'
import { useVueElementTracking } from '../composables/useVueNodeResizeTracking'
import NodeContent from './NodeContent.vue'
import NodeHeader from './NodeHeader.vue'
import NodeSlots from './NodeSlots.vue'
@@ -190,8 +190,8 @@ interface LGraphNodeProps {
const {
nodeData,
position,
size,
position = { x: 0, y: 0 },
size = { width: 100, height: 50 },
error = null,
readonly = false,
zoomLevel = 1
@@ -209,20 +209,13 @@ const emit = defineEmits<{
slotIndex: number,
isInput: boolean
]
dragStart: [event: DragEvent, nodeData: VueNodeData]
'update:collapsed': [nodeId: string, collapsed: boolean]
'update:title': [nodeId: string, newTitle: string]
}>()
useVueElementTracking(nodeData.id, 'node')
// Inject selection state from parent
const selectedNodeIds = inject(SelectedNodeIdsKey)
if (!selectedNodeIds) {
throw new Error(
'SelectedNodeIds not provided - LGraphNode must be used within a component that provides selection state'
)
}
const { selectedNodeIds } = storeToRefs(useCanvasStore())
// Inject transform state for coordinate conversion
const transformState = inject(TransformStateKey)
@@ -249,12 +242,7 @@ const hasAnyError = computed(
const bypassed = computed((): boolean => nodeData.mode === 4)
// Use canvas interactions for proper wheel event handling and pointer event capture control
const {
handleWheel,
handlePointer,
forwardEventToCanvas,
shouldHandleNodePointerEvents
} = useCanvasInteractions()
const { handleWheel, shouldHandleNodePointerEvents } = useCanvasInteractions()
// LOD (Level of Detail) system based on zoom level
const zoomRef = toRef(() => zoomLevel)
@@ -280,14 +268,16 @@ onErrorCaptured((error) => {
})
// Use layout system for node position and dragging
const { position: layoutPosition, zIndex, resize } = useNodeLayout(nodeData.id)
const {
position: layoutPosition,
zIndex,
startDrag,
handleDrag: handleLayoutDrag,
endDrag,
resize
} = useNodeLayout(nodeData.id)
handlePointerDown,
handlePointerUp,
handlePointerMove,
isDragging,
dragStyle
} = useNodePointerInteractions(nodeData, (event, nodeData, wasDragging) => {
emit('node-click', event, nodeData, wasDragging)
})
onMounted(() => {
if (size && transformState?.camera) {
@@ -300,16 +290,6 @@ onMounted(() => {
}
})
// Drag state for styling
const isDragging = ref(false)
const dragStyle = computed(() => ({
cursor: isDragging.value ? 'grabbing' : 'grab'
}))
const lastY = ref(0)
const lastX = ref(0)
// Treat tiny pointer jitter as a click, not a drag
const DRAG_THRESHOLD_PX = 4
// Track collapsed state
const isCollapsed = ref(nodeData.flags?.collapsed ?? false)
@@ -375,60 +355,6 @@ const outlineClass = computed(() => {
})
// Event handlers
const handlePointerDown = (event: PointerEvent) => {
if (!nodeData) {
console.warn('LGraphNode: nodeData is null/undefined in handlePointerDown')
return
}
// Don't handle pointer events when canvas is in panning mode - forward to canvas instead
if (!shouldHandleNodePointerEvents.value) {
forwardEventToCanvas(event)
return
}
// Start drag using layout system
isDragging.value = true
// Set Vue node dragging state for selection toolbox
layoutStore.isDraggingVueNodes.value = true
startDrag(event)
lastY.value = event.clientY
lastX.value = event.clientX
}
const handlePointerMove = (event: PointerEvent) => {
// Check if this should be forwarded to canvas (e.g., space panning, middle mouse)
handlePointer(event)
if (isDragging.value) {
void handleLayoutDrag(event)
}
}
const handlePointerUp = (event: PointerEvent) => {
if (isDragging.value) {
isDragging.value = false
void endDrag(event)
// Clear Vue node dragging state for selection toolbox
layoutStore.isDraggingVueNodes.value = false
}
// Don't emit node-click when canvas is in panning mode - forward to canvas instead
if (!shouldHandleNodePointerEvents.value) {
forwardEventToCanvas(event)
return
}
// Emit node-click for selection handling in GraphCanvas
const dx = event.clientX - lastX.value
const dy = event.clientY - lastY.value
const wasDragging = Math.hypot(dx, dy) > DRAG_THRESHOLD_PX
emit('node-click', event, nodeData, wasDragging)
}
const handleCollapse = () => {
isCollapsed.value = !isCollapsed.value
// Emit event so parent can sync with LiteGraph if needed
@@ -519,11 +445,4 @@ watch(
},
{ deep: true }
)
// Template ref for tooltip positioning
const nodeContainerRef = ref<HTMLElement>()
// Provide nodeImageUrls and tooltip container to child components
provide('nodeImageUrls', nodeImageUrls)
provide('tooltipContainer', nodeContainerRef)
</script>

View File

@@ -8,19 +8,17 @@
* - Layout mutations for visual feedback
* - Integration with LiteGraph canvas selection system
*/
import type { Ref } from 'vue'
import { createSharedComposable } from '@vueuse/core'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'
interface NodeManager {
getNode: (id: string) => any
}
export function useNodeEventHandlers(nodeManager: Ref<NodeManager | null>) {
function useNodeEventHandlersIndividual() {
const canvasStore = useCanvasStore()
const { nodeManager } = useVueNodeLifecycle()
const { bringNodeToFront } = useNodeZIndex()
const { shouldHandleNodePointerEvents } = useCanvasInteractions()
@@ -237,3 +235,7 @@ export function useNodeEventHandlers(nodeManager: Ref<NodeManager | null>) {
deselectNodes
}
}
export const useNodeEventHandlers = createSharedComposable(
useNodeEventHandlersIndividual
)

View File

@@ -0,0 +1,93 @@
import { type MaybeRefOrGetter, computed, ref, toValue } from 'vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
// Treat tiny pointer jitter as a click, not a drag
const DRAG_THRESHOLD_PX = 4
export function useNodePointerInteractions(
nodeDataMaybe: MaybeRefOrGetter<VueNodeData>,
onPointerUp: (
event: PointerEvent,
nodeData: VueNodeData,
wasDragging: boolean
) => void
) {
const nodeData = toValue(nodeDataMaybe)
const { startDrag, endDrag, handleDrag } = useNodeLayout(nodeData.id)
// Use canvas interactions for proper wheel event handling and pointer event capture control
const { forwardEventToCanvas, shouldHandleNodePointerEvents } =
useCanvasInteractions()
// Drag state for styling
const isDragging = ref(false)
const dragStyle = computed(() => ({
cursor: isDragging.value ? 'grabbing' : 'grab'
}))
const lastX = ref(0)
const lastY = ref(0)
const handlePointerDown = (event: PointerEvent) => {
if (!nodeData) {
console.warn(
'LGraphNode: nodeData is null/undefined in handlePointerDown'
)
return
}
// Don't handle pointer events when canvas is in panning mode - forward to canvas instead
if (!shouldHandleNodePointerEvents.value) {
forwardEventToCanvas(event)
return
}
// Start drag using layout system
isDragging.value = true
// Set Vue node dragging state for selection toolbox
layoutStore.isDraggingVueNodes.value = true
startDrag(event)
lastY.value = event.clientY
lastX.value = event.clientX
}
const handlePointerMove = (event: PointerEvent) => {
if (isDragging.value) {
void handleDrag(event)
}
}
const handlePointerUp = (event: PointerEvent) => {
if (isDragging.value) {
isDragging.value = false
void endDrag(event)
// Clear Vue node dragging state for selection toolbox
layoutStore.isDraggingVueNodes.value = false
}
// Don't emit node-click when canvas is in panning mode - forward to canvas instead
if (!shouldHandleNodePointerEvents.value) {
forwardEventToCanvas(event)
return
}
// Emit node-click for selection handling in GraphCanvas
const dx = event.clientX - lastX.value
const dy = event.clientY - lastY.value
const wasDragging = Math.hypot(dx, dy) > DRAG_THRESHOLD_PX
onPointerUp(event, nodeData, wasDragging)
}
return {
isDragging,
dragStyle,
handlePointerMove,
handlePointerDown,
handlePointerUp
}
}

View File

@@ -1,3 +1,4 @@
import { storeToRefs } from 'pinia'
/**
* Composable for individual Vue node components
*
@@ -6,7 +7,7 @@
*/
import { computed, inject } from 'vue'
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
@@ -17,14 +18,14 @@ import { LayoutSource, type Point } from '@/renderer/core/layout/types'
* Uses customRef for shared write access with Canvas renderer
*/
export function useNodeLayout(nodeId: string) {
const store = layoutStore
const mutations = useLayoutMutations()
const { selectedNodeIds } = storeToRefs(useCanvasStore())
// Get transform utilities from TransformPane if available
const transformState = inject(TransformStateKey)
// Get the customRef for this node (shared write access)
const layoutRef = store.getNodeLayoutRef(nodeId)
const layoutRef = layoutStore.getNodeLayoutRef(nodeId)
// Computed properties for easy access
const position = computed(() => {
@@ -53,8 +54,6 @@ export function useNodeLayout(nodeId: string) {
let dragStartMouse: Point | null = null
let otherSelectedNodesStartPositions: Map<string, Point> | null = null
const selectedNodeIds = inject(SelectedNodeIdsKey, null)
/**
* Start dragging the node
*/