[backport core/1.35] Fix: Minimap rendering (#7725)

## Summary
- Backport of #7639 to core/1.35
- Cherry-picked merge commit 6d57b4def5
- Resolved conflicts in test snapshot PNGs (accepted PR version for all
changed snapshots)

## Original PR
https://github.com/Comfy-Org/ComfyUI_frontend/pull/7639

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7725-backport-core-1-35-Fix-Minimap-rendering-2d16d73d36508164a229e35d18ce515e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-12-22 10:44:24 -08:00
committed by GitHub
parent 9ec20f26d4
commit 2f9c493139
27 changed files with 61 additions and 90 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -16,6 +16,7 @@
/>
<div
ref="containerRef"
class="litegraph-minimap relative border border-interface-stroke bg-comfy-menu-bg shadow-interface"
:style="containerStyles"
>
@@ -50,7 +51,12 @@
}"
/>
<canvas :width="width" :height="height" class="minimap-canvas" />
<canvas
ref="canvasRef"
:width="width"
:height="height"
class="minimap-canvas"
/>
<div class="minimap-viewport" :style="viewportStyles" />
@@ -69,7 +75,7 @@
<script setup lang="ts">
import Button from 'primevue/button'
import { onMounted, onUnmounted, ref } from 'vue'
import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'
import { useCommandStore } from '@/stores/commandStore'
@@ -77,8 +83,9 @@ import { useCommandStore } from '@/stores/commandStore'
import MiniMapPanel from './MiniMapPanel.vue'
const commandStore = useCommandStore()
const minimapRef = ref<HTMLDivElement>()
const containerRef = useTemplateRef<HTMLDivElement>('containerRef')
const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef')
const {
initialized,
@@ -101,7 +108,10 @@ const {
handlePointerCancel,
handleWheel,
setMinimapRef
} = useMinimap()
} = useMinimap({
containerRefMaybe: containerRef,
canvasRefMaybe: canvasRef
})
const showOptionsPanel = ref(false)

View File

