[refactor] Improve renderer domain organization (#5552)

* [refactor] Improve renderer architecture organization

Building on PR #5388, this refines the renderer domain structure:

**Key improvements:**
- Group all transform utilities in `transform/` subdirectory for better cohesion
- Move canvas state to dedicated `renderer/core/canvas/` domain
- Consolidate coordinate system logic (TransformPane, useTransformState, sync utilities)

**File organization:**
- `renderer/core/canvas/canvasStore.ts` (was `stores/graphStore.ts`)
- `renderer/core/layout/transform/` contains all coordinate system utilities
- Transform sync utilities co-located with core transform logic

This creates clearer domain boundaries and groups related functionality
while building on the foundation established in PR #5388.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Clean up linter-modified files

* Fix import paths and clean up unused imports after rebase

- Update all remaining @/stores/graphStore references to @/renderer/core/canvas/canvasStore
- Remove unused imports from selection toolbox components
- All tests pass, only reka-ui upstream issue remains in typecheck

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

Co-Authored-By: Claude <noreply@anthropic.com>

* [auto-fix] Apply ESLint and Prettier fixes

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Christian Byrne
2025-09-14 21:28:08 -07:00
committed by GitHub
parent 8c6ee026c0
commit 6349ceee6c
65 changed files with 89 additions and 72 deletions

View File

@@ -1,7 +1,7 @@
import { computed } from 'vue'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { app } from '@/scripts/app'
import { useCanvasStore } from '@/stores/graphStore'
import { useSettingStore } from '@/stores/settingStore'
/**

View File

@@ -1,5 +1,5 @@
// call nextTick on all changeTracker
import { useCanvasStore } from '@/stores/graphStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useWorkflowStore } from '@/stores/workflowStore'
/**

View File

@@ -1,115 +0,0 @@
import { onUnmounted, ref } from 'vue'
import type { LGraphCanvas } from '../../lib/litegraph/src/litegraph'
interface CanvasTransformSyncOptions {
/**
* Whether to automatically start syncing when canvas is available
* @default true
*/
autoStart?: boolean
}
interface CanvasTransformSyncCallbacks {
/**
* Called when sync starts
*/
onStart?: () => void
/**
* Called after each sync update with timing information
*/
onUpdate?: (duration: number) => void
/**
* Called when sync stops
*/
onStop?: () => void
}
/**
* Manages requestAnimationFrame-based synchronization with LiteGraph canvas transforms.
*
* This composable provides a clean way to sync Vue transform state with LiteGraph canvas
* on every frame. It handles RAF lifecycle management, provides performance timing,
* and ensures proper cleanup.
*
* The sync function typically reads canvas.ds (draw state) properties like offset and scale
* to keep Vue components aligned with the canvas coordinate system.
*
* @example
* ```ts
* const { isActive, startSync, stopSync } = useCanvasTransformSync(
* canvas,
* (canvas) => syncWithCanvas(canvas),
* {
* onStart: () => emit('rafStatusChange', true),
* onUpdate: (time) => emit('transformUpdate', time),
* onStop: () => emit('rafStatusChange', false)
* }
* )
* ```
*/
export function useCanvasTransformSync(
canvas: LGraphCanvas | undefined | null,
syncFn: (canvas: LGraphCanvas) => void,
callbacks: CanvasTransformSyncCallbacks = {},
options: CanvasTransformSyncOptions = {}
) {
const { autoStart = true } = options
const { onStart, onUpdate, onStop } = callbacks
const isActive = ref(false)
let rafId: number | null = null
const startSync = () => {
if (isActive.value || !canvas) return
isActive.value = true
onStart?.()
const sync = () => {
if (!isActive.value || !canvas) return
try {
const startTime = performance.now()
syncFn(canvas)
const endTime = performance.now()
onUpdate?.(endTime - startTime)
} catch (error) {
console.warn('Canvas transform sync error:', error)
}
rafId = requestAnimationFrame(sync)
}
sync()
}
const stopSync = () => {
if (!isActive.value) return
if (rafId !== null) {
cancelAnimationFrame(rafId)
rafId = null
}
isActive.value = false
onStop?.()
}
// Auto-start if canvas is available and autoStart is enabled
if (autoStart && canvas) {
startSync()
}
// Clean up on unmount
onUnmounted(() => {
stopSync()
})
return {
isActive,
startSync,
stopSync
}
}

View File

