refactor: Reorganize layout system into new renderer architecture (#5071)

- Move layout system to renderer/core/layout/
  - Store, operations, adapters, and sync modules organized clearly
  - Merged layoutTypes.ts and layoutOperations.ts into single types.ts
- Move canvas rendering to renderer/core/canvas/
  - LiteGraph-specific code in litegraph/ subdirectory
  - PathRenderer at canvas level
- Move spatial indexing to renderer/core/spatial/
- Move Vue node composables to renderer/extensions/vue-nodes/
- Update all import paths throughout codebase
- Apply consistent naming (renderer vs rendering)

This establishes clearer separation between core rendering concerns
and optional extensions, making the architecture more maintainable.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Byrne
2025-08-17 20:19:00 -07:00
committed by GitHub
parent d02cd527ae
commit 0e236b8e2c
22 changed files with 390 additions and 36 deletions

View File

@@ -133,8 +133,6 @@ import type {
NodeState,
VueNodeData
} from '@/composables/graph/useGraphNodeManager'
import { useLayout } from '@/composables/graph/useLayout'
import { useLayoutSync } from '@/composables/graph/useLayoutSync'
import { useNodeBadge } from '@/composables/node/useNodeBadge'
import { useCanvasDrop } from '@/composables/useCanvasDrop'
import { useContextMenuTranslation } from '@/composables/useContextMenuTranslation'
@@ -149,6 +147,9 @@ import { useWorkflowPersistence } from '@/composables/useWorkflowPersistence'
import { CORE_SETTINGS } from '@/constants/coreSettings'
import { i18n, t } from '@/i18n'
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import { useLayout } from '@/renderer/core/layout/sync/useLayout'
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
import { UnauthorizedError, api } from '@/scripts/api'
import { app as comfyApp } from '@/scripts/app'
import { ChangeTracker } from '@/scripts/changeTracker'
@@ -159,7 +160,6 @@ import { useWorkflowService } from '@/services/workflowService'
import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useCanvasStore } from '@/stores/graphStore'
import { layoutStore } from '@/stores/layoutStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useSettingStore } from '@/stores/settingStore'
import { useToastStore } from '@/stores/toastStore'

View File

@@ -92,8 +92,8 @@ import { computed, onErrorCaptured, ref, toRef, watch } from 'vue'
// Import the VueNodeData type
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { LODLevel, useLOD } from '@/composables/graph/useLOD'
import { useNodeLayout } from '@/composables/graph/useNodeLayout'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { useNodeLayout } from '@/renderer/extensions/vue-nodes/composables/useNodeLayout'
import { LiteGraph } from '../../../lib/litegraph/src/litegraph'
import NodeContent from './NodeContent.vue'

View File

@@ -4,7 +4,7 @@
*/
import { nextTick, reactive, readonly } from 'vue'
import { layoutMutations } from '@/services/layoutMutations'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import type { WidgetValue } from '@/types/simplifiedWidget'
import type { SpatialIndexDebugInfo } from '@/types/spatialIndex'

View File

@@ -7,7 +7,7 @@
import { reactive } from 'vue'
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
import { layoutMutations } from '@/services/layoutMutations'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
export interface ChangeDetectionMetrics {
updateTime: number

View File

@@ -6,8 +6,8 @@
*/
import { nextTick, reactive, readonly } from 'vue'
import { PERFORMANCE_CONFIG } from '@/constants/layout'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { PERFORMANCE_CONFIG } from '@/renderer/core/layout/constants'
import type { SafeWidgetData, VueNodeData, WidgetValue } from './useNodeWidgets'

View File

@@ -2,7 +2,7 @@ import { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
import {
type LinkRenderContext,
LitegraphLinkAdapter
} from '@/rendering/adapters/LitegraphLinkAdapter'
} from '@/renderer/core/canvas/litegraph/LitegraphLinkAdapter'
import { CanvasPointer } from './CanvasPointer'
import type { ContextMenu } from './ContextMenu'

View File

@@ -4,7 +4,7 @@ import {
calculateInputSlotPos,
calculateInputSlotPosFromSlot,
calculateOutputSlotPos
} from '@/utils/slotCalculations'
} from '@/renderer/core/canvas/litegraph/SlotCalculations'
import type { DragAndScale } from './DragAndScale'
import type { LGraph } from './LGraph'

View File

