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