Files
ComfyUI_frontend/src/renderer/extensions/minimap/composables/useMinimap.ts
Christian Byrne 6349ceee6c [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>
2025-09-14 21:28:08 -07:00

252 lines
6.9 KiB
TypeScript

import { useRafFn } from '@vueuse/core'
import { computed, nextTick, ref, watch } from 'vue'
import type { LGraph } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useSettingStore } from '@/stores/settingStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import type { MinimapCanvas, MinimapSettingsKey } from '../types'
import { useMinimapGraph } from './useMinimapGraph'
import { useMinimapInteraction } from './useMinimapInteraction'
import { useMinimapRenderer } from './useMinimapRenderer'
import { useMinimapSettings } from './useMinimapSettings'
import { useMinimapViewport } from './useMinimapViewport'
export function useMinimap() {
const canvasStore = useCanvasStore()
const workflowStore = useWorkflowStore()
const settingStore = useSettingStore()
const containerRef = ref<HTMLDivElement>()
const canvasRef = ref<HTMLCanvasElement>()
const minimapRef = ref<HTMLElement | null>(null)
const visible = ref(true)
const initialized = ref(false)
const width = 250
const height = 200
const canvas = computed(() => canvasStore.canvas as MinimapCanvas | null)
const graph = computed(() => {
// If we're in a subgraph, use that; otherwise use the canvas graph
const activeSubgraph = workflowStore.activeSubgraph
return (activeSubgraph || canvas.value?.graph) as LGraph | null
})
// Settings
const settings = useMinimapSettings()
const {
nodeColors,
showLinks,
showGroups,
renderBypass,
renderError,
containerStyles,
panelStyles
} = settings
const updateOption = async (key: MinimapSettingsKey, value: boolean) => {
await settingStore.set(key, value)
renderer.forceFullRedraw()
renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)
}
// Viewport management
const viewport = useMinimapViewport(canvas, graph, width, height)
// Interaction handling
const interaction = useMinimapInteraction(
containerRef,
viewport.bounds,
viewport.scale,
width,
height,
viewport.centerViewOn,
canvas
)
// Graph event management
const graphManager = useMinimapGraph(graph, () => {
renderer.forceFullRedraw()
renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)
})
// Rendering
const renderer = useMinimapRenderer(
canvasRef,
graph,
viewport.bounds,
viewport.scale,
graphManager.updateFlags,
settings,
width,
height
)
// RAF loop for continuous updates
const { pause: pauseChangeDetection, resume: resumeChangeDetection } =
useRafFn(
async () => {
if (visible.value) {
const hasChanges = await graphManager.checkForChanges()
if (hasChanges) {
renderer.updateMinimap(
viewport.updateBounds,
viewport.updateViewport
)
}
}
},
{ immediate: false }
)
const init = async () => {
if (initialized.value) return
visible.value = settingStore.get('Comfy.Minimap.Visible')
if (canvas.value && graph.value) {
graphManager.init()
if (containerRef.value) {
interaction.updateContainerRect()
}
viewport.updateCanvasDimensions()
window.addEventListener('resize', interaction.updateContainerRect)
window.addEventListener('scroll', interaction.updateContainerRect)
window.addEventListener('resize', viewport.updateCanvasDimensions)
renderer.forceFullRedraw()
renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)
viewport.updateViewport()
if (visible.value) {
resumeChangeDetection()
viewport.startViewportSync()
}
initialized.value = true
}
}
const destroy = () => {
pauseChangeDetection()
viewport.stopViewportSync()
graphManager.destroy()
window.removeEventListener('resize', interaction.updateContainerRect)
window.removeEventListener('scroll', interaction.updateContainerRect)
window.removeEventListener('resize', viewport.updateCanvasDimensions)
initialized.value = false
}
watch(
canvas,
async (newCanvas, oldCanvas) => {
if (oldCanvas) {
graphManager.cleanupEventListeners()
pauseChangeDetection()
viewport.stopViewportSync()
graphManager.destroy()
window.removeEventListener('resize', interaction.updateContainerRect)
window.removeEventListener('scroll', interaction.updateContainerRect)
window.removeEventListener('resize', viewport.updateCanvasDimensions)
}
if (newCanvas && !initialized.value) {
await init()
}
},
{ immediate: true, flush: 'post' }
)
// Watch for graph changes (e.g., when navigating to/from subgraphs)
watch(graph, (newGraph, oldGraph) => {
if (newGraph && newGraph !== oldGraph) {
graphManager.cleanupEventListeners(oldGraph || undefined)
graphManager.setupEventListeners()
renderer.forceFullRedraw()
renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)
}
})
watch(visible, async (isVisible) => {
if (isVisible) {
if (containerRef.value) {
interaction.updateContainerRect()
}
viewport.updateCanvasDimensions()
renderer.forceFullRedraw()
await nextTick()
await nextTick()
renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)
viewport.updateViewport()
resumeChangeDetection()
viewport.startViewportSync()
} else {
pauseChangeDetection()
viewport.stopViewportSync()
}
})
const toggle = async () => {
visible.value = !visible.value
await settingStore.set('Comfy.Minimap.Visible', visible.value)
}
const setMinimapRef = (ref: HTMLElement | null) => {
minimapRef.value = ref
}
// Dynamic viewport styles based on actual viewport transform
const viewportStyles = computed(() => {
const transform = viewport.viewportTransform.value
return {
transform: `translate(${transform.x}px, ${transform.y}px)`,
width: `${transform.width}px`,
height: `${transform.height}px`,
border: `2px solid ${settings.isLightTheme.value ? '#E0E0E0' : '#FFF'}`,
backgroundColor: `rgba(255, 255, 255, 0.2)`,
willChange: 'transform',
backfaceVisibility: 'hidden' as const,
perspective: '1000px',
pointerEvents: 'none' as const
}
})
return {
visible: computed(() => visible.value),
initialized: computed(() => initialized.value),
containerRef,
canvasRef,
containerStyles,
viewportStyles,
panelStyles,
width,
height,
nodeColors,
showLinks,
showGroups,
renderBypass,
renderError,
init,
destroy,
toggle,
renderMinimap: renderer.renderMinimap,
handlePointerDown: interaction.handlePointerDown,
handlePointerMove: interaction.handlePointerMove,
handlePointerUp: interaction.handlePointerUp,
handleWheel: interaction.handleWheel,
setMinimapRef,
updateOption
}
}