@dxos/plugin-explorer 0.9.1-main.c7dcc2e112 → 0.10.0

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.
Files changed (151) hide show
  1. package/dist/lib/neutral/ExplorerArticle-2WPXYTXV.mjs +101 -0
  2. package/dist/lib/neutral/ExplorerArticle-2WPXYTXV.mjs.map +7 -0
  3. package/dist/lib/neutral/ExplorerPlugin.mjs +1 -1
  4. package/dist/lib/neutral/NeighborhoodCompanion-WC6E3YE6.mjs +90 -0
  5. package/dist/lib/neutral/NeighborhoodCompanion-WC6E3YE6.mjs.map +7 -0
  6. package/dist/lib/neutral/app-graph-builder-L2TJ6GNR.mjs +37 -0
  7. package/dist/lib/neutral/app-graph-builder-L2TJ6GNR.mjs.map +7 -0
  8. package/dist/lib/neutral/capabilities/index.mjs +4 -2
  9. package/dist/lib/neutral/capabilities/index.mjs.map +3 -3
  10. package/dist/lib/neutral/{chunk-JXYIG54N.mjs → chunk-4F6AT7RA.mjs} +4 -2
  11. package/dist/lib/neutral/chunk-4F6AT7RA.mjs.map +7 -0
  12. package/dist/lib/neutral/{chunk-7XUDLV6E.mjs → chunk-EZ52JY7X.mjs} +34 -31
  13. package/dist/lib/neutral/chunk-EZ52JY7X.mjs.map +7 -0
  14. package/dist/lib/neutral/{chunk-YBCHBVCJ.mjs → chunk-TTFPTHAQ.mjs} +8 -8
  15. package/dist/lib/neutral/chunk-TTFPTHAQ.mjs.map +7 -0
  16. package/dist/lib/neutral/components/index.mjs +430 -506
  17. package/dist/lib/neutral/components/index.mjs.map +4 -4
  18. package/dist/lib/neutral/containers/index.mjs +4 -2
  19. package/dist/lib/neutral/containers/index.mjs.map +3 -3
  20. package/dist/lib/neutral/{create-object-F6TKVAGV.mjs → create-object-WKIN3OB6.mjs} +3 -4
  21. package/dist/lib/neutral/{create-object-F6TKVAGV.mjs.map → create-object-WKIN3OB6.mjs.map} +3 -3
  22. package/dist/lib/neutral/hooks/index.mjs +64 -1
  23. package/dist/lib/neutral/hooks/index.mjs.map +4 -4
  24. package/dist/lib/neutral/index.mjs +1 -1
  25. package/dist/lib/neutral/meta.json +1 -1
  26. package/dist/lib/neutral/react-surface-EWPG5PUQ.mjs +39 -0
  27. package/dist/lib/neutral/react-surface-EWPG5PUQ.mjs.map +7 -0
  28. package/dist/lib/neutral/testing/index.mjs +2 -2
  29. package/dist/lib/neutral/testing/index.mjs.map +2 -2
  30. package/dist/lib/neutral/translations.mjs +4 -1
  31. package/dist/lib/neutral/translations.mjs.map +2 -2
  32. package/dist/lib/neutral/types/index.mjs +1 -1
  33. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  34. package/dist/types/src/capabilities/app-graph-builder.d.ts +9 -0
  35. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/create-object.d.ts +1 -7
  37. package/dist/types/src/capabilities/create-object.d.ts.map +1 -1
  38. package/dist/types/src/capabilities/index.d.ts +1 -0
  39. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  40. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  41. package/dist/types/src/components/Lattice/Lattice.d.ts +1 -1
  42. package/dist/types/src/components/Lattice/Lattice.d.ts.map +1 -1
  43. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts +1 -1
  44. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Tree/Tree.stories.d.ts +1 -1
  46. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/Tree/index.d.ts +1 -3
  48. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  49. package/dist/types/src/components/Tree/types/tree.d.ts +10 -27
  50. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  51. package/dist/types/src/components/Tree/types/types.d.ts +1 -10
  52. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  53. package/dist/types/src/components/Visualization/Visualization.d.ts +39 -0
  54. package/dist/types/src/components/Visualization/Visualization.d.ts.map +1 -0
  55. package/dist/types/src/components/Visualization/Visualization.stories.d.ts +13 -0
  56. package/dist/types/src/components/Visualization/Visualization.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/Visualization/index.d.ts +3 -0
  58. package/dist/types/src/components/Visualization/index.d.ts.map +1 -0
  59. package/dist/types/src/components/Visualization/variants.d.ts +53 -0
  60. package/dist/types/src/components/Visualization/variants.d.ts.map +1 -0
  61. package/dist/types/src/components/index.d.ts +1 -0
  62. package/dist/types/src/components/index.d.ts.map +1 -1
  63. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts +1 -1
  64. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts.map +1 -1
  65. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts +2 -2
  66. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts.map +1 -1
  67. package/dist/types/src/containers/NeighborhoodCompanion/NeighborhoodCompanion.d.ts +14 -0
  68. package/dist/types/src/containers/NeighborhoodCompanion/NeighborhoodCompanion.d.ts.map +1 -0
  69. package/dist/types/src/containers/NeighborhoodCompanion/index.d.ts +2 -0
  70. package/dist/types/src/containers/NeighborhoodCompanion/index.d.ts.map +1 -0
  71. package/dist/types/src/containers/index.d.ts +1 -0
  72. package/dist/types/src/containers/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/index.d.ts +1 -0
  74. package/dist/types/src/hooks/index.d.ts.map +1 -1
  75. package/dist/types/src/hooks/useNeighborhoodModel.d.ts +14 -0
  76. package/dist/types/src/hooks/useNeighborhoodModel.d.ts.map +1 -0
  77. package/dist/types/src/meta.d.ts +4 -0
  78. package/dist/types/src/meta.d.ts.map +1 -1
  79. package/dist/types/src/testing/relations.d.ts +1 -2
  80. package/dist/types/src/testing/relations.d.ts.map +1 -1
  81. package/dist/types/src/translations.d.ts +6 -0
  82. package/dist/types/src/translations.d.ts.map +1 -1
  83. package/dist/types/src/types/Graph.d.ts +6 -8
  84. package/dist/types/src/types/Graph.d.ts.map +1 -1
  85. package/dist/types/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +41 -37
  87. package/src/ExplorerPlugin.tsx +2 -1
  88. package/src/capabilities/app-graph-builder.ts +40 -0
  89. package/src/capabilities/create-object.ts +3 -2
  90. package/src/capabilities/index.ts +1 -0
  91. package/src/capabilities/react-surface.tsx +14 -4
  92. package/src/components/Lattice/Lattice.tsx +1 -2
  93. package/src/components/Tree/EdgeBundling.stories.tsx +5 -3
  94. package/src/components/Tree/Tree.stories.tsx +7 -2
  95. package/src/components/Tree/index.ts +3 -3
  96. package/src/components/Tree/types/tree.ts +6 -6
  97. package/src/components/Tree/types/types.ts +1 -11
  98. package/src/components/Visualization/Visualization.stories.tsx +104 -0
  99. package/src/components/Visualization/Visualization.tsx +209 -0
  100. package/src/components/Visualization/index.ts +6 -0
  101. package/src/components/Visualization/variants.ts +508 -0
  102. package/src/components/index.ts +1 -0
  103. package/src/containers/ExplorerArticle/ExplorerArticle.stories.tsx +3 -3
  104. package/src/containers/ExplorerArticle/ExplorerArticle.tsx +7 -8
  105. package/src/containers/NeighborhoodCompanion/NeighborhoodCompanion.tsx +102 -0
  106. package/src/containers/NeighborhoodCompanion/index.ts +5 -0
  107. package/src/containers/index.ts +1 -0
  108. package/src/hooks/index.ts +1 -0
  109. package/src/hooks/useNeighborhoodModel.ts +81 -0
  110. package/src/testing/relations.ts +1 -3
  111. package/src/translations.ts +3 -0
  112. package/src/types/Graph.ts +18 -20
  113. package/dist/lib/neutral/ExplorerArticle-MHEINWUL.mjs +0 -460
  114. package/dist/lib/neutral/ExplorerArticle-MHEINWUL.mjs.map +0 -7
  115. package/dist/lib/neutral/chunk-7XUDLV6E.mjs.map +0 -7
  116. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs +0 -20
  117. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs.map +0 -7
  118. package/dist/lib/neutral/chunk-JXYIG54N.mjs.map +0 -7
  119. package/dist/lib/neutral/chunk-YBCHBVCJ.mjs.map +0 -7
  120. package/dist/lib/neutral/react-surface-APBW2VQG.mjs +0 -26
  121. package/dist/lib/neutral/react-surface-APBW2VQG.mjs.map +0 -7
  122. package/dist/types/src/components/Tree/Tree.d.ts +0 -24
  123. package/dist/types/src/components/Tree/Tree.d.ts.map +0 -1
  124. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts +0 -40
  125. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +0 -1
  126. package/dist/types/src/components/Tree/layout/RadialTree.d.ts +0 -37
  127. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +0 -1
  128. package/dist/types/src/components/Tree/layout/TidyTree.d.ts +0 -26
  129. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +0 -1
  130. package/dist/types/src/components/Tree/layout/hierarchy.d.ts +0 -17
  131. package/dist/types/src/components/Tree/layout/hierarchy.d.ts.map +0 -1
  132. package/dist/types/src/components/Tree/layout/index.d.ts +0 -6
  133. package/dist/types/src/components/Tree/layout/index.d.ts.map +0 -1
  134. package/dist/types/src/components/Tree/layout/slots.d.ts +0 -7
  135. package/dist/types/src/components/Tree/layout/slots.d.ts.map +0 -1
  136. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts +0 -15
  137. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts.map +0 -1
  138. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts +0 -18
  139. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts.map +0 -1
  140. package/dist/types/src/containers/ExplorerArticle/variants.d.ts +0 -9
  141. package/dist/types/src/containers/ExplorerArticle/variants.d.ts.map +0 -1
  142. package/src/components/Tree/Tree.tsx +0 -81
  143. package/src/components/Tree/layout/HierarchicalEdgeBundling.tsx +0 -335
  144. package/src/components/Tree/layout/RadialTree.tsx +0 -242
  145. package/src/components/Tree/layout/TidyTree.tsx +0 -246
  146. package/src/components/Tree/layout/hierarchy.ts +0 -32
  147. package/src/components/Tree/layout/index.ts +0 -9
  148. package/src/components/Tree/layout/slots.ts +0 -19
  149. package/src/components/Tree/layout/useContainerSize.ts +0 -43
  150. package/src/containers/ExplorerArticle/Visualization.tsx +0 -523
  151. package/src/containers/ExplorerArticle/variants.ts +0 -47
