darabos commited on
Commit
c470835
·
1 Parent(s): 728c1b0

Client-side CRDT working for nodes and edges.

Browse files
server/crdt.py CHANGED
@@ -18,8 +18,8 @@ class WebsocketServer(pycrdt_websocket.WebsocketServer):
18
  ystore = pycrdt_websocket.ystore.FileYStore(f'crdt_data/{name}.crdt')
19
  ydoc = pycrdt.Doc()
20
  ydoc['workspace'] = ws = pycrdt.Map()
21
- ws['nodes'] = []
22
- ws['edges'] = []
23
  ws['env'] = 'unset'
24
  # Replay updates from the store.
25
  try:
 
18
  ystore = pycrdt_websocket.ystore.FileYStore(f'crdt_data/{name}.crdt')
19
  ydoc = pycrdt.Doc()
20
  ydoc['workspace'] = ws = pycrdt.Map()
21
+ ws['nodes'] = pycrdt.Array()
22
+ ws['edges'] = pycrdt.Array()
23
  ws['env'] = 'unset'
24
  # Replay updates from the store.
25
  try:
web/package.json CHANGED
@@ -29,7 +29,6 @@
29
  "@sveltestack/svelte-query": "^1.6.0",
30
  "@xyflow/svelte": "^0.1.3",
31
  "bootstrap": "^5.3.3",
32
- "deep-object-diff": "^1.1.9",
33
  "echarts": "^5.5.0",
34
  "fuse.js": "^7.0.0",
35
  "svelte-echarts": "^1.0.0-rc1",
 
29
  "@sveltestack/svelte-query": "^1.6.0",
30
  "@xyflow/svelte": "^0.1.3",
31
  "bootstrap": "^5.3.3",
 
32
  "echarts": "^5.5.0",
33
  "fuse.js": "^7.0.0",
34
  "svelte-echarts": "^1.0.0-rc1",
web/src/LynxKiteFlow.svelte CHANGED
@@ -1,12 +1,13 @@
1
  <script lang="ts">
2
- import { diff } from 'deep-object-diff';
3
- import { writable, derived } from 'svelte/store';
4
  import {
5
  SvelteFlow,
6
  Controls,
7
  MiniMap,
8
  MarkerType,
9
  useSvelteFlow,
 
10
  type XYPosition,
11
  type Node,
12
  type Edge,
@@ -29,18 +30,23 @@
29
  import { syncedStore, getYjsDoc } from "@syncedstore/core";
30
  import { svelteSyncedStore } from "@syncedstore/svelte";
31
  import { WebsocketProvider } from "y-websocket";
 
32
 
33
  function getCRDTStore(path) {
34
  const sstore = syncedStore({ workspace: {} });
35
  const doc = getYjsDoc(sstore);
36
  const wsProvider = new WebsocketProvider("ws://localhost:8000/ws/crdt", path, doc);
37
- wsProvider.on('sync', function(isSynced: boolean) {
38
- console.log('synced', isSynced, 'ydoc', doc.toJSON());
39
- });
40
  return {store: svelteSyncedStore(sstore), sstore, doc};
41
  }
42
  $: connection = getCRDTStore(path);
43
  $: store = connection.store;
 
 
 
 
 
 
 
44
 
45
  export let path = '';
46
 
@@ -55,30 +61,8 @@
55
  area: NodeWithArea,
56
  };
57
 
58
- function substore(store, field) {
59
- if (!store) return;
60
- const ss = derived(store, (store) => store.workspace[field]?.value);
61
- ss.set = (value) => {
62
- console.log('set called', field, value);
63
- $store.workspace[field] = value;
64
- };
65
- ss.update = (fn) => {
66
- console.log('update called', field);
67
- console.log(JSON.stringify($store));
68
- console.log(JSON.stringify($store.workspace.nodes));
69
- const before = $store.workspace[field];
70
- console.log({before});
71
- const after = fn(before);
72
- console.log({after});
73
- $store.workspace[field] = after;
74
- };
75
- return ss;
76
- }
77
- $: nodes = substore(store, 'nodes');
78
- $: edges = substore(store, 'edges');
79
-
80
- // const nodes = writable<Node[]>([]);
81
- // const edges = writable<Edge[]>([]);
82
 
