@dxos/app-graph 0.8.3-main.7f5a14c → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +22 -15
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +22 -15
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +22 -15
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts +9 -2
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Tree.d.ts +1 -1
- package/dist/types/src/stories/Tree.d.ts.map +1 -1
- package/package.json +20 -17
- package/src/graph-builder.ts +27 -17
- package/src/stories/EchoGraph.stories.tsx +250 -15
- package/src/stories/Tree.tsx +1 -1
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
-
import { Rx, useRxValue } from '@effect-rx/rx-react';
|
|
7
|
+
import { type Registry, RegistryContext, Rx, useRxValue } from '@effect-rx/rx-react';
|
|
8
8
|
import { Pause, Play, Plus, Timer } from '@phosphor-icons/react';
|
|
9
9
|
import { Option, pipe } from 'effect';
|
|
10
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
10
|
+
import React, { type PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
13
|
live,
|
|
@@ -20,17 +20,21 @@ import {
|
|
|
20
20
|
type Live,
|
|
21
21
|
Filter,
|
|
22
22
|
} from '@dxos/client/echo';
|
|
23
|
+
import { Obj, Type } from '@dxos/echo';
|
|
23
24
|
import { faker } from '@dxos/random';
|
|
24
25
|
import { type Client, useClient } from '@dxos/react-client';
|
|
25
26
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
26
27
|
import { Button, Input, Select } from '@dxos/react-ui';
|
|
28
|
+
import { Path, Tree } from '@dxos/react-ui-list';
|
|
29
|
+
import { Tabs } from '@dxos/react-ui-tabs';
|
|
27
30
|
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
28
31
|
import { withTheme } from '@dxos/storybook-utils';
|
|
29
|
-
import { safeParseInt } from '@dxos/util';
|
|
32
|
+
import { byPosition, isNonNullable, safeParseInt } from '@dxos/util';
|
|
30
33
|
|
|
31
|
-
import {
|
|
34
|
+
import { JsonTree } from './Tree';
|
|
32
35
|
import { type ExpandableGraph, ROOT_ID } from '../graph';
|
|
33
36
|
import { GraphBuilder, createExtension, rxFromObservable, rxFromSignal } from '../graph-builder';
|
|
37
|
+
import { type Node } from '../node';
|
|
34
38
|
import { rxFromQuery } from '../testing';
|
|
35
39
|
|
|
36
40
|
const DEFAULT_PERIOD = 500;
|
|
@@ -53,7 +57,7 @@ const actionWeights = {
|
|
|
53
57
|
[Action.RENAME_OBJECT]: 4,
|
|
54
58
|
};
|
|
55
59
|
|
|
56
|
-
const createGraph = (client: Client): ExpandableGraph => {
|
|
60
|
+
const createGraph = (client: Client, registry: Registry.Registry): ExpandableGraph => {
|
|
57
61
|
const spaceBuilderExtension = createExtension({
|
|
58
62
|
id: 'space',
|
|
59
63
|
connector: (node) =>
|
|
@@ -89,7 +93,8 @@ const createGraph = (client: Client): ExpandableGraph => {
|
|
|
89
93
|
if (!query) {
|
|
90
94
|
query = space.db.query(Query.type(Expando, { type: 'test' }));
|
|
91
95
|
}
|
|
92
|
-
|
|
96
|
+
const objects = get(rxFromQuery(query));
|
|
97
|
+
return objects.map((object) => ({
|
|
93
98
|
id: object.id,
|
|
94
99
|
type: 'dxos.org/type/test',
|
|
95
100
|
properties: { label: object.name },
|
|
@@ -102,12 +107,14 @@ const createGraph = (client: Client): ExpandableGraph => {
|
|
|
102
107
|
},
|
|
103
108
|
});
|
|
104
109
|
|
|
105
|
-
const graph = new GraphBuilder()
|
|
110
|
+
const graph = new GraphBuilder({ registry })
|
|
111
|
+
.addExtension(spaceBuilderExtension)
|
|
112
|
+
.addExtension(objectBuilderExtension).graph;
|
|
106
113
|
graph.onNodeChanged.on(({ id }) => {
|
|
107
|
-
console.log('onNodeChanged', { id });
|
|
108
114
|
graph.expand(id);
|
|
109
115
|
});
|
|
110
116
|
graph.expand(ROOT_ID);
|
|
117
|
+
(window as any).graph = graph;
|
|
111
118
|
|
|
112
119
|
return graph;
|
|
113
120
|
};
|
|
@@ -153,7 +160,7 @@ const runAction = async (client: Client, action: Action) => {
|
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
case Action.ADD_OBJECT:
|
|
156
|
-
getRandomSpace(client)?.db.add(
|
|
163
|
+
getRandomSpace(client)?.db.add(Obj.make(Type.Expando, { type: 'test', name: faker.commerce.productName() }));
|
|
157
164
|
break;
|
|
158
165
|
|
|
159
166
|
case Action.REMOVE_OBJECT: {
|
|
@@ -176,14 +183,12 @@ const runAction = async (client: Client, action: Action) => {
|
|
|
176
183
|
}
|
|
177
184
|
};
|
|
178
185
|
|
|
179
|
-
const
|
|
186
|
+
const Controls = ({ children }: PropsWithChildren) => {
|
|
180
187
|
const [generating, setGenerating] = useState(false);
|
|
181
188
|
const [actionInterval, setActionInterval] = useState(String(DEFAULT_PERIOD));
|
|
182
189
|
const [action, setAction] = useState<Action>();
|
|
183
190
|
|
|
184
191
|
const client = useClient();
|
|
185
|
-
const graph = useMemo(() => createGraph(client), [client]);
|
|
186
|
-
const data = useRxValue(graph.json());
|
|
187
192
|
|
|
188
193
|
useEffect(() => {
|
|
189
194
|
if (!generating) {
|
|
@@ -235,14 +240,13 @@ const DefaultStory = () => {
|
|
|
235
240
|
</Select.Portal>
|
|
236
241
|
</Select.Root>
|
|
237
242
|
</div>
|
|
238
|
-
{
|
|
243
|
+
{children}
|
|
239
244
|
</>
|
|
240
245
|
);
|
|
241
246
|
};
|
|
242
247
|
|
|
243
248
|
export default {
|
|
244
249
|
title: 'sdk/app-graph/EchoGraph',
|
|
245
|
-
render: DefaultStory,
|
|
246
250
|
decorators: [
|
|
247
251
|
withTheme,
|
|
248
252
|
withClientProvider({
|
|
@@ -255,4 +259,235 @@ export default {
|
|
|
255
259
|
],
|
|
256
260
|
};
|
|
257
261
|
|
|
258
|
-
export const
|
|
262
|
+
export const JsonView = {
|
|
263
|
+
render: () => {
|
|
264
|
+
const client = useClient();
|
|
265
|
+
const registry = useContext(RegistryContext);
|
|
266
|
+
const graph = useMemo(() => createGraph(client, registry), [client, registry]);
|
|
267
|
+
const data = useRxValue(graph.json());
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<>
|
|
271
|
+
<Controls />
|
|
272
|
+
{data && <JsonTree data={data} />}
|
|
273
|
+
</>
|
|
274
|
+
);
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const TreeView = {
|
|
279
|
+
render: () => {
|
|
280
|
+
const client = useClient();
|
|
281
|
+
const registry = useContext(RegistryContext);
|
|
282
|
+
const graph = useMemo(() => createGraph(client, registry), [client, registry]);
|
|
283
|
+
const state = useMemo(() => new Map<string, Live<{ open: boolean; current: boolean }>>(), []);
|
|
284
|
+
|
|
285
|
+
const useItems = useCallback(
|
|
286
|
+
(node?: Node, options?: { disposition?: string; sort?: boolean }) => {
|
|
287
|
+
const connections = useRxValue(graph.connections(node?.id ?? ROOT_ID));
|
|
288
|
+
return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
|
|
289
|
+
},
|
|
290
|
+
[graph],
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const getProps = useCallback(
|
|
294
|
+
(node: Node, path: string[]) => {
|
|
295
|
+
const children = graph
|
|
296
|
+
.getConnections(node.id, 'outbound')
|
|
297
|
+
.map((n) => {
|
|
298
|
+
// Break cycles.
|
|
299
|
+
const nextPath = [...path, node.id];
|
|
300
|
+
return nextPath.includes(n.id) ? undefined : (n as Node);
|
|
301
|
+
})
|
|
302
|
+
.filter(isNonNullable) as Node[];
|
|
303
|
+
const parentOf =
|
|
304
|
+
children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
|
|
305
|
+
return {
|
|
306
|
+
id: node.id,
|
|
307
|
+
label: node.id,
|
|
308
|
+
icon: node.type === 'dxos.org/type/Space' ? 'ph--planet--regular' : 'ph--placeholder--regular',
|
|
309
|
+
parentOf,
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
[graph],
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const isOpen = useCallback(
|
|
316
|
+
(_path: string[]) => {
|
|
317
|
+
const path = Path.create(..._path);
|
|
318
|
+
const object = state.get(path) ?? live({ open: true, current: false });
|
|
319
|
+
if (!state.has(path)) {
|
|
320
|
+
state.set(path, object);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return object.open;
|
|
324
|
+
},
|
|
325
|
+
[state],
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const isCurrent = useCallback(
|
|
329
|
+
(_path: string[]) => {
|
|
330
|
+
const path = Path.create(..._path);
|
|
331
|
+
const object = state.get(path) ?? live({ open: false, current: false });
|
|
332
|
+
if (!state.has(path)) {
|
|
333
|
+
state.set(path, object);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return object.current;
|
|
337
|
+
},
|
|
338
|
+
[state],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const onOpenChange = useCallback(
|
|
342
|
+
({ path: _path, open }: { path: string[]; open: boolean }) => {
|
|
343
|
+
const path = Path.create(..._path);
|
|
344
|
+
const object = state.get(path);
|
|
345
|
+
object!.open = open;
|
|
346
|
+
},
|
|
347
|
+
[state],
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const onSelect = useCallback(
|
|
351
|
+
({ path: _path, current }: { path: string[]; current: boolean }) => {
|
|
352
|
+
const path = Path.create(..._path);
|
|
353
|
+
const object = state.get(path);
|
|
354
|
+
object!.current = current;
|
|
355
|
+
},
|
|
356
|
+
[state],
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<>
|
|
361
|
+
<Controls />
|
|
362
|
+
<Tree
|
|
363
|
+
id={ROOT_ID}
|
|
364
|
+
useItems={useItems}
|
|
365
|
+
getProps={getProps}
|
|
366
|
+
isOpen={isOpen}
|
|
367
|
+
isCurrent={isCurrent}
|
|
368
|
+
onOpenChange={onOpenChange}
|
|
369
|
+
onSelect={onSelect}
|
|
370
|
+
/>
|
|
371
|
+
</>
|
|
372
|
+
);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// TODO(wittjosiah): Remove.
|
|
377
|
+
export const TabTreeView = {
|
|
378
|
+
render: () => {
|
|
379
|
+
const client = useClient();
|
|
380
|
+
const registry = useContext(RegistryContext);
|
|
381
|
+
const graph = useMemo(() => createGraph(client, registry), [client, registry]);
|
|
382
|
+
const state = useMemo(() => new Map<string, Live<{ open: boolean; current: boolean }>>(), []);
|
|
383
|
+
|
|
384
|
+
const useItems = useCallback(
|
|
385
|
+
(node?: Node, options?: { disposition?: string; sort?: boolean }) => {
|
|
386
|
+
const connections = useRxValue(graph.connections(node?.id ?? ROOT_ID));
|
|
387
|
+
return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
|
|
388
|
+
},
|
|
389
|
+
[graph],
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const getProps = useCallback(
|
|
393
|
+
(node: Node, path: string[]) => {
|
|
394
|
+
const children = graph
|
|
395
|
+
.getConnections(node.id, 'outbound')
|
|
396
|
+
.map((n) => {
|
|
397
|
+
// Break cycles.
|
|
398
|
+
const nextPath = [...path, node.id];
|
|
399
|
+
return nextPath.includes(n.id) ? undefined : (n as Node);
|
|
400
|
+
})
|
|
401
|
+
.filter(isNonNullable) as Node[];
|
|
402
|
+
const parentOf =
|
|
403
|
+
children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
|
|
404
|
+
return {
|
|
405
|
+
id: node.id,
|
|
406
|
+
label: node.id,
|
|
407
|
+
icon: node.type === 'dxos.org/type/Space' ? 'ph--planet--regular' : 'ph--placeholder--regular',
|
|
408
|
+
parentOf,
|
|
409
|
+
};
|
|
410
|
+
},
|
|
411
|
+
[graph],
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
const isOpen = useCallback(
|
|
415
|
+
(_path: string[]) => {
|
|
416
|
+
const path = Path.create(..._path);
|
|
417
|
+
const object = state.get(path) ?? live({ open: true, current: false });
|
|
418
|
+
if (!state.has(path)) {
|
|
419
|
+
state.set(path, object);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return object.open;
|
|
423
|
+
},
|
|
424
|
+
[state],
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const isCurrent = useCallback(
|
|
428
|
+
(_path: string[]) => {
|
|
429
|
+
const path = Path.create(..._path);
|
|
430
|
+
const object = state.get(path) ?? live({ open: false, current: false });
|
|
431
|
+
if (!state.has(path)) {
|
|
432
|
+
state.set(path, object);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return object.current;
|
|
436
|
+
},
|
|
437
|
+
[state],
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const onOpenChange = useCallback(
|
|
441
|
+
({ path: _path, open }: { path: string[]; open: boolean }) => {
|
|
442
|
+
const path = Path.create(..._path);
|
|
443
|
+
const object = state.get(path);
|
|
444
|
+
object!.open = open;
|
|
445
|
+
},
|
|
446
|
+
[state],
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const onSelect = useCallback(
|
|
450
|
+
({ path: _path, current }: { path: string[]; current: boolean }) => {
|
|
451
|
+
const path = Path.create(..._path);
|
|
452
|
+
const object = state.get(path);
|
|
453
|
+
object!.current = current;
|
|
454
|
+
},
|
|
455
|
+
[state],
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const spaces = useItems(graph.root);
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<>
|
|
462
|
+
<Controls />
|
|
463
|
+
<Tabs.Root defaultValue={spaces[0].id}>
|
|
464
|
+
<Tabs.Tablist>
|
|
465
|
+
{spaces.map((space) => {
|
|
466
|
+
return (
|
|
467
|
+
<Tabs.Tab key={space.id} value={space.id}>
|
|
468
|
+
{space.id}
|
|
469
|
+
</Tabs.Tab>
|
|
470
|
+
);
|
|
471
|
+
})}
|
|
472
|
+
</Tabs.Tablist>
|
|
473
|
+
{spaces.map((space) => {
|
|
474
|
+
return (
|
|
475
|
+
<Tabs.Tabpanel key={space.id} value={space.id}>
|
|
476
|
+
<Tree
|
|
477
|
+
id={space.id}
|
|
478
|
+
root={space}
|
|
479
|
+
useItems={useItems}
|
|
480
|
+
getProps={getProps}
|
|
481
|
+
isOpen={isOpen}
|
|
482
|
+
isCurrent={isCurrent}
|
|
483
|
+
onOpenChange={onOpenChange}
|
|
484
|
+
onSelect={onSelect}
|
|
485
|
+
/>
|
|
486
|
+
</Tabs.Tabpanel>
|
|
487
|
+
);
|
|
488
|
+
})}
|
|
489
|
+
</Tabs.Root>
|
|
490
|
+
</>
|
|
491
|
+
);
|
|
492
|
+
},
|
|
493
|
+
};
|
package/src/stories/Tree.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { mx } from '@dxos/react-ui-theme';
|
|
|
8
8
|
|
|
9
9
|
// TODO(burdon): Copied form devtools.
|
|
10
10
|
|
|
11
|
-
export const
|
|
11
|
+
export const JsonTree: FC<{ data?: object }> = ({ data }) => {
|
|
12
12
|
return (
|
|
13
13
|
<div className='flex overflow-auto ml-2 border-l-4 border-blue-500'>
|
|
14
14
|
<Node data={data} root />
|