darabos commited on
Commit
a55eb17
·
1 Parent(s): 23cab17

Struggle with CRDT+React.

Browse files
web/src/workspace/LynxKiteState.ts CHANGED
@@ -1,3 +1,4 @@
1
  import { createContext } from "react";
 
2
 
3
- export const LynxKiteState = createContext({});
 
1
  import { createContext } from "react";
2
+ import { Workspace } from "../apiTypes.ts";
3
 
4
+ export const LynxKiteState = createContext({ workspace: {} as Workspace });
web/src/workspace/Workspace.tsx CHANGED
@@ -6,10 +6,8 @@ import favicon from '../assets/favicon.ico';
6
  import {
7
  ReactFlow,
8
  Controls,
9
- MiniMap,
10
  MarkerType,
11
  useReactFlow,
12
- useUpdateNodeInternals,
13
  ReactFlowProvider,
14
  applyEdgeChanges,
15
  applyNodeChanges,
@@ -50,26 +48,34 @@ export default function (props: any) {
50
 
51
 
52
  function LynxKiteFlow() {
53
- const updateNodeInternals = useUpdateNodeInternals();
54
- const { screenToFlowPosition } = useReactFlow();
55
  const [nodes, setNodes] = useState([] as Node[]);
56
  const [edges, setEdges] = useState([] as Edge[]);
57
  const { path } = useParams();
58
 
59
- const sstore = syncedStore({ workspace: {} });
60
  const doc = getYjsDoc(sstore);
61
  const wsProvider = useMemo(() => new WebsocketProvider("ws://localhost:8000/ws/crdt", path!, doc), [path]);
62
  wsProvider; // Just to disable the lint warning. The life cycle of this object is a mystery.
63
- const state: { workspace: Workspace } = useSyncedStore(sstore);
64
  const onNodesChange = useCallback(
65
  (changes: any[]) => {
66
  setNodes((nds) => applyNodeChanges(changes, nds));
 
67
  for (const ch of changes) {
 
 
 
 
68
  if (ch.type === 'position') {
69
- const node = state.workspace?.nodes?.find((n) => n.id === ch.id);
70
- if (node) {
71
- node.position = ch.position;
72
- }
 
 
 
 
73
  }
74
  }
75
  },
@@ -79,6 +85,7 @@ function LynxKiteFlow() {
79
  (changes: any[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),
80
  [],
81
  );
 
82
  if (state?.workspace?.nodes && JSON.stringify(nodes) !== JSON.stringify([...state.workspace.nodes as Node[]])) {
83
  const updated = Object.fromEntries(state.workspace.nodes.map((n) => [n.id, n]));
84
  const oldNodes = Object.fromEntries(nodes.map((n) => [n.id, n]));
@@ -127,7 +134,7 @@ function LynxKiteFlow() {
127
  </div>
128
  </div>
129
  <div style={{ height: "100%", width: '100vw' }}>
130
- <LynxKiteState.Provider value={state.workspace}>
131
  <ReactFlow
132
  nodes={nodes}
133
  edges={edges}
 
6
  import {
7
  ReactFlow,
8
  Controls,
 
9
  MarkerType,
10
  useReactFlow,
 
11
  ReactFlowProvider,
12
  applyEdgeChanges,
13
  applyNodeChanges,
 
48
 
49
 
50
  function LynxKiteFlow() {
51
+ const reactFlow = useReactFlow();
 
52
  const [nodes, setNodes] = useState([] as Node[]);
53
  const [edges, setEdges] = useState([] as Edge[]);
54
  const { path } = useParams();
55
 
56
+ const sstore = syncedStore({ workspace: {} as Workspace });
57
  const doc = getYjsDoc(sstore);
58
  const wsProvider = useMemo(() => new WebsocketProvider("ws://localhost:8000/ws/crdt", path!, doc), [path]);
59
  wsProvider; // Just to disable the lint warning. The life cycle of this object is a mystery.
60
+ const state = useSyncedStore(sstore);
61
  const onNodesChange = useCallback(
62
  (changes: any[]) => {
63
  setNodes((nds) => applyNodeChanges(changes, nds));
64
+ const wnodes = state.workspace!.nodes!;
65
  for (const ch of changes) {
66
+ const nodeIndex = wnodes.findIndex((n) => n.id === ch.id);
67
+ if (nodeIndex === -1) continue;
68
+ const node = wnodes[nodeIndex];
69
+ if (!node) continue;
70
  if (ch.type === 'position') {
71
+ node.position = ch.position;
72
+ } else if (ch.type === 'select') {
73
+ } else if (ch.type === 'dimensions') {
74
+ } else if (ch.type === 'replace') {
75
+ node.data.collapsed = ch.item.data.collapsed;
76
+ node.data.params = { ...ch.item.data.params };
77
+ } else {
78
+ console.log('Unknown node change', ch);
79
  }
80
  }
81
  },
 
85
  (changes: any[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),
86
  [],
87
  );
88
+
89
  if (state?.workspace?.nodes && JSON.stringify(nodes) !== JSON.stringify([...state.workspace.nodes as Node[]])) {
90
  const updated = Object.fromEntries(state.workspace.nodes.map((n) => [n.id, n]));
91
  const oldNodes = Object.fromEntries(nodes.map((n) => [n.id, n]));
 
134
  </div>
135
  </div>
136
  <div style={{ height: "100%", width: '100vw' }}>
137
+ <LynxKiteState.Provider value={state}>
138
  <ReactFlow
139
  nodes={nodes}
140
  edges={edges}
web/src/workspace/nodes/LynxKiteNode.tsx CHANGED
@@ -1,10 +1,11 @@
1
  import { useContext } from 'react';
2
  import { LynxKiteState } from '../LynxKiteState';
3
- import { Handle, NodeResizeControl } from '@xyflow/react';
4
  // @ts-ignore
5
  import ChevronDownRight from '~icons/tabler/chevron-down-right.jsx';
6
 
7
  interface LynxKiteNodeProps {
 
8
  width: number;
9
  height: number;
10
  nodeStyle: any;
@@ -12,13 +13,14 @@ interface LynxKiteNodeProps {
12
  children: any;
13
  }
14
 
15
- function getHandles(inputs, outputs) {
16
  const handles: {
17
  position: 'top' | 'bottom' | 'left' | 'right',
18
  name: string,
19
  index: number,
20
  offsetPercentage: number,
21
  showLabel: boolean,
 
22
  }[] = [];
23
  for (const e of Object.values(inputs)) {
24
  handles.push({ ...e, type: 'target' });
@@ -41,15 +43,17 @@ function getHandles(inputs, outputs) {
41
  }
42
 
43
  export default function LynxKiteNode(props: LynxKiteNodeProps) {
 
44
  const data = props.data;
45
  const state = useContext(LynxKiteState);
46
- const expanded = true;
47
  const handles = getHandles(data.meta?.inputs || {}, data.meta?.outputs || {});
48
  function asPx(n: number | undefined) {
49
  return (n ? n + 'px' : undefined) || '200px';
50
  }
51
- function titleClicked() { }
52
- function updateNodeData() { }
 
53
  const handleOffsetDirection = { top: 'left', bottom: 'left', left: 'top', right: 'top' };
54
 
55
  return (
@@ -69,7 +73,7 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
69
  {handles.map(handle => (
70
  <Handle
71
  key={handle.name}
72
- id={handle.name} type={handle.type} position={handle.position}
73
  style={{ [handleOffsetDirection[handle.position]]: handle.offsetPercentage + '%' }}>
74
  {handle.showLabel && <span className="handle-name">{handle.name.replace(/_/g, " ")}</span>}
75
  </Handle>
@@ -78,8 +82,8 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
78
  minWidth={100}
79
  minHeight={50}
80
  style={{ 'background': 'transparent', 'border': 'none' }}
81
- onResizeStart={() => updateNodeData(id, { beingResized: true })}
82
- onResizeEnd={() => updateNodeData(id, { beingResized: false })}
83
  >
84
  <ChevronDownRight className="node-resizer" />
85
  </NodeResizeControl>
 
1
  import { useContext } from 'react';
2
  import { LynxKiteState } from '../LynxKiteState';
3
+ import { useReactFlow, Handle, NodeResizeControl, Position } from '@xyflow/react';
4
  // @ts-ignore
5
  import ChevronDownRight from '~icons/tabler/chevron-down-right.jsx';
6
 
7
  interface LynxKiteNodeProps {
8
+ id: string;
9
  width: number;
10
  height: number;
11
  nodeStyle: any;
 
13
  children: any;
14
  }
15
 
16
+ function getHandles(inputs: object, outputs: object) {
17
  const handles: {
18
  position: 'top' | 'bottom' | 'left' | 'right',
19
  name: string,
20
  index: number,
21
  offsetPercentage: number,
22
  showLabel: boolean,
23
+ type: 'source' | 'target',
24
  }[] = [];
25
  for (const e of Object.values(inputs)) {
26
  handles.push({ ...e, type: 'target' });
 
43
  }
44
 
45
  export default function LynxKiteNode(props: LynxKiteNodeProps) {
46
+ const reactFlow = useReactFlow();
47
  const data = props.data;
48
  const state = useContext(LynxKiteState);
49
+ const expanded = !data.collapsed;
50
  const handles = getHandles(data.meta?.inputs || {}, data.meta?.outputs || {});
51
  function asPx(n: number | undefined) {
52
  return (n ? n + 'px' : undefined) || '200px';
53
  }
54
+ function titleClicked() {
55
+ reactFlow.updateNodeData(props.id, { collapsed: expanded });
56
+ }
57
  const handleOffsetDirection = { top: 'left', bottom: 'left', left: 'top', right: 'top' };
58
 
59
  return (
 
73
  {handles.map(handle => (
74
  <Handle
75
  key={handle.name}
76
+ id={handle.name} type={handle.type} position={Position[handle.position as keyof typeof Position]}
77
  style={{ [handleOffsetDirection[handle.position]]: handle.offsetPercentage + '%' }}>
78
  {handle.showLabel && <span className="handle-name">{handle.name.replace(/_/g, " ")}</span>}
79
  </Handle>
 
82
  minWidth={100}
83
  minHeight={50}
84
  style={{ 'background': 'transparent', 'border': 'none' }}
85
+ onResizeStart={() => reactFlow.updateNodeData(props.id, { beingResized: true })}
86
+ onResizeEnd={() => reactFlow.updateNodeData(props.id, { beingResized: false })}
87
  >
88
  <ChevronDownRight className="node-resizer" />
89
  </NodeResizeControl>
web/src/workspace/nodes/NodeWithParams.tsx CHANGED
@@ -1,18 +1,17 @@
1
  import { useContext } from 'react';
2
  import { LynxKiteState } from '../LynxKiteState';
3
  import LynxKiteNode from './LynxKiteNode';
4
- import { useNodesState } from '@xyflow/react';
5
  import NodeParameter from './NodeParameter';
6
 
7
- function NodeWithParams(props) {
 
8
  const metaParams = props.data.meta?.params;
9
  const state = useContext(LynxKiteState);
10
- function setParam(name, newValue) {
11
- const i = state.workspace.nodes.findIndex((n) => n.id === props.id);
12
- state.workspace.nodes[i].data.params[name] = newValue;
13
  }
14
- const [nodes, setNodes, onNodesChange] = useNodesState([]);
15
- const params = nodes && props.data?.params ? Object.entries(props.data.params) : [];
16
 
17
  return (
18
  <LynxKiteNode {...props}>
@@ -22,7 +21,7 @@ function NodeWithParams(props) {
22
  key={name}
23
  value={value}
24
  meta={metaParams?.[name]}
25
- onChange={(value) => setParam(name, value)}
26
  />
27
  )}
28
  </LynxKiteNode >
 
1
  import { useContext } from 'react';
2
  import { LynxKiteState } from '../LynxKiteState';
3
  import LynxKiteNode from './LynxKiteNode';
4
+ import { useReactFlow } from '@xyflow/react';
5
  import NodeParameter from './NodeParameter';
6
 
7
+ function NodeWithParams(props: any) {
8
+ const reactFlow = useReactFlow();
9
  const metaParams = props.data.meta?.params;
10
  const state = useContext(LynxKiteState);
11
+ function setParam(name: string, newValue: any) {
12
+ reactFlow.updateNodeData(props.id, { params: { ...props.data.params, [name]: newValue } });
 
13
  }
14
+ const params = props.data?.params ? Object.entries(props.data.params) : [];
 
15
 
16
  return (
17
  <LynxKiteNode {...props}>
 
21
  key={name}
22
  value={value}
23
  meta={metaParams?.[name]}
24
+ onChange={(value: any) => setParam(name, value)}
25
  />
26
  )}
27
  </LynxKiteNode >