@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,59 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { FileCityExplorer, type ProjectArea } from '../components/FileCityExplorer';
|
|
4
|
+
import type { CityData } from '../components/FileCity3D';
|
|
5
|
+
|
|
6
|
+
import electronAppCityData from '../../../../assets/electron-app-city-data.json';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Side-by-side test of the extracted `<FileCityExplorer>` component against
|
|
10
|
+
* the original story-template implementation in
|
|
11
|
+
* `FileCityExplorer.stories.tsx`. The two should look and behave identically;
|
|
12
|
+
* differences indicate regressions in the extraction.
|
|
13
|
+
*
|
|
14
|
+
* Persistence is intentionally namespaced to a separate `localStorage` key
|
|
15
|
+
* (`file-city.scope-overlay-component`) so the two stories don't fight over
|
|
16
|
+
* the same scopes/areas state.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const meta: Meta<typeof FileCityExplorer> = {
|
|
20
|
+
title: 'Experiments/FileCityExplorer (Component)',
|
|
21
|
+
component: FileCityExplorer,
|
|
22
|
+
parameters: { layout: 'fullscreen' },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
type Story = StoryObj<typeof FileCityExplorer>;
|
|
27
|
+
|
|
28
|
+
const DEFAULT_AREAS: ProjectArea[] = [
|
|
29
|
+
{
|
|
30
|
+
name: 'Documentation',
|
|
31
|
+
description: 'Project docs, READMEs, and design notes — not OTEL-instrumented.',
|
|
32
|
+
paths: ['docs'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Build & tooling',
|
|
36
|
+
description: 'Build scripts, bundler config, and developer tooling.',
|
|
37
|
+
paths: ['scripts', 'build'],
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export const Default: Story = {
|
|
42
|
+
render: () => (
|
|
43
|
+
<FileCityExplorer
|
|
44
|
+
cityData={electronAppCityData as CityData}
|
|
45
|
+
packageRoot="electron-app/"
|
|
46
|
+
initialAreas={DEFAULT_AREAS}
|
|
47
|
+
persistKey="file-city.scope-overlay-component"
|
|
48
|
+
/>
|
|
49
|
+
),
|
|
50
|
+
parameters: {
|
|
51
|
+
docs: {
|
|
52
|
+
description: {
|
|
53
|
+
story:
|
|
54
|
+
'Extracted `<FileCityExplorer>` component over the electron-app city. ' +
|
|
55
|
+
'Should behave identically to the original story (FileCityExplorer / Default).',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import { ArchitectureMapHighlightLayers } from '../components/ArchitectureMapHighlightLayers';
|
|
5
|
+
import type { CityData } from '../components/FileCity3D';
|
|
6
|
+
import { createFileColorHighlightLayers } from '../utils/fileColorHighlightLayers';
|
|
7
|
+
import authServerCityData from '../../../../assets/auth-server-city-data.json';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Prototypes/Leader Line Snippet Overlay',
|
|
11
|
+
parameters: { layout: 'fullscreen' },
|
|
12
|
+
} satisfies Meta;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
|
|
16
|
+
// Mirrors the default `padding` inside ArchitectureMapHighlightLayers, so the
|
|
17
|
+
// world->screen math here lines up with what the canvas actually draws.
|
|
18
|
+
const CANVAS_PADDING = 20;
|
|
19
|
+
|
|
20
|
+
const TARGET_PATH = 'auth-server/src/app/api/auth/workos/callback/route.ts';
|
|
21
|
+
|
|
22
|
+
const SNIPPET = `export async function GET(req: Request) {
|
|
23
|
+
const url = new URL(req.url);
|
|
24
|
+
const code = url.searchParams.get('code');
|
|
25
|
+
if (!code) {
|
|
26
|
+
return NextResponse.redirect(new URL('/login', req.url));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { user, sessionId } = await workos.userManagement
|
|
30
|
+
.authenticateWithCode({
|
|
31
|
+
clientId: env.WORKOS_CLIENT_ID,
|
|
32
|
+
code,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return setSessionCookie({ user, sessionId });
|
|
36
|
+
}`;
|
|
37
|
+
|
|
38
|
+
const PANEL_WIDTH = 360;
|
|
39
|
+
|
|
40
|
+
interface FitParams {
|
|
41
|
+
scale: number;
|
|
42
|
+
offsetX: number;
|
|
43
|
+
offsetZ: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Replicates calculateScaleAndOffset from ArchitectureMapHighlightLayers.
|
|
47
|
+
function fitCityToBox(
|
|
48
|
+
bounds: CityData['bounds'],
|
|
49
|
+
width: number,
|
|
50
|
+
height: number,
|
|
51
|
+
padding: number,
|
|
52
|
+
): FitParams {
|
|
53
|
+
const cityWidth = bounds.maxX - bounds.minX;
|
|
54
|
+
const cityDepth = bounds.maxZ - bounds.minZ;
|
|
55
|
+
const horizontalPadding = padding;
|
|
56
|
+
const verticalPadding = padding * 2;
|
|
57
|
+
const scaleX = (width - horizontalPadding) / cityDepth;
|
|
58
|
+
const scaleZ = (height - verticalPadding) / cityWidth;
|
|
59
|
+
const scale = Math.min(scaleX, scaleZ);
|
|
60
|
+
const scaledCityWidth = cityDepth * scale;
|
|
61
|
+
const scaledCityHeight = cityWidth * scale;
|
|
62
|
+
return {
|
|
63
|
+
scale,
|
|
64
|
+
offsetX: (width - scaledCityWidth) / 2,
|
|
65
|
+
offsetZ: (height - scaledCityHeight) / 2,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const SingleLeaderLine: StoryObj = {
|
|
70
|
+
render: function RenderSingleLeaderLine() {
|
|
71
|
+
const cityData = authServerCityData as CityData;
|
|
72
|
+
const highlightLayers = createFileColorHighlightLayers(cityData.buildings);
|
|
73
|
+
|
|
74
|
+
const target = cityData.buildings.find(b => b.path === TARGET_PATH);
|
|
75
|
+
|
|
76
|
+
const stageRef = useRef<HTMLDivElement | null>(null);
|
|
77
|
+
const canvasWrapRef = useRef<HTMLDivElement | null>(null);
|
|
78
|
+
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
79
|
+
|
|
80
|
+
const [layout, setLayout] = useState<{
|
|
81
|
+
stageW: number;
|
|
82
|
+
stageH: number;
|
|
83
|
+
anchor: { x: number; y: number } | null;
|
|
84
|
+
panel: { x: number; y: number; w: number; h: number } | null;
|
|
85
|
+
}>({ stageW: 0, stageH: 0, anchor: null, panel: null });
|
|
86
|
+
|
|
87
|
+
useLayoutEffect(() => {
|
|
88
|
+
const stage = stageRef.current;
|
|
89
|
+
const canvasWrap = canvasWrapRef.current;
|
|
90
|
+
const panel = panelRef.current;
|
|
91
|
+
if (!stage || !canvasWrap || !panel || !target) return;
|
|
92
|
+
|
|
93
|
+
const measure = () => {
|
|
94
|
+
const stageRect = stage.getBoundingClientRect();
|
|
95
|
+
const canvasRect = canvasWrap.getBoundingClientRect();
|
|
96
|
+
const panelRect = panel.getBoundingClientRect();
|
|
97
|
+
|
|
98
|
+
// Compute world->screen position for the target building inside the
|
|
99
|
+
// canvas wrapper, then translate to stage-local coords for the SVG.
|
|
100
|
+
const fit = fitCityToBox(
|
|
101
|
+
cityData.bounds,
|
|
102
|
+
canvasRect.width,
|
|
103
|
+
canvasRect.height,
|
|
104
|
+
CANVAS_PADDING,
|
|
105
|
+
);
|
|
106
|
+
const buildingX =
|
|
107
|
+
(target.position.x - cityData.bounds.minX) * fit.scale + fit.offsetX;
|
|
108
|
+
const buildingY =
|
|
109
|
+
(target.position.z - cityData.bounds.minZ) * fit.scale + fit.offsetZ;
|
|
110
|
+
|
|
111
|
+
const anchor = {
|
|
112
|
+
x: canvasRect.left - stageRect.left + buildingX,
|
|
113
|
+
y: canvasRect.top - stageRect.top + buildingY,
|
|
114
|
+
};
|
|
115
|
+
const panelLocal = {
|
|
116
|
+
x: panelRect.left - stageRect.left,
|
|
117
|
+
y: panelRect.top - stageRect.top,
|
|
118
|
+
w: panelRect.width,
|
|
119
|
+
h: panelRect.height,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
setLayout({
|
|
123
|
+
stageW: stageRect.width,
|
|
124
|
+
stageH: stageRect.height,
|
|
125
|
+
anchor,
|
|
126
|
+
panel: panelLocal,
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
measure();
|
|
131
|
+
const ro = new ResizeObserver(measure);
|
|
132
|
+
ro.observe(stage);
|
|
133
|
+
ro.observe(canvasWrap);
|
|
134
|
+
ro.observe(panel);
|
|
135
|
+
window.addEventListener('resize', measure);
|
|
136
|
+
return () => {
|
|
137
|
+
ro.disconnect();
|
|
138
|
+
window.removeEventListener('resize', measure);
|
|
139
|
+
};
|
|
140
|
+
}, [cityData.bounds, target]);
|
|
141
|
+
|
|
142
|
+
if (!target) {
|
|
143
|
+
return (
|
|
144
|
+
<div style={{ padding: 24, color: '#f87171' }}>
|
|
145
|
+
Target building not found: {TARGET_PATH}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Snippet panel anchor: left edge, vertically centered on the card.
|
|
151
|
+
const panelAnchor =
|
|
152
|
+
layout.panel != null
|
|
153
|
+
? { x: layout.panel.x, y: layout.panel.y + layout.panel.h / 2 }
|
|
154
|
+
: null;
|
|
155
|
+
|
|
156
|
+
// Smooth S-curve from building -> panel using a horizontal cubic Bezier.
|
|
157
|
+
const path =
|
|
158
|
+
layout.anchor && panelAnchor
|
|
159
|
+
? (() => {
|
|
160
|
+
const a = layout.anchor;
|
|
161
|
+
const b = panelAnchor;
|
|
162
|
+
const dx = Math.max(80, (b.x - a.x) * 0.5);
|
|
163
|
+
return `M ${a.x} ${a.y} C ${a.x + dx} ${a.y}, ${b.x - dx} ${b.y}, ${b.x} ${b.y}`;
|
|
164
|
+
})()
|
|
165
|
+
: null;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div
|
|
169
|
+
style={{
|
|
170
|
+
width: '100vw',
|
|
171
|
+
height: '100vh',
|
|
172
|
+
display: 'flex',
|
|
173
|
+
flexDirection: 'column',
|
|
174
|
+
backgroundColor: '#0f1419',
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
<div
|
|
178
|
+
style={{
|
|
179
|
+
padding: '12px 16px',
|
|
180
|
+
backgroundColor: '#1f2937',
|
|
181
|
+
borderBottom: '1px solid #374151',
|
|
182
|
+
color: '#9ca3af',
|
|
183
|
+
fontSize: 13,
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
Prototype: leader line from{' '}
|
|
187
|
+
<code style={{ color: '#e5e7eb' }}>{TARGET_PATH}</code> to a side-panel
|
|
188
|
+
snippet.
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div
|
|
192
|
+
ref={stageRef}
|
|
193
|
+
style={{
|
|
194
|
+
flex: 1,
|
|
195
|
+
position: 'relative',
|
|
196
|
+
display: 'flex',
|
|
197
|
+
minHeight: 0,
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<div
|
|
201
|
+
ref={canvasWrapRef}
|
|
202
|
+
style={{ flex: 1, position: 'relative', minWidth: 0 }}
|
|
203
|
+
>
|
|
204
|
+
<ArchitectureMapHighlightLayers
|
|
205
|
+
cityData={cityData}
|
|
206
|
+
highlightLayers={highlightLayers}
|
|
207
|
+
fullSize
|
|
208
|
+
canvasBackgroundColor="#0f1419"
|
|
209
|
+
defaultBuildingColor="#36454F"
|
|
210
|
+
defaultDirectoryColor="#111827"
|
|
211
|
+
enableZoom={false}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div
|
|
216
|
+
style={{
|
|
217
|
+
width: PANEL_WIDTH,
|
|
218
|
+
padding: 24,
|
|
219
|
+
display: 'flex',
|
|
220
|
+
flexDirection: 'column',
|
|
221
|
+
justifyContent: 'center',
|
|
222
|
+
borderLeft: '1px solid #1f2937',
|
|
223
|
+
backgroundColor: '#0b0f14',
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
<div
|
|
227
|
+
ref={panelRef}
|
|
228
|
+
style={{
|
|
229
|
+
backgroundColor: '#111827',
|
|
230
|
+
border: '1px solid #374151',
|
|
231
|
+
borderRadius: 8,
|
|
232
|
+
padding: 16,
|
|
233
|
+
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.4)',
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
<div
|
|
237
|
+
style={{
|
|
238
|
+
fontSize: 11,
|
|
239
|
+
color: '#9ca3af',
|
|
240
|
+
textTransform: 'uppercase',
|
|
241
|
+
letterSpacing: 0.5,
|
|
242
|
+
marginBottom: 8,
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
callback / route.ts
|
|
246
|
+
</div>
|
|
247
|
+
<pre
|
|
248
|
+
style={{
|
|
249
|
+
margin: 0,
|
|
250
|
+
fontSize: 12,
|
|
251
|
+
lineHeight: 1.5,
|
|
252
|
+
color: '#e5e7eb',
|
|
253
|
+
fontFamily:
|
|
254
|
+
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
255
|
+
whiteSpace: 'pre-wrap',
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
{SNIPPET}
|
|
259
|
+
</pre>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
{/* SVG overlay spans the whole stage so the line can cross columns. */}
|
|
264
|
+
<svg
|
|
265
|
+
width={layout.stageW}
|
|
266
|
+
height={layout.stageH}
|
|
267
|
+
style={{
|
|
268
|
+
position: 'absolute',
|
|
269
|
+
top: 0,
|
|
270
|
+
left: 0,
|
|
271
|
+
pointerEvents: 'none',
|
|
272
|
+
zIndex: 5,
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
{path && layout.anchor && panelAnchor && (
|
|
276
|
+
<>
|
|
277
|
+
<path
|
|
278
|
+
d={path}
|
|
279
|
+
fill="none"
|
|
280
|
+
stroke="#fbbf24"
|
|
281
|
+
strokeWidth={1.5}
|
|
282
|
+
strokeDasharray="4 4"
|
|
283
|
+
opacity={0.85}
|
|
284
|
+
/>
|
|
285
|
+
<circle
|
|
286
|
+
cx={layout.anchor.x}
|
|
287
|
+
cy={layout.anchor.y}
|
|
288
|
+
r={5}
|
|
289
|
+
fill="#fbbf24"
|
|
290
|
+
stroke="#0f1419"
|
|
291
|
+
strokeWidth={1.5}
|
|
292
|
+
/>
|
|
293
|
+
<circle
|
|
294
|
+
cx={panelAnchor.x}
|
|
295
|
+
cy={panelAnchor.y}
|
|
296
|
+
r={3}
|
|
297
|
+
fill="#fbbf24"
|
|
298
|
+
/>
|
|
299
|
+
</>
|
|
300
|
+
)}
|
|
301
|
+
</svg>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
@@ -54,6 +54,13 @@ export function buildFolderIndex(cityData: CityData): FolderIndex {
|
|
|
54
54
|
for (const d of cityData.districts) directorySet.add(d.path);
|
|
55
55
|
const dirs = Array.from(directorySet).sort();
|
|
56
56
|
for (const dir of dirs) {
|
|
57
|
+
// The empty path represents the synthetic project root that some city
|
|
58
|
+
// builders emit at depth 0. Including it here would register '' as its
|
|
59
|
+
// own parent (slash<0 → parent=''), which makes the recursive `walk`
|
|
60
|
+
// in `buildFolderElevatedPanels` loop forever the moment the root node
|
|
61
|
+
// is marked expanded. Top-level real folders already live under the
|
|
62
|
+
// empty-string parent, so skipping the empty entry costs nothing.
|
|
63
|
+
if (dir === '') continue;
|
|
57
64
|
const slash = dir.lastIndexOf('/');
|
|
58
65
|
const parent = slash >= 0 ? dir.slice(0, slash) : '';
|
|
59
66
|
const arr = children.get(parent);
|
|
@@ -105,7 +112,9 @@ export interface BuildFolderElevatedPanelsOptions {
|
|
|
105
112
|
*/
|
|
106
113
|
expandedFolders: ReadonlySet<string>;
|
|
107
114
|
/** Toggle handler invoked when an umbrella tile is clicked. */
|
|
108
|
-
onToggleFolder?: (folderPath: string) => void;
|
|
115
|
+
onToggleFolder?: (folderPath: string, event: MouseEvent) => void;
|
|
116
|
+
/** Double-click handler for an umbrella tile. */
|
|
117
|
+
onDoubleClickFolder?: (folderPath: string, event: MouseEvent) => void;
|
|
109
118
|
/**
|
|
110
119
|
* Scale label font size by descendant file count. Default true. When false,
|
|
111
120
|
* the renderer's auto-sized label is used (size derived from tile footprint).
|
|
@@ -134,6 +143,7 @@ export function buildFolderElevatedPanels(
|
|
|
134
143
|
cityData,
|
|
135
144
|
expandedFolders,
|
|
136
145
|
onToggleFolder,
|
|
146
|
+
onDoubleClickFolder,
|
|
137
147
|
scaleLabelByFileCount = true,
|
|
138
148
|
} = options;
|
|
139
149
|
const index = options.index ?? buildFolderIndex(cityData);
|
|
@@ -161,7 +171,10 @@ export function buildFolderElevatedPanels(
|
|
|
161
171
|
bounds,
|
|
162
172
|
label,
|
|
163
173
|
labelSize,
|
|
164
|
-
onClick: onToggleFolder ? () => onToggleFolder(folderPath) : undefined,
|
|
174
|
+
onClick: onToggleFolder ? (event: MouseEvent) => onToggleFolder(folderPath, event) : undefined,
|
|
175
|
+
onDoubleClick: onDoubleClickFolder
|
|
176
|
+
? (event: MouseEvent) => onDoubleClickFolder(folderPath, event)
|
|
177
|
+
: undefined,
|
|
165
178
|
});
|
|
166
179
|
};
|
|
167
180
|
|