1- import { cn } from "@/lib/utils" ;
2-
3- import { useCallback , useMemo } from "react" ;
1+ import { useCallback , useMemo , useRef } from "react" ;
42import {
53 ReactFlow ,
64 MiniMap ,
@@ -11,15 +9,21 @@ import {
119 addEdge ,
1210 BackgroundVariant ,
1311 ConnectionMode ,
14- type OnConnect ,
1512 type Node ,
13+ NodeChange ,
14+ Edge ,
15+ EdgeChange ,
16+ Connection ,
1617} from "@xyflow/react" ;
1718
1819import "@xyflow/react/dist/style.css" ;
1920
2021import { useEffect } from "react" ;
2122import { usePages } from "@/hooks/usePages" ;
2223import { NoteNode } from "./NoteNode" ;
24+ import * as Y from "yjs" ;
25+ import { WebsocketProvider } from "y-websocket" ;
26+ import { cn } from "@/lib/utils" ;
2327
2428const proOptions = { hideAttribution : true } ;
2529
@@ -29,38 +33,128 @@ interface CanvasProps {
2933
3034export default function Canvas ( { className } : CanvasProps ) {
3135 const [ nodes , setNodes , onNodesChange ] = useNodesState < Node > ( [ ] ) ;
32- const [ edges , setEdges , onEdgesChange ] = useEdgesState ( [ ] ) ;
36+ const [ edges , setEdges , onEdgesChange ] = useEdgesState < Edge > ( [ ] ) ;
3337
3438 const { pages } = usePages ( ) ;
3539
36- useEffect ( ( ) => {
37- if ( ! pages ) {
38- return ;
39- }
40+ const onConnect = useCallback (
41+ ( connection : Connection ) => {
42+ if ( ! connection . source || ! connection . target ) return ;
4043
41- const newNodes = pages . map ( ( page , index ) => ( {
42- id : page . id . toString ( ) ,
43- position : { x : 100 * index , y : 100 } ,
44- data : { title : page . title , id : page . id } ,
45- type : "note" ,
46- } ) ) ;
47- setNodes ( newNodes ) ;
48- } , [ pages , setNodes ] ) ;
44+ const newEdge : Edge = {
45+ id : `e${ connection . source } -${ connection . target } ` ,
46+ source : connection . source ,
47+ target : connection . target ,
48+ sourceHandle : connection . sourceHandle || undefined ,
49+ targetHandle : connection . targetHandle || undefined ,
50+ } ;
51+
52+ if ( ydoc . current ) {
53+ ydoc . current . getMap ( "edges" ) . set ( newEdge . id , newEdge ) ;
54+ }
4955
50- const onConnect : OnConnect = useCallback (
51- ( params ) => setEdges ( ( eds ) => addEdge ( params , eds ) ) ,
56+ setEdges ( ( eds ) => addEdge ( connection , eds ) ) ;
57+ } ,
5258 [ setEdges ] ,
5359 ) ;
5460
61+ const handleEdgesChange = useCallback (
62+ ( changes : EdgeChange [ ] ) => {
63+ onEdgesChange ( changes ) ;
64+
65+ if ( ! ydoc . current ) return ;
66+
67+ changes . forEach ( ( change ) => {
68+ if ( change . type === "remove" ) {
69+ ydoc . current ?. getMap ( "edges" ) . delete ( change . id ) ;
70+ }
71+ } ) ;
72+ } ,
73+ [ onEdgesChange ] ,
74+ ) ;
75+
5576 const nodeTypes = useMemo ( ( ) => ( { note : NoteNode } ) , [ ] ) ;
5677
78+ const ydoc = useRef < Y . Doc > ( ) ;
79+ const provider = useRef < WebsocketProvider > ( ) ;
80+
81+ useEffect ( ( ) => {
82+ const doc = new Y . Doc ( ) ;
83+ const wsProvider = new WebsocketProvider (
84+ "ws://localhost:1234" ,
85+ "flow-room" ,
86+ doc ,
87+ ) ;
88+ const nodesMap = doc . getMap ( "nodes" ) ;
89+ const edgesMap = doc . getMap ( "edges" ) ;
90+
91+ ydoc . current = doc ;
92+ provider . current = wsProvider ;
93+
94+ if ( nodesMap . size === 0 ) {
95+ nodes . forEach ( ( node ) => {
96+ nodesMap . set ( node . id , JSON . parse ( JSON . stringify ( node ) ) ) ;
97+ } ) ;
98+ }
99+
100+ nodesMap . observe ( ( ) => {
101+ const yNodes = Array . from ( nodesMap . values ( ) ) as Node [ ] ;
102+ setNodes ( yNodes ) ;
103+ } ) ;
104+
105+ edgesMap . observe ( ( ) => {
106+ const yEdges = Array . from ( edgesMap . values ( ) ) as Edge [ ] ;
107+ setEdges ( yEdges ) ;
108+ } ) ;
109+
110+ return ( ) => {
111+ wsProvider . destroy ( ) ;
112+ doc . destroy ( ) ;
113+ } ;
114+ } , [ ] ) ;
115+
116+ useEffect ( ( ) => {
117+ if ( pages ) {
118+ const newNodes = pages . map ( ( page , index ) => ( {
119+ id : page . id . toString ( ) ,
120+ position : { x : 100 * index , y : 100 } ,
121+ data : { title : page . title , id : page . id } ,
122+ type : "note" ,
123+ } ) ) ;
124+ setNodes ( newNodes ) ;
125+ }
126+ } , [ pages , setNodes ] ) ;
127+
128+ const handleNodesChange = useCallback (
129+ ( changes : NodeChange [ ] ) => {
130+ onNodesChange ( changes ) ;
131+ if ( ! ydoc . current ) {
132+ return ;
133+ }
134+
135+ changes . forEach ( ( change ) => {
136+ if ( change . type === "position" ) {
137+ const node = nodes . find ( ( n ) => n . id === change . id ) ;
138+ if ( node ) {
139+ const updatedNode = {
140+ ...node ,
141+ position : change . position || node . position ,
142+ } ;
143+ ydoc . current ?. getMap ( "nodes" ) . set ( change . id , updatedNode ) ;
144+ }
145+ }
146+ } ) ;
147+ } ,
148+ [ nodes , onNodesChange ] ,
149+ ) ;
150+
57151 return (
58152 < div className = { cn ( "" , className ) } >
59153 < ReactFlow
60154 nodes = { nodes }
61155 edges = { edges }
62- onNodesChange = { onNodesChange }
63- onEdgesChange = { onEdgesChange }
156+ onNodesChange = { handleNodesChange }
157+ onEdgesChange = { handleEdgesChange }
64158 onConnect = { onConnect }
65159 proOptions = { proOptions }
66160 nodeTypes = { nodeTypes }
0 commit comments