Skip to content

Commit ff5d092

Browse files
benceruleanluchristian-byrneAustinMrozclaude
authored
Refactor vue slot tracking (#5463)
* add dom element resize observer registry for vue node components * Update src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts Co-authored-by: AustinMroz <[email protected]> * refactor(vue-nodes): typed TransformState InjectionKey, safer ResizeObserver sizing, centralized slot tracking, and small readability updates * chore: make TransformState interface non-exported to satisfy knip pre-push * Revert "chore: make TransformState interface non-exported to satisfy knip pre-push" This reverts commit 110ecf3. * Revert "refactor(vue-nodes): typed TransformState InjectionKey, safer ResizeObserver sizing, centralized slot tracking, and small readability updates" This reverts commit 4287526. * [refactor] Improve resize tracking composable documentation and test utilities - Rename parameters in useVueElementTracking for clarity (appIdentifier, trackingType) - Add comprehensive docstring with examples to prevent DOM attribute confusion - Extract mountLGraphNode test utility to eliminate repetitive mock setup - Add technical implementation notes documenting optimization decisions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * remove typo comment * convert to functional bounds collection * remove inline import * add interfaces for bounds mutations * remove change log * fix bounds collection when vue nodes turned off * fix title offset on y * move from resize observer to selection toolbox bounds * refactor(vue-nodes): typed TransformState InjectionKey, safer ResizeObserver sizing, centralized slot tracking, and small readability updates * Fix conversion * Readd padding * revert churn reducings from layoutStore.ts * Rely on RO for resize, and batch * Improve churn * Cache canvas offset * rename from measure * remove unused * address review comments * Update legacy injection * nit * Split into store * nit * perf improvement --------- Co-authored-by: bymyself <[email protected]> Co-authored-by: AustinMroz <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 6b59f83 commit ff5d092

File tree

21 files changed

+498
-379
lines changed

21 files changed

+498
-379
lines changed

src/composables/canvas/useSelectionToolboxPosition.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export function useSelectionToolboxPosition(
102102

103103
worldPosition.value = {
104104
x: unionBounds.x + unionBounds.width / 2,
105+
// createBounds() applied a default padding of 10px
106+
// so adjust Y to maintain visual consistency
105107
y: unionBounds.y - 10
106108
}
107109

src/composables/element/useCanvasPositionConversion.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { useElementBounding } from '@vueuse/core'
22

3-
import type { LGraphCanvas, Vector2 } from '@/lib/litegraph/src/litegraph'
3+
import type { LGraphCanvas, Point } from '@/lib/litegraph/src/litegraph'
4+
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
5+
6+
let sharedConverter: ReturnType<typeof useCanvasPositionConversion> | null =
7+
null
48

59
/**
610
* Convert between canvas and client positions
@@ -14,15 +18,15 @@ export const useCanvasPositionConversion = (
1418
) => {
1519
const { left, top, update } = useElementBounding(canvasElement)
1620

17-
const clientPosToCanvasPos = (pos: Vector2): Vector2 => {
21+
const clientPosToCanvasPos = (pos: Point): Point => {
1822
const { offset, scale } = lgCanvas.ds
1923
return [
2024
(pos[0] - left.value) / scale - offset[0],
2125
(pos[1] - top.value) / scale - offset[1]
2226
]
2327
}
2428

25-
const canvasPosToClientPos = (pos: Vector2): Vector2 => {
29+
const canvasPosToClientPos = (pos: Point): Point => {
2630
const { offset, scale } = lgCanvas.ds
2731
return [
2832
(pos[0] + offset[0]) * scale + left.value,
@@ -36,3 +40,10 @@ export const useCanvasPositionConversion = (
3640
update
3741
}
3842
}
43+
44+
export function useSharedCanvasPositionConversion() {
45+
if (sharedConverter) return sharedConverter
46+
const lgCanvas = useCanvasStore().getCanvas()
47+
sharedConverter = useCanvasPositionConversion(lgCanvas.canvas, lgCanvas)
48+
return sharedConverter
49+
}

src/composables/useCanvasDrop.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Ref } from 'vue'
22

3+
import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
34
import { usePragmaticDroppable } from '@/composables/usePragmaticDragAndDrop'
45
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
56
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
@@ -27,16 +28,19 @@ export const useCanvasDrop = (canvasRef: Ref<HTMLCanvasElement>) => {
2728

2829
if (dndData.type === 'tree-explorer-node') {
2930
const node = dndData.data as RenderedTreeExplorerNode
31+
const conv = useSharedCanvasPositionConversion()
32+
const basePos = conv.clientPosToCanvasPos([loc.clientX, loc.clientY])
33+
3034
if (node.data instanceof ComfyNodeDefImpl) {
3135
const nodeDef = node.data
32-
const pos = comfyApp.clientPosToCanvasPos([loc.clientX, loc.clientY])
36+
const pos = [...basePos]
3337
// Add an offset on y to make sure after adding the node, the cursor
3438
// is on the node (top left corner)
3539
pos[1] += LiteGraph.NODE_TITLE_HEIGHT
3640
litegraphService.addNodeOnGraph(nodeDef, { pos })
3741
} else if (node.data instanceof ComfyModelDef) {
3842
const model = node.data
39-
const pos = comfyApp.clientPosToCanvasPos([loc.clientX, loc.clientY])
43+
const pos = basePos
4044
const nodeAtPos = comfyApp.graph.getNodeOnPos(pos[0], pos[1])
4145
let targetProvider: ModelNodeProvider | null = null
4246
let targetGraphNode: LGraphNode | null = null
@@ -73,11 +77,7 @@ export const useCanvasDrop = (canvasRef: Ref<HTMLCanvasElement>) => {
7377
}
7478
} else if (node.data instanceof ComfyWorkflow) {
7579
const workflow = node.data
76-
const position = comfyApp.clientPosToCanvasPos([
77-
loc.clientX,
78-
loc.clientY
79-
])
80-
await workflowService.insertWorkflow(workflow, { position })
80+
await workflowService.insertWorkflow(workflow, { position: basePos })
8181
}
8282
}
8383
}

src/extensions/core/widgetInputs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
INodeOutputSlot,
99
ISlotType,
1010
LLink,
11-
Vector2
11+
Point
1212
} from '@/lib/litegraph/src/litegraph'
1313
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
1414
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
@@ -557,7 +557,7 @@ app.registerExtension({
557557
}
558558
)
559559

560-
function isNodeAtPos(pos: Vector2) {
560+
function isNodeAtPos(pos: Point) {
561561
for (const n of app.graph.nodes) {
562562
if (n.pos[0] === pos[0] && n.pos[1] === pos[1]) {
563563
return true

src/platform/workflow/core/services/workflowService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { toRaw } from 'vue'
22

33
import { t } from '@/i18n'
44
import { LGraph, LGraphCanvas } from '@/lib/litegraph/src/litegraph'
5-
import type { SerialisableGraph, Vector2 } from '@/lib/litegraph/src/litegraph'
5+
import type { Point, SerialisableGraph } from '@/lib/litegraph/src/litegraph'
66
import { useSettingStore } from '@/platform/settings/settingStore'
77
import { useToastStore } from '@/platform/updates/common/toastStore'
88
import {
@@ -346,7 +346,7 @@ export const useWorkflowService = () => {
346346
*/
347347
const insertWorkflow = async (
348348
workflow: ComfyWorkflow,
349-
options: { position?: Vector2 } = {}
349+
options: { position?: Point } = {}
350350
) => {
351351
const loadedWorkflow = await workflow.load()
352352
const workflowJSON = toRaw(loadedWorkflow.initialState)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { InjectionKey } from 'vue'
2+
3+
import type { Point } from '@/renderer/core/layout/types'
4+
5+
/**
6+
* Lightweight, injectable transform state used by layout-aware components.
7+
*
8+
* Consumers use this interface to convert coordinates between LiteGraph's
9+
* canvas space and the DOM's screen space, access the current pan/zoom
10+
* (camera), and perform basic viewport culling checks.
11+
*
12+
* Coordinate mapping:
13+
* - screen = (canvas + offset) * scale
14+
* - canvas = screen / scale - offset
15+
*
16+
* The full implementation and additional helpers live in
17+
* `useTransformState()`. This interface deliberately exposes only the
18+
* minimal surface needed outside that composable.
19+
*
20+
* @example
21+
* const state = inject(TransformStateKey)!
22+
* const screen = state.canvasToScreen({ x: 100, y: 50 })
23+
*/
24+
interface TransformState {
25+
/** Convert a screen-space point (CSS pixels) to canvas space. */
26+
screenToCanvas: (p: Point) => Point
27+
/** Convert a canvas-space point to screen space (CSS pixels). */
28+
canvasToScreen: (p: Point) => Point
29+
/** Current pan/zoom; `x`/`y` are offsets, `z` is scale. */
30+
camera?: { x: number; y: number; z: number }
31+
/**
32+
* Test whether a node's rectangle intersects the (expanded) viewport.
33+
* Handy for viewport culling and lazy work.
34+
*
35+
* @param nodePos Top-left in canvas space `[x, y]`
36+
* @param nodeSize Size in canvas units `[width, height]`
37+
* @param viewport Screen-space viewport `{ width, height }`
38+
* @param margin Optional fractional margin (e.g. `0.2` = 20%)
39+
*/
40+
isNodeInViewport?: (
41+
nodePos: ArrayLike<number>,
42+
nodeSize: ArrayLike<number>,
43+
viewport: { width: number; height: number },
44+
margin?: number
45+
) => boolean
46+
}
47+
48+
export const TransformStateKey: InjectionKey<TransformState> =
49+
Symbol('transformState')

0 commit comments

Comments
 (0)