53 lines
2.6 KiB
TypeScript
53 lines
2.6 KiB
TypeScript
import type { NodeChange } from "@xyflow/react";
|
|
import { applyNodeChanges } from "@xyflow/react";
|
|
import type { CanvasNode } from "./canvas-elements.ts";
|
|
import { nodeCenter, nodeSize } from "./canvas-geometry.ts";
|
|
|
|
/** Membership changes only on content drops, never on frame movement or resize. */
|
|
export function containingFrame(node: CanvasNode, nodes: CanvasNode[]) {
|
|
if (node.data.kind === "frame") return undefined;
|
|
const center = nodeCenter(node);
|
|
return nodes.filter((frame) => {
|
|
if (frame.data.kind !== "frame") return false;
|
|
const { width, height } = nodeSize(frame);
|
|
return center.x >= frame.position.x && center.x <= frame.position.x + width && center.y >= frame.position.y && center.y <= frame.position.y + height;
|
|
}).sort((a, b) => nodeSize(a).width * nodeSize(a).height - nodeSize(b).width * nodeSize(b).height)[0];
|
|
}
|
|
|
|
export function frameDropFeedback(nodes: CanvasNode[], droppedIds: string[]) {
|
|
const ids = new Set(droppedIds);
|
|
return nodes.filter((node) => ids.has(node.id) && node.data.kind !== "frame").map((node) => ({
|
|
id: node.id, from: node.data.frameId, to: containingFrame(node, nodes)?.id,
|
|
}));
|
|
}
|
|
|
|
export function assignFrameMembership(nodes: CanvasNode[], droppedIds: string[]): CanvasNode[] {
|
|
const dropped = new Set(droppedIds);
|
|
return nodes.map((node) => {
|
|
if (!dropped.has(node.id) || node.data.kind === "frame") return node;
|
|
const frameId = containingFrame(node, nodes)?.id;
|
|
if (frameId === node.data.frameId) return node;
|
|
const { frameId: _old, ...data } = node.data;
|
|
return { ...node, data: { ...data, ...(frameId ? { frameId } : {}) } };
|
|
});
|
|
}
|
|
|
|
export function applyFrameNodeChanges(changes: NodeChange<CanvasNode>[], previous: CanvasNode[]) {
|
|
const nodes = applyNodeChanges(changes, previous);
|
|
const before = new Map(previous.map((node) => [node.id, node]));
|
|
const positioned = new Set(changes.flatMap((change) => change.type === "position" && change.position ? [change.id] : []));
|
|
const frames = new Map(nodes.filter((node) => node.data.kind === "frame").map((node) => [node.id, node]));
|
|
return nodes.map((node) => {
|
|
const frameId = node.data.frameId;
|
|
if (!frameId) return node;
|
|
const frame = frames.get(frameId);
|
|
if (!frame) {
|
|
const { frameId: _old, ...data } = node.data;
|
|
return { ...node, data };
|
|
}
|
|
const oldFrame = before.get(frameId);
|
|
if (!oldFrame || !positioned.has(frameId) || positioned.has(node.id)) return node;
|
|
return { ...node, position: { x: node.position.x + frame.position.x - oldFrame.position.x, y: node.position.y + frame.position.y - oldFrame.position.y } };
|
|
});
|
|
}
|