@@ -1,5 +1,6 @@
import { useRafFn } from '@vueuse/core'
import { computed, nextTick, ref, watch } from 'vue'
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
import type { ShallowRef } from 'vue'
import type { LGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'
@@ -13,14 +14,20 @@ import { useMinimapRenderer } from './useMinimapRenderer'
import { useMinimapSettings } from './useMinimapSettings'
import { useMinimapViewport } from './useMinimapViewport'
export function useMinimap() {
export function useMinimap({
canvasRefMaybe,
containerRefMaybe
}: {
canvasRefMaybe?: Readonly<ShallowRef<HTMLCanvasElement | null>>
containerRefMaybe?: Readonly<ShallowRef<HTMLDivElement | null>>
} = {}) {
const canvasStore = useCanvasStore()
const workflowStore = useWorkflowStore()
const settingStore = useSettingStore()
const containerRef = ref<HTMLDivElement>()
const canvasRef = ref<HTMLCanvasElement>()
const minimapRef = ref<HTMLElement | null>(null)
const canvasRef = canvasRefMaybe ?? shallowRef(null)
const containerRef = containerRefMaybe ?? shallowRef(null)
const visible = ref(true)
const initialized = ref(false)
@@ -223,8 +230,6 @@ export function useMinimap() {
visible: computed(() => visible.value),
initialized: computed(() => initialized.value),
containerRef,
canvasRef,
containerStyles,
viewportStyles,
panelStyles,

View File

@@ -1,10 +1,10 @@
import { ref } from 'vue'
import type { Ref } from 'vue'
import type { Ref, ShallowRef } from 'vue'
import type { MinimapCanvas } from '../types'
export function useMinimapInteraction(
containerRef: Ref<HTMLDivElement | undefined>,
containerRef: Readonly<ShallowRef<HTMLDivElement | null>>,
bounds: Ref<{ minX: number; minY: number; width: number; height: number }>,
scale: Ref<number>,
width: number,

View File

@@ -1,5 +1,5 @@
import { ref } from 'vue'
import type { Ref } from 'vue'
import type { Ref, ShallowRef } from 'vue'
import type { LGraph } from '@/lib/litegraph/src/litegraph'
@@ -7,7 +7,7 @@ import { renderMinimapToCanvas } from '../minimapCanvasRenderer'
import type { UpdateFlags } from '../types'
export function useMinimapRenderer(
canvasRef: Ref<HTMLCanvasElement | undefined>,
canvasRef: Readonly<ShallowRef<HTMLCanvasElement | null>>,
graph: Ref<LGraph | null>,
bounds: Ref<{ minX: number; minY: number; width: number; height: number }>,
scale: Ref<number>,

View File

@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { nextTick, shallowRef } from 'vue'
const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0))
@@ -164,10 +164,11 @@ describe('useMinimap', () => {
let mockContainerElement: any
let mockContext2D: any
const createAndInitializeMinimap = async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
async function createAndInitializeMinimap() {
const minimap = useMinimap({
containerRefMaybe: shallowRef(mockContainerElement),
canvasRefMaybe: shallowRef(mockCanvasElement)
})
await minimap.init()
await nextTick()
await flushPromises()
@@ -301,10 +302,7 @@ describe('useMinimap', () => {
})
it('should initialize minimap when canvas is available', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -336,9 +334,7 @@ describe('useMinimap', () => {
})
it('should setup event listeners on graph', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -349,9 +345,7 @@ describe('useMinimap', () => {
it('should handle visibility from settings', async () => {
defaultSettingStore.get.mockReturnValue(false)
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -362,9 +356,7 @@ describe('useMinimap', () => {
describe('destroy', () => {
it('should cleanup all resources', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
minimap.destroy()
@@ -389,9 +381,7 @@ describe('useMinimap', () => {
mockGraph.onNodeRemoved = originalCallbacks.onNodeRemoved
mockGraph.onConnectionChange = originalCallbacks.onConnectionChange
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
minimap.destroy()
@@ -429,9 +419,7 @@ describe('useMinimap', () => {
describe('rendering', () => {
it('should verify context is obtained during render', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
const getContextSpy = vi.spyOn(mockCanvasElement, 'getContext')
@@ -456,9 +444,7 @@ describe('useMinimap', () => {
})
it('should render at least once after initialization', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -498,9 +484,7 @@ describe('useMinimap', () => {
it('should not render when context is null', async () => {
mockCanvasElement.getContext = vi.fn().mockReturnValue(null)
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
await new Promise((resolve) => setTimeout(resolve, 100))
@@ -514,9 +498,7 @@ describe('useMinimap', () => {
const originalNodes = [...mockGraph._nodes]
mockGraph._nodes = []
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -695,9 +677,7 @@ describe('useMinimap', () => {
describe('wheel interactions', () => {
it('should handle wheel zoom in', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -720,9 +700,7 @@ describe('useMinimap', () => {
})
it('should handle wheel zoom out', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -745,9 +723,7 @@ describe('useMinimap', () => {
})
it('should respect zoom limits', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -770,9 +746,7 @@ describe('useMinimap', () => {
})
it('should update container rect if needed', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -795,9 +769,7 @@ describe('useMinimap', () => {
describe('viewport updates', () => {
it('should update viewport transform correctly', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
await nextTick()
@@ -814,9 +786,7 @@ describe('useMinimap', () => {
})
it('should handle canvas dimension updates', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -839,9 +809,7 @@ describe('useMinimap', () => {
describe('graph change handling', () => {
it('should handle node addition', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -861,9 +829,7 @@ describe('useMinimap', () => {
})
it('should handle node removal', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -878,9 +844,7 @@ describe('useMinimap', () => {
})
it('should handle connection changes', async () => {
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -907,9 +871,7 @@ describe('useMinimap', () => {
describe('edge cases', () => {
it('should handle missing node outputs', async () => {
mockGraph._nodes[0].outputs = null
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await expect(minimap.init()).resolves.not.toThrow()
expect(minimap.initialized.value).toBe(true)
@@ -919,9 +881,7 @@ describe('useMinimap', () => {
mockGraph.links.link1.target_id = 'invalid-node'
mockGraph.getNodeById.mockReturnValue(null)
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await expect(minimap.init()).resolves.not.toThrow()
expect(minimap.initialized.value).toBe(true)
@@ -930,9 +890,7 @@ describe('useMinimap', () => {
it('should handle high DPI displays', async () => {
window.devicePixelRatio = 2
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()
@@ -942,9 +900,7 @@ describe('useMinimap', () => {
it('should handle nodes without color', async () => {
mockGraph._nodes[0].color = undefined
const minimap = useMinimap()
minimap.containerRef.value = mockContainerElement
minimap.canvasRef.value = mockCanvasElement
const minimap = await createAndInitializeMinimap()
await minimap.init()

View File

@@ -280,7 +280,7 @@ describe('useMinimapInteraction', () => {
})
it('should handle null container gracefully', () => {
const containerRef = ref<HTMLDivElement | undefined>(undefined)
const containerRef = ref<HTMLDivElement | null>(null)
const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 })
const scaleRef = ref(0.5)
const canvasRef = ref(mockCanvas as any)

View File

@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ref } from 'vue'
import { ref, shallowRef } from 'vue'
import type { LGraph } from '@/lib/litegraph/src/litegraph'
import { useMinimapRenderer } from '@/renderer/extensions/minimap/composables/useMinimapRenderer'
@@ -32,7 +32,7 @@ describe('useMinimapRenderer', () => {
})
it('should initialize with full redraw needed', () => {
const canvasRef = ref(mockCanvas)
const canvasRef = shallowRef<HTMLCanvasElement | null>(mockCanvas)
const graphRef = ref(mockGraph as any)
const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 })
const scaleRef = ref(1)
@@ -230,7 +230,7 @@ describe('useMinimapRenderer', () => {
})
it('should handle null canvas gracefully', () => {
const canvasRef = ref<HTMLCanvasElement | undefined>(undefined)
const canvasRef = shallowRef<HTMLCanvasElement | null>(null)
const graphRef = ref(mockGraph as any)
const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 })
const scaleRef = ref(1)