@principal-ai/file-city-react 0.5.40 → 0.5.42
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/components/FileCity3D/FileCity3D.d.ts +8 -2
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +129 -40
- package/dist/components/FileCityExplorer/AddToAreaModal.d.ts +14 -0
- package/dist/components/FileCityExplorer/AddToAreaModal.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/AddToAreaModal.js +140 -0
- package/dist/components/FileCityExplorer/AddToScopeModal.d.ts +14 -0
- package/dist/components/FileCityExplorer/AddToScopeModal.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/AddToScopeModal.js +176 -0
- package/dist/components/FileCityExplorer/FileCityExplorer.d.ts +30 -0
- package/dist/components/FileCityExplorer/FileCityExplorer.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/FileCityExplorer.js +1045 -0
- package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts +10 -0
- package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/ScopeInfoOverlay.js +73 -0
- package/dist/components/FileCityExplorer/index.d.ts +3 -0
- package/dist/components/FileCityExplorer/index.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/index.js +1 -0
- package/dist/components/FileCityExplorer/layers.d.ts +16 -0
- package/dist/components/FileCityExplorer/layers.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/layers.js +61 -0
- package/dist/components/FileCityExplorer/model.d.ts +32 -0
- package/dist/components/FileCityExplorer/model.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/model.js +14 -0
- package/dist/components/FileCityExplorer/pathConversion.d.ts +19 -0
- package/dist/components/FileCityExplorer/pathConversion.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/pathConversion.js +26 -0
- package/dist/components/FileCityExplorer/scopeTreePaths.d.ts +21 -0
- package/dist/components/FileCityExplorer/scopeTreePaths.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/scopeTreePaths.js +42 -0
- package/dist/components/FileCityExplorer/styles.d.ts +9 -0
- package/dist/components/FileCityExplorer/styles.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/styles.js +28 -0
- package/dist/utils/folderElevatedPanels.d.ts +3 -1
- package/dist/utils/folderElevatedPanels.d.ts.map +1 -1
- package/dist/utils/folderElevatedPanels.js +13 -2
- package/package.json +2 -1
- package/src/components/FileCity3D/FileCity3D.tsx +200 -52
- package/src/components/FileCityExplorer/AddToAreaModal.tsx +273 -0
- package/src/components/FileCityExplorer/AddToScopeModal.tsx +320 -0
- package/src/components/FileCityExplorer/FileCityExplorer.tsx +1457 -0
- package/src/components/FileCityExplorer/ScopeInfoOverlay.tsx +229 -0
- package/src/components/FileCityExplorer/index.ts +2 -0
- package/src/components/FileCityExplorer/layers.ts +72 -0
- package/src/components/FileCityExplorer/model.ts +35 -0
- package/src/components/FileCityExplorer/pathConversion.ts +32 -0
- package/src/components/FileCityExplorer/scopeTreePaths.ts +52 -0
- package/src/components/FileCityExplorer/styles.ts +34 -0
- package/src/stories/2D3DComparison.stories.tsx +13 -2
- package/src/stories/ElevatedScopePanels.stories.tsx +295 -0
- package/src/stories/FileCity3D.stories.tsx +24 -3
- package/src/stories/FileCityExplorer.stories.tsx +2474 -0
- package/src/stories/FileCityExplorerComponent.stories.tsx +59 -0
- package/src/stories/LeaderLineSnippetOverlay.stories.tsx +306 -0
- package/src/utils/folderElevatedPanels.ts +15 -2
- package/src/stories/ScopeOverlay.stories.tsx +0 -1610
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Event, Namespace, Scope } from './model';
|
|
3
|
+
export declare const ScopeInfoOverlay: React.FC<{
|
|
4
|
+
info: {
|
|
5
|
+
scope: Scope;
|
|
6
|
+
ns: Namespace | null;
|
|
7
|
+
ev: Event | null;
|
|
8
|
+
};
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=ScopeInfoOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScopeInfoOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/ScopeInfoOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGvD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC;IACtC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAAC;QAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE,CAAC;CAChE,CA6NA,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTheme } from '@principal-ade/industry-theme';
|
|
3
|
+
import { makeOverlayStyle, makeSectionLabelStyle } from './styles';
|
|
4
|
+
export const ScopeInfoOverlay = ({ info }) => {
|
|
5
|
+
const { theme } = useTheme();
|
|
6
|
+
const overlayStyle = makeOverlayStyle(theme);
|
|
7
|
+
const sectionLabelStyle = makeSectionLabelStyle(theme);
|
|
8
|
+
const severityBg = {
|
|
9
|
+
ERROR: theme.colors.error,
|
|
10
|
+
WARN: theme.colors.warning,
|
|
11
|
+
INFO: theme.colors.info,
|
|
12
|
+
};
|
|
13
|
+
const defaultSeverityBg = theme.colors.backgroundSecondary;
|
|
14
|
+
const sectionDivider = `1px solid ${theme.colors.backgroundSecondary}`;
|
|
15
|
+
const codeChipStyle = {
|
|
16
|
+
fontSize: theme.fontSizes[0],
|
|
17
|
+
color: theme.colors.textSecondary,
|
|
18
|
+
background: theme.colors.backgroundDark ?? theme.colors.background,
|
|
19
|
+
padding: '4px 6px',
|
|
20
|
+
borderRadius: theme.radii[2],
|
|
21
|
+
wordBreak: 'break-all',
|
|
22
|
+
};
|
|
23
|
+
const { scope, ns, ev } = info;
|
|
24
|
+
// Event leaf selected — show event detail.
|
|
25
|
+
if (ns && ev) {
|
|
26
|
+
return (_jsxs("div", { style: overlayStyle, children: [_jsxs("div", { style: { padding: '14px 16px', borderBottom: sectionDivider }, children: [_jsx("div", { style: sectionLabelStyle, children: "Event" }), _jsx("div", { style: { fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1], marginTop: 6 }, children: ev.name }), ev.severity && (_jsx("div", { style: {
|
|
27
|
+
display: 'inline-block',
|
|
28
|
+
fontSize: theme.fontSizes[0],
|
|
29
|
+
marginTop: theme.space[2],
|
|
30
|
+
padding: '2px 6px',
|
|
31
|
+
borderRadius: theme.radii[1],
|
|
32
|
+
background: severityBg[ev.severity] ?? defaultSeverityBg,
|
|
33
|
+
color: theme.colors.highlight,
|
|
34
|
+
}, children: ev.severity })), ev.description && (_jsx("div", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textSecondary, marginTop: 10, lineHeight: 1.5 }, children: ev.description }))] }), _jsxs("div", { style: { padding: '14px 16px' }, children: [_jsx("div", { style: sectionLabelStyle, children: "Owning namespace" }), _jsxs("div", { style: { marginTop: 6, display: 'flex', alignItems: 'center', gap: theme.space[2] }, children: [_jsx("span", { style: { width: 12, height: 12, borderRadius: theme.radii[1], background: ns.color, flexShrink: 0 } }), _jsx("span", { style: { fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1] }, children: ns.name })] }), _jsx("div", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textTertiary, marginTop: 14, fontStyle: 'italic' }, children: "Files-per-event mapping not wired yet \u2014 for now the event highlights its parent namespace's paths." })] })] }));
|
|
35
|
+
}
|
|
36
|
+
// Namespace selected — show namespace detail.
|
|
37
|
+
if (ns) {
|
|
38
|
+
return (_jsxs("div", { style: overlayStyle, children: [_jsxs("div", { style: { padding: '14px 16px', borderBottom: sectionDivider }, children: [_jsx("div", { style: sectionLabelStyle, children: "Namespace" }), _jsxs("div", { style: { marginTop: 6, display: 'flex', alignItems: 'center', gap: theme.space[2] }, children: [_jsx("span", { style: { width: 12, height: 12, borderRadius: theme.radii[1], background: ns.color, flexShrink: 0 } }), _jsx("span", { style: { fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1] }, children: ns.name })] }), _jsx("div", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textMuted, marginTop: theme.space[2], lineHeight: 1.5 }, children: ns.description }), _jsxs("div", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textTertiary, marginTop: theme.space[2] }, children: ["in ", _jsx("span", { style: { fontFamily: theme.fonts.monospace }, children: scope.id })] })] }), _jsxs("div", { style: { padding: '14px 16px', borderBottom: sectionDivider }, children: [_jsxs("div", { style: sectionLabelStyle, children: ["Claimed paths (", ns.paths.length, ")"] }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: theme.space[1], marginTop: 6 }, children: ns.paths.map(p => (_jsx("code", { style: codeChipStyle, children: p }, p))) })] }), _jsxs("div", { style: { padding: '14px 16px' }, children: [_jsxs("div", { style: sectionLabelStyle, children: ["Events (", ns.events.length, ")"] }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: theme.space[1], marginTop: 6 }, children: ns.events.map(e => (_jsxs("div", { style: {
|
|
39
|
+
display: 'flex',
|
|
40
|
+
alignItems: 'center',
|
|
41
|
+
gap: 6,
|
|
42
|
+
padding: '4px 6px',
|
|
43
|
+
background: theme.colors.backgroundDark ?? theme.colors.background,
|
|
44
|
+
borderRadius: theme.radii[2],
|
|
45
|
+
}, children: [e.severity && (_jsx("span", { style: {
|
|
46
|
+
fontSize: theme.fontSizes[0],
|
|
47
|
+
padding: '1px 4px',
|
|
48
|
+
borderRadius: theme.radii[1],
|
|
49
|
+
background: severityBg[e.severity] ?? defaultSeverityBg,
|
|
50
|
+
color: theme.colors.highlight,
|
|
51
|
+
flexShrink: 0,
|
|
52
|
+
}, children: e.severity })), _jsx("code", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textSecondary }, children: e.name })] }, e.name))) })] })] }));
|
|
53
|
+
}
|
|
54
|
+
// Scope selected — show scope summary.
|
|
55
|
+
const totalEvents = scope.namespaces.reduce((n, x) => n + x.events.length, 0);
|
|
56
|
+
return (_jsxs("div", { style: overlayStyle, children: [_jsxs("div", { style: { padding: '14px 16px', borderBottom: sectionDivider }, children: [_jsx("div", { style: sectionLabelStyle, children: "Scope" }), _jsx("div", { style: { fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1], marginTop: 6 }, children: scope.id }), _jsx("div", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textMuted, marginTop: theme.space[2], lineHeight: 1.5 }, children: scope.description }), _jsxs("div", { style: { display: 'flex', gap: theme.space[3], marginTop: 12, fontSize: theme.fontSizes[0], color: theme.colors.textTertiary }, children: [_jsxs("div", { children: [_jsx("div", { children: scope.paths.length }), _jsx("div", { style: sectionLabelStyle, children: "scope paths" })] }), _jsxs("div", { children: [_jsx("div", { children: scope.namespaces.length }), _jsx("div", { style: sectionLabelStyle, children: "namespaces" })] }), _jsxs("div", { children: [_jsx("div", { children: totalEvents }), _jsx("div", { style: sectionLabelStyle, children: "events" })] })] })] }), scope.paths.length > 0 && (_jsxs("div", { style: { padding: '14px 16px', borderBottom: sectionDivider }, children: [_jsxs("div", { style: sectionLabelStyle, children: ["Scope-level paths (", scope.paths.length, ")"] }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: theme.space[1], marginTop: 6 }, children: scope.paths.map(p => (_jsx("code", { style: codeChipStyle, children: p }, p))) })] })), _jsxs("div", { style: { padding: '14px 16px' }, children: [_jsx("div", { style: sectionLabelStyle, children: "Namespaces" }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: theme.space[2], marginTop: theme.space[2] }, children: scope.namespaces.map(n => (_jsxs("div", { style: {
|
|
57
|
+
padding: theme.space[2],
|
|
58
|
+
background: theme.colors.backgroundDark ?? theme.colors.background,
|
|
59
|
+
borderRadius: theme.radii[3],
|
|
60
|
+
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: theme.space[2] }, children: [_jsx("span", { style: {
|
|
61
|
+
width: 10,
|
|
62
|
+
height: 10,
|
|
63
|
+
borderRadius: theme.radii[1],
|
|
64
|
+
background: n.color,
|
|
65
|
+
flexShrink: 0,
|
|
66
|
+
} }), _jsx("span", { style: { fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[0] }, children: n.name }), _jsxs("span", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textTertiary, marginLeft: 'auto' }, children: [n.events.length, " event", n.events.length === 1 ? '' : 's'] })] }), _jsx("div", { style: {
|
|
67
|
+
fontSize: theme.fontSizes[0],
|
|
68
|
+
color: theme.colors.textTertiary,
|
|
69
|
+
fontFamily: theme.fonts.monospace,
|
|
70
|
+
marginTop: theme.space[1],
|
|
71
|
+
wordBreak: 'break-all',
|
|
72
|
+
}, children: n.paths.join(' · ') })] }, n.name))) })] })] }));
|
|
73
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileCityExplorer } from './FileCityExplorer';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HighlightLayer } from '../FileCity3D';
|
|
2
|
+
import type { Scope } from './model';
|
|
3
|
+
export declare const NAMESPACE_PALETTE: string[];
|
|
4
|
+
/** Colour used for area umbrella tiles and their picker swatches. */
|
|
5
|
+
export declare const AREA_PANEL_COLOR = "#64748b";
|
|
6
|
+
export declare function pickNamespaceColor(scopes: readonly Scope[]): string;
|
|
7
|
+
/**
|
|
8
|
+
* Build highlight layers for a scope: one fill layer per namespace plus a
|
|
9
|
+
* border-only layer for scope-level paths. Priority is path depth (longest-
|
|
10
|
+
* prefix wins) per the partition convention in docs/scope-namespace-overlay.md.
|
|
11
|
+
*
|
|
12
|
+
* `toCityPath` translates scope-relative paths back to city paths so
|
|
13
|
+
* districts can be matched.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildLayersForScope(scope: Scope, toCityPath: (scopePath: string) => string): HighlightLayer[];
|
|
16
|
+
//# sourceMappingURL=layers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/layers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,eAAO,MAAM,iBAAiB,UAW7B,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAMnE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GACxC,cAAc,EAAE,CAiClB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const NAMESPACE_PALETTE = [
|
|
2
|
+
'#22c55e',
|
|
3
|
+
'#3b82f6',
|
|
4
|
+
'#f59e0b',
|
|
5
|
+
'#ec4899',
|
|
6
|
+
'#8b5cf6',
|
|
7
|
+
'#06b6d4',
|
|
8
|
+
'#ef4444',
|
|
9
|
+
'#14b8a6',
|
|
10
|
+
'#a855f7',
|
|
11
|
+
'#eab308',
|
|
12
|
+
];
|
|
13
|
+
/** Colour used for area umbrella tiles and their picker swatches. */
|
|
14
|
+
export const AREA_PANEL_COLOR = '#64748b';
|
|
15
|
+
export function pickNamespaceColor(scopes) {
|
|
16
|
+
const used = new Set(scopes.flatMap(s => s.namespaces.map(n => n.color)));
|
|
17
|
+
return (NAMESPACE_PALETTE.find(c => !used.has(c)) ??
|
|
18
|
+
NAMESPACE_PALETTE[scopes.length % NAMESPACE_PALETTE.length]);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build highlight layers for a scope: one fill layer per namespace plus a
|
|
22
|
+
* border-only layer for scope-level paths. Priority is path depth (longest-
|
|
23
|
+
* prefix wins) per the partition convention in docs/scope-namespace-overlay.md.
|
|
24
|
+
*
|
|
25
|
+
* `toCityPath` translates scope-relative paths back to city paths so
|
|
26
|
+
* districts can be matched.
|
|
27
|
+
*/
|
|
28
|
+
export function buildLayersForScope(scope, toCityPath) {
|
|
29
|
+
const layers = scope.namespaces.map(ns => {
|
|
30
|
+
const maxDepth = Math.max(1, ...ns.paths.map(p => p.split('/').length));
|
|
31
|
+
return {
|
|
32
|
+
id: `${scope.id}::${ns.name}`,
|
|
33
|
+
name: ns.name,
|
|
34
|
+
enabled: true,
|
|
35
|
+
color: ns.color,
|
|
36
|
+
opacity: 0.55,
|
|
37
|
+
priority: maxDepth,
|
|
38
|
+
items: ns.paths.map(p => ({
|
|
39
|
+
path: toCityPath(p),
|
|
40
|
+
type: 'directory',
|
|
41
|
+
renderStrategy: 'fill',
|
|
42
|
+
})),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
if (scope.paths.length > 0) {
|
|
46
|
+
layers.push({
|
|
47
|
+
id: `${scope.id}::__scope__`,
|
|
48
|
+
name: `${scope.id} (scope-level)`,
|
|
49
|
+
enabled: true,
|
|
50
|
+
color: '#64748b',
|
|
51
|
+
opacity: 0.4,
|
|
52
|
+
priority: 0,
|
|
53
|
+
items: scope.paths.map(p => ({
|
|
54
|
+
path: toCityPath(p),
|
|
55
|
+
type: 'directory',
|
|
56
|
+
renderStrategy: 'fill',
|
|
57
|
+
})),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return layers;
|
|
61
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope / namespace / area model for the explorer.
|
|
3
|
+
*
|
|
4
|
+
* `Event` and `Namespace` are derived from the upstream
|
|
5
|
+
* `EventNamespaceNode['namespace']` shape in `@principal-ai/principal-view-core`.
|
|
6
|
+
* `Namespace` adds a UI-required `color` (palette pick).
|
|
7
|
+
*
|
|
8
|
+
* `Scope` stays local: it flattens namespaces inline (`scope.namespaces[]`),
|
|
9
|
+
* which the upstream canvas-node split (`OtelScopeNode` + per-scope
|
|
10
|
+
* `*.events.canvas`) doesn't model. Treat as an explorer view-model.
|
|
11
|
+
*
|
|
12
|
+
* `ProjectArea` is re-exported directly from upstream.
|
|
13
|
+
*/
|
|
14
|
+
import type { EventNamespaceNode } from '@principal-ai/principal-view-core';
|
|
15
|
+
export type { ProjectArea } from '@principal-ai/principal-view-core';
|
|
16
|
+
export type Event = EventNamespaceNode['namespace']['events'][number];
|
|
17
|
+
export type Namespace = EventNamespaceNode['namespace'] & {
|
|
18
|
+
/** UI palette pick — not part of the upstream canvas-node shape. */
|
|
19
|
+
color: string;
|
|
20
|
+
/** Required here even though upstream allows `paths?` — explorer always sets it. */
|
|
21
|
+
paths: string[];
|
|
22
|
+
};
|
|
23
|
+
export interface Scope {
|
|
24
|
+
/** Maps to `OtelScopeNode.otel.scope` upstream. */
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
/** Scope-level paths — corresponds to optional `OtelScopeNode.paths` upstream. */
|
|
29
|
+
paths: string[];
|
|
30
|
+
namespaces: Namespace[];
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,YAAY,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAErE,MAAM,MAAM,KAAK,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,MAAM,MAAM,SAAS,GAAG,kBAAkB,CAAC,WAAW,CAAC,GAAG;IACxD,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,oFAAoF;IACpF,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,KAAK;IACpB,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope / namespace / area model for the explorer.
|
|
3
|
+
*
|
|
4
|
+
* `Event` and `Namespace` are derived from the upstream
|
|
5
|
+
* `EventNamespaceNode['namespace']` shape in `@principal-ai/principal-view-core`.
|
|
6
|
+
* `Namespace` adds a UI-required `color` (palette pick).
|
|
7
|
+
*
|
|
8
|
+
* `Scope` stays local: it flattens namespaces inline (`scope.namespaces[]`),
|
|
9
|
+
* which the upstream canvas-node split (`OtelScopeNode` + per-scope
|
|
10
|
+
* `*.events.canvas`) doesn't model. Treat as an explorer view-model.
|
|
11
|
+
*
|
|
12
|
+
* `ProjectArea` is re-exported directly from upstream.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path converters for the explorer.
|
|
3
|
+
*
|
|
4
|
+
* City data is rooted at `packageRoot` (e.g. `electron-app/`). Scope/namespace
|
|
5
|
+
* paths are authored relative to the package root (matching how principal-view
|
|
6
|
+
* canvases are stored). These converters round-trip between the two
|
|
7
|
+
* representations.
|
|
8
|
+
*
|
|
9
|
+
* `packageRoot` should include a trailing slash. Pass `''` if the city data
|
|
10
|
+
* is already rooted at the project root.
|
|
11
|
+
*/
|
|
12
|
+
export interface PathConverters {
|
|
13
|
+
/** City path → scope/namespace path (strip the package-root prefix). */
|
|
14
|
+
toScopePath: (cityPath: string) => string;
|
|
15
|
+
/** Scope/namespace path → city path (re-add the package-root prefix). */
|
|
16
|
+
toCityPath: (scopePath: string) => string;
|
|
17
|
+
}
|
|
18
|
+
export declare function createPathConverters(packageRoot: string): PathConverters;
|
|
19
|
+
//# sourceMappingURL=pathConversion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathConversion.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/pathConversion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,yEAAyE;IACzE,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3C;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAYxE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path converters for the explorer.
|
|
3
|
+
*
|
|
4
|
+
* City data is rooted at `packageRoot` (e.g. `electron-app/`). Scope/namespace
|
|
5
|
+
* paths are authored relative to the package root (matching how principal-view
|
|
6
|
+
* canvases are stored). These converters round-trip between the two
|
|
7
|
+
* representations.
|
|
8
|
+
*
|
|
9
|
+
* `packageRoot` should include a trailing slash. Pass `''` if the city data
|
|
10
|
+
* is already rooted at the project root.
|
|
11
|
+
*/
|
|
12
|
+
export function createPathConverters(packageRoot) {
|
|
13
|
+
return {
|
|
14
|
+
toScopePath(cityPath) {
|
|
15
|
+
let p = cityPath.endsWith('/') ? cityPath.slice(0, -1) : cityPath;
|
|
16
|
+
if (packageRoot && p.startsWith(packageRoot))
|
|
17
|
+
p = p.slice(packageRoot.length);
|
|
18
|
+
return p;
|
|
19
|
+
},
|
|
20
|
+
toCityPath(scopePath) {
|
|
21
|
+
if (!packageRoot)
|
|
22
|
+
return scopePath;
|
|
23
|
+
return scopePath.startsWith(packageRoot) ? scopePath : packageRoot + scopePath;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Scope } from './model';
|
|
2
|
+
export interface ScopeTreeSelection {
|
|
3
|
+
scopeId: string;
|
|
4
|
+
namespaceName?: string;
|
|
5
|
+
eventName?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Sentinel leaves used when a scope has no namespaces or a namespace has no
|
|
9
|
+
* events — the trees library infers directories from paths, so empty branches
|
|
10
|
+
* need a placeholder leaf to render.
|
|
11
|
+
*/
|
|
12
|
+
export declare const EMPTY_NS_SENTINEL = "(no namespaces)";
|
|
13
|
+
export declare const EMPTY_EVENTS_SENTINEL = "(no events)";
|
|
14
|
+
/**
|
|
15
|
+
* Build canonical paths for the scope tree: `<scope.id>/<namespace.name>/<event.name>`.
|
|
16
|
+
* Scopes are top-level directories, namespaces children, events leaves.
|
|
17
|
+
* Empty scopes/namespaces emit a sentinel leaf so they still appear.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildScopeTreePaths(scopes: readonly Scope[]): string[];
|
|
20
|
+
export declare function parseScopeTreePath(path: string): ScopeTreeSelection;
|
|
21
|
+
//# sourceMappingURL=scopeTreePaths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopeTreePaths.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/scopeTreePaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AACnD,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,EAAE,CAkBtE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAUnE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel leaves used when a scope has no namespaces or a namespace has no
|
|
3
|
+
* events — the trees library infers directories from paths, so empty branches
|
|
4
|
+
* need a placeholder leaf to render.
|
|
5
|
+
*/
|
|
6
|
+
export const EMPTY_NS_SENTINEL = '(no namespaces)';
|
|
7
|
+
export const EMPTY_EVENTS_SENTINEL = '(no events)';
|
|
8
|
+
/**
|
|
9
|
+
* Build canonical paths for the scope tree: `<scope.id>/<namespace.name>/<event.name>`.
|
|
10
|
+
* Scopes are top-level directories, namespaces children, events leaves.
|
|
11
|
+
* Empty scopes/namespaces emit a sentinel leaf so they still appear.
|
|
12
|
+
*/
|
|
13
|
+
export function buildScopeTreePaths(scopes) {
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const scope of scopes) {
|
|
16
|
+
if (scope.namespaces.length === 0) {
|
|
17
|
+
out.push(`${scope.id}/${EMPTY_NS_SENTINEL}`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
for (const ns of scope.namespaces) {
|
|
21
|
+
if (ns.events.length === 0) {
|
|
22
|
+
out.push(`${scope.id}/${ns.name}/${EMPTY_EVENTS_SENTINEL}`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
for (const ev of ns.events) {
|
|
26
|
+
out.push(`${scope.id}/${ns.name}/${ev.name}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
export function parseScopeTreePath(path) {
|
|
33
|
+
const [scopeId, namespaceName, eventName] = path.split('/');
|
|
34
|
+
const result = { scopeId };
|
|
35
|
+
if (namespaceName && namespaceName !== EMPTY_NS_SENTINEL) {
|
|
36
|
+
result.namespaceName = namespaceName;
|
|
37
|
+
}
|
|
38
|
+
if (eventName && eventName !== EMPTY_EVENTS_SENTINEL) {
|
|
39
|
+
result.eventName = eventName;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { Theme } from '@principal-ade/industry-theme';
|
|
3
|
+
/** Translucent overlay using the theme's background colour. */
|
|
4
|
+
export declare const withAlpha: (color: string, percent: number) => string;
|
|
5
|
+
/** Reused across overlays, modals, and the selected-folder card. */
|
|
6
|
+
export declare const makeSectionLabelStyle: (theme: Theme) => React.CSSProperties;
|
|
7
|
+
/** Frame style for the right-side info overlay. */
|
|
8
|
+
export declare const makeOverlayStyle: (theme: Theme) => React.CSSProperties;
|
|
9
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAE3D,+DAA+D;AAC/D,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,KAAG,MACD,CAAC;AAE3D,oEAAoE;AACpE,eAAO,MAAM,qBAAqB,GAAI,OAAO,KAAK,KAAG,KAAK,CAAC,aAKzD,CAAC;AAEH,mDAAmD;AACnD,eAAO,MAAM,gBAAgB,GAAI,OAAO,KAAK,KAAG,KAAK,CAAC,aAiBpD,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Translucent overlay using the theme's background colour. */
|
|
2
|
+
export const withAlpha = (color, percent) => `color-mix(in oklab, ${color} ${percent}%, transparent)`;
|
|
3
|
+
/** Reused across overlays, modals, and the selected-folder card. */
|
|
4
|
+
export const makeSectionLabelStyle = (theme) => ({
|
|
5
|
+
fontSize: theme.fontSizes[0],
|
|
6
|
+
color: theme.colors.textTertiary,
|
|
7
|
+
textTransform: 'uppercase',
|
|
8
|
+
letterSpacing: 0.5,
|
|
9
|
+
});
|
|
10
|
+
/** Frame style for the right-side info overlay. */
|
|
11
|
+
export const makeOverlayStyle = (theme) => ({
|
|
12
|
+
position: 'absolute',
|
|
13
|
+
top: 16,
|
|
14
|
+
left: 16,
|
|
15
|
+
width: 360,
|
|
16
|
+
maxHeight: 'calc(100vh - 32px)',
|
|
17
|
+
overflowY: 'auto',
|
|
18
|
+
background: withAlpha(theme.colors.background, 72),
|
|
19
|
+
backdropFilter: 'blur(8px)',
|
|
20
|
+
WebkitBackdropFilter: 'blur(8px)',
|
|
21
|
+
border: `1px solid ${theme.colors.border}`,
|
|
22
|
+
borderRadius: theme.radii[4],
|
|
23
|
+
color: theme.colors.text,
|
|
24
|
+
fontFamily: theme.fonts.body,
|
|
25
|
+
fontSize: theme.fontSizes[1],
|
|
26
|
+
zIndex: 100,
|
|
27
|
+
boxShadow: theme.shadows[3],
|
|
28
|
+
});
|
|
@@ -34,7 +34,9 @@ export interface BuildFolderElevatedPanelsOptions {
|
|
|
34
34
|
*/
|
|
35
35
|
expandedFolders: ReadonlySet<string>;
|
|
36
36
|
/** Toggle handler invoked when an umbrella tile is clicked. */
|
|
37
|
-
onToggleFolder?: (folderPath: string) => void;
|
|
37
|
+
onToggleFolder?: (folderPath: string, event: MouseEvent) => void;
|
|
38
|
+
/** Double-click handler for an umbrella tile. */
|
|
39
|
+
onDoubleClickFolder?: (folderPath: string, event: MouseEvent) => void;
|
|
38
40
|
/**
|
|
39
41
|
* Scale label font size by descendant file count. Default true. When false,
|
|
40
42
|
* the renderer's auto-sized label is used (size derived from tile footprint).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folderElevatedPanels.d.ts","sourceRoot":"","sources":["../../src/utils/folderElevatedPanels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAenE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,WAAW;IACnB,8GAA8G;IAC9G,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,qEAAqE;IACrE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,gDAAgD;IAChD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"folderElevatedPanels.d.ts","sourceRoot":"","sources":["../../src/utils/folderElevatedPanels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAenE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,WAAW;IACnB,8GAA8G;IAC9G,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,qEAAqE;IACrE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,gDAAgD;IAChD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,WAAW,CAsDhE;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,EAAE,QAAQ,CAAC;IACnB;;;OAGG;IACH,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,+DAA+D;IAC/D,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACjE,iDAAiD;IACjD,mBAAmB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtE;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,kBAAkB,EAAE,CA0CtB"}
|
|
@@ -34,6 +34,14 @@ export function buildFolderIndex(cityData) {
|
|
|
34
34
|
directorySet.add(d.path);
|
|
35
35
|
const dirs = Array.from(directorySet).sort();
|
|
36
36
|
for (const dir of dirs) {
|
|
37
|
+
// The empty path represents the synthetic project root that some city
|
|
38
|
+
// builders emit at depth 0. Including it here would register '' as its
|
|
39
|
+
// own parent (slash<0 → parent=''), which makes the recursive `walk`
|
|
40
|
+
// in `buildFolderElevatedPanels` loop forever the moment the root node
|
|
41
|
+
// is marked expanded. Top-level real folders already live under the
|
|
42
|
+
// empty-string parent, so skipping the empty entry costs nothing.
|
|
43
|
+
if (dir === '')
|
|
44
|
+
continue;
|
|
37
45
|
const slash = dir.lastIndexOf('/');
|
|
38
46
|
const parent = slash >= 0 ? dir.slice(0, slash) : '';
|
|
39
47
|
const arr = children.get(parent);
|
|
@@ -92,7 +100,7 @@ export function buildFolderIndex(cityData) {
|
|
|
92
100
|
* Mirror of the scope-tree expansion behavior, applied to file-tree folders.
|
|
93
101
|
*/
|
|
94
102
|
export function buildFolderElevatedPanels(options) {
|
|
95
|
-
const { cityData, expandedFolders, onToggleFolder, scaleLabelByFileCount = true, } = options;
|
|
103
|
+
const { cityData, expandedFolders, onToggleFolder, onDoubleClickFolder, scaleLabelByFileCount = true, } = options;
|
|
96
104
|
const index = options.index ?? buildFolderIndex(cityData);
|
|
97
105
|
const panels = [];
|
|
98
106
|
const walk = (folderPath) => {
|
|
@@ -118,7 +126,10 @@ export function buildFolderElevatedPanels(options) {
|
|
|
118
126
|
bounds,
|
|
119
127
|
label,
|
|
120
128
|
labelSize,
|
|
121
|
-
onClick: onToggleFolder ? () => onToggleFolder(folderPath) : undefined,
|
|
129
|
+
onClick: onToggleFolder ? (event) => onToggleFolder(folderPath, event) : undefined,
|
|
130
|
+
onDoubleClick: onDoubleClickFolder
|
|
131
|
+
? (event) => onDoubleClickFolder(folderPath, event)
|
|
132
|
+
: undefined,
|
|
122
133
|
});
|
|
123
134
|
};
|
|
124
135
|
for (const top of index.children.get('') ?? [])
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principal-ai/file-city-react",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.42",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React components for File City visualization",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@pierre/trees": "^1.0.0-beta.3",
|
|
30
30
|
"@principal-ade/industry-theme": "^0.1.19",
|
|
31
31
|
"@principal-ai/file-city-builder": "^0.4.5",
|
|
32
|
+
"@principal-ai/principal-view-core": "^0.28.5",
|
|
32
33
|
"@react-spring/three": "^10.0.3",
|
|
33
34
|
"@react-three/drei": "^10.0.6",
|
|
34
35
|
"@react-three/fiber": "^9.1.2",
|