83
  function closeNodeSearch() {
84
  nodeSearchSettings = undefined;
@@ -96,35 +80,31 @@
96
  }
97
  function addNode(e) {
98
  const meta = {...e.detail};
99
- console.log(store);
100
- nodes.update((nodes) => {
101
- console.log({v: nodes});
102
- const node = {
103
- type: meta.type,
104
- data: {
105
- meta: meta,
106
- title: meta.name,
107
- params: Object.fromEntries(
108
- Object.values(meta.params).map((p) => [p.name, p.default])),
109
- },
110
- };
111
- node.position = screenToFlowPosition({x: nodeSearchSettings.pos.x, y: nodeSearchSettings.pos.y});
112
- const title = node.data.title;
113
- let i = 1;
 
114
  node.id = `${title} ${i}`;
115
- console.log({nodes})
116
- while (nodes.find((x) => x.id === node.id)) {
117
- i += 1;
118
- node.id = `${title} ${i}`;
119
- }
120
- node.parentId = nodeSearchSettings.parentId;
121
- if (node.parentId) {
122
- node.extent = 'parent';
123
- const parent = nodes.find((x) => x.id === node.parentId);
124
- node.position = { x: node.position.x - parent.position.x, y: node.position.y - parent.position.y };
125
- }
126
- return [...nodes, node];
127
- });
128
  closeNodeSearch();
129
  }