@@ -31,13 +31,13 @@ import {
type RenderContext as PathRenderContext,
type Point,
type RenderMode
} from '@/rendering/canvas/PathRenderer'
import { layoutStore } from '@/stores/layoutStore'
} from '@/renderer/core/canvas/PathRenderer'
import {
type SlotPositionContext,
calculateInputSlotPos,
calculateOutputSlotPos
} from '@/utils/slotCalculations'
} from '@/renderer/core/canvas/litegraph/SlotCalculations'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
export interface LinkRenderContext {
// Canvas settings

View File

@@ -3,8 +3,8 @@
*
* Simple in-memory implementation for testing without CRDT overhead.
*/
import type { LayoutOperation } from '@/types/layoutOperations'
import type { NodeId, NodeLayout } from '@/types/layoutTypes'
import type { LayoutOperation } from '@/renderer/core/layout/types'
import type { NodeId, NodeLayout } from '@/renderer/core/layout/types'
import type { AdapterChange, LayoutAdapter } from './layoutAdapter'

View File

@@ -6,8 +6,13 @@
*/
import * as Y from 'yjs'
import type { LayoutOperation } from '@/types/layoutOperations'
import type { Bounds, NodeId, NodeLayout, Point } from '@/types/layoutTypes'
import type { LayoutOperation } from '@/renderer/core/layout/types'
import type {
Bounds,
NodeId,
NodeLayout,
Point
} from '@/renderer/core/layout/types'
import type { AdapterChange, LayoutAdapter } from './layoutAdapter'

View File

@@ -4,8 +4,8 @@
* Abstracts the underlying CRDT implementation to allow for different
* backends (Yjs, Automerge, etc.) and easier testing.
*/
import type { LayoutOperation } from '@/types/layoutOperations'
import type { NodeId, NodeLayout } from '@/types/layoutTypes'
import type { LayoutOperation } from '@/renderer/core/layout/types'
import type { NodeId, NodeLayout } from '@/renderer/core/layout/types'
/**
* Change event emitted by the adapter

View File

@@ -4,14 +4,14 @@
* Provides a clean API for layout operations that are CRDT-ready.
* Operations are synchronous and applied directly to the store.
*/
import { layoutStore } from '@/stores/layoutStore'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import type {
LayoutMutations,
NodeId,
NodeLayout,
Point,
Size
} from '@/types/layoutTypes'
} from '@/renderer/core/layout/types'
class LayoutMutationsImpl implements LayoutMutations {
/**

View File

@@ -8,8 +8,7 @@ import log from 'loglevel'
import { type ComputedRef, type Ref, computed, customRef } from 'vue'
import * as Y from 'yjs'
import { ACTOR_CONFIG, DEBUG_CONFIG } from '@/constants/layout'
import { SpatialIndexManager } from '@/services/spatialIndexManager'
import { ACTOR_CONFIG, DEBUG_CONFIG } from '@/renderer/core/layout/constants'
import type {
CreateNodeOperation,
DeleteNodeOperation,
@@ -17,7 +16,7 @@ import type {
MoveNodeOperation,
ResizeNodeOperation,
SetNodeZIndexOperation
} from '@/types/layoutOperations'
} from '@/renderer/core/layout/types'
import type {
Bounds,
LayoutChange,
@@ -25,7 +24,8 @@ import type {
NodeId,
NodeLayout,
Point
} from '@/types/layoutTypes'
} from '@/renderer/core/layout/types'
import { SpatialIndexManager } from '@/renderer/core/spatial/SpatialIndex'
// Create logger for layout store
const logger = log.getLogger(DEBUG_CONFIG.STORE_LOGGER_NAME)
@@ -662,4 +662,4 @@ class LayoutStoreImpl implements LayoutStore {
export const layoutStore = new LayoutStoreImpl()
// Export types for convenience
export type { LayoutStore } from '@/types/layoutTypes'
export type { LayoutStore } from '@/renderer/core/layout/types'

View File

@@ -3,9 +3,9 @@
*
* Provides unified access to the layout store and mutation API.
*/
import { layoutMutations } from '@/services/layoutMutations'
import { layoutStore } from '@/stores/layoutStore'
import type { Bounds, NodeId, Point } from '@/types/layoutTypes'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import type { Bounds, NodeId, Point } from '@/renderer/core/layout/types'
/**
* Main composable for accessing the layout system

View File

@@ -7,7 +7,7 @@
import log from 'loglevel'
import { onUnmounted } from 'vue'
import { layoutStore } from '@/stores/layoutStore'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
// Create a logger for layout debugging
const logger = log.getLogger('layout')

View File

@@ -0,0 +1,346 @@
/**
* Layout System - Type Definitions
*
* This file contains all type definitions for the layout system
* that manages node positions, bounds, spatial data, and operations.
*/
import type { ComputedRef, Ref } from 'vue'
// Basic geometric types
export interface Point {
x: number
y: number
}
export interface Size {
width: number
height: number
}
export interface Bounds {
x: number
y: number
width: number
height: number
}
// ID types for type safety
export type NodeId = string
export type SlotId = string
export type ConnectionId = string
// Layout data structures
export interface NodeLayout {
id: NodeId
position: Point
size: Size
zIndex: number
visible: boolean
// Computed bounds for hit testing
bounds: Bounds
}
export interface SlotLayout {
id: SlotId
nodeId: NodeId
position: Point // Relative to node
type: 'input' | 'output'
index: number
}
export interface ConnectionLayout {
id: ConnectionId
sourceSlot: SlotId
targetSlot: SlotId
// Control points for curved connections
controlPoints?: Point[]
}
// Mutation types (legacy - for compatibility)
export type LayoutMutationType =
| 'moveNode'
| 'resizeNode'
| 'setNodeZIndex'
| 'createNode'
| 'deleteNode'
| 'batch'
export interface LayoutMutation {
type: LayoutMutationType
timestamp: number
source: 'canvas' | 'vue' | 'external'
}
export interface MoveNodeMutation extends LayoutMutation {
type: 'moveNode'
nodeId: NodeId
position: Point
previousPosition?: Point
}
export interface ResizeNodeMutation extends LayoutMutation {
type: 'resizeNode'
nodeId: NodeId
size: Size
previousSize?: Size
}
export interface SetNodeZIndexMutation extends LayoutMutation {
type: 'setNodeZIndex'
nodeId: NodeId
zIndex: number
previousZIndex?: number
}
export interface CreateNodeMutation extends LayoutMutation {
type: 'createNode'
nodeId: NodeId
layout: NodeLayout
}
export interface DeleteNodeMutation extends LayoutMutation {
type: 'deleteNode'
nodeId: NodeId
previousLayout?: NodeLayout
}
export interface BatchMutation extends LayoutMutation {
type: 'batch'
mutations: AnyLayoutMutation[]
}
// Union type for all mutations
export type AnyLayoutMutation =
| MoveNodeMutation
| ResizeNodeMutation
| SetNodeZIndexMutation
| CreateNodeMutation
| DeleteNodeMutation
| BatchMutation
// CRDT Operation Types
/**
* Base operation interface that all operations extend
*/
export interface BaseOperation {
/** Unique operation ID for deduplication */
id?: string
/** Timestamp for ordering operations */
timestamp: number
/** Actor who performed the operation (for CRDT) */
actor: string
/** Source system that initiated the operation */
source: 'canvas' | 'vue' | 'external'
/** Node this operation affects */
nodeId: NodeId
}
/**
* Operation type discriminator for type narrowing
*/
export type OperationType =
| 'moveNode'
| 'resizeNode'
| 'setNodeZIndex'
| 'createNode'
| 'deleteNode'
| 'setNodeVisibility'
| 'batchUpdate'
/**
* Move node operation
*/
export interface MoveNodeOperation extends BaseOperation {
type: 'moveNode'
position: Point
previousPosition: Point
}
/**
* Resize node operation
*/
export interface ResizeNodeOperation extends BaseOperation {
type: 'resizeNode'
size: { width: number; height: number }
previousSize: { width: number; height: number }
}
/**
* Set node z-index operation
*/
export interface SetNodeZIndexOperation extends BaseOperation {
type: 'setNodeZIndex'
zIndex: number
previousZIndex: number
}
/**
* Create node operation
*/
export interface CreateNodeOperation extends BaseOperation {
type: 'createNode'
layout: NodeLayout
}
/**
* Delete node operation
*/
export interface DeleteNodeOperation extends BaseOperation {
type: 'deleteNode'
previousLayout: NodeLayout
}
/**
* Set node visibility operation
*/
export interface SetNodeVisibilityOperation extends BaseOperation {
type: 'setNodeVisibility'
visible: boolean
previousVisible: boolean
}
/**
* Batch update operation for atomic multi-property changes
*/
export interface BatchUpdateOperation extends BaseOperation {
type: 'batchUpdate'
updates: Partial<NodeLayout>
previousValues: Partial<NodeLayout>
}
/**
* Union of all operation types
*/
export type LayoutOperation =
| MoveNodeOperation
| ResizeNodeOperation
| SetNodeZIndexOperation
| CreateNodeOperation
| DeleteNodeOperation
| SetNodeVisibilityOperation
| BatchUpdateOperation
// Legacy alias for compatibility
export type AnyLayoutOperation = LayoutOperation
/**
* Type guards for operations
*/
export const isBaseOperation = (op: unknown): op is BaseOperation => {
return (
typeof op === 'object' &&
op !== null &&
'timestamp' in op &&
'actor' in op &&
'source' in op &&
'nodeId' in op
)
}
export const isMoveNodeOperation = (
op: LayoutOperation
): op is MoveNodeOperation => op.type === 'moveNode'
export const isResizeNodeOperation = (
op: LayoutOperation
): op is ResizeNodeOperation => op.type === 'resizeNode'
export const isCreateNodeOperation = (
op: LayoutOperation
): op is CreateNodeOperation => op.type === 'createNode'
export const isDeleteNodeOperation = (
op: LayoutOperation
): op is DeleteNodeOperation => op.type === 'deleteNode'
/**
* Operation application interface
*/
export interface OperationApplicator<
T extends LayoutOperation = LayoutOperation
> {
canApply(operation: T): boolean
apply(operation: T): void
reverse(operation: T): void
}
/**
* Operation serialization for network/storage
*/
export interface OperationSerializer {
serialize(operation: LayoutOperation): string
deserialize(data: string): LayoutOperation
}
/**
* Conflict resolution strategy
*/
export interface ConflictResolver {
resolve(op1: LayoutOperation, op2: LayoutOperation): LayoutOperation[]
}
// Change notification types
export interface LayoutChange {
type: 'create' | 'update' | 'delete'
nodeIds: NodeId[]
timestamp: number
source: 'canvas' | 'vue' | 'external'
operation: LayoutOperation
}
// Store interfaces
export interface LayoutStore {
// CustomRef accessors for shared write access
getNodeLayoutRef(nodeId: NodeId): Ref<NodeLayout | null>
getNodesInBounds(bounds: Bounds): ComputedRef<NodeId[]>
getAllNodes(): ComputedRef<ReadonlyMap<NodeId, NodeLayout>>
getVersion(): ComputedRef<number>
// Spatial queries (non-reactive)
queryNodeAtPoint(point: Point): NodeId | null
queryNodesInBounds(bounds: Bounds): NodeId[]
// Direct mutation API (CRDT-ready)
applyOperation(operation: LayoutOperation): void
// Change subscription
onChange(callback: (change: LayoutChange) => void): () => void
// Initialization
initializeFromLiteGraph(
nodes: Array<{ id: string; pos: [number, number]; size: [number, number] }>
): void
// Source and actor management
setSource(source: 'canvas' | 'vue' | 'external'): void
setActor(actor: string): void
getCurrentSource(): 'canvas' | 'vue' | 'external'
getCurrentActor(): string
}
// Simplified mutation API
export interface LayoutMutations {
// Single node operations (synchronous, CRDT-ready)
moveNode(nodeId: NodeId, position: Point): void
resizeNode(nodeId: NodeId, size: Size): void
setNodeZIndex(nodeId: NodeId, zIndex: number): void
// Lifecycle operations
createNode(nodeId: NodeId, layout: Partial<NodeLayout>): void
deleteNode(nodeId: NodeId): void
// Stacking operations
bringNodeToFront(nodeId: NodeId): void
// Source tracking
setSource(source: 'canvas' | 'vue' | 'external'): void
setActor(actor: string): void // For CRDT
}
// CRDT-ready operation log (for future CRDT integration)
export interface OperationLog {
operations: LayoutOperation[]
addOperation(operation: LayoutOperation): void
getOperationsSince(timestamp: number): LayoutOperation[]
getOperationsByActor(actor: string): LayoutOperation[]
}

View File

@@ -4,8 +4,11 @@
* Manages spatial indexing for efficient node queries based on bounds.
* Uses QuadTree for fast spatial lookups with caching for performance.
*/
import { PERFORMANCE_CONFIG, QUADTREE_CONFIG } from '@/constants/layout'
import type { Bounds, NodeId } from '@/types/layoutTypes'
import {
PERFORMANCE_CONFIG,
QUADTREE_CONFIG
} from '@/renderer/core/layout/constants'
import type { Bounds, NodeId } from '@/renderer/core/layout/types'
import { QuadTree } from '@/utils/spatial/QuadTree'
/**

View File

@@ -7,9 +7,9 @@
import log from 'loglevel'
import { computed, inject } from 'vue'
import { layoutMutations } from '@/services/layoutMutations'
import { layoutStore } from '@/stores/layoutStore'
import type { Point } from '@/types/layoutTypes'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import type { Point } from '@/renderer/core/layout/types'
// Create a logger for layout debugging
const logger = log.getLogger('layout')

View File

@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it } from 'vitest'
import { layoutStore } from '@/stores/layoutStore'
import type { NodeLayout } from '@/types/layoutTypes'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import type { NodeLayout } from '@/renderer/core/layout/types'
describe('layoutStore CRDT operations', () => {
beforeEach(() => {