blob: 861437f2f95ca36473d99289fd47714c33b3b19f [file] [log] [blame]
import "@xyflow/react/dist/style.css";
import {
ReactFlow,
Background,
Controls,
Connection,
BackgroundVariant,
Edge,
useReactFlow,
Panel,
useStoreApi,
} from "@xyflow/react";
import { useStateStore, AppState, AppNode, useZoom } from "@/lib/state";
import { useShallow } from "zustand/react/shallow";
import { useCallback, useEffect, useMemo } from "react";
import { NodeGatewayHttps } from "@/components/node-gateway-https";
import { NodeApp } from "@/components/node-app";
import { NodeVolume } from "./node-volume";
import { NodePostgreSQL } from "./node-postgresql";
import { NodeMongoDB } from "./node-mongodb";
import { NodeGithub } from "./node-github";
import { Actions } from "./actions";
import { NodeGatewayTCP } from "./node-gateway-tcp";
import { NodeNetwork } from "./node-network";
const selector = (state: AppState) => ({
nodes: state.nodes,
edges: state.edges,
onNodesChange: state.onNodesChange,
onEdgesChange: state.onEdgesChange,
onConnect: state.onConnect,
});
export function Canvas() {
const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useStateStore(useShallow(selector));
const store = useStateStore();
const instance = useReactFlow();
const flow = useStoreApi();
const viewport = useZoom();
useEffect(() => {
store.setViewport({
width: flow.getState().width,
height: flow.getState().height,
transformX: flow.getState().transform[0],
transformY: flow.getState().transform[1],
transformZoom: flow.getState().transform[2],
});
}, [store, flow]);
useEffect(() => {
instance.setViewport(viewport);
}, [viewport, instance]);
const nodeTypes = useMemo(
() => ({
network: NodeNetwork,
app: NodeApp,
"gateway-https": NodeGatewayHttps,
"gateway-tcp": NodeGatewayTCP,
volume: NodeVolume,
postgresql: NodePostgreSQL,
mongodb: NodeMongoDB,
github: NodeGithub,
}),
[],
);
const isValidConnection = useCallback(
(c: Edge | Connection) => {
if (c.source === c.target) {
return false;
}
const sn = instance.getNode(c.source)! as AppNode;
const tn = instance.getNode(c.target)! as AppNode;
if (sn.type === "github") {
return c.targetHandle === "repository";
}
if (sn.type === "app") {
if (c.sourceHandle === "ports" && (!sn.data.ports || sn.data.ports.length === 0)) {
return false;
}
}
if (tn.type === "gateway-https") {
if (c.targetHandle === "https" && tn.data.https !== undefined) {
return false;
}
}
if (sn.type === "volume") {
if (c.targetHandle !== "volume") {
return false;
}
return true;
}
if (tn.type === "network") {
if (c.sourceHandle !== "subdomain") {
return false;
}
if (sn.type !== "gateway-https" && sn.type !== "gateway-tcp") {
return false;
}
}
return true;
},
[instance],
);
return (
<div style={{ width: "100%", height: "100%" }}>
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
isValidConnection={isValidConnection}
fitView
proOptions={{ hideAttribution: true }}
>
<Controls showInteractive={false} />
<Background
variant={store.mode === "deploy" ? BackgroundVariant.Dots : BackgroundVariant.Lines}
gap={12}
size={1}
/>
<Panel position="bottom-right">
<Actions />
</Panel>
</ReactFlow>
</div>
);
}
export default Canvas;