@principal-ai/principal-view-react 0.16.34 → 0.16.35
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/subsystem/ComponentDetail.d.ts +12 -0
- package/dist/subsystem/ComponentDetail.d.ts.map +1 -0
- package/dist/subsystem/ComponentDetail.js +95 -0
- package/dist/subsystem/ComponentDetail.js.map +1 -0
- package/dist/subsystem/EdgeLegendModal.d.ts +18 -0
- package/dist/subsystem/EdgeLegendModal.d.ts.map +1 -0
- package/dist/subsystem/EdgeLegendModal.js +98 -0
- package/dist/subsystem/EdgeLegendModal.js.map +1 -0
- package/dist/subsystem/FileDrawer.d.ts +16 -0
- package/dist/subsystem/FileDrawer.d.ts.map +1 -0
- package/dist/subsystem/FileDrawer.js +72 -0
- package/dist/subsystem/FileDrawer.js.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts +0 -3
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +28 -190
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +0 -3
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +2 -2
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +3 -21
- package/dist/subsystem/nodes.js.map +1 -1
- package/package.json +1 -1
- package/src/stories/SubsystemComponentGraph.stories.tsx +10 -10
- package/src/subsystem/ComponentDetail.tsx +187 -0
- package/src/subsystem/EdgeLegendModal.tsx +146 -0
- package/src/subsystem/FileDrawer.tsx +105 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +42 -327
- package/src/subsystem/model.ts +0 -3
- package/src/subsystem/nodes.tsx +2 -29
|
@@ -32,40 +32,21 @@ import {
|
|
|
32
32
|
applyNodeChanges,
|
|
33
33
|
} from '@xyflow/react';
|
|
34
34
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
35
|
-
import {
|
|
35
|
+
import { Map as MapIcon } from 'lucide-react';
|
|
36
36
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
37
37
|
import {
|
|
38
38
|
buildSubsystemGraph,
|
|
39
|
-
KIND_COLOR,
|
|
40
39
|
MECHANISM_COLOR,
|
|
41
|
-
deriveNameFromSymbol,
|
|
42
40
|
type SubsystemComponentEdge,
|
|
43
41
|
type SubsystemComponent,
|
|
44
42
|
type SubsystemEdgeMechanism,
|
|
45
43
|
} from './model';
|
|
46
|
-
import type { GraphifyComponentDetail } from '../graphify';
|
|
47
44
|
import { SubsystemComponentNode, SubsystemEdge, SUBSYSTEM_CALLBACKS } from './nodes';
|
|
48
45
|
import { SubsystemFileTree } from './SubsystemFileTree';
|
|
49
46
|
import { GraphLayoutCover } from './GraphLayoutCover';
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
['imports_from', 'imported by (reverse dependency)', true],
|
|
54
|
-
['re_exports', 're-exports symbols from', true],
|
|
55
|
-
['defines', 'defines / declares symbol', true],
|
|
56
|
-
['calls', 'function/method call (call graph edge)', true],
|
|
57
|
-
['extends', 'class inheritance', true],
|
|
58
|
-
['inherits', 'class inheritance', true],
|
|
59
|
-
['implements', 'implements interface / protocol', true],
|
|
60
|
-
['mixes_in', 'applies mixin', true],
|
|
61
|
-
['uses', 'general dependency (import, call, or reference)', false],
|
|
62
|
-
['method', 'structural: has method / member', true],
|
|
63
|
-
['references', 'type / symbol reference (not a call)', true],
|
|
64
|
-
['contains', 'structural: contains / encapsulates', true],
|
|
65
|
-
['feeds', 'data flow: output feeds into input', false],
|
|
66
|
-
['produces', 'data flow: produces / outputs', false],
|
|
67
|
-
['registers-into', 'registration pattern', false],
|
|
68
|
-
];
|
|
47
|
+
import { ComponentDetail } from './ComponentDetail';
|
|
48
|
+
import { FileDrawer } from './FileDrawer';
|
|
49
|
+
import { EdgeLegendModal, MECHANISM_DESCRIPTIONS } from './EdgeLegendModal';
|
|
69
50
|
|
|
70
51
|
export interface SubsystemComponentGraphProps {
|
|
71
52
|
components: SubsystemComponent[];
|
|
@@ -78,9 +59,6 @@ export interface SubsystemComponentGraphProps {
|
|
|
78
59
|
maxNodeWidth?: number;
|
|
79
60
|
/** Show edge labels (mechanism names) on the graph. @default true */
|
|
80
61
|
showEdgeLabels?: boolean;
|
|
81
|
-
/** Show the click-to-open file chip under each component name.
|
|
82
|
-
* @default true */
|
|
83
|
-
showFileBadges?: boolean;
|
|
84
62
|
/** Subsystem title displayed in the sidebar. */
|
|
85
63
|
title?: string;
|
|
86
64
|
/** Markdown description rendered in the sidebar. */
|
|
@@ -123,7 +101,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
|
|
|
123
101
|
measured: { w: number; h: number } | null;
|
|
124
102
|
}
|
|
125
103
|
|
|
126
|
-
function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels,
|
|
104
|
+
function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, title, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, renderFileView, renderFileViewer, onFileSelect }: InnerProps) {
|
|
127
105
|
const { theme } = useTheme();
|
|
128
106
|
const { fitView } = useReactFlow();
|
|
129
107
|
const viewport = useViewport();
|
|
@@ -135,6 +113,8 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
135
113
|
const [selected, setSelected] = useState<SubsystemComponent | null>(null);
|
|
136
114
|
// File currently shown in the bottom drawer (repo-root-relative path).
|
|
137
115
|
const [openFile, setOpenFile] = useState<string | null>(null);
|
|
116
|
+
// Edge-legend modal visibility (opened from the canvas's top-left button).
|
|
117
|
+
const [legendOpen, setLegendOpen] = useState(false);
|
|
138
118
|
// Component the pointer is over (null on leave) → transient tree highlight.
|
|
139
119
|
const [hoveredComponentId, setHoveredComponentId] = useState<string | null>(null);
|
|
140
120
|
// Ref mirror of `selected` so the SUBSYSTEM_CALLBACKS click handler (a
|
|
@@ -248,19 +228,15 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
248
228
|
// component lives in that file — the node renderer spotlights matches and
|
|
249
229
|
// dims non-matches (mirrors the edge-dimming behavior on selection).
|
|
250
230
|
const dispNodes = useMemo(() => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
data: { ...(n.data as object), showFileBadges },
|
|
254
|
-
}));
|
|
255
|
-
if (!openFile) return ns;
|
|
256
|
-
return ns.map((n) => {
|
|
231
|
+
if (!openFile) return xyflowNodesBase;
|
|
232
|
+
return xyflowNodesBase.map((n) => {
|
|
257
233
|
const comp = (n.data as { component?: SubsystemComponent } | undefined)?.component;
|
|
258
234
|
return {
|
|
259
235
|
...n,
|
|
260
236
|
data: { ...(n.data as object), fileMatch: comp?.file === openFile },
|
|
261
237
|
};
|
|
262
238
|
});
|
|
263
|
-
}, [xyflowNodesBase, openFile
|
|
239
|
+
}, [xyflowNodesBase, openFile]);
|
|
264
240
|
|
|
265
241
|
const baseNodesKey = useMemo(() => nodes.map((n) => n.id).sort().join(','), [nodes]);
|
|
266
242
|
const baseEdgesKey = useMemo(
|
|
@@ -404,8 +380,6 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
404
380
|
return new Set(edgeLabels.map((l) => l.mechanism));
|
|
405
381
|
}, [edgeLabels]);
|
|
406
382
|
|
|
407
|
-
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
408
|
-
|
|
409
383
|
// Unique source files across components → sidebar file tree.
|
|
410
384
|
const files = useMemo(
|
|
411
385
|
() => Array.from(new Set(components.map((c) => c.file).filter(Boolean))).sort(),
|
|
@@ -436,8 +410,8 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
436
410
|
|
|
437
411
|
return (
|
|
438
412
|
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'row' }}>
|
|
439
|
-
{/* Sidebar: scrollable title/description
|
|
440
|
-
{(title || description ||
|
|
413
|
+
{/* Sidebar: scrollable title/description on top, file tree pinned to the bottom half */}
|
|
414
|
+
{(title || description || sidebarExtra || sidebarAfterDescription || files.length > 0) && (
|
|
441
415
|
<div
|
|
442
416
|
style={{
|
|
443
417
|
width: 340,
|
|
@@ -490,29 +464,6 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
490
464
|
</div>
|
|
491
465
|
)}
|
|
492
466
|
{sidebarAfterDescription}
|
|
493
|
-
{usedMechanisms.size > 0 && (
|
|
494
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
495
|
-
<span style={{ fontSize: theme.fontSizes[0] * 0.8, fontFamily: theme.fonts.monospace, textTransform: 'uppercase', color: muted, fontWeight: 600 }}>
|
|
496
|
-
Edge Legend
|
|
497
|
-
</span>
|
|
498
|
-
{MECHANISM_DESCRIPTIONS.filter(([m]) => usedMechanisms.has(m)).map(([mechanism, desc, verifiable]) => (
|
|
499
|
-
<div key={mechanism} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: theme.fontSizes[0] }}>
|
|
500
|
-
<span
|
|
501
|
-
style={{
|
|
502
|
-
width: 8,
|
|
503
|
-
height: 8,
|
|
504
|
-
borderRadius: verifiable ? '50%' : '30% 70% 70% 30% / 30% 30% 70% 70%',
|
|
505
|
-
background: MECHANISM_COLOR[mechanism],
|
|
506
|
-
border: verifiable ? 'none' : `1px dashed ${MECHANISM_COLOR[mechanism]}`,
|
|
507
|
-
flexShrink: 0,
|
|
508
|
-
}}
|
|
509
|
-
/>
|
|
510
|
-
<span style={{ fontFamily: theme.fonts.monospace, color: MECHANISM_COLOR[mechanism], fontWeight: 500 }}>{mechanism}</span>
|
|
511
|
-
<span style={{ color: muted }}>{desc}</span>
|
|
512
|
-
</div>
|
|
513
|
-
))}
|
|
514
|
-
</div>
|
|
515
|
-
)}
|
|
516
467
|
</div>
|
|
517
468
|
{files.length > 0 && (
|
|
518
469
|
<SubsystemFileTree
|
|
@@ -612,287 +563,51 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
612
563
|
<Background variant={BackgroundVariant.Dots} gap={16} size={1} />
|
|
613
564
|
<Controls showZoom showFitView showInteractive />
|
|
614
565
|
</ReactFlow>
|
|
615
|
-
{
|
|
616
|
-
|
|
617
|
-
{fileViewer && openFile ? fileViewer(openFile) : null}
|
|
618
|
-
</FileDrawer>
|
|
619
|
-
{/* Startup cover — hides measurement, layout swap, and camera settle. */}
|
|
620
|
-
<GraphLayoutCover revealed={layoutReady} />
|
|
621
|
-
{canvasOverlay}
|
|
622
|
-
</div>
|
|
623
|
-
</div>
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/** Detail panel for the selected component — the verifiable identity + sources
|
|
628
|
-
* moved off the canvas so nodes stay minimal. File content lives in the
|
|
629
|
-
* bottom FileDrawer, not here. */
|
|
630
|
-
function ComponentDetail({ component }: { component: SubsystemComponent }) {
|
|
631
|
-
const { theme } = useTheme();
|
|
632
|
-
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
633
|
-
const color = KIND_COLOR[component.kind] ?? '#888';
|
|
634
|
-
const displayName = deriveNameFromSymbol(component.symbol, component.kind, component.name, component.file);
|
|
635
|
-
const rows: Array<[string, string]> = [];
|
|
636
|
-
if (component.symbol) rows.push(['Symbol', component.symbol]);
|
|
637
|
-
if (component.file) rows.push(['File', component.file]);
|
|
638
|
-
if (component.purpose) rows.push(['Purpose', component.purpose]);
|
|
639
|
-
rows.push(['PURL', component.purl]);
|
|
640
|
-
|
|
641
|
-
return (
|
|
642
|
-
<div
|
|
643
|
-
style={{
|
|
644
|
-
borderTop: `1px solid ${theme.colors.border}`,
|
|
645
|
-
background: theme.colors.background,
|
|
646
|
-
padding: '10px 12px',
|
|
647
|
-
fontFamily: theme.fonts.body,
|
|
648
|
-
}}
|
|
649
|
-
>
|
|
650
|
-
<div
|
|
651
|
-
style={{
|
|
652
|
-
display: 'flex',
|
|
653
|
-
alignItems: 'center',
|
|
654
|
-
gap: 8,
|
|
655
|
-
fontSize: theme.fontSizes[1],
|
|
656
|
-
fontWeight: 600,
|
|
657
|
-
marginBottom: 6,
|
|
658
|
-
}}
|
|
659
|
-
>
|
|
660
|
-
<span style={{ color }}>{displayName}</span>
|
|
661
|
-
<span
|
|
662
|
-
style={{
|
|
663
|
-
fontSize: theme.fontSizes[0] * 0.8,
|
|
664
|
-
fontFamily: theme.fonts.monospace,
|
|
665
|
-
textTransform: 'uppercase',
|
|
666
|
-
color,
|
|
667
|
-
}}
|
|
668
|
-
>
|
|
669
|
-
{component.kind}
|
|
670
|
-
</span>
|
|
671
|
-
</div>
|
|
672
|
-
{rows.map(([k, v]) => (
|
|
673
|
-
<div
|
|
674
|
-
key={k}
|
|
675
|
-
style={{ display: 'flex', gap: 8, fontSize: theme.fontSizes[0], lineHeight: 1.5 }}
|
|
676
|
-
>
|
|
677
|
-
<span
|
|
678
|
-
style={{
|
|
679
|
-
width: 64,
|
|
680
|
-
flexShrink: 0,
|
|
681
|
-
color: muted,
|
|
682
|
-
fontFamily: theme.fonts.monospace,
|
|
683
|
-
textTransform: 'uppercase',
|
|
684
|
-
fontSize: theme.fontSizes[0] * 0.8,
|
|
685
|
-
}}
|
|
686
|
-
>
|
|
687
|
-
{k}
|
|
688
|
-
</span>
|
|
689
|
-
<span style={{ color: theme.colors.text, fontFamily: theme.fonts.monospace }}>
|
|
690
|
-
{v}
|
|
691
|
-
</span>
|
|
692
|
-
</div>
|
|
693
|
-
))}
|
|
694
|
-
{component.detail && <GraphifyDetailSections detail={component.detail} />}
|
|
695
|
-
</div>
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/** Bottom panel that slides up from the bottom of the graph area to show a
|
|
700
|
-
* file's contents — opened by node clicks and sidebar file-tree clicks
|
|
701
|
-
* alike. Sits in normal flow (canvas shrinks while open, nothing covered)
|
|
702
|
-
* and animates via height; stays mounted so open/close animates. */
|
|
703
|
-
function FileDrawer({
|
|
704
|
-
file,
|
|
705
|
-
onClose,
|
|
706
|
-
children,
|
|
707
|
-
}: {
|
|
708
|
-
file: string | null;
|
|
709
|
-
onClose: () => void;
|
|
710
|
-
children?: ReactNode;
|
|
711
|
-
}) {
|
|
712
|
-
const { theme } = useTheme();
|
|
713
|
-
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
714
|
-
const open = file !== null;
|
|
715
|
-
|
|
716
|
-
useEffect(() => {
|
|
717
|
-
if (!open) return;
|
|
718
|
-
const onKey = (e: KeyboardEvent) => {
|
|
719
|
-
if (e.key === 'Escape') onClose();
|
|
720
|
-
};
|
|
721
|
-
window.addEventListener('keydown', onKey);
|
|
722
|
-
return () => window.removeEventListener('keydown', onKey);
|
|
723
|
-
}, [open, onClose]);
|
|
724
|
-
|
|
725
|
-
return (
|
|
726
|
-
<div
|
|
727
|
-
style={{
|
|
728
|
-
position: 'relative',
|
|
729
|
-
// Above the absolute edge-label overlay (zIndex 5), which spans the
|
|
730
|
-
// whole graph-area container including this panel's slice.
|
|
731
|
-
zIndex: 6,
|
|
732
|
-
flexShrink: 0,
|
|
733
|
-
height: open ? '45%' : 0,
|
|
734
|
-
minHeight: 0,
|
|
735
|
-
overflow: 'hidden',
|
|
736
|
-
display: 'flex',
|
|
737
|
-
flexDirection: 'column',
|
|
738
|
-
background: theme.colors.background,
|
|
739
|
-
borderTop: open ? `1px solid ${theme.colors.border}` : 'none',
|
|
740
|
-
transition: 'height 200ms ease',
|
|
741
|
-
}}
|
|
742
|
-
>
|
|
743
|
-
<div
|
|
744
|
-
style={{
|
|
745
|
-
display: 'flex',
|
|
746
|
-
alignItems: 'center',
|
|
747
|
-
gap: 8,
|
|
748
|
-
padding: '6px 10px',
|
|
749
|
-
borderBottom: `1px solid ${theme.colors.border}`,
|
|
750
|
-
flexShrink: 0,
|
|
751
|
-
}}
|
|
752
|
-
>
|
|
753
|
-
<span
|
|
754
|
-
title={file ?? undefined}
|
|
755
|
-
style={{
|
|
756
|
-
flex: 1,
|
|
757
|
-
minWidth: 0,
|
|
758
|
-
overflow: 'hidden',
|
|
759
|
-
textOverflow: 'ellipsis',
|
|
760
|
-
whiteSpace: 'nowrap',
|
|
761
|
-
fontFamily: theme.fonts.monospace,
|
|
762
|
-
fontSize: theme.fontSizes[0],
|
|
763
|
-
color: muted,
|
|
764
|
-
}}
|
|
765
|
-
>
|
|
766
|
-
{file}
|
|
767
|
-
</span>
|
|
566
|
+
{/* Legend button — top-left overlay on the canvas; opens the modal. */}
|
|
567
|
+
{usedMechanisms.size > 0 && (
|
|
768
568
|
<button
|
|
769
569
|
type="button"
|
|
770
|
-
onClick={
|
|
771
|
-
aria-label="Close file"
|
|
570
|
+
onClick={() => setLegendOpen(true)}
|
|
772
571
|
style={{
|
|
572
|
+
position: 'absolute',
|
|
573
|
+
top: 10,
|
|
574
|
+
left: 10,
|
|
575
|
+
zIndex: 7,
|
|
773
576
|
display: 'inline-flex',
|
|
774
577
|
alignItems: 'center',
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
border: 'none',
|
|
780
|
-
borderRadius: 4,
|
|
781
|
-
background: 'transparent',
|
|
578
|
+
gap: 6,
|
|
579
|
+
padding: '4px 10px',
|
|
580
|
+
fontSize: theme.fontSizes[0],
|
|
581
|
+
fontFamily: theme.fonts.body,
|
|
782
582
|
color: theme.colors.text,
|
|
583
|
+
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
584
|
+
border: `1px solid ${theme.colors.border}`,
|
|
585
|
+
borderRadius: 6,
|
|
783
586
|
cursor: 'pointer',
|
|
587
|
+
boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
|
|
784
588
|
}}
|
|
785
589
|
>
|
|
786
|
-
<
|
|
590
|
+
<MapIcon size={13} />
|
|
591
|
+
Legend
|
|
787
592
|
</button>
|
|
593
|
+
)}
|
|
594
|
+
<EdgeLegendModal
|
|
595
|
+
open={legendOpen}
|
|
596
|
+
mechanisms={usedMechanisms}
|
|
597
|
+
onClose={() => setLegendOpen(false)}
|
|
598
|
+
/>
|
|
599
|
+
{selected && <ComponentDetail component={selected} />}
|
|
600
|
+
<FileDrawer file={openFile} onClose={() => setOpenFile(null)}>
|
|
601
|
+
{fileViewer && openFile ? fileViewer(openFile) : null}
|
|
602
|
+
</FileDrawer>
|
|
603
|
+
{/* Startup cover — hides measurement, layout swap, and camera settle. */}
|
|
604
|
+
<GraphLayoutCover revealed={layoutReady} />
|
|
605
|
+
{canvasOverlay}
|
|
788
606
|
</div>
|
|
789
|
-
<div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>{children}</div>
|
|
790
607
|
</div>
|
|
791
608
|
);
|
|
792
609
|
}
|
|
793
610
|
|
|
794
|
-
/** Renders the graphify drill-down payload for a component. */
|
|
795
|
-
function GraphifyDetailSections({ detail }: { detail: GraphifyComponentDetail }) {
|
|
796
|
-
const { theme } = useTheme();
|
|
797
|
-
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
798
|
-
|
|
799
|
-
const label = (k: string) => (
|
|
800
|
-
<span
|
|
801
|
-
style={{
|
|
802
|
-
width: 64,
|
|
803
|
-
flexShrink: 0,
|
|
804
|
-
color: muted,
|
|
805
|
-
fontFamily: theme.fonts.monospace,
|
|
806
|
-
textTransform: 'uppercase',
|
|
807
|
-
fontSize: theme.fontSizes[0] * 0.8,
|
|
808
|
-
}}
|
|
809
|
-
>
|
|
810
|
-
{k}
|
|
811
|
-
</span>
|
|
812
|
-
);
|
|
813
|
-
const chip = (name: string, meta?: string, key?: string | number) => (
|
|
814
|
-
<span
|
|
815
|
-
key={key}
|
|
816
|
-
style={{
|
|
817
|
-
fontFamily: theme.fonts.monospace,
|
|
818
|
-
fontSize: theme.fontSizes[0],
|
|
819
|
-
color: theme.colors.text,
|
|
820
|
-
border: `1px solid ${theme.colors.border}`,
|
|
821
|
-
borderRadius: 4,
|
|
822
|
-
padding: '1px 6px',
|
|
823
|
-
marginRight: 6,
|
|
824
|
-
marginBottom: 4,
|
|
825
|
-
display: 'inline-block',
|
|
826
|
-
}}
|
|
827
|
-
>
|
|
828
|
-
{name}
|
|
829
|
-
{meta ? <span style={{ color: muted }}> {meta}</span> : ''}
|
|
830
|
-
</span>
|
|
831
|
-
);
|
|
832
|
-
const row = (k: string, children: ReactNode) => (
|
|
833
|
-
<div
|
|
834
|
-
style={{ display: 'flex', gap: 8, fontSize: theme.fontSizes[0], lineHeight: 1.5, marginBottom: 4 }}
|
|
835
|
-
>
|
|
836
|
-
{label(k)}
|
|
837
|
-
<div style={{ minWidth: 0 }}>{children}</div>
|
|
838
|
-
</div>
|
|
839
|
-
);
|
|
840
|
-
|
|
841
|
-
switch (detail.kind) {
|
|
842
|
-
case 'class':
|
|
843
|
-
return (
|
|
844
|
-
<>
|
|
845
|
-
{detail.methods.length > 0 &&
|
|
846
|
-
row('methods', detail.methods.map((m, i) => chip(m.name, m.returnType, i)))}
|
|
847
|
-
{detail.properties.length > 0 &&
|
|
848
|
-
row('props', detail.properties.map((p, i) => chip(p.name, p.type, i)))}
|
|
849
|
-
{detail.extends.length > 0 && row('extends', detail.extends.map((e, i) => chip(e, undefined, i)))}
|
|
850
|
-
{detail.implements.length > 0 && row('impl', detail.implements.map((e, i) => chip(e, undefined, i)))}
|
|
851
|
-
{detail.instantiations.length > 0 &&
|
|
852
|
-
row('insts', detail.instantiations.map((i, idx) => chip(i.name, undefined, idx)))}
|
|
853
|
-
{detail.references.length > 0 &&
|
|
854
|
-
row('refs', detail.references.map((r, idx) => chip(r.name, undefined, idx)))}
|
|
855
|
-
</>
|
|
856
|
-
);
|
|
857
|
-
case 'function':
|
|
858
|
-
return (
|
|
859
|
-
<>
|
|
860
|
-
{detail.parameters.length > 0 &&
|
|
861
|
-
row('params', detail.parameters.map((p, i) => chip(p.type, undefined, i)))}
|
|
862
|
-
{detail.returnType && row('return', <span>{detail.returnType}</span>)}
|
|
863
|
-
{detail.callers.length > 0 &&
|
|
864
|
-
row('callers', detail.callers.map((c, i) => chip(c.name, undefined, i)))}
|
|
865
|
-
{detail.callees.length > 0 &&
|
|
866
|
-
row('callees', detail.callees.map((c, i) => chip(c.name, undefined, i)))}
|
|
867
|
-
</>
|
|
868
|
-
);
|
|
869
|
-
case 'type':
|
|
870
|
-
return (
|
|
871
|
-
<>
|
|
872
|
-
{detail.properties.length > 0 &&
|
|
873
|
-
row('props', detail.properties.map((p, i) => chip(p.name, p.type, i)))}
|
|
874
|
-
{detail.usedBy.length > 0 &&
|
|
875
|
-
row('used by', detail.usedBy.map((u, i) => chip(u.name, undefined, i)))}
|
|
876
|
-
{detail.implementors.length > 0 &&
|
|
877
|
-
row('impl', detail.implementors.map((e, i) => chip(e, undefined, i)))}
|
|
878
|
-
</>
|
|
879
|
-
);
|
|
880
|
-
case 'module':
|
|
881
|
-
return (
|
|
882
|
-
<>
|
|
883
|
-
{detail.exports.length > 0 &&
|
|
884
|
-
row('exports', detail.exports.map((e, i) => chip(e, undefined, i)))}
|
|
885
|
-
{detail.imports.length > 0 &&
|
|
886
|
-
row('imports', detail.imports.map((i, idx) => chip(i.name, undefined, idx)))}
|
|
887
|
-
{detail.symbols.length > 0 &&
|
|
888
|
-
row('symbols', detail.symbols.map((s, i) => chip(s, undefined, i)))}
|
|
889
|
-
</>
|
|
890
|
-
);
|
|
891
|
-
case 'external':
|
|
892
|
-
return row('source', <span>{detail.label}</span>);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
|
|
896
611
|
export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
|
|
897
612
|
const wrapRef = useRef<HTMLDivElement>(null);
|
|
898
613
|
const [size, setSize] = useState<{ w: number; h: number } | null>(null);
|
package/src/subsystem/model.ts
CHANGED
|
@@ -136,9 +136,6 @@ export interface SubsystemGraphNodeData extends Record<string, unknown> {
|
|
|
136
136
|
* lives in that file (spotlighted), false otherwise (dimmed). Absent when
|
|
137
137
|
* no file is open — render neutrally. */
|
|
138
138
|
fileMatch?: boolean;
|
|
139
|
-
/** False hides the click-to-open file chip under the component name.
|
|
140
|
-
* Absent/true renders it (default). */
|
|
141
|
-
showFileBadges?: boolean;
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
export type SubsystemGraphNode = Node<SubsystemGraphNodeData, SubsystemGraphNodeType>;
|
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Subsystem component/group node + edge renderers.
|
|
3
3
|
*
|
|
4
4
|
* Lightweight, purpose-built for the subsystem component graph: a component
|
|
5
|
-
* renders its name (kind-tagged, colored by package) plus its symbol/purpose
|
|
6
|
-
*
|
|
5
|
+
* renders its name (kind-tagged, colored by package) plus its symbol/purpose.
|
|
6
|
+
* Groups render as package containers. Clicking
|
|
7
7
|
* a component calls `onSelect` (to open the entry point / file).
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -72,7 +72,6 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
|
|
|
72
72
|
// Set while a file is open in the drawer: true → spotlight, false → dim,
|
|
73
73
|
// absent (no file open) → neutral.
|
|
74
74
|
const fileMatch = data.fileMatch as boolean | undefined;
|
|
75
|
-
const showFileBadges = data.showFileBadges !== false;
|
|
76
75
|
|
|
77
76
|
return (
|
|
78
77
|
<div
|
|
@@ -204,32 +203,6 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
|
|
|
204
203
|
</div>
|
|
205
204
|
)}
|
|
206
205
|
|
|
207
|
-
{c.file && showFileBadges && (
|
|
208
|
-
<div
|
|
209
|
-
onClick={(e) => {
|
|
210
|
-
e.stopPropagation();
|
|
211
|
-
SUBSYSTEM_CALLBACKS.onSelect?.(c.id);
|
|
212
|
-
}}
|
|
213
|
-
style={{
|
|
214
|
-
alignSelf: 'center',
|
|
215
|
-
marginTop: 3,
|
|
216
|
-
fontSize: theme.fontSizes[0] * 0.78,
|
|
217
|
-
fontFamily: theme.fonts.monospace,
|
|
218
|
-
color: theme.colors.primary,
|
|
219
|
-
border: `1px solid ${theme.colors.border}`,
|
|
220
|
-
borderRadius: 4,
|
|
221
|
-
padding: '1px 5px',
|
|
222
|
-
cursor: 'pointer',
|
|
223
|
-
maxWidth: '100%',
|
|
224
|
-
overflow: 'hidden',
|
|
225
|
-
textOverflow: 'ellipsis',
|
|
226
|
-
whiteSpace: 'nowrap',
|
|
227
|
-
}}
|
|
228
|
-
>
|
|
229
|
-
{c.file.split('/').pop()}
|
|
230
|
-
</div>
|
|
231
|
-
)}
|
|
232
|
-
|
|
233
206
|
<Handle type="target" position={Position.Left} style={{ opacity: 0 }} />
|
|
234
207
|
<Handle type="source" position={Position.Right} style={{ opacity: 0 }} />
|
|
235
208
|
</div>
|