Files
imagegen-tools/src/components/canvas/canvas-connections.tsx
T

122 lines
3.0 KiB
TypeScript

"use client";
import type { MouseEvent as ReactMouseEvent } from "react";
import type { CanvasConnection, CanvasNode, CanvasPosition } from "@/lib/canvas/types";
import { getActiveConnectionPath, getConnectionPath } from "@/lib/canvas/geometry";
type CanvasConnectionsProps = {
connections: CanvasConnection[];
nodes: CanvasNode[];
selectedConnectionId: string | null;
activeConnection?: {
fromNodeId: string;
mouseWorld: CanvasPosition;
} | null;
onSelectConnection: (id: string) => void;
};
export function CanvasConnections({
connections,
nodes,
selectedConnectionId,
activeConnection,
onSelectConnection,
}: CanvasConnectionsProps) {
const byId = new Map(nodes.map((node) => [node.id, node]));
const activeFrom = activeConnection
? byId.get(activeConnection.fromNodeId)
: undefined;
return (
<svg
className="pointer-events-none absolute left-0 top-0 overflow-visible"
style={{ width: 1, height: 1 }}
>
{connections.map((connection) => {
const from = byId.get(connection.fromNodeId);
const to = byId.get(connection.toNodeId);
if (!from || !to) return null;
return (
<ConnectionPath
key={connection.id}
active={connection.id === selectedConnectionId}
connection={connection}
from={from}
to={to}
onSelect={() => onSelectConnection(connection.id)}
/>
);
})}
{activeConnection && activeFrom ? (
<path
d={getActiveConnectionPath(
{
x: activeFrom.position.x + activeFrom.width,
y: activeFrom.position.y + activeFrom.height / 2,
},
activeConnection.mouseWorld,
)}
fill="none"
stroke="#18181b"
strokeDasharray="7 7"
strokeWidth="2.5"
/>
) : null}
</svg>
);
}
function ConnectionPath({
connection,
from,
to,
active,
onSelect,
}: {
connection: CanvasConnection;
from: CanvasNode;
to: CanvasNode;
active: boolean;
onSelect: () => void;
}) {
const path = getConnectionPath(from, to);
const stroke = active ? "#18181b" : "#a1a1aa";
function handleContextMenu(event: ReactMouseEvent<SVGPathElement>) {
event.preventDefault();
event.stopPropagation();
onSelect();
}
return (
<g>
<path
data-connection-id={connection.id}
d={path}
fill="none"
stroke="transparent"
strokeWidth="18"
style={{ cursor: "pointer", pointerEvents: "stroke" }}
onClick={(event) => {
event.stopPropagation();
onSelect();
}}
onContextMenu={handleContextMenu}
/>
<path
d={path}
fill="none"
stroke={stroke}
strokeLinecap="round"
strokeOpacity={active ? 1 : 0.72}
strokeWidth={active ? 3 : 2}
style={{
filter: active ? "drop-shadow(0 0 8px rgba(17,24,39,.25))" : undefined,
pointerEvents: "none",
}}
/>
</g>
);
}