@@ -5,3 +5,4 @@
5
5
  import { type ComponentType, lazy } from 'react';
6
6
 
7
7
  export const ExplorerArticle: ComponentType<any> = lazy(() => import('./ExplorerArticle'));
8
+ export const NeighborhoodCompanion: ComponentType<any> = lazy(() => import('./NeighborhoodCompanion'));
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './useGraphModel';
6
+ export * from './useNeighborhoodModel';
@@ -0,0 +1,81 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { useEffect, useState } from 'react';
6
+
7
+ import { Obj } from '@dxos/echo';
8
+ import { type SpaceGraphEdge, type SpaceGraphModel, type SpaceGraphNode } from '@dxos/schema';
9
+
10
+ import { useGraphModel } from './useGraphModel';
11
+
12
+ /** Default traversal depth (hops) for the neighborhood graph. */
13
+ export const DEFAULT_NEIGHBORHOOD_DEPTH = 2;
14
+
15
+ /**
16
+ * Builds an ego graph around `object`: the n-hop neighbourhood reached by following refs and
17
+ * relations from the active node. The full space graph is materialised (reactively) and then
18
+ * narrowed to the nodes within `depth` hops, keeping only edges whose endpoints both survive.
19
+ *
20
+ * NOTE: Derives the subgraph from the whole-space model — the same cost the main explorer pays.
21
+ * A direct ECHO traversal that loads only the reachable objects is a future optimisation.
22
+ */
23
+ export const useNeighborhoodModel = (
24
+ object: Obj.Unknown | undefined,
25
+ depth = DEFAULT_NEIGHBORHOOD_DEPTH,
26
+ ): SpaceGraphModel | undefined => {
27
+ const db = object ? Obj.getDatabase(object) : undefined;
28
+ const fullModel = useGraphModel(db);
29
+ const [model, setModel] = useState<SpaceGraphModel | undefined>(undefined);
30
+
31
+ useEffect(() => {
32
+ if (!fullModel || !object) {
33
+ setModel(undefined);
34
+ return;
35
+ }
36
+
37
+ const recompute = () => setModel(buildNeighborhood(fullModel, object.id, depth));
38
+ recompute();
39
+ return fullModel.subscribe(recompute);
40
+ }, [fullModel, object?.id, depth]);
41
+
42
+ return model;
43
+ };
44
+
45
+ /**
46
+ * Narrow the full graph to the BFS neighbourhood of `rootId` within `depth` hops (edges treated as
47
+ * undirected so the neighbourhood reads as graph distance regardless of ref/relation direction).
48
+ */
49
+ const buildNeighborhood = (fullModel: SpaceGraphModel, rootId: string, depth: number): SpaceGraphModel => {
50
+ const { nodes, edges } = fullModel.graph;
51
+
52
+ const adjacency = new Map<string, string[]>();
53
+ const link = (from: string, to: string) => {
54
+ const list = adjacency.get(from) ?? [];
55
+ list.push(to);
56
+ adjacency.set(from, list);
57
+ };
58
+ for (const edge of edges) {
59
+ link(edge.source, edge.target);
60
+ link(edge.target, edge.source);
61
+ }
62
+
63
+ const included = new Set<string>([rootId]);
64
+ let frontier = [rootId];
65
+ for (let hop = 0; hop < depth && frontier.length; hop++) {
66
+ const next: string[] = [];
67
+ for (const id of frontier) {
68
+ for (const neighbour of adjacency.get(id) ?? []) {
69
+ if (!included.has(neighbour)) {
70
+ included.add(neighbour);
71
+ next.push(neighbour);
72
+ }
73
+ }
74
+ }
75
+ frontier = next;
76
+ }
77
+
78
+ const subNodes = nodes.filter((node: SpaceGraphNode) => included.has(node.id));
79
+ const subEdges = edges.filter((edge: SpaceGraphEdge) => included.has(edge.source) && included.has(edge.target));
80
+ return fullModel.copy({ nodes: subNodes, edges: subEdges });
81
+ };
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { type Space } from '@dxos/client/echo';
6
6
  import { Obj, Query, Relation } from '@dxos/echo';
