@dxos/graph 0.8.2-staging.7ac8446 → 0.8.3-main.672df60
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 +139 -98
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +137 -94
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +139 -98
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model.d.ts +32 -37
- package/dist/types/src/model.d.ts.map +1 -1
- package/dist/types/src/selection.d.ts +22 -0
- package/dist/types/src/selection.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +35 -36
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +0 -8
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +19 -18
- package/src/index.ts +1 -0
- package/src/model.test.ts +78 -10
- package/src/model.ts +72 -21
- package/src/selection.ts +70 -0
- package/src/types.ts +39 -29
- package/src/util.ts +0 -47
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
/**
|
|
3
|
+
* Reactive selection model.
|
|
4
|
+
*/
|
|
5
|
+
export declare class SelectionModel {
|
|
6
|
+
private readonly _singleSelect;
|
|
7
|
+
private readonly _selected;
|
|
8
|
+
private readonly _selectedIds;
|
|
9
|
+
constructor(_singleSelect?: boolean);
|
|
10
|
+
toJSON(): {
|
|
11
|
+
selected: string[];
|
|
12
|
+
};
|
|
13
|
+
get size(): number;
|
|
14
|
+
get selected(): ReadonlySignal<string[]>;
|
|
15
|
+
contains(id: string): boolean;
|
|
16
|
+
clear(): void;
|
|
17
|
+
add(id: string): void;
|
|
18
|
+
remove(id: string): void;
|
|
19
|
+
setSelected(ids: string[], shift?: boolean): void;
|
|
20
|
+
toggleSelected(ids: string[], shift?: boolean): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAoB,KAAK,cAAc,EAAe,MAAM,sBAAsB,CAAC;AAI1F;;GAEG;AACH,qBAAa,cAAc;IAIb,OAAO,CAAC,QAAQ,CAAC,aAAa;IAH1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkD;IAC5E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6D;gBAE7D,aAAa,GAAE,OAAe;IAE3D,MAAM,IAAI;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE;IAMhC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,QAAQ,IAAI,cAAc,CAAC,MAAM,EAAE,CAAC,CAEvC;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI7B,KAAK,IAAI,IAAI;IAIb,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAOrB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAOxB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,UAAQ,GAAG,IAAI;IAI/C,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,UAAQ,GAAG,IAAI;CAYnD"}
|
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
2
|
import { type Specialize } from '@dxos/util';
|
|
3
|
-
export declare const BaseGraphNode:
|
|
4
|
-
id: typeof
|
|
5
|
-
type:
|
|
6
|
-
data:
|
|
3
|
+
export declare const BaseGraphNode: Schema.Struct<{
|
|
4
|
+
id: typeof Schema.String;
|
|
5
|
+
type: Schema.optional<typeof Schema.String>;
|
|
6
|
+
data: Schema.optional<typeof Schema.Any>;
|
|
7
7
|
}>;
|
|
8
8
|
/** Raw base type. */
|
|
9
|
-
export type BaseGraphNode =
|
|
9
|
+
export type BaseGraphNode = Schema.Schema.Type<typeof BaseGraphNode>;
|
|
10
10
|
/** Typed node data. */
|
|
11
|
-
|
|
11
|
+
type GraphNode<Data = any, Optional extends boolean = false> = Specialize<BaseGraphNode, Optional extends true ? {
|
|
12
12
|
data?: Data;
|
|
13
13
|
} : {
|
|
14
14
|
data: Data;
|
|
15
15
|
}>;
|
|
16
16
|
export declare namespace GraphNode {
|
|
17
|
-
type
|
|
17
|
+
type Any = GraphNode<any, true>;
|
|
18
|
+
type Optional<Data = any> = GraphNode<Data, true>;
|
|
19
|
+
type Required<Data = any> = GraphNode<Data, false>;
|
|
18
20
|
}
|
|
19
|
-
export declare const BaseGraphEdge:
|
|
20
|
-
id: typeof
|
|
21
|
-
type:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
export declare const BaseGraphEdge: Schema.Struct<{
|
|
22
|
+
id: typeof Schema.String;
|
|
23
|
+
type: Schema.optional<typeof Schema.String>;
|
|
24
|
+
source: typeof Schema.String;
|
|
25
|
+
target: typeof Schema.String;
|
|
26
|
+
data: Schema.optional<typeof Schema.Any>;
|
|
25
27
|
}>;
|
|
26
28
|
/** Raw base type. */
|
|
27
|
-
export type BaseGraphEdge =
|
|
29
|
+
export type BaseGraphEdge = Schema.Schema.Type<typeof BaseGraphEdge>;
|
|
28
30
|
/** Typed edge data. */
|
|
29
31
|
export type GraphEdge<Data = any, Optional extends boolean = false> = Specialize<BaseGraphEdge, Optional extends true ? {
|
|
30
32
|
data?: Data;
|
|
@@ -32,28 +34,25 @@ export type GraphEdge<Data = any, Optional extends boolean = false> = Specialize
|
|
|
32
34
|
data: Data;
|
|
33
35
|
}>;
|
|
34
36
|
export declare namespace GraphEdge {
|
|
35
|
-
type
|
|
37
|
+
type Any = GraphEdge<any, true>;
|
|
38
|
+
type Optional<Data = any> = GraphEdge<Data, true>;
|
|
39
|
+
type Required<Data = any> = GraphEdge<Data, false>;
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
edges: S.mutable<S.Array$<S.Struct<{
|
|
51
|
-
id: typeof S.String;
|
|
52
|
-
type: S.optional<typeof S.String>;
|
|
53
|
-
data: S.optional<typeof S.Any>;
|
|
54
|
-
source: typeof S.String;
|
|
55
|
-
target: typeof S.String;
|
|
41
|
+
export declare const Graph: Schema.Struct<{
|
|
42
|
+
id: Schema.optional<typeof Schema.String>;
|
|
43
|
+
nodes: Schema.mutable<Schema.Array$<Schema.Struct<{
|
|
44
|
+
id: typeof Schema.String;
|
|
45
|
+
type: Schema.optional<typeof Schema.String>;
|
|
46
|
+
data: Schema.optional<typeof Schema.Any>;
|
|
47
|
+
}>>>;
|
|
48
|
+
edges: Schema.mutable<Schema.Array$<Schema.Struct<{
|
|
49
|
+
id: typeof Schema.String;
|
|
50
|
+
type: Schema.optional<typeof Schema.String>;
|
|
51
|
+
source: typeof Schema.String;
|
|
52
|
+
target: typeof Schema.String;
|
|
53
|
+
data: Schema.optional<typeof Schema.Any>;
|
|
56
54
|
}>>>;
|
|
57
55
|
}>;
|
|
58
|
-
export type Graph =
|
|
56
|
+
export type Graph = Schema.Schema.Type<typeof Graph>;
|
|
57
|
+
export {};
|
|
59
58
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAO7C,eAAO,MAAM,aAAa;;;;EAIxB,CAAC;AAEH,qBAAqB;AACrB,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAErE,uBAAuB;AACvB,KAAK,SAAS,CAAC,IAAI,GAAG,GAAG,EAAE,QAAQ,SAAS,OAAO,GAAG,KAAK,IAAI,UAAU,CACvE,aAAa,EACb,QAAQ,SAAS,IAAI,GAAG;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CACzD,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,SAAS,CAAC;IACjC,KAAY,GAAG,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzD,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;CAC3D;AAMD,eAAO,MAAM,aAAa;;;;;;EAMxB,CAAC;AAEH,qBAAqB;AACrB,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAErE,uBAAuB;AACvB,MAAM,MAAM,SAAS,CAAC,IAAI,GAAG,GAAG,EAAE,QAAQ,SAAS,OAAO,GAAG,KAAK,IAAI,UAAU,CAC9E,aAAa,EACb,QAAQ,SAAS,IAAI,GAAG;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CACzD,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,SAAS,CAAC;IACjC,KAAY,GAAG,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzD,KAAY,QAAQ,CAAC,IAAI,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;CAC3D;AAMD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;EAIhB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC"}
|
package/dist/types/src/util.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { type ReactiveEchoObject } from '@dxos/echo-db';
|
|
2
|
-
import { GraphModel } from './model';
|
|
3
|
-
import { type GraphNode } from './types';
|
|
4
1
|
type EdgeMeta = {
|
|
5
2
|
source: string;
|
|
6
3
|
target: string;
|
|
@@ -8,10 +5,5 @@ type EdgeMeta = {
|
|
|
8
5
|
};
|
|
9
6
|
export declare const createEdgeId: ({ source, target, relation }: EdgeMeta) => string;
|
|
10
7
|
export declare const parseEdgeId: (id: string) => EdgeMeta;
|
|
11
|
-
/**
|
|
12
|
-
* Creates a new reactive graph from a set of ECHO objects.
|
|
13
|
-
* References are mapped onto graph edges.
|
|
14
|
-
*/
|
|
15
|
-
export declare const createGraph: (objects: ReactiveEchoObject<any>[]) => GraphModel<GraphNode<ReactiveEchoObject<any>>>;
|
|
16
8
|
export {};
|
|
17
9
|
//# sourceMappingURL=util.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AASA,KAAK,QAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,eAAO,MAAM,YAAY,GAAI,8BAA8B,QAAQ,WAIlE,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,IAAI,MAAM,KAAG,QAIxC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"5.
|
|
1
|
+
{"version":"5.8.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/graph",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3-main.672df60",
|
|
4
4
|
"description": "Low-level graph API",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -18,11 +18,8 @@
|
|
|
18
18
|
"types": "dist/types/src/index.d.ts",
|
|
19
19
|
"typesVersions": {
|
|
20
20
|
"*": {
|
|
21
|
-
"meta": [
|
|
22
|
-
"dist/types/src/meta.d.ts"
|
|
23
|
-
],
|
|
24
21
|
"types": [
|
|
25
|
-
"dist/types/src/types
|
|
22
|
+
"dist/types/src/types.d.ts"
|
|
26
23
|
]
|
|
27
24
|
}
|
|
28
25
|
},
|
|
@@ -31,25 +28,29 @@
|
|
|
31
28
|
"src"
|
|
32
29
|
],
|
|
33
30
|
"dependencies": {
|
|
34
|
-
"@preact/signals-core": "^1.
|
|
31
|
+
"@preact/signals-core": "^1.9.0",
|
|
32
|
+
"effect": "3.14.21",
|
|
35
33
|
"lodash.defaultsdeep": "^4.6.1",
|
|
36
|
-
"@dxos/async": "0.8.
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/echo-schema": "0.8.
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
34
|
+
"@dxos/async": "0.8.3-main.672df60",
|
|
35
|
+
"@dxos/debug": "0.8.3-main.672df60",
|
|
36
|
+
"@dxos/echo-db": "0.8.3-main.672df60",
|
|
37
|
+
"@dxos/echo-schema": "0.8.3-main.672df60",
|
|
38
|
+
"@dxos/echo-signals": "0.8.3-main.672df60",
|
|
39
|
+
"@dxos/invariant": "0.8.3-main.672df60",
|
|
40
|
+
"@dxos/util": "0.8.3-main.672df60",
|
|
41
|
+
"@dxos/live-object": "0.8.3-main.672df60",
|
|
42
|
+
"@dxos/log": "0.8.3-main.672df60"
|
|
45
43
|
},
|
|
46
44
|
"devDependencies": {
|
|
47
45
|
"@antv/graphlib": "^2.0.4",
|
|
48
46
|
"@antv/layout": "^1.2.13",
|
|
49
47
|
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
50
|
-
"@dxos/echo-db": "0.8.
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
48
|
+
"@dxos/echo-db": "0.8.3-main.672df60",
|
|
49
|
+
"@dxos/echo-schema": "0.8.3-main.672df60",
|
|
50
|
+
"@dxos/random": "0.8.3-main.672df60"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"effect": "^3.13.3"
|
|
53
54
|
},
|
|
54
55
|
"publishConfig": {
|
|
55
56
|
"access": "public"
|
package/src/index.ts
CHANGED
package/src/model.test.ts
CHANGED
|
@@ -2,21 +2,27 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { effect } from '@preact/signals-core';
|
|
6
|
+
import { Schema } from 'effect';
|
|
5
7
|
import { describe, test } from 'vitest';
|
|
6
8
|
|
|
7
|
-
import {
|
|
9
|
+
import { Trigger } from '@dxos/async';
|
|
10
|
+
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
11
|
+
import { live } from '@dxos/live-object';
|
|
8
12
|
|
|
9
|
-
import { GraphModel } from './model';
|
|
10
|
-
import { BaseGraphNode, type GraphNode } from './types';
|
|
13
|
+
import { GraphModel, ReactiveGraphModel } from './model';
|
|
14
|
+
import { BaseGraphNode, type Graph, type GraphNode } from './types';
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
registerSignalsRuntime();
|
|
17
|
+
|
|
18
|
+
const TestNode = Schema.extend(
|
|
13
19
|
BaseGraphNode,
|
|
14
|
-
|
|
15
|
-
value:
|
|
20
|
+
Schema.Struct({
|
|
21
|
+
value: Schema.String,
|
|
16
22
|
}),
|
|
17
23
|
);
|
|
18
24
|
|
|
19
|
-
type TestNode =
|
|
25
|
+
type TestNode = Schema.Schema.Type<typeof TestNode>;
|
|
20
26
|
|
|
21
27
|
type TestData = { value: string };
|
|
22
28
|
|
|
@@ -25,7 +31,7 @@ describe('Graph', () => {
|
|
|
25
31
|
const graph = new GraphModel();
|
|
26
32
|
expect(graph.nodes).to.have.length(0);
|
|
27
33
|
expect(graph.edges).to.have.length(0);
|
|
28
|
-
expect(graph.toJSON()).to.deep.eq({ nodes:
|
|
34
|
+
expect(graph.toJSON()).to.deep.eq({ nodes: 0, edges: 0 });
|
|
29
35
|
});
|
|
30
36
|
|
|
31
37
|
test('extended', ({ expect }) => {
|
|
@@ -34,9 +40,71 @@ describe('Graph', () => {
|
|
|
34
40
|
expect(node.value.length).to.eq(4);
|
|
35
41
|
});
|
|
36
42
|
|
|
43
|
+
test('reactive', async ({ expect }) => {
|
|
44
|
+
const graph = new GraphModel(live({ nodes: [], edges: [] }));
|
|
45
|
+
|
|
46
|
+
const done = new Trigger<Graph>();
|
|
47
|
+
|
|
48
|
+
// NOTE: Requires `registerSignalsRuntime` to be called.
|
|
49
|
+
const unsubscribe = effect(() => {
|
|
50
|
+
if (graph.edges.length === 2) {
|
|
51
|
+
done.wake(graph.graph);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
graph.builder.addNode({ id: 'node-1' });
|
|
57
|
+
graph.builder.addNode({ id: 'node-2' });
|
|
58
|
+
graph.builder.addNode({ id: 'node-3' });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
graph.builder.addEdge({ source: 'node-1', target: 'node-2' });
|
|
63
|
+
graph.builder.addEdge({ source: 'node-2', target: 'node-3' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
const graph = await done.wait();
|
|
68
|
+
expect(graph.nodes).to.have.length(3);
|
|
69
|
+
expect(graph.edges).to.have.length(2);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
unsubscribe();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('reactive model', async ({ expect }) => {
|
|
76
|
+
const graph = new ReactiveGraphModel();
|
|
77
|
+
|
|
78
|
+
const done = new Trigger<Graph>();
|
|
79
|
+
const unsubscribe = graph.subscribe((graph) => {
|
|
80
|
+
if (graph.edges.length === 2) {
|
|
81
|
+
done.wake(graph.graph);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
graph.builder.addNode({ id: 'node-1' });
|
|
87
|
+
graph.builder.addNode({ id: 'node-2' });
|
|
88
|
+
graph.builder.addNode({ id: 'node-3' });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
graph.builder.addEdge({ source: 'node-1', target: 'node-2' });
|
|
93
|
+
graph.builder.addEdge({ source: 'node-2', target: 'node-3' });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
const graph = await done.wait();
|
|
98
|
+
expect(graph.nodes).to.have.length(3);
|
|
99
|
+
expect(graph.edges).to.have.length(2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
unsubscribe();
|
|
103
|
+
});
|
|
104
|
+
|
|
37
105
|
test('optional', ({ expect }) => {
|
|
38
106
|
{
|
|
39
|
-
const graph = new GraphModel<GraphNode<string>>();
|
|
107
|
+
const graph = new GraphModel<GraphNode.Required<string>>();
|
|
40
108
|
const node = graph.addNode({ id: 'test', data: 'test' });
|
|
41
109
|
expect(node.data.length).to.eq(4);
|
|
42
110
|
}
|
|
@@ -49,7 +117,7 @@ describe('Graph', () => {
|
|
|
49
117
|
});
|
|
50
118
|
|
|
51
119
|
test('add and remove subgraphs', ({ expect }) => {
|
|
52
|
-
const graph = new GraphModel<GraphNode<TestData>>();
|
|
120
|
+
const graph = new GraphModel<GraphNode.Required<TestData>>();
|
|
53
121
|
graph.builder
|
|
54
122
|
.addNode({ id: 'node1', data: { value: 'test' } })
|
|
55
123
|
.addNode({ id: 'node2', data: { value: 'test' } })
|
package/src/model.ts
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { effect } from '@preact/signals-core';
|
|
6
|
+
|
|
5
7
|
import { inspectCustom } from '@dxos/debug';
|
|
6
8
|
import { failedInvariant, invariant } from '@dxos/invariant';
|
|
7
|
-
import {
|
|
8
|
-
import { type MakeOptional, isNotFalsy, removeBy
|
|
9
|
+
import { type Live, live } from '@dxos/live-object';
|
|
10
|
+
import { type MakeOptional, isNotFalsy, removeBy } from '@dxos/util';
|
|
9
11
|
|
|
10
|
-
import { type
|
|
12
|
+
import { type BaseGraphEdge, type BaseGraphNode, type Graph, type GraphEdge, type GraphNode } from './types';
|
|
11
13
|
import { createEdgeId } from './util';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
16
|
+
* Readonly Graph wrapper.
|
|
15
17
|
*/
|
|
16
18
|
export class ReadonlyGraphModel<
|
|
17
19
|
Node extends BaseGraphNode = BaseGraphNode,
|
|
@@ -20,10 +22,13 @@ export class ReadonlyGraphModel<
|
|
|
20
22
|
protected readonly _graph: Graph;
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
* NOTE: Pass in simple Graph or
|
|
25
|
+
* NOTE: Pass in simple Graph or Live.
|
|
24
26
|
*/
|
|
25
27
|
constructor(graph?: Graph) {
|
|
26
|
-
this._graph = graph ?? {
|
|
28
|
+
this._graph = graph ?? {
|
|
29
|
+
nodes: [],
|
|
30
|
+
edges: [],
|
|
31
|
+
};
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
[inspectCustom]() {
|
|
@@ -33,12 +38,11 @@ export class ReadonlyGraphModel<
|
|
|
33
38
|
/**
|
|
34
39
|
* Return stable sorted JSON representation of graph.
|
|
35
40
|
*/
|
|
36
|
-
// TODO(burdon): Create separate toJson method with computed signal.
|
|
37
41
|
toJSON() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
return {
|
|
43
|
+
nodes: this.nodes.length,
|
|
44
|
+
edges: this.edges.length,
|
|
45
|
+
};
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
get graph(): Graph {
|
|
@@ -65,7 +69,7 @@ export class ReadonlyGraphModel<
|
|
|
65
69
|
return this.findNode(id) ?? failedInvariant();
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
filterNodes({ type }: Partial<GraphNode> = {}): Node[] {
|
|
72
|
+
filterNodes({ type }: Partial<GraphNode.Any> = {}): Node[] {
|
|
69
73
|
return this.nodes.filter((node) => !type || type === node.type);
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -111,13 +115,13 @@ export class ReadonlyGraphModel<
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
/**
|
|
114
|
-
*
|
|
118
|
+
* Mutable Graph wrapper.
|
|
115
119
|
*/
|
|
116
120
|
export abstract class AbstractGraphModel<
|
|
117
|
-
Node extends BaseGraphNode,
|
|
118
|
-
Edge extends BaseGraphEdge,
|
|
119
|
-
Model extends AbstractGraphModel<Node, Edge, Model, Builder
|
|
120
|
-
Builder extends AbstractGraphBuilder<Node, Edge, Model>,
|
|
121
|
+
Node extends BaseGraphNode = BaseGraphNode,
|
|
122
|
+
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
123
|
+
Model extends AbstractGraphModel<Node, Edge, Model, Builder> = any,
|
|
124
|
+
Builder extends AbstractGraphBuilder<Node, Edge, Model> = AbstractGraphBuilder<Node, Edge, Model>,
|
|
121
125
|
> extends ReadonlyGraphModel<Node, Edge> {
|
|
122
126
|
/**
|
|
123
127
|
* Allows chaining.
|
|
@@ -177,8 +181,8 @@ export abstract class AbstractGraphModel<
|
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
removeNode(id: string): Model {
|
|
180
|
-
const nodes = removeBy<Node>(this._graph.nodes as Node[], (node) => node.id === id);
|
|
181
184
|
const edges = removeBy<Edge>(this._graph.edges as Edge[], (edge) => edge.source === id || edge.target === id);
|
|
185
|
+
const nodes = removeBy<Node>(this._graph.nodes as Node[], (node) => node.id === id);
|
|
182
186
|
return this.copy({ nodes, edges });
|
|
183
187
|
}
|
|
184
188
|
|
|
@@ -199,7 +203,7 @@ export abstract class AbstractGraphModel<
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
/**
|
|
202
|
-
* Chainable wrapper
|
|
206
|
+
* Chainable builder wrapper
|
|
203
207
|
*/
|
|
204
208
|
export abstract class AbstractGraphBuilder<
|
|
205
209
|
Node extends BaseGraphNode,
|
|
@@ -242,6 +246,9 @@ export abstract class AbstractGraphBuilder<
|
|
|
242
246
|
}
|
|
243
247
|
}
|
|
244
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Basic model.
|
|
251
|
+
*/
|
|
245
252
|
export class GraphModel<
|
|
246
253
|
Node extends BaseGraphNode = BaseGraphNode,
|
|
247
254
|
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
@@ -250,16 +257,60 @@ export class GraphModel<
|
|
|
250
257
|
return new GraphBuilder<Node, Edge>(this);
|
|
251
258
|
}
|
|
252
259
|
|
|
253
|
-
override copy(graph?: Partial<Graph>) {
|
|
260
|
+
override copy(graph?: Partial<Graph>): GraphModel<Node, Edge> {
|
|
254
261
|
return new GraphModel<Node, Edge>({ nodes: graph?.nodes ?? [], edges: graph?.edges ?? [] });
|
|
255
262
|
}
|
|
256
263
|
}
|
|
257
264
|
|
|
265
|
+
export type GraphModelSubscription = (model: GraphModel, graph: Live<Graph>) => void;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Subscription.
|
|
269
|
+
* NOTE: Requires `registerSignalsRuntime` to be called.
|
|
270
|
+
*/
|
|
271
|
+
export const subscribe = (model: GraphModel, cb: GraphModelSubscription, fire = false) => {
|
|
272
|
+
if (fire) {
|
|
273
|
+
cb(model, model.graph);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return effect(() => {
|
|
277
|
+
cb(model, model.graph); // TODO(burdon): This won't work unless model.graph is reactive.
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Basic reactive model.
|
|
283
|
+
*/
|
|
284
|
+
export class ReactiveGraphModel<
|
|
285
|
+
Node extends BaseGraphNode = BaseGraphNode,
|
|
286
|
+
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
287
|
+
> extends GraphModel<Node, Edge> {
|
|
288
|
+
constructor(graph?: Partial<Graph>) {
|
|
289
|
+
super(
|
|
290
|
+
live({
|
|
291
|
+
nodes: graph?.nodes ?? [],
|
|
292
|
+
edges: graph?.edges ?? [],
|
|
293
|
+
}),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
override copy(graph?: Partial<Graph>): ReactiveGraphModel<Node, Edge> {
|
|
298
|
+
return new ReactiveGraphModel<Node, Edge>(graph);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
subscribe(cb: GraphModelSubscription, fire = false): () => void {
|
|
302
|
+
return subscribe(this, cb, fire);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Basic builder.
|
|
308
|
+
*/
|
|
258
309
|
export class GraphBuilder<
|
|
259
310
|
Node extends BaseGraphNode = BaseGraphNode,
|
|
260
311
|
Edge extends BaseGraphEdge = BaseGraphEdge,
|
|
261
312
|
> extends AbstractGraphBuilder<Node, Edge, GraphModel<Node, Edge>> {
|
|
262
|
-
override call(cb: (builder: this) => void) {
|
|
313
|
+
override call(cb: (builder: this) => void): this {
|
|
263
314
|
cb(this);
|
|
264
315
|
return this;
|
|
265
316
|
}
|
package/src/selection.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { computed, signal, type ReadonlySignal, type Signal } from '@preact/signals-core';
|
|
6
|
+
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reactive selection model.
|
|
11
|
+
*/
|
|
12
|
+
export class SelectionModel {
|
|
13
|
+
private readonly _selected: Signal<Set<string>> = signal(new Set<string>());
|
|
14
|
+
private readonly _selectedIds = computed(() => Array.from(this._selected.value.values()));
|
|
15
|
+
|
|
16
|
+
constructor(private readonly _singleSelect: boolean = false) {}
|
|
17
|
+
|
|
18
|
+
toJSON(): { selected: string[] } {
|
|
19
|
+
return {
|
|
20
|
+
selected: Array.from(this._selected.value.values()),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get size(): number {
|
|
25
|
+
return this._selected.value.size;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get selected(): ReadonlySignal<string[]> {
|
|
29
|
+
return this._selectedIds;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
contains(id: string): boolean {
|
|
33
|
+
return this._selected.value.has(id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
clear(): void {
|
|
37
|
+
this._selected.value = new Set();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
add(id: string): void {
|
|
41
|
+
invariant(id);
|
|
42
|
+
this._selected.value = new Set<string>(
|
|
43
|
+
this._singleSelect ? [id] : [...Array.from(this._selected.value.values()), id],
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
remove(id: string): void {
|
|
48
|
+
invariant(id);
|
|
49
|
+
this._selected.value = new Set<string>(Array.from(this._selected.value.values()).filter((_id) => _id !== id));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// TODO(burdon): Handle single select.
|
|
53
|
+
|
|
54
|
+
setSelected(ids: string[], shift = false): void {
|
|
55
|
+
this._selected.value = new Set([...(shift ? Array.from(this._selected.value.values()) : []), ...ids]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
toggleSelected(ids: string[], shift = false): void {
|
|
59
|
+
const set = new Set<string>(shift ? Array.from(this._selected.value.values()) : undefined);
|
|
60
|
+
ids.forEach((id) => {
|
|
61
|
+
if (this._selected.value.has(id)) {
|
|
62
|
+
set.delete(id);
|
|
63
|
+
} else {
|
|
64
|
+
set.add(id);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this._selected.value = set;
|
|
69
|
+
}
|
|
70
|
+
}
|