@principal-ai/file-city-react 0.5.41 → 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.
|
@@ -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);
|
package/package.json
CHANGED
|
@@ -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);
|