mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 05:49:54 +00:00
## Summary Sometimes the saved position is super far away from any of the nodes, which causes general confusion. This PR changes the `loadGraphData` logic to fit-to-view in those scenarios. Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/7425 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7435-feat-when-restored-position-has-no-nodes-in-viewport-automatically-fit-to-view-2c86d73d36508119bf2ed9d361ec868f) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
|
|
import type { Bounds } from '@/renderer/core/layout/types'
|
|
|
|
/** Simple 2D point or size as [x, y] or [width, height] */
|
|
type Vec2 = readonly [number, number]
|
|
|
|
/**
|
|
* Finds the greatest common divisor (GCD) for two numbers using iterative
|
|
* Euclidean algorithm. Uses iteration instead of recursion to avoid stack
|
|
* overflow with large inputs or small floating-point step values.
|
|
*
|
|
* For floating-point numbers, uses a tolerance-based approach to handle
|
|
* precision issues and limits iterations to prevent hangs.
|
|
*
|
|
* @param a - The first number.
|
|
* @param b - The second number.
|
|
* @returns The GCD of the two numbers.
|
|
*/
|
|
export const gcd = (a: number, b: number): number => {
|
|
// Use absolute values to handle negative numbers
|
|
let x = Math.abs(a)
|
|
let y = Math.abs(b)
|
|
|
|
// Handle edge cases
|
|
if (x === 0) return y
|
|
if (y === 0) return x
|
|
|
|
// For floating-point numbers, use tolerance-based comparison
|
|
// This prevents infinite loops due to floating-point precision issues
|
|
const epsilon = 1e-10
|
|
const maxIterations = 100
|
|
|
|
let iterations = 0
|
|
while (y > epsilon && iterations < maxIterations) {
|
|
;[x, y] = [y, x % y]
|
|
iterations++
|
|
}
|
|
|
|
return x
|
|
}
|
|
|
|
/**
|
|
* Finds the least common multiple (LCM) for two numbers.
|
|
*
|
|
* @param a - The first number.
|
|
* @param b - The second number.
|
|
* @returns The LCM of the two numbers.
|
|
*/
|
|
export const lcm = (a: number, b: number): number => {
|
|
return Math.abs(a * b) / gcd(a, b)
|
|
}
|
|
|
|
/**
|
|
* Computes the union (bounding box) of multiple rectangles using a single-pass algorithm.
|
|
*
|
|
* Finds the minimum and maximum x/y coordinates across all rectangles to create
|
|
* a single bounding rectangle that contains all input rectangles. Optimized for
|
|
* performance with V8-friendly tuple access patterns.
|
|
*
|
|
* @param rectangles - Array of rectangle tuples in [x, y, width, height] format
|
|
* @returns Bounds object with union rectangle, or null if no rectangles provided
|
|
*/
|
|
export function computeUnionBounds(
|
|
rectangles: readonly ReadOnlyRect[]
|
|
): Bounds | null {
|
|
const n = rectangles.length
|
|
if (n === 0) {
|
|
return null
|
|
}
|
|
|
|
const r0 = rectangles[0]
|
|
let minX = r0[0]
|
|
let minY = r0[1]
|
|
let maxX = minX + r0[2]
|
|
let maxY = minY + r0[3]
|
|
|
|
for (let i = 1; i < n; i++) {
|
|
const r = rectangles[i]
|
|
const x1 = r[0]
|
|
const y1 = r[1]
|
|
const x2 = x1 + r[2]
|
|
const y2 = y1 + r[3]
|
|
|
|
if (x1 < minX) minX = x1
|
|
if (y1 < minY) minY = y1
|
|
if (x2 > maxX) maxX = x2
|
|
if (y2 > maxY) maxY = y2
|
|
}
|
|
|
|
return {
|
|
x: minX,
|
|
y: minY,
|
|
width: maxX - minX,
|
|
height: maxY - minY
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if any item with pos/size overlaps a rectangle (AABB test).
|
|
* @param items Items with pos [x, y] and size [width, height]
|
|
* @param rect Rectangle as [x, y, width, height]
|
|
* @returns `true` if any item overlaps the rect
|
|
*/
|
|
export function anyItemOverlapsRect(
|
|
items: Iterable<{ pos: Vec2; size: Vec2 }>,
|
|
rect: ReadOnlyRect
|
|
): boolean {
|
|
const rectRight = rect[0] + rect[2]
|
|
const rectBottom = rect[1] + rect[3]
|
|
|
|
for (const item of items) {
|
|
const overlaps =
|
|
item.pos[0] < rectRight &&
|
|
item.pos[0] + item.size[0] > rect[0] &&
|
|
item.pos[1] < rectBottom &&
|
|
item.pos[1] + item.size[1] > rect[1]
|
|
|
|
if (overlaps) return true
|
|
}
|
|
return false
|
|
}
|