130
  const catalog = useQuery(['catalog'], async () => {
@@ -152,19 +132,29 @@
152
  parentId: node.id,
153
  };
154
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  $: parentDir = path.split('/').slice(0, -1).join('/');
156
- $: console.log($store);
157
- // <br>{JSON.stringify($store)}
158
- // <br>{JSON.stringify($store?.workspace)}
159
- // <br>{JSON.stringify($store?.workspace?.nodes)}
160
- // <br>{JSON.stringify($store?.workspace?.nodes?.value)}
161
- // <br>{$store.workspace?.nodes?.toArray()}
162
- // <br>{$store.workspace?.nodes?.length}
163
-
164
  </script>
165
 
166
  <div class="page">
167
- <br>{JSON.stringify($store)}
168
  {#if $store.workspace !== undefined}
169
  <div class="top-bar">
170
  <div class="ws-name">
@@ -176,7 +166,6 @@
176
  options={Object.keys($catalog.data || {})}
177
  value={$store.workspace.env}
178
  onChange={(env) => {
179
- console.log('env change', env);
180
  $store.workspace.env = env;
181
  }}
182
  />
@@ -189,6 +178,8 @@
189
  <SvelteFlow {nodes} {edges} {nodeTypes} fitView
190
  on:paneclick={toggleNodeSearch}
191
  on:nodeclick={nodeClick}
 
 
192
  proOptions={{ hideAttribution: true }}
193
  maxZoom={3}
194
  minZoom={0.3}
 
1
  <script lang="ts">
2
+ import { setContext } from 'svelte';
3
+ import { writable } from 'svelte/store';
4
  import {
5
  SvelteFlow,
6
  Controls,
7
  MiniMap,
8
  MarkerType,
9
  useSvelteFlow,
10
+ useUpdateNodeInternals,
11
  type XYPosition,
12
  type Node,
13
  type Edge,
 
30
  import { syncedStore, getYjsDoc } from "@syncedstore/core";
31
  import { svelteSyncedStore } from "@syncedstore/svelte";
32
  import { WebsocketProvider } from "y-websocket";
33
+ const updateNodeInternals = useUpdateNodeInternals();
34
 
35
  function getCRDTStore(path) {
36
  const sstore = syncedStore({ workspace: {} });
37
  const doc = getYjsDoc(sstore);
38
  const wsProvider = new WebsocketProvider("ws://localhost:8000/ws/crdt", path, doc);
 
 
 
39
  return {store: svelteSyncedStore(sstore), sstore, doc};
40
  }
41
  $: connection = getCRDTStore(path);
42
  $: store = connection.store;
43
+ $: store.subscribe((value) => {
44
+ if (!value?.workspace?.edges) return;
45
+ $nodes = [...value.workspace.nodes];
46
+ $edges = [...value.workspace.edges];
47
+ updateNodeInternals();
48
+ });
49
+ $: setContext('LynxKite store', store);
50
 
51
  export let path = '';
52
 
 
61
  area: NodeWithArea,
62
  };
63
 
64
+ const nodes = writable<Node[]>([]);
65
+ const edges = writable<Edge[]>([]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  function closeNodeSearch() {
68
  nodeSearchSettings = undefined;
 
80
  }
81
  function addNode(e) {
82
  const meta = {...e.detail};
83
+ const node = {
84
+ type: meta.type,
85
+ data: {
86
+ meta: meta,
87
+ title: meta.name,
88
+ params: Object.fromEntries(
89
+ Object.values(meta.params).map((p) => [p.name, p.default])),
90
+ },
91
+ };
92
+ node.position = screenToFlowPosition({x: nodeSearchSettings.pos.x, y: nodeSearchSettings.pos.y});
93
+ const title = node.data.title;
94
+ let i = 1;
95
+ node.id = `${title} ${i}`;
96
+ const nodes = $store.workspace.nodes;
97
+ while (nodes.find((x) => x.id === node.id)) {
98
+ i += 1;
99
  node.id = `${title} ${i}`;
100
+ }
101
+ node.parentId = nodeSearchSettings.parentId;
102
+ if (node.parentId) {
103
+ node.extent = 'parent';
104
+ const parent = nodes.find((x) => x.id === node.parentId);
105
+ node.position = { x: node.position.x - parent.position.x, y: node.position.y - parent.position.y };
106
+ }
107
+ nodes.push(node);
 
 
 
 
 
108
  closeNodeSearch();
109
  }
110
  const catalog = useQuery(['catalog'], async () => {
 
132
  parentId: node.id,
133
  };
134
  }
135
+ function onConnect(params: Connection) {
136
+ const edge = {
137
+ id: `${params.source} ${params.target}`,
138
+ source: params.source,
139
+ target: params.target,
140
+ };
141
+ $store.workspace.edges.push(edge);
142
+ }
143
+ function onDelete(params) {
144
+ const { nodes, edges } = params;
145
+ for (const node of nodes) {
146
+ const index = $store.workspace.nodes.findIndex((x) => x.id === node.id);
147
+ if (index !== -1) $store.workspace.nodes.splice(index, 1);
148
+ }
149
+ for (const edge of edges) {
150
+ const index = $store.workspace.edges.findIndex((x) => x.id === edge.id);
151
+ if (index !== -1) $store.workspace.edges.splice(index, 1);
152
+ }
153
+ }
154
  $: parentDir = path.split('/').slice(0, -1).join('/');
 
 
 
 
 
 
 
 
155
  </script>
156
 
157
  <div class="page">
 
158
  {#if $store.workspace !== undefined}
159
  <div class="top-bar">
160
  <div class="ws-name">
 
166
  options={Object.keys($catalog.data || {})}
167
  value={$store.workspace.env}
168
  onChange={(env) => {
 
169
  $store.workspace.env = env;
170
  }}
171
  />
 
178
  <SvelteFlow {nodes} {edges} {nodeTypes} fitView
179
  on:paneclick={toggleNodeSearch}
180
  on:nodeclick={nodeClick}
181
+ onconnect={onConnect}
182
+ ondelete={onDelete}
183
  proOptions={{ hideAttribution: true }}
184
  maxZoom={3}
185
  minZoom={0.3}
web/src/LynxKiteNode.svelte CHANGED
@@ -1,4 +1,5 @@
1
  <script lang="ts">
 
2
  import { Handle, useSvelteFlow, useUpdateNodeInternals, type NodeProps, NodeResizeControl } from '@xyflow/svelte';
3
  import ChevronDownRight from 'virtual:icons/tabler/chevron-down-right';
4
 
@@ -24,11 +25,14 @@
24
  export let positionAbsoluteY: $$Props['positionAbsoluteY'] = undefined; positionAbsoluteY;
25
  export let onToggle = () => {};
26
 
 
27
  $: expanded = !data.collapsed;
28
  function titleClicked() {
29
- updateNodeData(id, { collapsed: expanded });
30
- data = data;
31
  onToggle({ expanded });
 
 
32
  updateNodeInternals();
33
  }
34
  function asPx(n: number | undefined) {
 
1
  <script lang="ts">
2
+ import { getContext } from 'svelte';
3
  import { Handle, useSvelteFlow, useUpdateNodeInternals, type NodeProps, NodeResizeControl } from '@xyflow/svelte';
4
  import ChevronDownRight from 'virtual:icons/tabler/chevron-down-right';
5
 
 
25
  export let positionAbsoluteY: $$Props['positionAbsoluteY'] = undefined; positionAbsoluteY;
26
  export let onToggle = () => {};
27
 
28
+ $: store = getContext('LynxKite store');
29
  $: expanded = !data.collapsed;
30
  function titleClicked() {
31
+ const i = $store.workspace.nodes.findIndex((n) => n.id === id);
32
+ $store.workspace.nodes[i].data.collapsed = expanded;
33
  onToggle({ expanded });
34
+ // Trigger update.
35
+ data = data;
36
  updateNodeInternals();
37
  }
38
  function asPx(n: number | undefined) {
web/src/NodeWithParams.svelte CHANGED
@@ -1,12 +1,20 @@
1
  <script lang="ts">
2
- import { type NodeProps, useSvelteFlow } from '@xyflow/svelte';
 
3
  import LynxKiteNode from './LynxKiteNode.svelte';
4
  import NodeParameter from './NodeParameter.svelte';
5
  type $$Props = NodeProps;
6
  export let id: $$Props['id'];
7
  export let data: $$Props['data'];
8
  const { updateNodeData } = useSvelteFlow();
 
9
  $: metaParams = data.meta?.params;
 
 
 
 
 
 
10
  </script>
11
 
12
  <LynxKiteNode {...$$props}>
@@ -15,7 +23,7 @@
15
  {name}
16
  {value}
17
  meta={metaParams?.[name]}
18
- onChange={(newValue) => updateNodeData(id, { params: { ...data.params, [name]: newValue } })}
19
  />
20
  {/each}
21
  <slot />
 
1
  <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import { type NodeProps, useSvelteFlow, useUpdateNodeInternals } from '@xyflow/svelte';
4
  import LynxKiteNode from './LynxKiteNode.svelte';
5
  import NodeParameter from './NodeParameter.svelte';
6
  type $$Props = NodeProps;
7
  export let id: $$Props['id'];
8
  export let data: $$Props['data'];
9
  const { updateNodeData } = useSvelteFlow();
10
+ const updateNodeInternals = useUpdateNodeInternals();
11
  $: metaParams = data.meta?.params;
12
+ $: store = getContext('LynxKite store');
13
+ function setParam(name, newValue) {
14
+ const i = $store.workspace.nodes.findIndex((n) => n.id === id);
15
+ $store.workspace.nodes[i].data.params[name] = newValue;
16
+ updateNodeInternals();
17
+ }
18
  </script>
19
 
20
  <LynxKiteNode {...$$props}>
 
23
  {name}
24
  {value}
25
  meta={metaParams?.[name]}
26
+ onChange={(value) => setParam(name, value)}
27
  />
28
  {/each}
29
  <slot />