mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-31 05:19:53 +00:00
This pull request refactors the minimap rendering system to use a unified, extensible data source abstraction for all minimap operations. By introducing a data source interface and factory, the minimap can now seamlessly support multiple sources of node layout (such as the `LayoutStore` or the underlying `LiteGraph`), improving maintainability and future extensibility. Rendering logic and change detection throughout the minimap have been updated to use this new abstraction, resulting in cleaner code and easier support for new data models. **Core architecture improvements:** * Introduced a new `IMinimapDataSource` interface and related data types (`MinimapNodeData`, `MinimapLinkData`, `MinimapGroupData`) to standardize node, link, and group data for minimap rendering. * Added an abstract base class `AbstractMinimapDataSource` that provides shared logic for bounds and group/link extraction, and implemented two concrete data sources: `LiteGraphDataSource` (for classic graph data) and `LayoutStoreDataSource` (for layout store data). [[1]](diffhunk://#diff-ea46218fc9ffced84168a5ff975e4a30e43f7bf134ee8f02ed2eae66efbb729dR1-R95) [[2]](diffhunk://#diff-9a6b7c6be25b4dbeb358fea18f3a21e78797058ccc86c818ed1e5f69c7355273R1-R30) [[3]](diffhunk://#diff-f200ba9495a03157198abff808ed6c3761746071404a52adbad98f6a9d01249bR1-R42) * Created a `MinimapDataSourceFactory` that selects the appropriate data source based on the presence of layout store data, enabling seamless switching between data models. **Minimap rendering and logic refactoring:** * Updated all minimap rendering functions (`renderGroups`, `renderNodes`, `renderConnections`) and the main `renderMinimapToCanvas` entry point to use the unified data source interface, significantly simplifying the rendering code and decoupling it from the underlying graph structure. [[1]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L1-R11) [[2]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121R33-R75) [[3]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L66-R124) [[4]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L134-R161) [[5]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L153-R187) [[6]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L187-L188) [[7]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121R227-R231) [[8]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L230-R248) * Refactored minimap viewport and graph change detection logic to use the data source abstraction for bounds, node, and link change detection, and to respond to layout store version changes. [[1]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6L2-R10) [[2]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6R33-R35) [[3]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6L99-R141) [[4]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6R157-R160) [[5]](diffhunk://#diff-338d14c67dabffaf6f68fbf09b16e8d67bead2b9df340e46601b2fbd57331521L8-R11) [[6]](diffhunk://#diff-338d14c67dabffaf6f68fbf09b16e8d67bead2b9df340e46601b2fbd57331521L56-R64) These changes make the minimap codebase more modular and robust, and lay the groundwork for supporting additional node layout strategies in the future. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5547-Layoutstore-Minimap-calculation-26e6d73d3650813e9457c051dff41ca1) by [Unito](https://www.unito.io)
175 lines
5.2 KiB
TypeScript
175 lines
5.2 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import { type ComputedRef, computed } from 'vue'
|
|
|
|
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
|
import type { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
|
|
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
|
import type { NodeLayout } from '@/renderer/core/layout/types'
|
|
import { MinimapDataSourceFactory } from '@/renderer/extensions/minimap/data/MinimapDataSourceFactory'
|
|
|
|
// Mock layoutStore
|
|
vi.mock('@/renderer/core/layout/store/layoutStore', () => ({
|
|
layoutStore: {
|
|
getAllNodes: vi.fn()
|
|
}
|
|
}))
|
|
|
|
// Helper to create mock links that satisfy LGraph['links'] type
|
|
function createMockLinks(): LGraph['links'] {
|
|
const map = new Map<number, LLink>()
|
|
return Object.assign(map, {}) as LGraph['links']
|
|
}
|
|
|
|
describe('MinimapDataSource', () => {
|
|
describe('MinimapDataSourceFactory', () => {
|
|
it('should create LayoutStoreDataSource when LayoutStore has data', () => {
|
|
// Arrange
|
|
const mockNodes = new Map<string, NodeLayout>([
|
|
[
|
|
'node1',
|
|
{
|
|
id: 'node1',
|
|
position: { x: 0, y: 0 },
|
|
size: { width: 100, height: 50 },
|
|
zIndex: 0,
|
|
visible: true,
|
|
bounds: { x: 0, y: 0, width: 100, height: 50 }
|
|
}
|
|
]
|
|
])
|
|
|
|
// Create a computed ref that returns the map
|
|
const computedNodes: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => mockNodes)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedNodes)
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
|
|
// Assert
|
|
expect(dataSource).toBeDefined()
|
|
expect(dataSource.hasData()).toBe(true)
|
|
expect(dataSource.getNodeCount()).toBe(1)
|
|
})
|
|
|
|
it('should create LiteGraphDataSource when LayoutStore is empty', () => {
|
|
// Arrange
|
|
const emptyMap = new Map<string, NodeLayout>()
|
|
const computedEmpty: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => emptyMap)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedEmpty)
|
|
|
|
const mockNode: Pick<
|
|
LGraphNode,
|
|
'id' | 'pos' | 'size' | 'bgcolor' | 'mode' | 'has_errors' | 'outputs'
|
|
> = {
|
|
id: 'node1' as NodeId,
|
|
pos: [0, 0],
|
|
size: [100, 50],
|
|
bgcolor: '#fff',
|
|
mode: 0,
|
|
has_errors: false,
|
|
outputs: []
|
|
}
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [mockNode as LGraphNode],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
|
|
// Assert
|
|
expect(dataSource).toBeDefined()
|
|
expect(dataSource.hasData()).toBe(true)
|
|
expect(dataSource.getNodeCount()).toBe(1)
|
|
|
|
const nodes = dataSource.getNodes()
|
|
expect(nodes).toHaveLength(1)
|
|
expect(nodes[0]).toMatchObject({
|
|
id: 'node1',
|
|
x: 0,
|
|
y: 0,
|
|
width: 100,
|
|
height: 50
|
|
})
|
|
})
|
|
|
|
it('should handle empty graph correctly', () => {
|
|
// Arrange
|
|
const emptyMap = new Map<string, NodeLayout>()
|
|
const computedEmpty: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => emptyMap)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedEmpty)
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
|
|
// Assert
|
|
expect(dataSource.hasData()).toBe(false)
|
|
expect(dataSource.getNodeCount()).toBe(0)
|
|
expect(dataSource.getNodes()).toEqual([])
|
|
expect(dataSource.getLinks()).toEqual([])
|
|
expect(dataSource.getGroups()).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('Bounds calculation', () => {
|
|
it('should calculate correct bounds from nodes', () => {
|
|
// Arrange
|
|
const emptyMap = new Map<string, NodeLayout>()
|
|
const computedEmpty: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => emptyMap)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedEmpty)
|
|
|
|
const mockNode1: Pick<LGraphNode, 'id' | 'pos' | 'size' | 'outputs'> = {
|
|
id: 'node1' as NodeId,
|
|
pos: [0, 0],
|
|
size: [100, 50],
|
|
outputs: []
|
|
}
|
|
|
|
const mockNode2: Pick<LGraphNode, 'id' | 'pos' | 'size' | 'outputs'> = {
|
|
id: 'node2' as NodeId,
|
|
pos: [200, 100],
|
|
size: [150, 75],
|
|
outputs: []
|
|
}
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [mockNode1 as LGraphNode, mockNode2 as LGraphNode],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
const bounds = dataSource.getBounds()
|
|
|
|
// Assert
|
|
expect(bounds).toEqual({
|
|
minX: 0,
|
|
minY: 0,
|
|
maxX: 350,
|
|
maxY: 175,
|
|
width: 350,
|
|
height: 175
|
|
})
|
|
})
|
|
})
|
|
})
|