mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 21:06:04 +00:00
public commit
This commit is contained in:
203
app/src/ui/components/canvas/Canvas.tsx
Normal file
203
app/src/ui/components/canvas/Canvas.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import {
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
MarkerType,
|
||||
MiniMap,
|
||||
type MiniMapProps,
|
||||
ReactFlow,
|
||||
type ReactFlowProps,
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useReactFlow
|
||||
} from "@xyflow/react";
|
||||
import { type ReactNode, useCallback, useEffect, useState } from "react";
|
||||
import { useBkndSystemTheme } from "ui/client/schema/system/use-bknd-system";
|
||||
|
||||
type CanvasProps = ReactFlowProps & {
|
||||
externalProvider?: boolean;
|
||||
backgroundStyle?: "lines" | "dots";
|
||||
minimap?: boolean | MiniMapProps;
|
||||
children?: JSX.Element | ReactNode;
|
||||
onDropNewNode?: (base: any) => any;
|
||||
onDropNewEdge?: (base: any) => any;
|
||||
};
|
||||
|
||||
export function Canvas({
|
||||
nodes: _nodes,
|
||||
edges: _edges,
|
||||
externalProvider,
|
||||
backgroundStyle = "lines",
|
||||
minimap = false,
|
||||
children,
|
||||
onDropNewNode,
|
||||
onDropNewEdge,
|
||||
...props
|
||||
}: CanvasProps) {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(_nodes ?? []);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(_edges ?? []);
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const { theme } = useBkndSystemTheme();
|
||||
|
||||
const [isCommandPressed, setIsCommandPressed] = useState(false);
|
||||
const [isSpacePressed, setIsSpacePressed] = useState(false);
|
||||
const [isPointerPressed, setIsPointerPressed] = useState(false);
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.metaKey) {
|
||||
setIsCommandPressed(true);
|
||||
}
|
||||
if (event.key === " ") {
|
||||
//event.preventDefault(); // Prevent default space scrolling behavior
|
||||
setIsSpacePressed(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
if (!event.metaKey) {
|
||||
setIsCommandPressed(false);
|
||||
}
|
||||
if (event.key === " ") {
|
||||
setIsSpacePressed(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointerDown = () => {
|
||||
if (isSpacePressed) {
|
||||
setIsPointerPressed(false);
|
||||
return;
|
||||
}
|
||||
setIsPointerPressed(true);
|
||||
};
|
||||
|
||||
const handlePointerUp = () => {
|
||||
if (isSpacePressed) {
|
||||
setIsPointerPressed(false);
|
||||
return;
|
||||
}
|
||||
setIsPointerPressed(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.querySelector("html")?.classList.add("fixed");
|
||||
|
||||
// Add global key listeners
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("keyup", handleKeyUp);
|
||||
|
||||
// Add global pointer listeners
|
||||
window.addEventListener("pointerdown", handlePointerDown);
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
|
||||
// Cleanup event listeners on component unmount
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
window.removeEventListener("pointerdown", handlePointerDown);
|
||||
window.removeEventListener("pointerup", handlePointerUp);
|
||||
document.querySelector("html")?.classList.remove("fixed");
|
||||
};
|
||||
}, []);
|
||||
|
||||
//console.log("mode", { cmd: isCommandPressed, space: isSpacePressed, mouse: isPointerPressed });
|
||||
|
||||
useEffect(() => {
|
||||
setNodes(_nodes ?? []);
|
||||
setEdges(_edges ?? []);
|
||||
}, [_nodes, _edges]);
|
||||
|
||||
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
|
||||
|
||||
const onConnectEnd = useCallback(
|
||||
(event, connectionState) => {
|
||||
if (!onDropNewNode || !onDropNewEdge) return;
|
||||
|
||||
const { fromNode, fromHandle, fromPosition } = connectionState;
|
||||
// when a connection is dropped on the pane it's not valid
|
||||
if (!connectionState.isValid) {
|
||||
console.log("conn", { event, connectionState });
|
||||
// we need to remove the wrapper bounds, in order to get the correct position
|
||||
|
||||
const { clientX, clientY } =
|
||||
"changedTouches" in event ? event.changedTouches[0] : event;
|
||||
const newNode = onDropNewNode({
|
||||
id: "select",
|
||||
type: "default",
|
||||
data: { label: "" },
|
||||
position: screenToFlowPosition({
|
||||
x: clientX,
|
||||
y: clientY
|
||||
}),
|
||||
origin: [0.0, 0.0]
|
||||
});
|
||||
|
||||
setNodes((nds) => nds.concat(newNode as any));
|
||||
setEdges((eds) =>
|
||||
eds.concat(
|
||||
onDropNewNode({
|
||||
id: newNode.id,
|
||||
source: connectionState.fromNode.id,
|
||||
target: newNode.id
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
[screenToFlowPosition]
|
||||
);
|
||||
//console.log("edges1", edges);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
colorMode={theme}
|
||||
onConnect={onConnect}
|
||||
onConnectEnd={onConnectEnd}
|
||||
className={
|
||||
isCommandPressed
|
||||
? "cursor-zoom-in"
|
||||
: isSpacePressed
|
||||
? isPointerPressed
|
||||
? "cursor-grabbing"
|
||||
: "cursor-grab"
|
||||
: ""
|
||||
}
|
||||
proOptions={{
|
||||
hideAttribution: true
|
||||
}}
|
||||
fitView
|
||||
fitViewOptions={{
|
||||
maxZoom: 1.5,
|
||||
...props.fitViewOptions
|
||||
}}
|
||||
nodeDragThreshold={25}
|
||||
panOnScrollSpeed={1}
|
||||
snapToGrid
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
nodesConnectable={false}
|
||||
/*panOnDrag={isSpacePressed}*/
|
||||
panOnDrag={true}
|
||||
zoomOnScroll={isCommandPressed}
|
||||
panOnScroll={!isCommandPressed}
|
||||
zoomOnDoubleClick={false}
|
||||
selectionOnDrag={!isSpacePressed}
|
||||
{...props}
|
||||
>
|
||||
{backgroundStyle === "lines" && (
|
||||
<Background
|
||||
color={theme === "light" ? "rgba(0,0,0,.1)" : "rgba(255,255,255,.1)"}
|
||||
gap={[50, 50]}
|
||||
variant={BackgroundVariant.Lines}
|
||||
/>
|
||||
)}
|
||||
{backgroundStyle === "dots" && (
|
||||
<Background color={theme === "light" ? "rgba(0,0,0,.5)" : "rgba(255,255,255,.2)"} />
|
||||
)}
|
||||
{minimap && <MiniMap {...(typeof minimap === "object" ? minimap : {})} />}
|
||||
{children}
|
||||
</ReactFlow>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user