blob: ba171532a653522fb92810dbb527130d54e89a92 [file] [log] [blame]
gio678746b2025-07-06 14:45:27 +00001import "@xyflow/react/dist/style.css";
2import {
3 ReactFlow,
4 Background,
5 Controls,
6 Connection,
7 BackgroundVariant,
8 Edge,
9 useReactFlow,
10 Panel,
11 useStoreApi,
12} from "@xyflow/react";
13import { useStateStore, AppState, useZoom } from "@/lib/state";
14import { useShallow } from "zustand/react/shallow";
15import { useCallback, useEffect, useMemo } from "react";
16import { NodeGatewayHttps } from "@/components/node-gateway-https";
17import { NodeApp } from "@/components/node-app";
18import { NodeVolume } from "./components/node-volume";
19import { NodePostgreSQL } from "./components/node-postgresql";
20import { NodeMongoDB } from "./components/node-mongodb";
21import { NodeGithub } from "./components/node-github";
22import { Actions } from "./components/actions";
23import { NodeGatewayTCP } from "./components/node-gateway-tcp";
24import { NodeNetwork } from "./components/node-network";
25import { AppNode } from "config";
gio5f2f1002025-03-20 18:38:48 +040026
gio678746b2025-07-06 14:45:27 +000027const selector = (state: AppState) => ({
28 nodes: state.nodes,
29 edges: state.edges,
30 onNodesChange: state.onNodesChange,
31 onEdgesChange: state.onEdgesChange,
32 onConnect: state.onConnect,
33});
34
35export function Canvas({ className }: { className?: string }) {
36 const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useStateStore(useShallow(selector));
gio818da4e2025-05-12 14:45:35 +000037 const store = useStateStore();
gio678746b2025-07-06 14:45:27 +000038 const instance = useReactFlow();
39 const flow = useStoreApi();
40 const viewport = useZoom();
41 useEffect(() => {
42 store.setViewport({
43 width: flow.getState().width,
44 height: flow.getState().height,
45 transformX: flow.getState().transform[0],
46 transformY: flow.getState().transform[1],
47 transformZoom: flow.getState().transform[2],
48 });
49 }, [store, flow]);
50 useEffect(() => {
51 instance.setViewport(viewport);
52 }, [viewport, instance]);
53 const nodeTypes = useMemo(
54 () => ({
55 network: NodeNetwork,
56 app: NodeApp,
57 "gateway-https": NodeGatewayHttps,
58 "gateway-tcp": NodeGatewayTCP,
59 volume: NodeVolume,
60 postgresql: NodePostgreSQL,
61 mongodb: NodeMongoDB,
62 github: NodeGithub,
63 }),
64 [],
65 );
66 const isValidConnection = useCallback(
67 (c: Edge | Connection) => {
68 if (c.source === c.target) {
69 return false;
70 }
71 const sn = instance.getNode(c.source)! as AppNode;
72 const tn = instance.getNode(c.target)! as AppNode;
73
74 if (sn.type === "github") {
75 return c.targetHandle === "repository";
76 }
77 if (sn.type === "app") {
78 if (c.sourceHandle === "ports" && (!sn.data.ports || sn.data.ports.length === 0)) {
79 return false;
80 }
81 }
82 if (tn.type === "gateway-https") {
83 if (c.targetHandle === "https" && tn.data.https !== undefined) {
84 return false;
85 }
86 }
87 if (sn.type === "volume") {
88 if (c.targetHandle !== "volume") {
89 return false;
90 }
91 return true;
92 }
93 if (tn.type === "network") {
94 if (c.sourceHandle !== "subdomain") {
95 return false;
96 }
97 if (sn.type !== "gateway-https" && sn.type !== "gateway-tcp") {
98 return false;
99 }
100 }
101 return true;
102 },
103 [instance],
104 );
giod0026612025-05-08 13:00:36 +0000105 return (
gio678746b2025-07-06 14:45:27 +0000106 <div style={{ width: "100%", height: "100%" }} className={className}>
107 <ReactFlow
108 nodeTypes={nodeTypes}
109 nodes={nodes}
110 edges={edges}
111 onNodesChange={onNodesChange}
112 onEdgesChange={onEdgesChange}
113 onConnect={onConnect}
114 isValidConnection={isValidConnection}
115 fitView
116 proOptions={{ hideAttribution: true }}
117 >
118 <Controls showInteractive={false} />
119 <Background
120 variant={store.mode === "deploy" ? BackgroundVariant.Dots : BackgroundVariant.Lines}
121 gap={12}
122 size={1}
123 />
124 <Panel position="top-right">
125 <Actions />
126 </Panel>
127 </ReactFlow>
128 </div>
giod0026612025-05-08 13:00:36 +0000129 );
gio5f2f1002025-03-20 18:38:48 +0400130}
gio678746b2025-07-06 14:45:27 +0000131
132export default Canvas;