@principal-ai/file-city-react 0.5.43 → 0.5.45
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 +42 -1
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +62 -9
- package/dist/components/FileCity3D/index.d.ts +2 -2
- package/dist/components/FileCity3D/index.d.ts.map +1 -1
- package/dist/components/FileCity3D/index.js +1 -1
- package/package.json +2 -1
- package/src/components/FileCity3D/FileCity3D.tsx +113 -3
- package/src/components/FileCity3D/index.ts +3 -0
- package/src/stories/CameraOffsetForOverlays.stories.tsx +470 -0
- package/src/stories/HighlightLayersFlatDebug.stories.tsx +319 -0
- package/src/stories/LeaderLineSnippetOverlay3D.stories.tsx +1060 -0
- package/src/stories/SequenceDiagramOverlay.stories.tsx +427 -0
- package/src/stories/WorkflowSequenceDiagramPrototype.stories.tsx +754 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import '@xyflow/react/dist/style.css';
|
|
5
|
+
import { SequenceDiagramRenderer } from '@principal-ai/principal-view-react';
|
|
6
|
+
import type {
|
|
7
|
+
SequenceEvent,
|
|
8
|
+
SequenceEdge,
|
|
9
|
+
} from '@principal-ai/principal-view-react';
|
|
10
|
+
|
|
11
|
+
import { FileCity3D } from '../components/FileCity3D';
|
|
12
|
+
import type { CityData, HighlightLayer } from '../components/FileCity3D';
|
|
13
|
+
import authServerCityData from '../../../../assets/auth-server-city-data.json';
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: 'Prototypes/Sequence Diagram Overlay (3D)',
|
|
17
|
+
parameters: { layout: 'fullscreen' },
|
|
18
|
+
} satisfies Meta;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hand-tagged auth-server flow: each event carries a `sourcePath` pointing
|
|
24
|
+
* at a real building in the auth-server city data. Clicking a step in the
|
|
25
|
+
* diagram drives a HighlightLayer on the matching building below.
|
|
26
|
+
*/
|
|
27
|
+
const AUTH_FLOW: { events: SequenceEvent[]; edges: SequenceEdge[] } = {
|
|
28
|
+
events: [
|
|
29
|
+
{
|
|
30
|
+
id: '1',
|
|
31
|
+
name: 'request.middleware.intercepted',
|
|
32
|
+
label: 'Middleware Intercepts',
|
|
33
|
+
sourcePath: 'auth-server/src/middleware.ts',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: '2',
|
|
37
|
+
name: 'workos.start.requested',
|
|
38
|
+
label: 'Start WorkOS Auth',
|
|
39
|
+
sourcePath: 'auth-server/src/app/api/auth/workos/start/route.ts',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: '3',
|
|
43
|
+
name: 'workos.callback.received',
|
|
44
|
+
label: 'Callback Received',
|
|
45
|
+
sourcePath: 'auth-server/src/app/api/auth/workos/callback/route.ts',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: '4',
|
|
49
|
+
name: 'workos.token.exchanged',
|
|
50
|
+
label: 'Exchange Code → Token',
|
|
51
|
+
sourcePath: 'auth-server/src/app/api/auth/workos/token/route.ts',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: '5',
|
|
55
|
+
name: 'token.stored',
|
|
56
|
+
label: 'Persist Token',
|
|
57
|
+
sourcePath: 'auth-server/src/lib/token-store.ts',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: '6',
|
|
61
|
+
name: 'session.created',
|
|
62
|
+
label: 'Create Session',
|
|
63
|
+
sourcePath: 'auth-server/src/lib/auth-session-manager.ts',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: '7',
|
|
67
|
+
name: 'org.membership.checked',
|
|
68
|
+
label: 'Check Org Membership',
|
|
69
|
+
sourcePath: 'auth-server/src/lib/org-membership.ts',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: '8',
|
|
73
|
+
name: 'cli.room-token.minted',
|
|
74
|
+
label: 'Mint CLI Room Token',
|
|
75
|
+
sourcePath: 'auth-server/src/app/api/auth/cli/room-token/route.ts',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: '9',
|
|
79
|
+
name: 'workos.token.verified',
|
|
80
|
+
label: 'Verify Token',
|
|
81
|
+
sourcePath: 'auth-server/src/app/api/auth/workos/verify/route.ts',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: '10',
|
|
85
|
+
name: 'user.fetched',
|
|
86
|
+
label: 'Fetch User',
|
|
87
|
+
sourcePath: 'auth-server/src/app/api/auth/user/route.ts',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
edges: [
|
|
91
|
+
{ id: 'e1', fromEvent: '1', toEvent: '2' },
|
|
92
|
+
{ id: 'e2', fromEvent: '2', toEvent: '3' },
|
|
93
|
+
{ id: 'e3', fromEvent: '3', toEvent: '4' },
|
|
94
|
+
{ id: 'e4', fromEvent: '4', toEvent: '5' },
|
|
95
|
+
{ id: 'e5', fromEvent: '5', toEvent: '6' },
|
|
96
|
+
{ id: 'e6', fromEvent: '6', toEvent: '7' },
|
|
97
|
+
{ id: 'e7', fromEvent: '7', toEvent: '8' },
|
|
98
|
+
{ id: 'e8', fromEvent: '8', toEvent: '9' },
|
|
99
|
+
{ id: 'e9', fromEvent: '9', toEvent: '10' },
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type Story = StoryObj;
|
|
104
|
+
|
|
105
|
+
const OVERLAY_HEIGHT_PCT = 50;
|
|
106
|
+
const COLLAPSE_MS = 320;
|
|
107
|
+
|
|
108
|
+
export const Default: Story = {
|
|
109
|
+
render: () => {
|
|
110
|
+
const cityData = useMemo(() => authServerCityData as CityData, []);
|
|
111
|
+
const [opacity, setOpacity] = useState(1);
|
|
112
|
+
const [interactive, setInteractive] = useState(true);
|
|
113
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
114
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
115
|
+
const [selectedEventId, setSelectedEventId] = useState<string | null>(
|
|
116
|
+
null,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const eventById = useMemo(() => {
|
|
120
|
+
const m = new Map<string, SequenceEvent>();
|
|
121
|
+
for (const e of AUTH_FLOW.events) m.set(e.id, e);
|
|
122
|
+
return m;
|
|
123
|
+
}, []);
|
|
124
|
+
|
|
125
|
+
const selectedPath = useMemo(() => {
|
|
126
|
+
if (!selectedEventId) return null;
|
|
127
|
+
return eventById.get(selectedEventId)?.sourcePath ?? null;
|
|
128
|
+
}, [selectedEventId, eventById]);
|
|
129
|
+
|
|
130
|
+
const highlightLayers = useMemo<HighlightLayer[]>(() => {
|
|
131
|
+
if (!selectedPath) return [];
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
id: 'sequence-selection',
|
|
135
|
+
name: 'Sequence Selection',
|
|
136
|
+
enabled: true,
|
|
137
|
+
color: '#22d3ee',
|
|
138
|
+
opacity: 0.9,
|
|
139
|
+
borderWidth: 4,
|
|
140
|
+
priority: 100,
|
|
141
|
+
items: [
|
|
142
|
+
{
|
|
143
|
+
path: selectedPath,
|
|
144
|
+
type: 'file',
|
|
145
|
+
renderStrategy: 'fill',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
}, [selectedPath]);
|
|
151
|
+
|
|
152
|
+
const handleNodeClick = useCallback((nodeId: string) => {
|
|
153
|
+
setSelectedEventId(prev => (prev === nodeId ? null : nodeId));
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div
|
|
158
|
+
style={{
|
|
159
|
+
position: 'fixed',
|
|
160
|
+
inset: 0,
|
|
161
|
+
backgroundColor: '#0b0f14',
|
|
162
|
+
overflow: 'hidden',
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
{/* 2D city background (flat mode) */}
|
|
166
|
+
<div style={{ position: 'absolute', inset: 0 }}>
|
|
167
|
+
<FileCity3D
|
|
168
|
+
cityData={cityData}
|
|
169
|
+
width="100%"
|
|
170
|
+
height="100%"
|
|
171
|
+
isGrown={false}
|
|
172
|
+
backgroundColor="#0b0f14"
|
|
173
|
+
highlightLayers={highlightLayers}
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{/* Semi-transparent sequence diagram overlay — bottom half, collapsible */}
|
|
178
|
+
<div
|
|
179
|
+
style={{
|
|
180
|
+
position: 'absolute',
|
|
181
|
+
bottom: 0,
|
|
182
|
+
left: 0,
|
|
183
|
+
right: 0,
|
|
184
|
+
height: `${OVERLAY_HEIGHT_PCT}vh`,
|
|
185
|
+
transform: collapsed
|
|
186
|
+
? 'translateY(calc(100% - 36px))'
|
|
187
|
+
: 'translateY(0)',
|
|
188
|
+
transition: `transform ${COLLAPSE_MS}ms cubic-bezier(0.32, 0.72, 0, 1)`,
|
|
189
|
+
pointerEvents: 'none',
|
|
190
|
+
zIndex: 30,
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{/* Drawer handle / collapse toggle pinned to the top edge */}
|
|
194
|
+
<div
|
|
195
|
+
style={{
|
|
196
|
+
position: 'absolute',
|
|
197
|
+
left: 0,
|
|
198
|
+
right: 0,
|
|
199
|
+
top: 0,
|
|
200
|
+
height: 36,
|
|
201
|
+
display: 'flex',
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
justifyContent: 'flex-end',
|
|
204
|
+
paddingRight: 16,
|
|
205
|
+
pointerEvents: 'auto',
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<button
|
|
209
|
+
onClick={() => setCollapsed(c => !c)}
|
|
210
|
+
style={{
|
|
211
|
+
display: 'flex',
|
|
212
|
+
alignItems: 'center',
|
|
213
|
+
gap: 8,
|
|
214
|
+
padding: '6px 14px',
|
|
215
|
+
backgroundColor: 'rgba(17, 24, 39, 0.92)',
|
|
216
|
+
border: '1px solid #374151',
|
|
217
|
+
borderRadius: 999,
|
|
218
|
+
color: '#e5e7eb',
|
|
219
|
+
fontSize: 11,
|
|
220
|
+
fontFamily:
|
|
221
|
+
'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
222
|
+
fontWeight: 600,
|
|
223
|
+
letterSpacing: 0.4,
|
|
224
|
+
textTransform: 'uppercase',
|
|
225
|
+
cursor: 'pointer',
|
|
226
|
+
boxShadow: '0 4px 14px rgba(0,0,0,0.45)',
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<span
|
|
230
|
+
style={{
|
|
231
|
+
display: 'inline-block',
|
|
232
|
+
transform: collapsed ? 'rotate(0deg)' : 'rotate(180deg)',
|
|
233
|
+
transition: `transform ${COLLAPSE_MS}ms ease`,
|
|
234
|
+
fontSize: 13,
|
|
235
|
+
lineHeight: 1,
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
▲
|
|
239
|
+
</span>
|
|
240
|
+
<span>{collapsed ? 'Show sequence' : 'Hide sequence'}</span>
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* Diagram pane */}
|
|
245
|
+
<div
|
|
246
|
+
style={{
|
|
247
|
+
position: 'absolute',
|
|
248
|
+
top: 36,
|
|
249
|
+
left: 0,
|
|
250
|
+
right: 0,
|
|
251
|
+
bottom: 0,
|
|
252
|
+
backgroundColor: '#0b0f14',
|
|
253
|
+
borderTop: '1px solid #1f2937',
|
|
254
|
+
opacity: collapsed ? 0 : opacity,
|
|
255
|
+
pointerEvents: interactive && !collapsed ? 'auto' : 'none',
|
|
256
|
+
transition: `opacity ${COLLAPSE_MS}ms ease`,
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<SequenceDiagramRenderer
|
|
260
|
+
events={AUTH_FLOW.events}
|
|
261
|
+
edges={AUTH_FLOW.edges}
|
|
262
|
+
width="100%"
|
|
263
|
+
height="100%"
|
|
264
|
+
showControls={false}
|
|
265
|
+
showBackground={false}
|
|
266
|
+
stickyHeaders
|
|
267
|
+
selectedNodeId={selectedEventId ?? undefined}
|
|
268
|
+
onNodeClick={handleNodeClick}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{/* Settings gear button — always visible, toggles the panel */}
|
|
274
|
+
<button
|
|
275
|
+
onClick={() => setSettingsOpen(o => !o)}
|
|
276
|
+
aria-label={settingsOpen ? 'Hide settings' : 'Show settings'}
|
|
277
|
+
style={{
|
|
278
|
+
position: 'absolute',
|
|
279
|
+
top: 16,
|
|
280
|
+
left: 16,
|
|
281
|
+
zIndex: 60,
|
|
282
|
+
width: 36,
|
|
283
|
+
height: 36,
|
|
284
|
+
display: 'flex',
|
|
285
|
+
alignItems: 'center',
|
|
286
|
+
justifyContent: 'center',
|
|
287
|
+
backgroundColor: settingsOpen
|
|
288
|
+
? 'rgba(34, 211, 238, 0.18)'
|
|
289
|
+
: 'rgba(17, 24, 39, 0.92)',
|
|
290
|
+
border: `1px solid ${settingsOpen ? '#22d3ee' : '#374151'}`,
|
|
291
|
+
borderRadius: 8,
|
|
292
|
+
color: settingsOpen ? '#22d3ee' : '#e5e7eb',
|
|
293
|
+
cursor: 'pointer',
|
|
294
|
+
boxShadow: '0 4px 14px rgba(0,0,0,0.45)',
|
|
295
|
+
transition:
|
|
296
|
+
'background-color 160ms ease, border-color 160ms ease, color 160ms ease',
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
299
|
+
<svg width={18} height={18} viewBox="0 0 24 24" fill="none">
|
|
300
|
+
<path
|
|
301
|
+
d="M12 15.5a3.5 3.5 0 100-7 3.5 3.5 0 000 7z"
|
|
302
|
+
stroke="currentColor"
|
|
303
|
+
strokeWidth={1.7}
|
|
304
|
+
/>
|
|
305
|
+
<path
|
|
306
|
+
d="M19.4 13.6a7.7 7.7 0 000-3.2l1.7-1.3-1.8-3.1-2 .8a7.7 7.7 0 00-2.8-1.6l-.3-2.1h-3.6l-.3 2.1a7.7 7.7 0 00-2.8 1.6l-2-.8-1.8 3.1 1.7 1.3a7.7 7.7 0 000 3.2l-1.7 1.3 1.8 3.1 2-.8a7.7 7.7 0 002.8 1.6l.3 2.1h3.6l.3-2.1a7.7 7.7 0 002.8-1.6l2 .8 1.8-3.1-1.7-1.3z"
|
|
307
|
+
stroke="currentColor"
|
|
308
|
+
strokeWidth={1.7}
|
|
309
|
+
strokeLinejoin="round"
|
|
310
|
+
/>
|
|
311
|
+
</svg>
|
|
312
|
+
</button>
|
|
313
|
+
|
|
314
|
+
{/* Floating control panel */}
|
|
315
|
+
<div
|
|
316
|
+
style={{
|
|
317
|
+
position: 'absolute',
|
|
318
|
+
top: 16,
|
|
319
|
+
left: 64,
|
|
320
|
+
zIndex: 50,
|
|
321
|
+
backgroundColor: 'rgba(17, 24, 39, 0.92)',
|
|
322
|
+
border: '1px solid #374151',
|
|
323
|
+
borderRadius: 10,
|
|
324
|
+
padding: '14px 16px',
|
|
325
|
+
color: '#e5e7eb',
|
|
326
|
+
fontFamily:
|
|
327
|
+
'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
328
|
+
fontSize: 12,
|
|
329
|
+
display: 'flex',
|
|
330
|
+
flexDirection: 'column',
|
|
331
|
+
gap: 10,
|
|
332
|
+
minWidth: 240,
|
|
333
|
+
boxShadow: '0 8px 24px rgba(0,0,0,0.45)',
|
|
334
|
+
transformOrigin: 'top left',
|
|
335
|
+
transform: settingsOpen
|
|
336
|
+
? 'translateX(0) scale(1)'
|
|
337
|
+
: 'translateX(-8px) scale(0.96)',
|
|
338
|
+
opacity: settingsOpen ? 1 : 0,
|
|
339
|
+
pointerEvents: settingsOpen ? 'auto' : 'none',
|
|
340
|
+
transition:
|
|
341
|
+
'opacity 180ms ease, transform 180ms ease',
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
<div
|
|
345
|
+
style={{
|
|
346
|
+
fontSize: 10,
|
|
347
|
+
textTransform: 'uppercase',
|
|
348
|
+
letterSpacing: 1.2,
|
|
349
|
+
color: '#9ca3af',
|
|
350
|
+
}}
|
|
351
|
+
>
|
|
352
|
+
Sequence Diagram Overlay
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<label
|
|
356
|
+
style={{ display: 'flex', flexDirection: 'column', gap: 4 }}
|
|
357
|
+
>
|
|
358
|
+
<span style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
359
|
+
<span>Opacity</span>
|
|
360
|
+
<span style={{ color: '#9ca3af' }}>{opacity.toFixed(2)}</span>
|
|
361
|
+
</span>
|
|
362
|
+
<input
|
|
363
|
+
type="range"
|
|
364
|
+
min={0}
|
|
365
|
+
max={1}
|
|
366
|
+
step={0.01}
|
|
367
|
+
value={opacity}
|
|
368
|
+
onChange={e => setOpacity(parseFloat(e.target.value))}
|
|
369
|
+
style={{ width: '100%' }}
|
|
370
|
+
/>
|
|
371
|
+
</label>
|
|
372
|
+
|
|
373
|
+
<label
|
|
374
|
+
style={{ display: 'flex', alignItems: 'center', gap: 8 }}
|
|
375
|
+
>
|
|
376
|
+
<input
|
|
377
|
+
type="checkbox"
|
|
378
|
+
checked={interactive}
|
|
379
|
+
onChange={e => setInteractive(e.target.checked)}
|
|
380
|
+
/>
|
|
381
|
+
<span>Interactive (capture pointer)</span>
|
|
382
|
+
</label>
|
|
383
|
+
|
|
384
|
+
<div style={{ fontSize: 10, color: '#6b7280', lineHeight: 1.4 }}>
|
|
385
|
+
Click an event to highlight the matching file in the city below.
|
|
386
|
+
Use the handle at the seam to slide the diagram down.
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
{selectedPath && (
|
|
390
|
+
<div
|
|
391
|
+
style={{
|
|
392
|
+
marginTop: 4,
|
|
393
|
+
paddingTop: 10,
|
|
394
|
+
borderTop: '1px solid #1f2937',
|
|
395
|
+
display: 'flex',
|
|
396
|
+
flexDirection: 'column',
|
|
397
|
+
gap: 4,
|
|
398
|
+
}}
|
|
399
|
+
>
|
|
400
|
+
<div
|
|
401
|
+
style={{
|
|
402
|
+
fontSize: 10,
|
|
403
|
+
textTransform: 'uppercase',
|
|
404
|
+
letterSpacing: 1.2,
|
|
405
|
+
color: '#9ca3af',
|
|
406
|
+
}}
|
|
407
|
+
>
|
|
408
|
+
Selected
|
|
409
|
+
</div>
|
|
410
|
+
<code
|
|
411
|
+
style={{
|
|
412
|
+
fontSize: 11,
|
|
413
|
+
color: '#e5e7eb',
|
|
414
|
+
fontFamily:
|
|
415
|
+
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
416
|
+
wordBreak: 'break-all',
|
|
417
|
+
}}
|
|
418
|
+
>
|
|
419
|
+
{selectedPath}
|
|
420
|
+
</code>
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
},
|
|
427
|
+
};
|