7
+ import { type BundleEdge, type TreeNode } from '@dxos/react-ui-graph';
7
8
  import {
8
9
  type RelationSpec,
9
10
  type TypeSpec,
@@ -13,9 +14,6 @@ import {
13
14
  } from '@dxos/schema/testing';
14
15
  import { HasConnection, Organization, Person } from '@dxos/types';
15
16
 
16
- import { type BundleEdge } from '../components/Tree/layout';
17
- import { type TreeNode } from '../components/Tree/types';
18
-
19
17
  const SECTORS = ['Technology', 'Finance', 'Research', 'Media'];
20
18
  const CONNECTION_KINDS = ['partner', 'investor', 'vendor', 'customer'];
21
19
 
@@ -26,6 +26,9 @@ export const translations = [
26
26
  [meta.profile.key]: {
27
27
  'plugin.name': 'Explorer',
28
28
  'object-title.label': 'Title',
29
+ 'neighborhood-companion.label': 'Graph',
30
+ 'depth.label_one': '{{count}} hop',
31
+ 'depth.label_other': '{{count}} hops',
29
32
  },
30
33
  },
31
34
  },
@@ -4,29 +4,27 @@
4
4
 
5
5
  import * as Schema from 'effect/Schema';
6
6
 
7
- import { DXN, Annotation, Filter, Obj, Query, QueryAST, Ref, Type, View } from '@dxos/echo';
7
+ import { Annotation, DXN, Filter, Obj, Query, QueryAST, Ref, Type, View } from '@dxos/echo';
8
8
  import { FormInputAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';
9
9
  import { ViewAnnotation } from '@dxos/schema';
10
10
 
11
- const GraphSchema = Schema.Struct({
12
- name: Schema.optional(Schema.String),
13
- view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),
14
- query: Schema.Struct({
15
- raw: Schema.optional(Schema.String),
16
- ast: QueryAST.Query,
17
- }).pipe(FormInputAnnotation.set(false)),
18
- }).pipe(
19
- LabelAnnotation.set(['name']),
20
- ViewAnnotation.set(['view']),
21
- Annotation.IconAnnotation.set({ icon: 'ph--graph--regular', hue: 'green' }),
22
- Type.makeObject(DXN.make('org.dxos.type.graph', '0.1.0')),
23
- );
24
- // TODO(wittjosiah): Try to clean up this type inference.
25
- export interface Graph extends Type.InstanceType<typeof GraphSchema> {}
26
- export const Graph: Type.Obj<Graph> = GraphSchema as any;
11
+ export class Graph extends Type.makeObject<Graph>(DXN.make('org.dxos.type.graph', '0.1.0'))(
12
+ Schema.Struct({
13
+ name: Schema.optional(Schema.String),
14
+ view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false), Schema.optional),
15
+ query: Schema.Struct({
16
+ raw: Schema.optional(Schema.String),
17
+ ast: QueryAST.Query,
18
+ }).pipe(FormInputAnnotation.set(false)),
19
+ }).pipe(
20
+ LabelAnnotation.set(['name']),
21
+ ViewAnnotation.set(['view']),
22
+ Annotation.IconAnnotation.set({ icon: 'ph--graph--regular', hue: 'green' }),
23
+ ),
24
+ ) {}
27
25
 
