@dxos/app-graph 0.6.13 → 0.6.14-main.1366248
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 +97 -20
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +97 -20
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +885 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/types/src/graph-builder.d.ts +8 -2
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +11 -6
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/package.json +22 -20
- package/src/graph-builder.test.ts +41 -8
- package/src/graph-builder.ts +20 -2
- package/src/graph.test.ts +11 -9
- package/src/graph.ts +60 -14
- package/src/stories/EchoGraph.stories.tsx +45 -51
- package/src/stories/Tree.tsx +7 -7
package/src/graph.ts
CHANGED
|
@@ -7,9 +7,10 @@ import { batch, effect, untracked } from '@preact/signals-core';
|
|
|
7
7
|
import { asyncTimeout, Trigger } from '@dxos/async';
|
|
8
8
|
import { type ReactiveObject, create } from '@dxos/echo-schema';
|
|
9
9
|
import { invariant } from '@dxos/invariant';
|
|
10
|
+
import { log } from '@dxos/log';
|
|
10
11
|
import { nonNullable } from '@dxos/util';
|
|
11
12
|
|
|
12
|
-
import { type Relation, type Node, type NodeArg, type NodeFilter, isActionLike } from './node';
|
|
13
|
+
import { type Relation, type Node, type NodeArg, type NodeFilter, isActionLike, actionGroupSymbol } from './node';
|
|
13
14
|
|
|
14
15
|
const graphSymbol = Symbol('graph');
|
|
15
16
|
type DeepWriteable<T> = { -readonly [K in keyof T]: DeepWriteable<T[K]> };
|
|
@@ -64,6 +65,15 @@ export type GraphTraversalOptions = {
|
|
|
64
65
|
expansion?: boolean;
|
|
65
66
|
};
|
|
66
67
|
|
|
68
|
+
export type GraphParams = {
|
|
69
|
+
// TODO(wittjosiah): Make data optional instead of omitting.
|
|
70
|
+
nodes?: Omit<Node, 'data'>[];
|
|
71
|
+
edges?: Record<string, string[]>;
|
|
72
|
+
onInitialNode?: Graph['_onInitialNode'];
|
|
73
|
+
onInitialNodes?: Graph['_onInitialNodes'];
|
|
74
|
+
onRemoveNode?: Graph['_onRemoveNode'];
|
|
75
|
+
};
|
|
76
|
+
|
|
67
77
|
/**
|
|
68
78
|
* The Graph represents the structure of the application constructed via plugins.
|
|
69
79
|
*/
|
|
@@ -85,20 +95,38 @@ export class Graph {
|
|
|
85
95
|
*/
|
|
86
96
|
readonly _edges: Record<string, ReactiveObject<{ inbound: string[]; outbound: string[] }>> = {};
|
|
87
97
|
|
|
88
|
-
constructor({
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
constructor({ nodes, edges, onInitialNode, onInitialNodes, onRemoveNode }: GraphParams = {}) {
|
|
99
|
+
this._nodes[ROOT_ID] = this._constructNode({ id: ROOT_ID, type: ROOT_TYPE, properties: {}, data: null });
|
|
100
|
+
if (nodes) {
|
|
101
|
+
nodes.forEach((node) => {
|
|
102
|
+
if (node.type === ACTION_TYPE) {
|
|
103
|
+
this._addNode({ ...node, data: () => log.warn('Pickled action invocation') });
|
|
104
|
+
} else if (node.type === ACTION_GROUP_TYPE) {
|
|
105
|
+
this._addNode({ ...node, data: actionGroupSymbol });
|
|
106
|
+
} else {
|
|
107
|
+
this._addNode(node);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this._edges[ROOT_ID] = create({ inbound: [], outbound: [] });
|
|
113
|
+
if (edges) {
|
|
114
|
+
Object.entries(edges).forEach(([source, edges]) => {
|
|
115
|
+
edges.forEach((target) => {
|
|
116
|
+
this._addEdge({ source, target });
|
|
117
|
+
});
|
|
118
|
+
this._sortEdges(source, 'outbound', edges);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
97
122
|
this._onInitialNode = onInitialNode;
|
|
98
123
|
this._onInitialNodes = onInitialNodes;
|
|
99
124
|
this._onRemoveNode = onRemoveNode;
|
|
100
|
-
|
|
101
|
-
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static from(pickle: string, options: Omit<GraphParams, 'nodes' | 'edges'> = {}) {
|
|
128
|
+
const { nodes, edges } = JSON.parse(pickle);
|
|
129
|
+
return new Graph({ nodes, edges, ...options });
|
|
102
130
|
}
|
|
103
131
|
|
|
104
132
|
/**
|
|
@@ -138,15 +166,33 @@ export class Graph {
|
|
|
138
166
|
return toJSON(root);
|
|
139
167
|
}
|
|
140
168
|
|
|
169
|
+
pickle() {
|
|
170
|
+
const nodes = Object.values(this._nodes).map((node) => {
|
|
171
|
+
return {
|
|
172
|
+
id: node.id,
|
|
173
|
+
type: node.type,
|
|
174
|
+
properties: node.properties,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const edges = Object.fromEntries(
|
|
179
|
+
Object.entries(this._edges)
|
|
180
|
+
.map(([id, { outbound }]): [string, string[]] => [id, outbound])
|
|
181
|
+
.toSorted(([a], [b]) => a.localeCompare(b)),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return JSON.stringify({ nodes, edges });
|
|
185
|
+
}
|
|
186
|
+
|
|
141
187
|
/**
|
|
142
188
|
* Find the node with the given id in the graph.
|
|
143
189
|
*
|
|
144
190
|
* If a node is not found within the graph and an `onInitialNode` callback is provided,
|
|
145
191
|
* it is called with the id and type of the node, potentially initializing the node.
|
|
146
192
|
*/
|
|
147
|
-
findNode(id: string): Node | undefined {
|
|
193
|
+
findNode(id: string, expansion = true): Node | undefined {
|
|
148
194
|
const existingNode = this._nodes[id];
|
|
149
|
-
if (!existingNode) {
|
|
195
|
+
if (!existingNode && expansion) {
|
|
150
196
|
void this._onInitialNode?.(id);
|
|
151
197
|
}
|
|
152
198
|
|
|
@@ -7,21 +7,21 @@ import '@dxos-theme';
|
|
|
7
7
|
import { Pause, Play, Plus, Timer } from '@phosphor-icons/react';
|
|
8
8
|
import React, { useEffect, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import { type EchoReactiveObject, create } from '@dxos/echo-schema';
|
|
11
|
-
import { registerSignalRuntime } from '@dxos/echo-signals';
|
|
12
|
-
import { faker } from '@dxos/random';
|
|
13
|
-
import { type Client, useClient } from '@dxos/react-client';
|
|
14
10
|
import {
|
|
11
|
+
create,
|
|
12
|
+
type Echo,
|
|
13
|
+
type EchoReactiveObject,
|
|
14
|
+
type FilterSource,
|
|
15
15
|
type Space,
|
|
16
16
|
SpaceState,
|
|
17
17
|
isSpace,
|
|
18
|
-
type Echo,
|
|
19
|
-
type FilterSource,
|
|
20
18
|
type QueryOptions,
|
|
21
19
|
type Query,
|
|
22
|
-
} from '@dxos/
|
|
20
|
+
} from '@dxos/client/echo';
|
|
21
|
+
import { faker } from '@dxos/random';
|
|
22
|
+
import { type Client, useClient } from '@dxos/react-client';
|
|
23
23
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
24
|
-
import { Button,
|
|
24
|
+
import { Button, Input, Select, useAsyncEffect } from '@dxos/react-ui';
|
|
25
25
|
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
26
26
|
import { withTheme } from '@dxos/storybook-utils';
|
|
27
27
|
import { safeParseInt } from '@dxos/util';
|
|
@@ -35,8 +35,6 @@ const DEFAULT_PERIOD = 500;
|
|
|
35
35
|
|
|
36
36
|
const EMPTY_ARRAY: never[] = [];
|
|
37
37
|
|
|
38
|
-
registerSignalRuntime();
|
|
39
|
-
|
|
40
38
|
enum Action {
|
|
41
39
|
CREATE_SPACE = 'CREATE_SPACE',
|
|
42
40
|
CLOSE_SPACE = 'CLOSE_SPACE',
|
|
@@ -185,7 +183,7 @@ const runAction = async (client: Client, action: Action) => {
|
|
|
185
183
|
}
|
|
186
184
|
};
|
|
187
185
|
|
|
188
|
-
const
|
|
186
|
+
const DefaultStory = () => {
|
|
189
187
|
const [generating, setGenerating] = useState(false);
|
|
190
188
|
const [actionInterval, setActionInterval] = useState(String(DEFAULT_PERIOD));
|
|
191
189
|
const [action, setAction] = useState<Action>();
|
|
@@ -211,44 +209,40 @@ const Story = () => {
|
|
|
211
209
|
return (
|
|
212
210
|
<>
|
|
213
211
|
<div className='flex shrink-0 p-2 space-x-2'>
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
</Select.Content>
|
|
249
|
-
</Select.Portal>
|
|
250
|
-
</Select.Root>
|
|
251
|
-
</DensityProvider>
|
|
212
|
+
<Button onClick={() => setGenerating((generating) => !generating)}>{generating ? <Pause /> : <Play />}</Button>
|
|
213
|
+
<div className='relative' title='mutation period'>
|
|
214
|
+
<Input.Root>
|
|
215
|
+
<Input.TextInput
|
|
216
|
+
autoComplete='off'
|
|
217
|
+
size={5}
|
|
218
|
+
classNames='w-[100px] text-right pie-[22px]'
|
|
219
|
+
placeholder='Interval'
|
|
220
|
+
value={actionInterval}
|
|
221
|
+
onChange={({ target: { value } }) => setActionInterval(value)}
|
|
222
|
+
/>
|
|
223
|
+
</Input.Root>
|
|
224
|
+
<Timer className={mx('absolute inline-end-1 block-start-1 mt-[6px]', getSize(3))} />
|
|
225
|
+
</div>
|
|
226
|
+
<Button onClick={() => action && runAction(client, action)}>
|
|
227
|
+
<Plus />
|
|
228
|
+
</Button>
|
|
229
|
+
<Select.Root value={action?.toString()} onValueChange={(action) => setAction(action as unknown as Action)}>
|
|
230
|
+
<Select.TriggerButton placeholder='Select value' />
|
|
231
|
+
<Select.Portal>
|
|
232
|
+
<Select.Content>
|
|
233
|
+
<Select.ScrollUpButton />
|
|
234
|
+
<Select.Viewport>
|
|
235
|
+
{Object.keys(actionWeights).map((action) => (
|
|
236
|
+
<Select.Option key={action} value={action}>
|
|
237
|
+
{action}
|
|
238
|
+
</Select.Option>
|
|
239
|
+
))}
|
|
240
|
+
</Select.Viewport>
|
|
241
|
+
<Select.ScrollDownButton />
|
|
242
|
+
<Select.Arrow />
|
|
243
|
+
</Select.Content>
|
|
244
|
+
</Select.Portal>
|
|
245
|
+
</Select.Root>
|
|
252
246
|
</div>
|
|
253
247
|
{graph && <Tree data={graph.toJSON()} />}
|
|
254
248
|
</>
|
|
@@ -256,7 +250,8 @@ const Story = () => {
|
|
|
256
250
|
};
|
|
257
251
|
|
|
258
252
|
export default {
|
|
259
|
-
title: 'app-graph/EchoGraph',
|
|
253
|
+
title: 'sdk/app-graph/EchoGraph',
|
|
254
|
+
render: DefaultStory,
|
|
260
255
|
decorators: [
|
|
261
256
|
withTheme,
|
|
262
257
|
withClientProvider({
|
|
@@ -267,7 +262,6 @@ export default {
|
|
|
267
262
|
},
|
|
268
263
|
}),
|
|
269
264
|
],
|
|
270
|
-
render: Story,
|
|
271
265
|
};
|
|
272
266
|
|
|
273
267
|
export const Default = {};
|
package/src/stories/Tree.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { mx } from '@dxos/react-ui-theme';
|
|
|
10
10
|
|
|
11
11
|
export const Tree: FC<{ data?: object }> = ({ data }) => {
|
|
12
12
|
return (
|
|
13
|
-
<div className='flex overflow-auto ml-2 border-l-
|
|
13
|
+
<div className='flex overflow-auto ml-2 border-l-4 border-blue-500'>
|
|
14
14
|
<Node data={data} root />
|
|
15
15
|
</div>
|
|
16
16
|
);
|
|
@@ -23,18 +23,18 @@ export const Node: FC<{ data?: any; root?: boolean }> = ({ data, root }) => {
|
|
|
23
23
|
|
|
24
24
|
if (Array.isArray(data)) {
|
|
25
25
|
return (
|
|
26
|
-
<div className='flex flex-col space-y-
|
|
26
|
+
<div className='flex flex-col space-y-1'>
|
|
27
27
|
{data.map((value, index) => (
|
|
28
|
-
<KeyValue key={index} label={String(index)} data={value} className='
|
|
28
|
+
<KeyValue key={index} label={String(index)} data={value} className='' />
|
|
29
29
|
))}
|
|
30
30
|
</div>
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<div className='flex flex-col space-y-
|
|
35
|
+
<div className='flex flex-col space-y-1'>
|
|
36
36
|
{Object.entries(data).map(([key, value]) => (
|
|
37
|
-
<KeyValue key={key} label={key} data={value} className='
|
|
37
|
+
<KeyValue key={key} label={key} data={value} className='' />
|
|
38
38
|
))}
|
|
39
39
|
</div>
|
|
40
40
|
);
|
|
@@ -49,7 +49,7 @@ export const KeyValue: FC<{ label: string; data?: any; className?: string }> = (
|
|
|
49
49
|
return (
|
|
50
50
|
<div className='flex'>
|
|
51
51
|
<Box
|
|
52
|
-
className={mx('border-blue-
|
|
52
|
+
className={mx('border-blue-500 text-sm select-none cursor-pointer min-w-[6rem]', className)}
|
|
53
53
|
onClick={() => setOpen((open) => !open)}
|
|
54
54
|
>
|
|
55
55
|
{label}
|
|
@@ -61,7 +61,7 @@ export const KeyValue: FC<{ label: string; data?: any; className?: string }> = (
|
|
|
61
61
|
|
|
62
62
|
const Scalar: FC<{ value: any }> = ({ value }) => {
|
|
63
63
|
return (
|
|
64
|
-
<Box className='
|
|
64
|
+
<Box className='border-green-500 text-sm font-thin'>
|
|
65
65
|
{(value === undefined && 'undefined') ||
|
|
66
66
|
(value === null && 'null') ||
|
|
67
67
|
(typeof value === 'string' && value) ||
|