Save current image generation workspace
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user