28
26
  type MakeProps = Omit<Partial<Obj.MakeProps<typeof Graph>>, 'view'> & {
29
- view: View.View;
27
+ view?: View.View;
30
28
  };
31
29
 
32
30
  /**
@@ -36,6 +34,6 @@ export const make = ({
36
34
  name,
37
35
  query = { raw: '', ast: Query.select(Filter.nothing()).ast },
38
36
  view,
39
- }: MakeProps): Graph => {
40
- return Obj.make(Graph, { name, view: Ref.make(view), query });
37
+ }: MakeProps = {}): Graph => {
38
+ return Obj.make(Graph, { name, view: view && Ref.make(view), query });
41
39
  };
@@ -1,460 +0,0 @@
1
- import {
2
- getNodeFillForObject
3
- } from "./chunk-IKHJV3Q4.mjs";
4
- import "./chunk-J5LGTIGS.mjs";
5
-
6
- // src/containers/ExplorerArticle/ExplorerArticle.tsx
7
- import React2, { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
8
- import { AppSurface } from "@dxos/app-toolkit/ui";
9
- import { Obj as Obj2 } from "@dxos/echo";
10
- import { QueryBuilder } from "@dxos/echo-query";
11
- import { useObject } from "@dxos/react-client/echo";
12
- import { DxAnchorActivate, Icon, Panel, Toolbar } from "@dxos/react-ui";
13
- import { QueryEditor } from "@dxos/react-ui-components";
14
- import "@dxos/react-ui-graph/styles/graph.css";
15
- import { useGraphModel } from "#hooks";
16
-
17
- // src/containers/ExplorerArticle/variants.ts
18
- var VARIANTS = [
19
- {
20
- value: "force",
21
- icon: "ph--graph--regular",
22
- label: "Force-directed"
23
- },
24
- {
25
- value: "cluster",
26
- icon: "ph--asterisk-simple--regular",
27
- label: "Radial"
28
- },
29
- {
30
- value: "bundle",
31
- icon: "ph--circles-three-plus--regular",
32
- label: "Connections"
33
- },
34
- {
35
- value: "plexus",
36
- icon: "ph--share-network--regular",
37
- label: "Plexus"
38
- },
39
- {
40
- value: "lattice",
41
- icon: "ph--grid-four--regular",
42
- label: "Lattice"
43
- },
44
- {
45
- value: "swarm",
46
- icon: "ph--microscope--regular",
47
- label: "Swarm"
48
- }
49
- ];
50
- var isVariant = (value) => value === "force" || value === "cluster" || value === "bundle" || value === "lattice" || value === "swarm" || value === "plexus";
51
-
52
- // src/containers/ExplorerArticle/Visualization.tsx
53
- import { curveCatmullRom, line as d3Line } from "d3";
54
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
55
- import { Obj } from "@dxos/echo";
56
- import { CLUSTER_NODE_TYPE_LEAF, CLUSTER_NODE_TYPE_ROOT, GraphBundleProjector, GraphClusterProjector, GraphForceProjector, GraphLatticeProjector, GraphPlexusProjector, GraphSwarmProjector, PLEXUS_NODE_TYPE_FOCUS, PLEXUS_NODE_TYPE_RELATION, SVG, appendRadialGroupLabel, appendRadialLeafLabel, appendRootLabel } from "@dxos/react-ui-graph";
57
- import { mx } from "@dxos/ui-theme";
58
- var Visualization = ({ classNames, debug = true, variant, model, onNodeHover, onSurfaceClick }) => {
59
- const svgRef = useRef(null);
60
- const [projector, setProjector] = useState();
61
- const projectorRef = useRef(void 0);
62
- projectorRef.current = projector;
63
- const [focusId, setFocusId] = useState(void 0);
64
- const lastLayoutRef = useRef(void 0);
65
- useEffect(() => model?.subscribe(() => void 0), [
66
- model
67
- ]);
68
- useEffect(() => {
69
- if (variant !== "plexus") {
70
- setFocusId(void 0);
71
- }
72
- }, [
73
- variant
74
- ]);
75
- useEffect(() => {
76
- if (projectorRef.current?.layout) {
77
- lastLayoutRef.current = projectorRef.current.layout;
78
- }
79
- if (!svgRef.current) {
80
- return;
81
- }
82
- setProjector(createProjector(svgRef.current, variant, focusId, lastLayoutRef.current));
83
- }, [
84
- variant,
85
- focusId
86
- ]);
87
- const handleSelect = useCallback((node) => {
88
- if (variant !== "plexus") {
89
- return;
90
- }
91
- if (!node.data?.data?.object) {
92
- return;
93
- }
94
- setFocusId((current) => current === node.id ? void 0 : node.id);
95
- }, [
96
- variant
97
- ]);
98
- const renderNode = useMemo(() => createRenderNode(variant), [
99
- variant
100
- ]);
101
- const applyNode = useMemo(() => variant === "swarm" ? applyNodeSwarm : void 0, [
102
- variant
103
- ]);
104
- const handlePointerMove = useCallback((point) => {
105
- if (projector instanceof GraphSwarmProjector) {
106
- projector.setCursor(point.x, point.y);
107
- }
108
- }, [
109
- projector
110
- ]);
111
- const handlePointerLeave = useCallback(() => {
112
- if (projector instanceof GraphSwarmProjector) {
113
- projector.setCursor(null);
114
- }
115
- }, [
116
- projector
117
- ]);
118
- const handleInspect = useCallback((node, event) => {
119
- if (variant === "plexus") {
120
- return;
121
- }
122
- onNodeHover?.(node ? {
123
- id: node.id,
124
- data: node.data?.data?.object
125
- } : null, event);
126
- }, [
127
- variant === "plexus" ? void 0 : onNodeHover
128
- ]);
129
- const swarmPointerProps = variant === "swarm" ? {
130
- onPointerMove: handlePointerMove,
131
- onPointerLeave: handlePointerLeave
132
- } : void 0;
133
- return /* @__PURE__ */ React.createElement("div", {
134
- className: mx("dx-expander relative", classNames),
135
- onClick: onSurfaceClick
136
- }, /* @__PURE__ */ React.createElement(SVG.Root, {
137
- ref: svgRef
138
- }, /* @__PURE__ */ React.createElement(SVG.Zoom, {
139
- extent: [
140
- 1 / 2,
141
- 2
142
- ]
143
- }, /* @__PURE__ */ React.createElement(SVG.Graph, {
144
- model,
145
- projector,
146
- renderNode,
147
- applyNode,
148
- edgeOpacity: variant === "swarm" ? 0.3 : void 0,
149
- drag: variant === "force",
150
- highlightOnHover: variant === "bundle",
151
- onSelect: handleSelect,
152
- onInspect: handleInspect,
153
- ...swarmPointerProps
154
- })), debug && /* @__PURE__ */ React.createElement(SVG.FPS, null)));
155
- };
156
- var TWEEN_MS = 500;
157
- var LABEL_FADE_MS = 200;
158
- var PLEXUS_LEAF_RADIUS = 5;
159
- var PLEXUS_FOCUS_RADIUS = PLEXUS_LEAF_RADIUS * 2;
160
- var swarmTrailLine = d3Line().curve(curveCatmullRom.alpha(0.5));
161
- var applyNodeSwarm = (group, node) => {
162
- const swarm = node;
163
- const path = group.querySelector("path.dx-swarm-tail");
164
- const grad = group.querySelector("linearGradient");
165
- if (!path || !grad) {
166
- return;
167
- }
168
- const history = swarm.history ?? [];
169
- if (history.length === 0) {
170
- path.setAttribute("d", "");
171
- return;
172
- }
173
- const hx = swarm.x ?? 0;
174
- const hy = swarm.y ?? 0;
175
- const points = [
176
- [
177
- 0,
178
- 0
179
- ]
180
- ];
181
- for (let i = history.length - 1; i >= 0; i--) {
182
- points.push([
183
- history[i].x - hx,
184
- history[i].y - hy
185
- ]);
186
- }
187
- path.setAttribute("d", swarmTrailLine(points) ?? "");
188
- const oldest = history[0];
189
- grad.setAttribute("x2", String(oldest.x - hx));
190
- grad.setAttribute("y2", String(oldest.y - hy));
191
- };
192
- var createProjector = (ctx, variant, focusId, prev) => {
193
- switch (variant) {
194
- case "force":
195
- return new GraphForceProjector(ctx, void 0, void 0, prev);
196
- case "swarm":
197
- return new GraphSwarmProjector(ctx, void 0, void 0, prev);
198
- case "lattice":
199
- return new GraphLatticeProjector(ctx, {
200
- duration: TWEEN_MS,
201
- // Plugin-explorer overrides the projector's force-matched default (6)
202
- // with a smaller node so the lattice reads as a dense matrix.
203
- radius: 4,
204
- // Cluster by typename first so same-type rects sit together; break ties by label.
205
- sortBy: (node) => {
206
- const obj = node.data?.data?.object;
207
- const typename = obj ? Obj.getTypename(obj) ?? "(untyped)" : "(untyped)";
208
- const label = (obj && Obj.getLabel(obj)) ?? node.data?.data?.label ?? node.id;
209
- return `${typename} ${label}`;
210
- }
211
- }, void 0, prev);
212
- case "cluster":
213
- return new GraphClusterProjector(ctx, {
214
- duration: TWEEN_MS,
215
- groupOf: typenameGroupOf,
216
- rootLabel: "Database",
217
- groupLabel: shortTypename,
218
- // All three node kinds share the same radius — leaves, groups, and root read
219
- // as members of the same circle rather than ranked by size.
220
- rootRadius: 4,
221
- groupRadius: 4
222
- }, void 0, prev);
223
- case "bundle":
224
- return new GraphBundleProjector(ctx, {
225
- duration: TWEEN_MS,
226
- groupOf: typenameGroupOf
227
- }, void 0, prev);
228
- case "plexus":
229
- if (!focusId) {
230
- return new GraphBundleProjector(ctx, {
231
- duration: TWEEN_MS,
232
- groupOf: typenameGroupOf
233
- }, void 0, prev);
234
- }
235
- return new GraphPlexusProjector(ctx, {
236
- duration: TWEEN_MS,
237
- focus: focusId,
238
- relationOf: plexusRelationOf,
239
- leafRadius: PLEXUS_LEAF_RADIUS,
240
- relationRadius: PLEXUS_LEAF_RADIUS,
241
- focusRadius: PLEXUS_FOCUS_RADIUS
242
- }, void 0, prev);
243
- }
244
- };
245
- var plexusRelationOf = (edge, focusId) => {
246
- const outgoing = edge.source.id === focusId;
247
- const incoming = edge.target.id === focusId;
248
- if (!outgoing && !incoming) {
249
- return void 0;
250
- }
251
- const direction = outgoing ? "out" : "in";
252
- const arrow = outgoing ? "\u2192" : "\u2190";
253
- if (edge.type === "relation") {
254
- const relation = edge.data?.object;
255
- const typename = relation ? Obj.getTypename(relation) : void 0;
256
- const name = typename ? shortTypename(typename) : "Relation";
257
- return {
258
- key: `relation:${direction}:${typename ?? "?"}`,
259
- label: `${name} ${arrow}`
260
- };
261
- }
262
- if (edge.type === "ref") {
263
- const property = edge.data?.property;
264
- const name = property ?? "References";
265
- return {
266
- key: `ref:${direction}:${property ?? "?"}`,
267
- label: `${name} ${arrow}`
268
- };
269
- }
270
- return void 0;
271
- };
272
- var createRenderNode = (variant) => {
273
- switch (variant) {
274
- case "force":
275
- return (group, node) => {
276
- const r = node.r ?? 6;
277
- group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", getNodeFillForObject(node.data?.data?.object));
278
- };
279
- case "swarm":
280
- return (group, node) => {
281
- const fill = getNodeFillForObject(node.data?.data?.object);
282
- const r = node.r ?? 6;
283
- const strokeWidth = Math.max(1, r * 0.6);
284
- const gradId = `dx-swarm-grad-${String(node.id).replace(/[^\w-]/g, "_")}`;
285
- const grad = group.append("defs").append("linearGradient").attr("id", gradId).attr("gradientUnits", "userSpaceOnUse").attr("x1", 0).attr("y1", 0).attr("x2", 0).attr("y2", 0);
286
- grad.append("stop").attr("offset", 0).attr("stop-color", fill).attr("stop-opacity", 0.7);
287
- grad.append("stop").attr("offset", 1).attr("stop-color", fill).attr("stop-opacity", 0);
288
- group.append("path").classed("dx-swarm-tail", true).attr("fill", "none").attr("stroke", `url(#${gradId})`).attr("stroke-width", strokeWidth).attr("stroke-linecap", "round").attr("stroke-linejoin", "round").attr("pointer-events", "none");
289
- group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", fill);
290
- };
291
- case "lattice":
292
- return (group, node) => {
293
- const r = node.r ?? 6;
294
- const sz = r * 2;
295
- group.append("rect").attr("x", -r).attr("y", -r).attr("width", sz).attr("height", sz).attr("rx", r * 0.3).attr("ry", r * 0.3).style("cursor", "pointer").style("fill", getNodeFillForObject(node.data?.data?.object));
296
- };
297
- case "cluster":
298
- return (group, node) => {
299
- const obj = node.data?.data?.object;
300
- const r = node.r ?? 4;
301
- group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", obj ? getNodeFillForObject(obj) : "var(--color-neutral-500)");
302
- const labelOptions = {
303
- delay: TWEEN_MS,
304
- duration: LABEL_FADE_MS
305
- };
306
- if (node.type === CLUSTER_NODE_TYPE_LEAF) {
307
- const text = labelForLeaf(node, obj);
308
- if (text) {
309
- appendRadialLeafLabel(group, node, text, r, labelOptions);
310
- }
311
- } else if (node.type === CLUSTER_NODE_TYPE_ROOT) {
312
- if (node.label) {
313
- appendRootLabel(group, node.label, r, labelOptions);
314
- }
315
- } else {
316
- if (node.label) {
317
- appendRadialGroupLabel(group, node, node.label, r, labelOptions);
318
- }
319
- }
320
- };
321
- case "bundle":
322
- return (group, node) => {
323
- const obj = node.data?.data?.object;
324
- const r = node.r ?? 4;
325
- group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", obj ? getNodeFillForObject(obj) : "var(--color-neutral-500)");
326
- const text = labelForLeaf(node, obj);
327
- if (text) {
328
- appendRadialLeafLabel(group, node, text, r, {
329
- delay: TWEEN_MS,
330
- duration: LABEL_FADE_MS
331
- });
332
- }
333
- };
334
- case "plexus":
335
- return (group, node) => {
336
- const obj = node.data?.data?.object;
337
- const labelOptions = {
338
- delay: TWEEN_MS,
339
- duration: LABEL_FADE_MS
340
- };
341
- if (node.type === PLEXUS_NODE_TYPE_RELATION) {
342
- const r2 = PLEXUS_LEAF_RADIUS;
343
- group.append("circle").attr("r", r2).style("cursor", "default").style("fill", "var(--color-neutral-500)");
344
- if (node.label) {
345
- appendRadialGroupLabel(group, node, node.label, r2, labelOptions);
346
- }
347
- return;
348
- }
349
- const isFocus = node.type === PLEXUS_NODE_TYPE_FOCUS;
350
- const r = isFocus ? PLEXUS_FOCUS_RADIUS : PLEXUS_LEAF_RADIUS;
351
- group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", obj ? getNodeFillForObject(obj) : "var(--color-neutral-500)");
352
- const text = labelForLeaf(node, obj);
353
- if (text) {
354
- if (isFocus) {
355
- appendRootLabel(group, text, r, labelOptions);
356
- } else {
357
- appendRadialLeafLabel(group, node, text, r, labelOptions);
358
- }
359
- }
360
- };
361
- }
362
- };
363
- var typenameGroupOf = (node) => {
364
- const obj = node.data?.data?.object;
365
- return obj ? Obj.getTypename(obj) ?? "(untyped)" : void 0;
366
- };
367
- var shortTypename = (typename) => {
368
- const last = typename.split(".").pop() ?? typename;
369
- return last.charAt(0).toUpperCase() + last.slice(1);
370
- };
371
- var labelForLeaf = (node, obj) => (obj && Obj.getLabel(obj)) ?? node.data?.data?.label ?? node.id;
372
-
373
- // src/containers/ExplorerArticle/ExplorerArticle.tsx
374
- var ExplorerArticle = ({ role, subject, variant }) => {
375
- const [view] = useObject(subject);
376
- const [filter, setFilter] = useState2();
377
- const db = view && Obj2.getDatabase(view);
378
- const model = useGraphModel(db, filter);
379
- const builder = useMemo2(() => new QueryBuilder(), []);
380
- const handleChange = useCallback2((value) => {
381
- setFilter(builder.build(value).filter);
382
- }, [
383
- builder
384
- ]);
385
- const [selected, setSelected] = useState2(isVariant(variant) ? variant : "force");
386
- useEffect2(() => {
387
- if (isVariant(variant)) {
388
- setSelected(variant);
389
- }
390
- }, [
391
- variant
392
- ]);
393
- const handleVariantChange = useCallback2((value) => {
394
- if (isVariant(value)) {
395
- setSelected(value);
396
- }
397
- }, []);
398
- const handleDismiss = useCallback2(() => {
399
- document.defaultView?.dispatchEvent(new DxAnchorActivate({
400
- dxn: "",
401
- label: "",
402
- trigger: document.body,
403
- state: false
404
- }));
405
- }, []);
406
- const handleHover = useCallback2((node, event) => {
407
- if (!node || !event) {
408
- return;
409
- }
410
- const obj = node.data;
411
- if (!obj || !Obj2.isObject(obj)) {
412
- return;
413
- }
414
- const dxn = Obj2.getURI(obj);
415
- if (!dxn) {
416
- return;
417
- }
418
- const target = event.target;
419
- target.dispatchEvent(new DxAnchorActivate({
420
- dxn,
421
- kind: "card",
422
- trigger: target,
423
- label: Obj2.getLabel(obj) ?? dxn
424
- }));
425
- }, []);
426
- const showToolbar = role === AppSurface.Article.role;
427
- if (!db || !model) {
428
- return null;
429
- }
430
- return /* @__PURE__ */ React2.createElement(Panel.Root, {
431
- role
432
- }, showToolbar && /* @__PURE__ */ React2.createElement(Panel.Toolbar, {
433
- asChild: true
434
- }, /* @__PURE__ */ React2.createElement(Toolbar.Root, null, /* @__PURE__ */ React2.createElement(QueryEditor, {
435
- db,
436
- onChange: handleChange
437
- }), /* @__PURE__ */ React2.createElement(Toolbar.ToggleGroup, {
438
- type: "single",
439
- value: selected,
440
- onValueChange: handleVariantChange
441
- }, VARIANTS.map(({ value, icon, label }) => /* @__PURE__ */ React2.createElement(Toolbar.ToggleGroupItem, {
442
- key: value,
443
- value,
444
- "aria-label": label,
445
- title: label
446
- }, /* @__PURE__ */ React2.createElement(Icon, {
447
- icon,
448
- size: 4
449
- })))))), /* @__PURE__ */ React2.createElement(Panel.Content, null, /* @__PURE__ */ React2.createElement(Visualization, {
450
- classNames: "bg-base-surface",
451
- variant: selected,
452
- model,
453
- onNodeHover: handleHover,
454
- onSurfaceClick: handleDismiss
455
- })));
456
- };
457
- export {
458
- ExplorerArticle as default
459
- };
460
- //# sourceMappingURL=ExplorerArticle-MHEINWUL.mjs.map