@@ -2,8 +2,8 @@ import { computed } from 'vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import { LGraphGroup } from '@/lib/litegraph/src/litegraph'
import { useTitleEditorStore } from '@/renderer/core/canvas/canvasStore'
import { app } from '@/scripts/app'
import { useTitleEditorStore } from '@/stores/graphStore'
import { useSettingStore } from '@/stores/settingStore'
/**

View File

@@ -5,7 +5,7 @@ import {
type LGraphGroup,
type LGraphNode
} from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useSettingStore } from '@/stores/settingStore'
import { useWorkflowStore } from '@/stores/workflowStore'

View File

@@ -2,7 +2,7 @@ import { useI18n } from 'vue-i18n'
import type { Direction } from '@/lib/litegraph/src/interfaces'
import { alignNodes, distributeNodes } from '@/lib/litegraph/src/utils/arrange'
import { useCanvasStore } from '@/stores/graphStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { isLGraphNode } from '@/utils/litegraphUtil'
import { useCanvasRefresh } from './useCanvasRefresh'

View File

@@ -8,7 +8,7 @@ import {
RenderShape,
isColorable
} from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { adjustColor } from '@/utils/colorUtil'

View File

@@ -1,9 +1,12 @@
// import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems' // Unused for now
import { t } from '@/i18n'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import {
useCanvasStore,
useTitleEditorStore
} from '@/renderer/core/canvas/canvasStore'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore'
import { useToastStore } from '@/stores/toastStore'
import { useWorkflowStore } from '@/stores/workflowStore'

View File

@@ -7,7 +7,7 @@ import {
LGraphNode,
SubgraphNode
} from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useSettingStore } from '@/stores/settingStore'
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'
@@ -35,7 +35,9 @@ export function useSelectionState() {
const { selectedItems } = storeToRefs(canvasStore)
const selectedNodes = computed(() => {
return selectedItems.value.filter((i) => isLGraphNode(i)) as LGraphNode[]
return selectedItems.value.filter((i: unknown) =>
isLGraphNode(i)
) as LGraphNode[]
})
const nodeDef = computed(() => {
@@ -61,7 +63,7 @@ export function useSelectionState() {
)
const hasSubgraphs = computed(() =>
selectedItems.value.some((i) => i instanceof SubgraphNode)
selectedItems.value.some((i: unknown) => i instanceof SubgraphNode)
)
const hasAny3DNodeSelected = computed(() => {

View File

@@ -1,6 +1,6 @@
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import { SubgraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'

View File

@@ -1,151 +0,0 @@
import { useDebounceFn, useEventListener, useThrottleFn } from '@vueuse/core'
import { ref } from 'vue'
import type { MaybeRefOrGetter } from 'vue'
interface TransformSettlingOptions {
/**
* Delay in ms before transform is considered "settled" after last interaction
* @default 200
*/
settleDelay?: number
/**
* Whether to track both zoom (wheel) and pan (pointer drag) interactions
* @default false
*/
trackPan?: boolean
/**
* Throttle delay for high-frequency pointermove events (only used when trackPan is true)
* @default 16 (~60fps)
*/
pointerMoveThrottle?: number
/**
* Whether to use passive event listeners (better performance but can't preventDefault)
* @default true
*/
passive?: boolean
}
/**
* Tracks when canvas transforms (zoom/pan) are actively changing vs settled.
*
* This composable helps optimize rendering quality during transformations.
* When the user is actively zooming or panning, we can reduce rendering quality
* for better performance. Once the transform "settles" (stops changing), we can
* trigger high-quality re-rasterization.
*
* The settling concept prevents constant quality switching during interactions
* by waiting for a period of inactivity before considering the transform complete.
*
* Uses VueUse's useEventListener for automatic cleanup and useDebounceFn for
* efficient settle detection.
*
* @example
* ```ts
* const { isTransforming } = useTransformSettling(canvasRef, {
* settleDelay: 200,
* trackPan: true
* })
*
* // Use in CSS classes or rendering logic
* const cssClass = computed(() => ({
* 'low-quality': isTransforming.value,
* 'high-quality': !isTransforming.value
* }))
* ```
*/
export function useTransformSettling(
target: MaybeRefOrGetter<HTMLElement | null | undefined>,
options: TransformSettlingOptions = {}
) {
const {
settleDelay = 200,
trackPan = false,
pointerMoveThrottle = 16,
passive = true
} = options
const isTransforming = ref(false)
let isPanning = false
/**
* Mark transform as active
*/
const markTransformActive = () => {
isTransforming.value = true
}
/**
* Mark transform as settled (debounced)
*/
const markTransformSettled = useDebounceFn(() => {
isTransforming.value = false
}, settleDelay)
/**
* Handle any transform event - mark active then queue settle
*/
const handleTransformEvent = () => {
markTransformActive()
void markTransformSettled()
}
// Wheel handler
const handleWheel = () => {
handleTransformEvent()
}
// Pointer handlers for panning
const handlePointerDown = () => {
if (trackPan) {
isPanning = true
handleTransformEvent()
}
}
// Throttled pointer move handler for performance
const handlePointerMove = trackPan
? useThrottleFn(() => {
if (isPanning) {
handleTransformEvent()
}
}, pointerMoveThrottle)
: undefined
const handlePointerEnd = () => {
if (trackPan) {
isPanning = false
// Don't immediately stop - let the debounced settle handle it
}
}
// Register event listeners with auto-cleanup
useEventListener(target, 'wheel', handleWheel, {
capture: true,
passive
})
if (trackPan) {
useEventListener(target, 'pointerdown', handlePointerDown, {
capture: true
})
if (handlePointerMove) {
useEventListener(target, 'pointermove', handlePointerMove, {
capture: true,
passive
})
}
useEventListener(target, 'pointerup', handlePointerEnd, {
capture: true
})
useEventListener(target, 'pointercancel', handlePointerEnd, {
capture: true
})
}
return {
isTransforming
}
}

View File

@@ -9,8 +9,8 @@
import { type Ref, computed } from 'vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { app as comfyApp } from '@/scripts/app'
import { useCanvasStore } from '@/stores/graphStore'
interface NodeManager {
getNode: (id: string) => any

View File

@@ -16,13 +16,13 @@ import type {
VueNodeData
} from '@/composables/graph/useGraphNodeManager'
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
import { useLinkLayoutSync } from '@/renderer/core/layout/sync/useLinkLayoutSync'
import { useSlotLayoutSync } from '@/renderer/core/layout/sync/useSlotLayoutSync'
import { app as comfyApp } from '@/scripts/app'
import { useCanvasStore } from '@/stores/graphStore'
export function useVueNodeLifecycle(isVueNodesEnabled: Ref<boolean>) {
const canvasStore = useCanvasStore()