@principal-ai/file-city-react 0.5.36 → 0.5.38
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 +72 -1
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +148 -16
- 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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/FileCity3D/FileCity3D.tsx +300 -10
- package/src/components/FileCity3D/index.ts +7 -0
- package/src/index.ts +1 -0
- package/src/stories/FileCity3D.stories.tsx +669 -0
- package/src/stories/ScopeOverlay.stories.tsx +1535 -0
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type HighlightLayer,
|
|
19
19
|
type IsolationMode,
|
|
20
20
|
} from '../components/FileCity3D';
|
|
21
|
+
import { createFileColorHighlightLayers } from '../utils/fileColorHighlightLayers';
|
|
21
22
|
|
|
22
23
|
const meta: Meta<typeof FileCity3D> = {
|
|
23
24
|
title: 'Components/FileCity3D',
|
|
@@ -2209,3 +2210,671 @@ export const FlatToGrownTransition: Story = {
|
|
|
2209
2210
|
},
|
|
2210
2211
|
},
|
|
2211
2212
|
};
|
|
2213
|
+
|
|
2214
|
+
/**
|
|
2215
|
+
* Repository Profile - Simulates how FileCity3D is used in the electron-app's RepositoryProfilePanel
|
|
2216
|
+
*
|
|
2217
|
+
* Features:
|
|
2218
|
+
* - File suffix color layers (TypeScript, JavaScript, Python, etc.) using createFileColorHighlightLayers
|
|
2219
|
+
* - Git status overlays with borders (staged=green, modified=orange, untracked=blue, deleted=red)
|
|
2220
|
+
* - Toggle controls for layers
|
|
2221
|
+
* - Linear height scaling
|
|
2222
|
+
* - Background color theming
|
|
2223
|
+
* - Starts flat with manual grow button
|
|
2224
|
+
* - Uses real electron-app city data
|
|
2225
|
+
*/
|
|
2226
|
+
const RepositoryProfileTemplate: React.FC = () => {
|
|
2227
|
+
const [showSuffixLayers, setShowSuffixLayers] = React.useState(true);
|
|
2228
|
+
const [showGitStatus, setShowGitStatus] = React.useState(true);
|
|
2229
|
+
const [gitStatusType, setGitStatusType] = React.useState<'clean' | 'working' | 'all'>('working');
|
|
2230
|
+
|
|
2231
|
+
// Sample git status files from electron-app
|
|
2232
|
+
const gitStatusFiles = {
|
|
2233
|
+
staged: ['src/renderer/panels/RepositoryProfilePanel.tsx', 'src/renderer/components/FileCity3D/FileCity3D.tsx'],
|
|
2234
|
+
modified: ['src/renderer/principal-window/index.tsx', 'src/main/stores/initialization.ts', 'src/renderer/App.tsx'],
|
|
2235
|
+
untracked: ['src/renderer/components/NewFeature.tsx', 'docs/NEW_ARCHITECTURE.md'],
|
|
2236
|
+
deleted: ['src/renderer/components/OldComponent.tsx'],
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
// Create highlight layers
|
|
2240
|
+
const highlightLayers = React.useMemo(() => {
|
|
2241
|
+
const layers: HighlightLayer[] = [];
|
|
2242
|
+
const backgroundColor = '#1e293b'; // slate-800
|
|
2243
|
+
const hasGitChanges = showGitStatus && gitStatusType !== 'clean';
|
|
2244
|
+
|
|
2245
|
+
// 1. File suffix color layers (higher priority = rendered underneath)
|
|
2246
|
+
if (showSuffixLayers) {
|
|
2247
|
+
const fileSuffixLayers = createFileColorHighlightLayers((electronAppCityData as CityData).buildings);
|
|
2248
|
+
|
|
2249
|
+
// Dim suffix layers when git changes are present to help focus on git status
|
|
2250
|
+
const adjustedSuffixLayers = hasGitChanges
|
|
2251
|
+
? fileSuffixLayers.map(layer => ({
|
|
2252
|
+
...layer,
|
|
2253
|
+
opacity: (layer.opacity ?? 1.0) * 0.2,
|
|
2254
|
+
priority: layer.priority + 100,
|
|
2255
|
+
}))
|
|
2256
|
+
: fileSuffixLayers.map(layer => ({
|
|
2257
|
+
...layer,
|
|
2258
|
+
priority: layer.priority + 100,
|
|
2259
|
+
}));
|
|
2260
|
+
|
|
2261
|
+
layers.push(...adjustedSuffixLayers);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
// 2. Git status layers with borders (lower priority = rendered on top)
|
|
2265
|
+
if (showGitStatus && gitStatusType !== 'clean') {
|
|
2266
|
+
// Staged files - green border
|
|
2267
|
+
if ((gitStatusType === 'all' || gitStatusType === 'working') && gitStatusFiles.staged.length > 0) {
|
|
2268
|
+
layers.push({
|
|
2269
|
+
id: 'git-staged',
|
|
2270
|
+
name: 'Staged Files',
|
|
2271
|
+
enabled: true,
|
|
2272
|
+
color: '#22c55e', // green-500
|
|
2273
|
+
priority: 4,
|
|
2274
|
+
items: gitStatusFiles.staged.map(path => ({
|
|
2275
|
+
type: 'file' as const,
|
|
2276
|
+
path,
|
|
2277
|
+
renderStrategy: 'border' as const
|
|
2278
|
+
})),
|
|
2279
|
+
opacity: 0.8,
|
|
2280
|
+
borderWidth: 4,
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// Modified files - orange border
|
|
2285
|
+
if (gitStatusFiles.modified.length > 0) {
|
|
2286
|
+
layers.push({
|
|
2287
|
+
id: 'git-modified',
|
|
2288
|
+
name: 'Modified Files',
|
|
2289
|
+
enabled: true,
|
|
2290
|
+
color: '#f59e0b', // amber-500
|
|
2291
|
+
priority: 3,
|
|
2292
|
+
items: gitStatusFiles.modified.map(path => ({
|
|
2293
|
+
type: 'file' as const,
|
|
2294
|
+
path,
|
|
2295
|
+
renderStrategy: 'border' as const
|
|
2296
|
+
})),
|
|
2297
|
+
opacity: 0.8,
|
|
2298
|
+
borderWidth: 4,
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// Untracked files - blue border
|
|
2303
|
+
if (gitStatusFiles.untracked.length > 0) {
|
|
2304
|
+
layers.push({
|
|
2305
|
+
id: 'git-untracked',
|
|
2306
|
+
name: 'Untracked Files',
|
|
2307
|
+
enabled: true,
|
|
2308
|
+
color: '#3b82f6', // blue-500
|
|
2309
|
+
priority: 2,
|
|
2310
|
+
items: gitStatusFiles.untracked.map(path => ({
|
|
2311
|
+
type: 'file' as const,
|
|
2312
|
+
path,
|
|
2313
|
+
renderStrategy: 'border' as const
|
|
2314
|
+
})),
|
|
2315
|
+
opacity: 0.8,
|
|
2316
|
+
borderWidth: 4,
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// Deleted files - red border
|
|
2321
|
+
if (gitStatusType === 'all' && gitStatusFiles.deleted.length > 0) {
|
|
2322
|
+
layers.push({
|
|
2323
|
+
id: 'git-deleted',
|
|
2324
|
+
name: 'Deleted Files',
|
|
2325
|
+
enabled: true,
|
|
2326
|
+
color: '#ef4444', // red-500
|
|
2327
|
+
priority: 1,
|
|
2328
|
+
items: gitStatusFiles.deleted.map(path => ({
|
|
2329
|
+
type: 'file' as const,
|
|
2330
|
+
path,
|
|
2331
|
+
renderStrategy: 'border' as const
|
|
2332
|
+
})),
|
|
2333
|
+
opacity: 0.8,
|
|
2334
|
+
borderWidth: 4,
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
return layers;
|
|
2340
|
+
}, [showSuffixLayers, showGitStatus, gitStatusType]);
|
|
2341
|
+
|
|
2342
|
+
return (
|
|
2343
|
+
<div
|
|
2344
|
+
style={{ height: '100vh', display: 'flex', flexDirection: 'column', backgroundColor: '#0f172a' }}
|
|
2345
|
+
>
|
|
2346
|
+
{/* Header area */}
|
|
2347
|
+
<div style={{ padding: 16, borderBottom: '1px solid #334155' }}>
|
|
2348
|
+
<h2 style={{ margin: 0, color: '#e2e8f0', fontSize: 18, fontFamily: 'system-ui, sans-serif' }}>
|
|
2349
|
+
Repository Profile Panel Layout
|
|
2350
|
+
</h2>
|
|
2351
|
+
</div>
|
|
2352
|
+
|
|
2353
|
+
{/* Main content area - split layout */}
|
|
2354
|
+
<div style={{ flex: 1, display: 'flex', gap: 16, padding: 16, overflow: 'hidden' }}>
|
|
2355
|
+
{/* Left side - Stats panel with controls */}
|
|
2356
|
+
<div
|
|
2357
|
+
style={{
|
|
2358
|
+
flex: 1,
|
|
2359
|
+
minWidth: 0,
|
|
2360
|
+
background: '#1e293b',
|
|
2361
|
+
border: '1px solid #334155',
|
|
2362
|
+
borderRadius: 8,
|
|
2363
|
+
padding: 16,
|
|
2364
|
+
overflow: 'auto',
|
|
2365
|
+
display: 'flex',
|
|
2366
|
+
flexDirection: 'column',
|
|
2367
|
+
gap: 16,
|
|
2368
|
+
}}
|
|
2369
|
+
>
|
|
2370
|
+
<div>
|
|
2371
|
+
<h3 style={{ margin: '0 0 16px', color: '#e2e8f0', fontSize: 14, fontFamily: 'system-ui, sans-serif', fontWeight: 600 }}>
|
|
2372
|
+
Repository Profile View
|
|
2373
|
+
</h3>
|
|
2374
|
+
|
|
2375
|
+
{/* File Type Colors Toggle */}
|
|
2376
|
+
<div style={{ marginBottom: 16 }}>
|
|
2377
|
+
<label
|
|
2378
|
+
style={{
|
|
2379
|
+
display: 'flex',
|
|
2380
|
+
alignItems: 'center',
|
|
2381
|
+
gap: 8,
|
|
2382
|
+
cursor: 'pointer',
|
|
2383
|
+
fontSize: 13,
|
|
2384
|
+
color: '#e2e8f0',
|
|
2385
|
+
fontFamily: 'system-ui, sans-serif',
|
|
2386
|
+
}}
|
|
2387
|
+
>
|
|
2388
|
+
<input
|
|
2389
|
+
type="checkbox"
|
|
2390
|
+
checked={showSuffixLayers}
|
|
2391
|
+
onChange={e => setShowSuffixLayers(e.target.checked)}
|
|
2392
|
+
style={{ cursor: 'pointer' }}
|
|
2393
|
+
/>
|
|
2394
|
+
<span>Show File Type Colors</span>
|
|
2395
|
+
</label>
|
|
2396
|
+
<div style={{ fontSize: 11, color: '#64748b', marginLeft: 28, marginTop: 4, fontFamily: 'system-ui, sans-serif' }}>
|
|
2397
|
+
Color-codes files by extension (TS, JS, Python, etc.)
|
|
2398
|
+
</div>
|
|
2399
|
+
</div>
|
|
2400
|
+
|
|
2401
|
+
{/* Git Status Toggle */}
|
|
2402
|
+
<div style={{ marginBottom: 16 }}>
|
|
2403
|
+
<label
|
|
2404
|
+
style={{
|
|
2405
|
+
display: 'flex',
|
|
2406
|
+
alignItems: 'center',
|
|
2407
|
+
gap: 8,
|
|
2408
|
+
cursor: 'pointer',
|
|
2409
|
+
fontSize: 13,
|
|
2410
|
+
marginBottom: 8,
|
|
2411
|
+
color: '#e2e8f0',
|
|
2412
|
+
fontFamily: 'system-ui, sans-serif',
|
|
2413
|
+
}}
|
|
2414
|
+
>
|
|
2415
|
+
<input
|
|
2416
|
+
type="checkbox"
|
|
2417
|
+
checked={showGitStatus}
|
|
2418
|
+
onChange={e => setShowGitStatus(e.target.checked)}
|
|
2419
|
+
style={{ cursor: 'pointer' }}
|
|
2420
|
+
/>
|
|
2421
|
+
<span>Show Git Status</span>
|
|
2422
|
+
</label>
|
|
2423
|
+
|
|
2424
|
+
{showGitStatus && (
|
|
2425
|
+
<div style={{ marginLeft: 28 }}>
|
|
2426
|
+
<div style={{ fontSize: 11, color: '#64748b', marginBottom: 6, fontFamily: 'system-ui, sans-serif' }}>
|
|
2427
|
+
Status Type:
|
|
2428
|
+
</div>
|
|
2429
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
2430
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, cursor: 'pointer', color: '#e2e8f0', fontFamily: 'system-ui, sans-serif' }}>
|
|
2431
|
+
<input
|
|
2432
|
+
type="radio"
|
|
2433
|
+
checked={gitStatusType === 'clean'}
|
|
2434
|
+
onChange={() => setGitStatusType('clean')}
|
|
2435
|
+
style={{ cursor: 'pointer' }}
|
|
2436
|
+
/>
|
|
2437
|
+
Clean (no changes)
|
|
2438
|
+
</label>
|
|
2439
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, cursor: 'pointer', color: '#e2e8f0', fontFamily: 'system-ui, sans-serif' }}>
|
|
2440
|
+
<input
|
|
2441
|
+
type="radio"
|
|
2442
|
+
checked={gitStatusType === 'working'}
|
|
2443
|
+
onChange={() => setGitStatusType('working')}
|
|
2444
|
+
style={{ cursor: 'pointer' }}
|
|
2445
|
+
/>
|
|
2446
|
+
Working Changes
|
|
2447
|
+
</label>
|
|
2448
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, cursor: 'pointer', color: '#e2e8f0', fontFamily: 'system-ui, sans-serif' }}>
|
|
2449
|
+
<input
|
|
2450
|
+
type="radio"
|
|
2451
|
+
checked={gitStatusType === 'all'}
|
|
2452
|
+
onChange={() => setGitStatusType('all')}
|
|
2453
|
+
style={{ cursor: 'pointer' }}
|
|
2454
|
+
/>
|
|
2455
|
+
All Status (+ staged & deleted)
|
|
2456
|
+
</label>
|
|
2457
|
+
</div>
|
|
2458
|
+
</div>
|
|
2459
|
+
)}
|
|
2460
|
+
</div>
|
|
2461
|
+
|
|
2462
|
+
{/* Legend */}
|
|
2463
|
+
{showGitStatus && gitStatusType !== 'clean' && (
|
|
2464
|
+
<div
|
|
2465
|
+
style={{
|
|
2466
|
+
marginTop: 16,
|
|
2467
|
+
paddingTop: 16,
|
|
2468
|
+
borderTop: '1px solid #334155',
|
|
2469
|
+
}}
|
|
2470
|
+
>
|
|
2471
|
+
<div style={{ fontSize: 11, color: '#64748b', marginBottom: 8, fontFamily: 'system-ui, sans-serif' }}>
|
|
2472
|
+
Git Status Legend:
|
|
2473
|
+
</div>
|
|
2474
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
2475
|
+
{gitStatusType === 'all' && (
|
|
2476
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: '#e2e8f0', fontFamily: 'system-ui, sans-serif' }}>
|
|
2477
|
+
<div
|
|
2478
|
+
style={{
|
|
2479
|
+
width: 16,
|
|
2480
|
+
height: 16,
|
|
2481
|
+
border: '3px solid #22c55e',
|
|
2482
|
+
borderRadius: 2,
|
|
2483
|
+
}}
|
|
2484
|
+
/>
|
|
2485
|
+
<span>Staged ({gitStatusFiles.staged.length})</span>
|
|
2486
|
+
</div>
|
|
2487
|
+
)}
|
|
2488
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: '#e2e8f0', fontFamily: 'system-ui, sans-serif' }}>
|
|
2489
|
+
<div
|
|
2490
|
+
style={{
|
|
2491
|
+
width: 16,
|
|
2492
|
+
height: 16,
|
|
2493
|
+
border: '3px solid #f59e0b',
|
|
2494
|
+
borderRadius: 2,
|
|
2495
|
+
}}
|
|
2496
|
+
/>
|
|
2497
|
+
<span>Modified ({gitStatusFiles.modified.length})</span>
|
|
2498
|
+
</div>
|
|
2499
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: '#e2e8f0', fontFamily: 'system-ui, sans-serif' }}>
|
|
2500
|
+
<div
|
|
2501
|
+
style={{
|
|
2502
|
+
width: 16,
|
|
2503
|
+
height: 16,
|
|
2504
|
+
border: '3px solid #3b82f6',
|
|
2505
|
+
borderRadius: 2,
|
|
2506
|
+
}}
|
|
2507
|
+
/>
|
|
2508
|
+
<span>Untracked ({gitStatusFiles.untracked.length})</span>
|
|
2509
|
+
</div>
|
|
2510
|
+
{gitStatusType === 'all' && (
|
|
2511
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: '#e2e8f0', fontFamily: 'system-ui, sans-serif' }}>
|
|
2512
|
+
<div
|
|
2513
|
+
style={{
|
|
2514
|
+
width: 16,
|
|
2515
|
+
height: 16,
|
|
2516
|
+
border: '3px solid #ef4444',
|
|
2517
|
+
borderRadius: 2,
|
|
2518
|
+
}}
|
|
2519
|
+
/>
|
|
2520
|
+
<span>Deleted ({gitStatusFiles.deleted.length})</span>
|
|
2521
|
+
</div>
|
|
2522
|
+
)}
|
|
2523
|
+
</div>
|
|
2524
|
+
</div>
|
|
2525
|
+
)}
|
|
2526
|
+
|
|
2527
|
+
{/* Active layers count */}
|
|
2528
|
+
<div
|
|
2529
|
+
style={{
|
|
2530
|
+
marginTop: 16,
|
|
2531
|
+
paddingTop: 16,
|
|
2532
|
+
borderTop: '1px solid #334155',
|
|
2533
|
+
fontSize: 11,
|
|
2534
|
+
color: '#64748b',
|
|
2535
|
+
fontFamily: 'system-ui, sans-serif',
|
|
2536
|
+
}}
|
|
2537
|
+
>
|
|
2538
|
+
Active Layers: {highlightLayers.length}
|
|
2539
|
+
</div>
|
|
2540
|
+
</div>
|
|
2541
|
+
|
|
2542
|
+
{/* Additional info */}
|
|
2543
|
+
<div
|
|
2544
|
+
style={{
|
|
2545
|
+
marginTop: 'auto',
|
|
2546
|
+
paddingTop: 16,
|
|
2547
|
+
borderTop: '1px solid #334155',
|
|
2548
|
+
}}
|
|
2549
|
+
>
|
|
2550
|
+
<div style={{ color: '#64748b', fontSize: 12, fontFamily: 'system-ui, sans-serif', lineHeight: 1.6 }}>
|
|
2551
|
+
<p style={{ margin: '0 0 8px' }}>In the actual RepositoryProfilePanel, this section also displays:</p>
|
|
2552
|
+
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
|
2553
|
+
<li>Activity heatmap</li>
|
|
2554
|
+
<li>Contributor list</li>
|
|
2555
|
+
<li>Changed files details</li>
|
|
2556
|
+
<li>Commit playback controls</li>
|
|
2557
|
+
</ul>
|
|
2558
|
+
</div>
|
|
2559
|
+
</div>
|
|
2560
|
+
</div>
|
|
2561
|
+
|
|
2562
|
+
{/* Right side - File City 3D (constrained to square-ish aspect ratio) */}
|
|
2563
|
+
<div
|
|
2564
|
+
style={{
|
|
2565
|
+
flex: 1,
|
|
2566
|
+
minWidth: 0,
|
|
2567
|
+
height: '100%',
|
|
2568
|
+
borderRadius: 8,
|
|
2569
|
+
overflow: 'hidden',
|
|
2570
|
+
border: '1px solid #334155',
|
|
2571
|
+
backgroundColor: '#1e293b',
|
|
2572
|
+
position: 'relative',
|
|
2573
|
+
}}
|
|
2574
|
+
>
|
|
2575
|
+
<FileCity3D
|
|
2576
|
+
cityData={electronAppCityData as CityData}
|
|
2577
|
+
height="100%"
|
|
2578
|
+
width="100%"
|
|
2579
|
+
heightScaling="linear"
|
|
2580
|
+
linearScale={0.5}
|
|
2581
|
+
animation={{
|
|
2582
|
+
startFlat: true,
|
|
2583
|
+
autoStartDelay: null, // Manual control like in RepositoryProfilePanel
|
|
2584
|
+
}}
|
|
2585
|
+
showControls={true}
|
|
2586
|
+
backgroundColor="#1e293b" // slate-800
|
|
2587
|
+
highlightLayers={highlightLayers}
|
|
2588
|
+
style={{
|
|
2589
|
+
width: '100%',
|
|
2590
|
+
height: '100%',
|
|
2591
|
+
}}
|
|
2592
|
+
/>
|
|
2593
|
+
</div>
|
|
2594
|
+
</div>
|
|
2595
|
+
|
|
2596
|
+
{/* Info banner - bottom */}
|
|
2597
|
+
<div
|
|
2598
|
+
style={{
|
|
2599
|
+
position: 'absolute',
|
|
2600
|
+
bottom: 0,
|
|
2601
|
+
left: 0,
|
|
2602
|
+
right: 0,
|
|
2603
|
+
zIndex: 100,
|
|
2604
|
+
background: 'rgba(15, 23, 42, 0.95)',
|
|
2605
|
+
borderTop: '1px solid #334155',
|
|
2606
|
+
padding: '12px 24px',
|
|
2607
|
+
color: '#94a3b8',
|
|
2608
|
+
fontFamily: 'system-ui, sans-serif',
|
|
2609
|
+
fontSize: 12,
|
|
2610
|
+
textAlign: 'center',
|
|
2611
|
+
}}
|
|
2612
|
+
>
|
|
2613
|
+
This story replicates how FileCity3D is used in the electron-app's RepositoryProfilePanel:
|
|
2614
|
+
file type colors + git status overlays with borders
|
|
2615
|
+
</div>
|
|
2616
|
+
</div>
|
|
2617
|
+
);
|
|
2618
|
+
};
|
|
2619
|
+
|
|
2620
|
+
export const RepositoryProfile: Story = {
|
|
2621
|
+
render: () => <RepositoryProfileTemplate />,
|
|
2622
|
+
parameters: {
|
|
2623
|
+
docs: {
|
|
2624
|
+
description: {
|
|
2625
|
+
story:
|
|
2626
|
+
'Simulates how FileCity3D is used in the electron-app\'s RepositoryProfilePanel. ' +
|
|
2627
|
+
'Shows file suffix color layers (using createFileColorHighlightLayers) and git status overlays with borders. ' +
|
|
2628
|
+
'Demonstrates the layering system where git status borders render on top of dimmed file type colors.',
|
|
2629
|
+
},
|
|
2630
|
+
},
|
|
2631
|
+
},
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2634
|
+
/**
|
|
2635
|
+
* Repository Profile - Async Data Loading Test
|
|
2636
|
+
*
|
|
2637
|
+
* Tests the camera initialization bug that caused electron-app to switch to 2D view.
|
|
2638
|
+
* Simulates the actual RepositoryProfilePanel behavior where city data loads asynchronously
|
|
2639
|
+
* after component mount (file tree fetch -> build city data -> setState).
|
|
2640
|
+
*
|
|
2641
|
+
* The bug: Camera would initialize at (0,0,0) before city bounds were calculated,
|
|
2642
|
+
* resulting in a black screen or wrong view position.
|
|
2643
|
+
*/
|
|
2644
|
+
const AsyncDataLoadingTemplate: React.FC = () => {
|
|
2645
|
+
const [cityData, setCityData] = React.useState<CityData | null>(null);
|
|
2646
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
2647
|
+
const [showSuffixLayers, setShowSuffixLayers] = React.useState(true);
|
|
2648
|
+
|
|
2649
|
+
// Simulate async data loading like RepositoryProfilePanel
|
|
2650
|
+
React.useEffect(() => {
|
|
2651
|
+
setIsLoading(true);
|
|
2652
|
+
|
|
2653
|
+
// Simulate network delay + processing time
|
|
2654
|
+
const timer = setTimeout(() => {
|
|
2655
|
+
console.log('[AsyncTest] Loading city data...');
|
|
2656
|
+
setCityData(electronAppCityData as CityData);
|
|
2657
|
+
setIsLoading(false);
|
|
2658
|
+
console.log('[AsyncTest] City data loaded, bounds:', (electronAppCityData as CityData).bounds);
|
|
2659
|
+
}, 1500); // 1.5s delay simulates file tree fetch + build time
|
|
2660
|
+
|
|
2661
|
+
return () => clearTimeout(timer);
|
|
2662
|
+
}, []);
|
|
2663
|
+
|
|
2664
|
+
// Create highlight layers
|
|
2665
|
+
const highlightLayers = React.useMemo(() => {
|
|
2666
|
+
if (!cityData) return [];
|
|
2667
|
+
|
|
2668
|
+
const layers: HighlightLayer[] = [];
|
|
2669
|
+
|
|
2670
|
+
if (showSuffixLayers) {
|
|
2671
|
+
const fileSuffixLayers = createFileColorHighlightLayers(cityData.buildings);
|
|
2672
|
+
layers.push(...fileSuffixLayers.map(layer => ({
|
|
2673
|
+
...layer,
|
|
2674
|
+
priority: layer.priority + 100,
|
|
2675
|
+
})));
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
return layers;
|
|
2679
|
+
}, [cityData, showSuffixLayers]);
|
|
2680
|
+
|
|
2681
|
+
return (
|
|
2682
|
+
<div
|
|
2683
|
+
style={{ height: '100vh', display: 'flex', flexDirection: 'column', backgroundColor: '#0f172a' }}
|
|
2684
|
+
>
|
|
2685
|
+
{/* Header */}
|
|
2686
|
+
<div style={{ padding: 16, borderBottom: '1px solid #334155' }}>
|
|
2687
|
+
<h2 style={{ margin: 0, color: '#e2e8f0', fontSize: 18, fontFamily: 'system-ui, sans-serif' }}>
|
|
2688
|
+
Async Data Loading Test - Camera Initialization Bug
|
|
2689
|
+
</h2>
|
|
2690
|
+
<p style={{ margin: '8px 0 0', color: '#94a3b8', fontSize: 13, fontFamily: 'system-ui, sans-serif' }}>
|
|
2691
|
+
Tests the camera bug that caused electron-app to switch from 3D to 2D view
|
|
2692
|
+
</p>
|
|
2693
|
+
</div>
|
|
2694
|
+
|
|
2695
|
+
{/* Main content */}
|
|
2696
|
+
<div style={{ flex: 1, display: 'flex', gap: 16, padding: 16, overflow: 'hidden' }}>
|
|
2697
|
+
{/* Left panel */}
|
|
2698
|
+
<div
|
|
2699
|
+
style={{
|
|
2700
|
+
flex: 1,
|
|
2701
|
+
minWidth: 0,
|
|
2702
|
+
background: '#1e293b',
|
|
2703
|
+
border: '1px solid #334155',
|
|
2704
|
+
borderRadius: 8,
|
|
2705
|
+
padding: 16,
|
|
2706
|
+
overflow: 'auto',
|
|
2707
|
+
display: 'flex',
|
|
2708
|
+
flexDirection: 'column',
|
|
2709
|
+
gap: 16,
|
|
2710
|
+
}}
|
|
2711
|
+
>
|
|
2712
|
+
<div>
|
|
2713
|
+
<h3 style={{ margin: '0 0 16px', color: '#e2e8f0', fontSize: 14, fontFamily: 'system-ui, sans-serif', fontWeight: 600 }}>
|
|
2714
|
+
Test Status
|
|
2715
|
+
</h3>
|
|
2716
|
+
|
|
2717
|
+
<div style={{ marginBottom: 16 }}>
|
|
2718
|
+
<div style={{ fontSize: 13, color: '#e2e8f0', marginBottom: 8, fontFamily: 'system-ui, sans-serif' }}>
|
|
2719
|
+
Loading State: <strong style={{ color: isLoading ? '#f59e0b' : '#22c55e' }}>
|
|
2720
|
+
{isLoading ? 'LOADING' : 'LOADED'}
|
|
2721
|
+
</strong>
|
|
2722
|
+
</div>
|
|
2723
|
+
<div style={{ fontSize: 13, color: '#e2e8f0', marginBottom: 8, fontFamily: 'system-ui, sans-serif' }}>
|
|
2724
|
+
City Data: <strong style={{ color: cityData ? '#22c55e' : '#64748b' }}>
|
|
2725
|
+
{cityData ? 'READY' : 'NULL'}
|
|
2726
|
+
</strong>
|
|
2727
|
+
</div>
|
|
2728
|
+
{cityData && (
|
|
2729
|
+
<div style={{ fontSize: 11, color: '#64748b', marginTop: 8, fontFamily: 'monospace' }}>
|
|
2730
|
+
Bounds: [{cityData.bounds.minX}, {cityData.bounds.maxX}] x [{cityData.bounds.minZ}, {cityData.bounds.maxZ}]
|
|
2731
|
+
</div>
|
|
2732
|
+
)}
|
|
2733
|
+
</div>
|
|
2734
|
+
|
|
2735
|
+
<div style={{ marginBottom: 16, paddingTop: 16, borderTop: '1px solid #334155' }}>
|
|
2736
|
+
<label
|
|
2737
|
+
style={{
|
|
2738
|
+
display: 'flex',
|
|
2739
|
+
alignItems: 'center',
|
|
2740
|
+
gap: 8,
|
|
2741
|
+
cursor: 'pointer',
|
|
2742
|
+
fontSize: 13,
|
|
2743
|
+
color: '#e2e8f0',
|
|
2744
|
+
fontFamily: 'system-ui, sans-serif',
|
|
2745
|
+
}}
|
|
2746
|
+
>
|
|
2747
|
+
<input
|
|
2748
|
+
type="checkbox"
|
|
2749
|
+
checked={showSuffixLayers}
|
|
2750
|
+
onChange={e => setShowSuffixLayers(e.target.checked)}
|
|
2751
|
+
style={{ cursor: 'pointer' }}
|
|
2752
|
+
/>
|
|
2753
|
+
<span>Show File Type Colors</span>
|
|
2754
|
+
</label>
|
|
2755
|
+
</div>
|
|
2756
|
+
|
|
2757
|
+
<button
|
|
2758
|
+
onClick={() => {
|
|
2759
|
+
setCityData(null);
|
|
2760
|
+
setIsLoading(true);
|
|
2761
|
+
setTimeout(() => {
|
|
2762
|
+
setCityData(electronAppCityData as CityData);
|
|
2763
|
+
setIsLoading(false);
|
|
2764
|
+
}, 1500);
|
|
2765
|
+
}}
|
|
2766
|
+
style={{
|
|
2767
|
+
padding: '8px 16px',
|
|
2768
|
+
background: '#3b82f6',
|
|
2769
|
+
color: 'white',
|
|
2770
|
+
border: '1px solid #334155',
|
|
2771
|
+
borderRadius: 6,
|
|
2772
|
+
cursor: 'pointer',
|
|
2773
|
+
fontSize: 13,
|
|
2774
|
+
fontFamily: 'system-ui, sans-serif',
|
|
2775
|
+
}}
|
|
2776
|
+
>
|
|
2777
|
+
Reload Data (Test Again)
|
|
2778
|
+
</button>
|
|
2779
|
+
</div>
|
|
2780
|
+
|
|
2781
|
+
{/* Bug description */}
|
|
2782
|
+
<div
|
|
2783
|
+
style={{
|
|
2784
|
+
marginTop: 'auto',
|
|
2785
|
+
paddingTop: 16,
|
|
2786
|
+
borderTop: '1px solid #334155',
|
|
2787
|
+
}}
|
|
2788
|
+
>
|
|
2789
|
+
<div style={{ fontSize: 12, color: '#e2e8f0', marginBottom: 8, fontFamily: 'system-ui, sans-serif', fontWeight: 600 }}>
|
|
2790
|
+
The Bug:
|
|
2791
|
+
</div>
|
|
2792
|
+
<div style={{ color: '#94a3b8', fontSize: 12, fontFamily: 'system-ui, sans-serif', lineHeight: 1.6 }}>
|
|
2793
|
+
<p style={{ margin: '0 0 8px' }}>
|
|
2794
|
+
When FileCity3D mounts before cityData is available (or while it's null),
|
|
2795
|
+
the camera may initialize at position (0,0,0) instead of calculating the
|
|
2796
|
+
proper viewing position from city bounds.
|
|
2797
|
+
</p>
|
|
2798
|
+
<p style={{ margin: '8px 0 0' }}>
|
|
2799
|
+
This caused electron-app's RepositoryProfilePanel to show a black screen
|
|
2800
|
+
or wrong camera position, leading to a switch to the 2D view.
|
|
2801
|
+
</p>
|
|
2802
|
+
</div>
|
|
2803
|
+
</div>
|
|
2804
|
+
</div>
|
|
2805
|
+
|
|
2806
|
+
{/* Right panel - 3D view */}
|
|
2807
|
+
<div
|
|
2808
|
+
style={{
|
|
2809
|
+
flex: 1,
|
|
2810
|
+
minWidth: 0,
|
|
2811
|
+
height: '100%',
|
|
2812
|
+
borderRadius: 8,
|
|
2813
|
+
overflow: 'hidden',
|
|
2814
|
+
border: '1px solid #334155',
|
|
2815
|
+
backgroundColor: '#1e293b',
|
|
2816
|
+
position: 'relative',
|
|
2817
|
+
}}
|
|
2818
|
+
>
|
|
2819
|
+
{isLoading || !cityData ? (
|
|
2820
|
+
<div
|
|
2821
|
+
style={{
|
|
2822
|
+
width: '100%',
|
|
2823
|
+
height: '100%',
|
|
2824
|
+
display: 'flex',
|
|
2825
|
+
flexDirection: 'column',
|
|
2826
|
+
alignItems: 'center',
|
|
2827
|
+
justifyContent: 'center',
|
|
2828
|
+
color: '#94a3b8',
|
|
2829
|
+
gap: 16,
|
|
2830
|
+
}}
|
|
2831
|
+
>
|
|
2832
|
+
<div style={{ fontSize: 14, fontFamily: 'system-ui, sans-serif' }}>
|
|
2833
|
+
{isLoading ? 'Loading city data...' : 'No data'}
|
|
2834
|
+
</div>
|
|
2835
|
+
{isLoading && (
|
|
2836
|
+
<div style={{ fontSize: 12, color: '#64748b', fontFamily: 'system-ui, sans-serif' }}>
|
|
2837
|
+
Simulating async file tree fetch + build
|
|
2838
|
+
</div>
|
|
2839
|
+
)}
|
|
2840
|
+
</div>
|
|
2841
|
+
) : (
|
|
2842
|
+
<FileCity3D
|
|
2843
|
+
cityData={cityData}
|
|
2844
|
+
height="100%"
|
|
2845
|
+
width="100%"
|
|
2846
|
+
heightScaling="linear"
|
|
2847
|
+
linearScale={0.5}
|
|
2848
|
+
animation={{
|
|
2849
|
+
startFlat: true,
|
|
2850
|
+
autoStartDelay: null,
|
|
2851
|
+
}}
|
|
2852
|
+
showControls={true}
|
|
2853
|
+
backgroundColor="#1e293b"
|
|
2854
|
+
highlightLayers={highlightLayers}
|
|
2855
|
+
style={{
|
|
2856
|
+
width: '100%',
|
|
2857
|
+
height: '100%',
|
|
2858
|
+
}}
|
|
2859
|
+
/>
|
|
2860
|
+
)}
|
|
2861
|
+
</div>
|
|
2862
|
+
</div>
|
|
2863
|
+
</div>
|
|
2864
|
+
);
|
|
2865
|
+
};
|
|
2866
|
+
|
|
2867
|
+
export const AsyncDataLoadingTest: Story = {
|
|
2868
|
+
render: () => <AsyncDataLoadingTemplate />,
|
|
2869
|
+
parameters: {
|
|
2870
|
+
docs: {
|
|
2871
|
+
description: {
|
|
2872
|
+
story:
|
|
2873
|
+
'Tests the camera initialization bug that caused electron-app to switch from FileCity3D to 2D view. ' +
|
|
2874
|
+
'Simulates async data loading (file tree fetch -> build city data -> setState) to reproduce the race condition ' +
|
|
2875
|
+
'where the camera would initialize at (0,0,0) before city bounds were available. ' +
|
|
2876
|
+
'Click "Reload Data" to test the initialization sequence multiple times.',
|
|
2877
|
+
},
|
|
2878
|
+
},
|
|
2879
|
+
},
|
|
2880
|
+
};
|