@principal-ai/subsystems-react 0.56.13 → 0.56.14
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/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/subsystem/C4ProjectionView.d.ts +60 -0
- package/dist/subsystem/C4ProjectionView.d.ts.map +1 -0
- package/dist/subsystem/C4ProjectionView.js +336 -0
- package/dist/subsystem/C4ProjectionView.js.map +1 -0
- package/dist/subsystem/C4SketchView.d.ts +23 -0
- package/dist/subsystem/C4SketchView.d.ts.map +1 -0
- package/dist/subsystem/C4SketchView.js +119 -0
- package/dist/subsystem/C4SketchView.js.map +1 -0
- package/dist/subsystem/__fixtures__/c4Drilldown.d.ts +4 -27
- package/dist/subsystem/__fixtures__/c4Drilldown.d.ts.map +1 -1
- package/dist/subsystem/__fixtures__/c4Drilldown.js +4 -56
- package/dist/subsystem/__fixtures__/c4Drilldown.js.map +1 -1
- package/dist/subsystem/__fixtures__/c4Sketch.d.ts +9 -0
- package/dist/subsystem/__fixtures__/c4Sketch.d.ts.map +1 -0
- package/dist/subsystem/__fixtures__/c4Sketch.js +89 -0
- package/dist/subsystem/__fixtures__/c4Sketch.js.map +1 -0
- package/dist/subsystem/c4Sketch.d.ts +74 -0
- package/dist/subsystem/c4Sketch.d.ts.map +1 -0
- package/dist/subsystem/c4Sketch.js +9 -0
- package/dist/subsystem/c4Sketch.js.map +1 -0
- package/dist/subsystem/c4TrailFocus.d.ts +30 -0
- package/dist/subsystem/c4TrailFocus.d.ts.map +1 -0
- package/dist/subsystem/c4TrailFocus.js +57 -0
- package/dist/subsystem/c4TrailFocus.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +14 -0
- package/src/stories/Canvases/C4/C4Sketch.stories.tsx +36 -0
- package/src/stories/Canvases/C4/C4ToComponentDrill.stories.tsx +18 -429
- package/src/subsystem/C4ProjectionView.tsx +595 -0
- package/src/subsystem/C4SketchView.tsx +241 -0
- package/src/subsystem/__fixtures__/c4Drilldown.ts +7 -58
- package/src/subsystem/__fixtures__/c4Sketch.ts +94 -0
- package/src/subsystem/c4Sketch.ts +78 -0
- package/src/subsystem/c4TrailFocus.ts +62 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -298,6 +298,20 @@ export type {
|
|
|
298
298
|
// --- C4 projection -----------------------------------------------------
|
|
299
299
|
export { C4Graph } from './subsystem/C4Graph';
|
|
300
300
|
export type { C4GraphProps } from './subsystem/C4Graph';
|
|
301
|
+
export { C4ProjectionView } from './subsystem/C4ProjectionView';
|
|
302
|
+
export type { C4ProjectionViewProps } from './subsystem/C4ProjectionView';
|
|
303
|
+
export { C4SketchView } from './subsystem/C4SketchView';
|
|
304
|
+
export type { C4SketchViewProps } from './subsystem/C4SketchView';
|
|
305
|
+
export type {
|
|
306
|
+
C4Sketch,
|
|
307
|
+
C4SketchSummary,
|
|
308
|
+
SubsystemC4Source,
|
|
309
|
+
SubsystemC4Projection,
|
|
310
|
+
} from './subsystem/c4Sketch';
|
|
311
|
+
export {
|
|
312
|
+
deriveLitC4Edges,
|
|
313
|
+
deriveTrailPlayerElements,
|
|
314
|
+
} from './subsystem/c4TrailFocus';
|
|
301
315
|
export { GraphSidebar } from './subsystem/GraphSidebar';
|
|
302
316
|
export type { GraphSidebarProps, GraphSidebarTab } from './subsystem/GraphSidebar';
|
|
303
317
|
export { GraphWithSidebar } from './subsystem/GraphWithSidebar';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import '@xyflow/react/dist/style.css';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
5
|
+
import { C4SketchView } from '../../../subsystem/C4SketchView';
|
|
6
|
+
import { SKETCH_FIXTURE } from '../../../subsystem/__fixtures__/c4Sketch';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Canvases/C4/C4Sketch',
|
|
10
|
+
component: C4SketchView,
|
|
11
|
+
parameters: { layout: 'fullscreen' },
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
decorators: [
|
|
14
|
+
(Story) => (
|
|
15
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
16
|
+
<Story />
|
|
17
|
+
</ThemeProvider>
|
|
18
|
+
),
|
|
19
|
+
],
|
|
20
|
+
} satisfies Meta<typeof C4SketchView>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
function Demo() {
|
|
26
|
+
return (
|
|
27
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
28
|
+
<C4SketchView sketch={SKETCH_FIXTURE} />
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A standalone authored sketch: no model, no grounding, no promotion. */
|
|
34
|
+
export const Sketch: Story = {
|
|
35
|
+
render: () => <Demo />,
|
|
36
|
+
};
|
|
@@ -1,74 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import '@xyflow/react/dist/style.css';
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
4
|
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
5
|
-
import {
|
|
6
|
-
import { C4Graph } from '../../../subsystem/C4Graph';
|
|
7
|
-
import { GraphWithSidebar } from '../../../subsystem/GraphWithSidebar';
|
|
8
|
-
import { TrailStepsNav } from '../../../subsystem/TrailStepsNav';
|
|
9
|
-
import type { C4ConstructGraph } from '../../../subsystem/C4Graph';
|
|
10
|
-
import { deriveGraphEdges, trailStepGraphEdgeId } from '../../../subsystem/model';
|
|
5
|
+
import { C4ProjectionView } from '../../../subsystem/C4ProjectionView';
|
|
11
6
|
import {
|
|
12
7
|
DRILLDOWN_C4_MODEL,
|
|
13
8
|
DRILLDOWN_COMPONENTS,
|
|
14
9
|
DRILLDOWN_DOC,
|
|
15
|
-
DRILLDOWN_ELEMENTS,
|
|
16
10
|
DRILLDOWN_MEMBERS,
|
|
17
11
|
DRILLDOWN_TRAILS,
|
|
18
|
-
deriveLitC4Edges,
|
|
19
|
-
deriveTrailPlayerElements,
|
|
20
12
|
} from '../../../subsystem/__fixtures__/c4Drilldown';
|
|
21
13
|
|
|
22
|
-
/** The model this story is grounded in — shown in the sidebar header. */
|
|
23
|
-
const MODEL_DESCRIPTION =
|
|
24
|
-
'The subsystem this C4 view summarizes. Focusing a trail opens the containers that hold its cast and folds the trail in as real nodes and edges, nested under their C4 component.';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The C4 → construct drill-down, in one canvas.
|
|
28
|
-
*
|
|
29
|
-
* The C4 view is the high-level summary: containers, externals, and the
|
|
30
|
-
* rolled-up lines between them. Focusing a trail folds the implementation into
|
|
31
|
-
* it — the constructs (the component graph's own `subsystem-component` nodes
|
|
32
|
-
* and `subsystem-edge` edges) are laid out by ELK inside the opened containers
|
|
33
|
-
* and morphed in with them, so the C4 canvas carries both the summary and the
|
|
34
|
-
* implementation. The model is the REAL subsystem model this repo lives under
|
|
35
|
-
* (`sg-1788913589907-ieokymqg9` "Gist share from Studio": 19 components, 15/15
|
|
36
|
-
* verified claims, 7 walkthroughs checked).
|
|
37
|
-
*
|
|
38
|
-
* Interaction:
|
|
39
|
-
* - click a C4 edge → it selects, and the story focuses the first trail that
|
|
40
|
-
* backs the line — the "edge is the summary, the trail is the
|
|
41
|
-
* implementation" jump;
|
|
42
|
-
* - the C4 edge inspector lists every backing trail; clicking one focuses it;
|
|
43
|
-
* - focusing a trail HIDES every C4 element whose components don't appear in
|
|
44
|
-
* it, and OPENS the containers that hold the cast, so the participating
|
|
45
|
-
* components come in at Level 3 instead of staying a collapsed box;
|
|
46
|
-
* - each opened container hosts the focused trail's components as real
|
|
47
|
-
* `subsystem-component` nodes and `subsystem-edge` edges. The constructs
|
|
48
|
-
* nest under the authored C4 component that claims them (container →
|
|
49
|
-
* component → construct); one no component claims sits directly in the
|
|
50
|
-
* container. An edge between constructs in the same container draws as a
|
|
51
|
-
* subsystem edge; one that would cross containers is left to the C4
|
|
52
|
-
* container-level line. Clicking a construct opens its **functionality** —
|
|
53
|
-
* the rendered declaration and its purpose — in the bottom-left panel. The
|
|
54
|
-
* host resolves it (`constructGraphByContainer`) so the C4 view stays free
|
|
55
|
-
* of the document;
|
|
56
|
-
* - hovering a *step* row DIMS the constructs down to that single hop: every
|
|
57
|
-
* construct except the step's `from`/`to`, and every construct edge except
|
|
58
|
-
* the step's own, fades (`dimmedConstructIds` / `dimmedConstructEdgeIds`).
|
|
59
|
-
* Transient: falls back to the focused view on leave;
|
|
60
|
-
* - the C4 view is composed with its own sidebar through the shared
|
|
61
|
-
* `GraphWithSidebar` wrapper: the sidebar carries the **trails**. The C4
|
|
62
|
-
* selection inspector stays the graph's own floating overlay; the wrapper
|
|
63
|
-
* is the composition point, so the C4 graph itself stays overlay-only.
|
|
64
|
-
*
|
|
65
|
-
* The status bar below the C4 view shows the current selection, so the wiring
|
|
66
|
-
* is visible while it is being argued with.
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
14
|
const meta = {
|
|
70
15
|
title: 'Canvases/C4/C4ToComponentDrill',
|
|
71
|
-
component:
|
|
16
|
+
component: C4ProjectionView,
|
|
72
17
|
parameters: { layout: 'fullscreen' },
|
|
73
18
|
tags: ['autodocs'],
|
|
74
19
|
decorators: [
|
|
@@ -78,386 +23,30 @@ const meta = {
|
|
|
78
23
|
</ThemeProvider>
|
|
79
24
|
),
|
|
80
25
|
],
|
|
81
|
-
} satisfies Meta<typeof
|
|
26
|
+
} satisfies Meta<typeof C4ProjectionView>;
|
|
82
27
|
|
|
83
28
|
export default meta;
|
|
84
29
|
type Story = StoryObj<typeof meta>;
|
|
85
30
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const [focusedTrailId, setFocusedTrailId] = useState<string | null>(null);
|
|
89
|
-
// The trail (or step) currently hovered in the trails panel — its transient
|
|
90
|
-
// preview drives a step-scoped dim. `stepIndex: null` means the whole trail
|
|
91
|
-
// (a collapsed row hover).
|
|
92
|
-
const [hoveredStep, setHoveredStep] = useState<{
|
|
93
|
-
trailId: string;
|
|
94
|
-
stepIndex: number | null;
|
|
95
|
-
} | null>(null);
|
|
96
|
-
// The persistently-focused step (a step click in the sidebar). Drives the
|
|
97
|
-
// sticky construct spotlight and the C4 graph's code drawer; the hover below
|
|
98
|
-
// is the transient preview that shares the same dim.
|
|
99
|
-
const [focusedStep, setFocusedStep] = useState<{
|
|
100
|
-
trailId: string;
|
|
101
|
-
stepIndex: number;
|
|
102
|
-
} | null>(null);
|
|
103
|
-
/**
|
|
104
|
-
* EXPERIMENT: when a trail is focused, remove the elements it does not touch
|
|
105
|
-
* from the model outright, so ELK lays the diagram out to the cast alone
|
|
106
|
-
* instead of leaving gaps where the hidden boxes were. `null` = nothing
|
|
107
|
-
* focused, the whole model.
|
|
108
|
-
*/
|
|
109
|
-
const trailPlayers = useMemo(() => {
|
|
110
|
-
if (!focusedTrailId) return null;
|
|
111
|
-
const trail = DRILLDOWN_TRAILS.find((t) => t.id === focusedTrailId);
|
|
112
|
-
return trail ? deriveTrailPlayerElements(trail.steps, DRILLDOWN_MEMBERS) : null;
|
|
113
|
-
}, [focusedTrailId]);
|
|
114
|
-
|
|
115
|
-
const model = useMemo(() => {
|
|
116
|
-
if (!trailPlayers) return { ...DRILLDOWN_C4_MODEL, openContainerIds: openIds };
|
|
117
|
-
const nodes = DRILLDOWN_C4_MODEL.nodes.filter((n) => trailPlayers.has(n.id));
|
|
118
|
-
const ids = new Set(nodes.map((n) => n.id));
|
|
119
|
-
const edges = DRILLDOWN_C4_MODEL.edges.filter(
|
|
120
|
-
(e) => ids.has(e.source) && ids.has(e.target),
|
|
121
|
-
);
|
|
122
|
-
return { ...DRILLDOWN_C4_MODEL, nodes, edges, openContainerIds: openIds };
|
|
123
|
-
}, [openIds, trailPlayers]);
|
|
124
|
-
|
|
125
|
-
const toggle = useCallback((id: string) => {
|
|
126
|
-
setOpenIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id].sort()));
|
|
127
|
-
}, []);
|
|
128
|
-
|
|
129
|
-
const allElementIds = useMemo(
|
|
130
|
-
() => DRILLDOWN_C4_MODEL.nodes.map((n) => n.id),
|
|
131
|
-
[],
|
|
132
|
-
);
|
|
133
|
-
const allEdgeIds = useMemo(
|
|
134
|
-
() => DRILLDOWN_C4_MODEL.edges.map((e) => e.id),
|
|
135
|
-
[],
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* The C4 containers that hold a component the trail touches — opened when the
|
|
140
|
-
* trail is focused so its cast comes in at Level 3 instead of staying a
|
|
141
|
-
* collapsed container box. A trail whose endpoints no container claims (the
|
|
142
|
-
* anti-library keys) opens nothing.
|
|
143
|
-
*/
|
|
144
|
-
const containersForTrail = useCallback((trailId: string): string[] => {
|
|
145
|
-
const trail = DRILLDOWN_TRAILS.find((t) => t.id === trailId);
|
|
146
|
-
if (!trail) return [];
|
|
147
|
-
const aliases = new Set<string>();
|
|
148
|
-
for (const s of trail.steps) {
|
|
149
|
-
aliases.add(s.from);
|
|
150
|
-
aliases.add(s.to);
|
|
151
|
-
}
|
|
152
|
-
const out: string[] = [];
|
|
153
|
-
for (const el of DRILLDOWN_C4_MODEL.nodes) {
|
|
154
|
-
if (el.kind !== 'container') continue;
|
|
155
|
-
const members = DRILLDOWN_MEMBERS[el.id] ?? [];
|
|
156
|
-
if (members.some((a) => aliases.has(a))) out.push(el.id);
|
|
157
|
-
}
|
|
158
|
-
return out;
|
|
159
|
-
}, []);
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* The containers the *current* trail focus opened — remembered so clearing
|
|
163
|
-
* the focus (or moving to another trail) can close exactly those and leave any
|
|
164
|
-
* the reader opened by hand alone.
|
|
165
|
-
*/
|
|
166
|
-
const trailOpenedRef = useRef<Set<string>>(new Set());
|
|
167
|
-
|
|
168
|
-
const openContainersForTrail = useCallback(
|
|
169
|
-
(trailId: string) => {
|
|
170
|
-
const ids = containersForTrail(trailId);
|
|
171
|
-
if (ids.length === 0) return;
|
|
172
|
-
trailOpenedRef.current = new Set([...trailOpenedRef.current, ...ids]);
|
|
173
|
-
setOpenIds((prev) => [...new Set([...prev, ...ids])].sort());
|
|
174
|
-
},
|
|
175
|
-
[containersForTrail],
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
/** Close the containers the current trail focus opened. */
|
|
179
|
-
const closeTrailContainers = useCallback(() => {
|
|
180
|
-
const opened = trailOpenedRef.current;
|
|
181
|
-
trailOpenedRef.current = new Set();
|
|
182
|
-
if (opened.size === 0) return;
|
|
183
|
-
setOpenIds((prev) => prev.filter((id) => !opened.has(id)));
|
|
184
|
-
}, []);
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* The single source of the C4 emphasis, derived from the two focus facts —
|
|
188
|
-
* mirroring the component graph:
|
|
189
|
-
*
|
|
190
|
-
* hidden — an EXPANDED/focused trail removes every element it does not
|
|
191
|
-
* touch (the analogue of the component graph's opened-flow hide),
|
|
192
|
-
* lines included; `null` when nothing is focused.
|
|
193
|
-
* dimmed — the HOVER spotlight (a step, or a whole trail for a collapsed
|
|
194
|
-
* row) fades everything outside it; `null` when nothing is hovered.
|
|
195
|
-
*
|
|
196
|
-
* A step internal to a container folds into no C4 line, so its hover dims
|
|
197
|
-
* every edge while the container stays bright — no drawn edge represents it.
|
|
198
|
-
*
|
|
199
|
-
* The spotlight is the *selected* step when one is focused (sticky), else the
|
|
200
|
-
* hovered one (transient) — so a step click keeps its implementation lit.
|
|
201
|
-
*/
|
|
202
|
-
const activeStep = focusedStep ?? hoveredStep;
|
|
203
|
-
const emphasis = useMemo(() => {
|
|
204
|
-
let hiddenElementIds: ReadonlySet<string> | null = null;
|
|
205
|
-
let hiddenEdgeIds: ReadonlySet<string> | null = null;
|
|
206
|
-
if (focusedTrailId) {
|
|
207
|
-
const trail = DRILLDOWN_TRAILS.find((t) => t.id === focusedTrailId);
|
|
208
|
-
const players = trail
|
|
209
|
-
? deriveTrailPlayerElements(trail.steps, DRILLDOWN_MEMBERS)
|
|
210
|
-
: new Set<string>();
|
|
211
|
-
hiddenElementIds = new Set(allElementIds.filter((id) => !players.has(id)));
|
|
212
|
-
const lit = deriveLitC4Edges({ trailId: focusedTrailId }, DRILLDOWN_C4_MODEL.edges);
|
|
213
|
-
hiddenEdgeIds = new Set(allEdgeIds.filter((id) => !lit.has(id)));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
let dimmedElementIds: ReadonlySet<string> | null = null;
|
|
217
|
-
let dimmedEdgeIds: ReadonlySet<string> | null = null;
|
|
218
|
-
if (activeStep) {
|
|
219
|
-
const trail = DRILLDOWN_TRAILS.find((t) => t.id === activeStep.trailId);
|
|
220
|
-
const steps =
|
|
221
|
-
!trail
|
|
222
|
-
? []
|
|
223
|
-
: activeStep.stepIndex != null
|
|
224
|
-
? trail.steps.slice(activeStep.stepIndex, activeStep.stepIndex + 1)
|
|
225
|
-
: trail.steps;
|
|
226
|
-
const spotlight = deriveTrailPlayerElements(steps, DRILLDOWN_MEMBERS);
|
|
227
|
-
dimmedElementIds = new Set(allElementIds.filter((id) => !spotlight.has(id)));
|
|
228
|
-
const lit = deriveLitC4Edges(
|
|
229
|
-
{
|
|
230
|
-
trailId: activeStep.trailId,
|
|
231
|
-
...(activeStep.stepIndex != null ? { stepIndex: activeStep.stepIndex } : {}),
|
|
232
|
-
},
|
|
233
|
-
DRILLDOWN_C4_MODEL.edges,
|
|
234
|
-
);
|
|
235
|
-
dimmedEdgeIds = new Set(allEdgeIds.filter((id) => !lit.has(id)));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return { hiddenElementIds, hiddenEdgeIds, dimmedElementIds, dimmedEdgeIds };
|
|
239
|
-
}, [focusedTrailId, activeStep, allElementIds, allEdgeIds]);
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* The construct-level subgraph each opened container hosts: the focused
|
|
243
|
-
* trail's referenced components, grouped by the authored C4 component that
|
|
244
|
-
* claims each (so constructs nest container → component → construct), with
|
|
245
|
-
* the trail's steps as their edges. Scoped per container, so an edge that
|
|
246
|
-
* crosses containers is dropped here and stays a C4 container-level line.
|
|
247
|
-
*/
|
|
248
|
-
const constructGraphByContainer = useMemo(() => {
|
|
249
|
-
if (!focusedTrailId) return undefined;
|
|
250
|
-
const trail = DRILLDOWN_TRAILS.find((t) => t.id === focusedTrailId);
|
|
251
|
-
if (!trail) return undefined;
|
|
252
|
-
const byAlias = new Map(DRILLDOWN_COMPONENTS.map((c) => [c.alias, c]));
|
|
253
|
-
const referenced = new Set<string>();
|
|
254
|
-
for (const s of trail.steps) {
|
|
255
|
-
referenced.add(s.from);
|
|
256
|
-
referenced.add(s.to);
|
|
257
|
-
}
|
|
258
|
-
const componentOf = new Map<string, string>();
|
|
259
|
-
for (const el of DRILLDOWN_ELEMENTS) {
|
|
260
|
-
if (el.kind === 'component') for (const a of el.members ?? []) componentOf.set(a, el.id);
|
|
261
|
-
}
|
|
262
|
-
const out = new Map<string, C4ConstructGraph>();
|
|
263
|
-
for (const el of DRILLDOWN_ELEMENTS) {
|
|
264
|
-
if (el.kind !== 'container') continue;
|
|
265
|
-
const aliases = (DRILLDOWN_MEMBERS[el.id] ?? []).filter(
|
|
266
|
-
(a) => referenced.has(a) && byAlias.has(a),
|
|
267
|
-
);
|
|
268
|
-
if (aliases.length === 0) continue;
|
|
269
|
-
out.set(el.id, {
|
|
270
|
-
components: aliases.map((a) => byAlias.get(a)!),
|
|
271
|
-
trails: [trail],
|
|
272
|
-
componentOf,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
return out;
|
|
276
|
-
}, [focusedTrailId]);
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* The construct-level spotlight for the active step, matching the component
|
|
280
|
-
* graph: every construct except the step's `from`/`to`, and every construct
|
|
281
|
-
* edge except the step's own, fades. The C4-element spotlight (`emphasis`)
|
|
282
|
-
* only reaches containers, so this is what narrows the constructs to the hop.
|
|
283
|
-
*/
|
|
284
|
-
const constructEmphasis = useMemo(() => {
|
|
285
|
-
const none = { ids: null as ReadonlySet<string> | null, edgeIds: null as ReadonlySet<string> | null };
|
|
286
|
-
if (!activeStep) return none;
|
|
287
|
-
const trail = DRILLDOWN_TRAILS.find((t) => t.id === activeStep.trailId);
|
|
288
|
-
if (!trail) return none;
|
|
289
|
-
const steps =
|
|
290
|
-
activeStep.stepIndex != null ? [trail.steps[activeStep.stepIndex]] : trail.steps;
|
|
291
|
-
const spotlight = new Set<string>();
|
|
292
|
-
const litEdges = new Set<string>();
|
|
293
|
-
for (const s of steps) {
|
|
294
|
-
spotlight.add(s.from);
|
|
295
|
-
spotlight.add(s.to);
|
|
296
|
-
litEdges.add(trailStepGraphEdgeId(s));
|
|
297
|
-
}
|
|
298
|
-
const allAliases = new Set<string>();
|
|
299
|
-
const allEdgeIds = new Set<string>();
|
|
300
|
-
for (const cg of constructGraphByContainer?.values() ?? []) {
|
|
301
|
-
for (const c of cg.components) allAliases.add(c.alias);
|
|
302
|
-
for (const e of deriveGraphEdges({ trails: [...cg.trails] })) allEdgeIds.add(e.id);
|
|
303
|
-
}
|
|
304
|
-
return {
|
|
305
|
-
ids: new Set([...allAliases].filter((a) => !spotlight.has(a))),
|
|
306
|
-
edgeIds: new Set([...allEdgeIds].filter((id) => !litEdges.has(id))),
|
|
307
|
-
};
|
|
308
|
-
}, [activeStep, constructGraphByContainer]);
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Focus a trail: the C4 hides non-members, opens the containers that hold
|
|
312
|
-
* them (so the cast shows at Level 3), and the component graph deep-links.
|
|
313
|
-
*/
|
|
314
|
-
const focusTrail = useCallback(
|
|
315
|
-
(trailId: string) => {
|
|
316
|
-
setFocusedTrailId(trailId);
|
|
317
|
-
setHoveredStep(null);
|
|
318
|
-
// Swap the previous trail's opened containers for this one's, so moving
|
|
319
|
-
// between trails does not accumulate open boxes.
|
|
320
|
-
closeTrailContainers();
|
|
321
|
-
openContainersForTrail(trailId);
|
|
322
|
-
},
|
|
323
|
-
[openContainersForTrail, closeTrailContainers],
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
const clearFocus = useCallback(() => {
|
|
327
|
-
setFocusedTrailId(null);
|
|
328
|
-
setHoveredStep(null);
|
|
329
|
-
// Collapse the containers the focus opened, so Back leaves the diagram as
|
|
330
|
-
// it was rather than with the trail's boxes still expanded.
|
|
331
|
-
closeTrailContainers();
|
|
332
|
-
}, [closeTrailContainers]);
|
|
31
|
+
const DESCRIPTION =
|
|
32
|
+
'The subsystem this C4 view summarizes. Focusing a trail opens the containers that hold its cast and folds the trail in as real nodes and edges, nested under their C4 component.';
|
|
333
33
|
|
|
34
|
+
function Demo() {
|
|
334
35
|
return (
|
|
335
|
-
<div
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
<header
|
|
346
|
-
style={{
|
|
347
|
-
flexShrink: 0,
|
|
348
|
-
display: 'flex',
|
|
349
|
-
alignItems: 'center',
|
|
350
|
-
gap: 12,
|
|
351
|
-
padding: '12px 16px',
|
|
352
|
-
background: defaultEditorTheme.colors.background,
|
|
353
|
-
borderBottom: `1px solid ${defaultEditorTheme.colors.border}`,
|
|
354
|
-
}}
|
|
355
|
-
>
|
|
356
|
-
<h2
|
|
357
|
-
style={{
|
|
358
|
-
margin: 0,
|
|
359
|
-
minWidth: 0,
|
|
360
|
-
fontSize: defaultEditorTheme.fontSizes[2],
|
|
361
|
-
fontWeight: 600,
|
|
362
|
-
color: defaultEditorTheme.colors.text,
|
|
363
|
-
fontFamily: defaultEditorTheme.fonts.monospace,
|
|
364
|
-
overflowWrap: 'anywhere',
|
|
365
|
-
}}
|
|
366
|
-
>
|
|
367
|
-
{DRILLDOWN_DOC.title}
|
|
368
|
-
</h2>
|
|
369
|
-
</header>
|
|
370
|
-
{/* C4 — the summary and, on focus, the construct implementation. */}
|
|
371
|
-
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
|
|
372
|
-
<GraphWithSidebar
|
|
373
|
-
persistKey="c4-drill"
|
|
374
|
-
defaultWidth={320}
|
|
375
|
-
minWidth={240}
|
|
376
|
-
maxWidth={480}
|
|
377
|
-
headerMaxHeight="30%"
|
|
378
|
-
header={
|
|
379
|
-
<>
|
|
380
|
-
<div style={{ fontSize: 12, lineHeight: 1.5 }}>
|
|
381
|
-
<IndustryMarkdownSlide
|
|
382
|
-
content={MODEL_DESCRIPTION}
|
|
383
|
-
slideIdPrefix="c4-desc"
|
|
384
|
-
slideIndex={0}
|
|
385
|
-
isVisible={true}
|
|
386
|
-
theme={defaultEditorTheme}
|
|
387
|
-
disableScroll={true}
|
|
388
|
-
disableBasePadding
|
|
389
|
-
enableKeyboardScrolling={false}
|
|
390
|
-
autoFocusOnVisible={false}
|
|
391
|
-
/>
|
|
392
|
-
</div>
|
|
393
|
-
</>
|
|
394
|
-
}
|
|
395
|
-
sidebarBody={
|
|
396
|
-
<TrailStepsNav
|
|
397
|
-
trails={DRILLDOWN_TRAILS}
|
|
398
|
-
focusedTrailId={focusedTrailId}
|
|
399
|
-
focusedStepIndex={
|
|
400
|
-
focusedStep && focusedStep.trailId === focusedTrailId
|
|
401
|
-
? focusedStep.stepIndex
|
|
402
|
-
: null
|
|
403
|
-
}
|
|
404
|
-
hoveredTrailStep={hoveredStep}
|
|
405
|
-
onFocusFlow={(tl) => {
|
|
406
|
-
setFocusedStep(null);
|
|
407
|
-
focusTrail(tl.id);
|
|
408
|
-
}}
|
|
409
|
-
onClearFocus={() => {
|
|
410
|
-
setFocusedStep(null);
|
|
411
|
-
clearFocus();
|
|
412
|
-
}}
|
|
413
|
-
onFocusStep={(tl, stepIndex) => {
|
|
414
|
-
// Toggle: clicking the selected step again clears it.
|
|
415
|
-
setFocusedStep((prev) =>
|
|
416
|
-
prev && prev.trailId === tl.id && prev.stepIndex === stepIndex
|
|
417
|
-
? null
|
|
418
|
-
: { trailId: tl.id, stepIndex },
|
|
419
|
-
);
|
|
420
|
-
focusTrail(tl.id);
|
|
421
|
-
}}
|
|
422
|
-
onHoverStep={(tl, stepIndex) => {
|
|
423
|
-
setHoveredStep({ trailId: tl.id, stepIndex });
|
|
424
|
-
}}
|
|
425
|
-
onHoverFlow={(tl) => {
|
|
426
|
-
setHoveredStep({ trailId: tl.id, stepIndex: null });
|
|
427
|
-
}}
|
|
428
|
-
onLeaveStep={() => {
|
|
429
|
-
setHoveredStep(null);
|
|
430
|
-
}}
|
|
431
|
-
/>
|
|
432
|
-
}
|
|
433
|
-
>
|
|
434
|
-
<C4Graph
|
|
435
|
-
model={model}
|
|
436
|
-
onSelectEdge={(edge) => {
|
|
437
|
-
if (edge && (edge.trailRefs ?? []).length > 0) {
|
|
438
|
-
// Auto-drill: an edge click focuses its first backing trail.
|
|
439
|
-
focusTrail(edge.trailRefs![0]!.trailId);
|
|
440
|
-
}
|
|
441
|
-
}}
|
|
442
|
-
onTrailClick={(ref) => focusTrail(ref.trailId)}
|
|
443
|
-
onToggleContainer={toggle}
|
|
444
|
-
hiddenElementIds={emphasis.hiddenElementIds}
|
|
445
|
-
hiddenEdgeIds={emphasis.hiddenEdgeIds}
|
|
446
|
-
dimmedElementIds={emphasis.dimmedElementIds}
|
|
447
|
-
dimmedEdgeIds={emphasis.dimmedEdgeIds}
|
|
448
|
-
dimmedConstructIds={constructEmphasis.ids}
|
|
449
|
-
dimmedConstructEdgeIds={constructEmphasis.edgeIds}
|
|
450
|
-
constructGraphByContainer={constructGraphByContainer}
|
|
451
|
-
focusedStep={focusedStep}
|
|
452
|
-
onCloseStep={() => setFocusedStep(null)}
|
|
453
|
-
/>
|
|
454
|
-
</GraphWithSidebar>
|
|
455
|
-
</div>
|
|
456
|
-
|
|
36
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
37
|
+
<C4ProjectionView
|
|
38
|
+
model={DRILLDOWN_C4_MODEL}
|
|
39
|
+
components={DRILLDOWN_COMPONENTS}
|
|
40
|
+
trails={DRILLDOWN_TRAILS}
|
|
41
|
+
memberships={DRILLDOWN_MEMBERS}
|
|
42
|
+
title={DRILLDOWN_DOC.title}
|
|
43
|
+
description={DESCRIPTION}
|
|
44
|
+
persistKey="c4-projection-drill"
|
|
45
|
+
/>
|
|
457
46
|
</div>
|
|
458
47
|
);
|
|
459
48
|
}
|
|
460
49
|
|
|
461
50
|
export const DrillIntoTrails: Story = {
|
|
462
51
|
render: () => <Demo />,
|
|
463
|
-
};
|
|
52
|
+
};
|