@principal-ai/file-city-react 0.5.26 → 0.5.28
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":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"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"}
|
|
@@ -662,6 +662,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
662
662
|
const isOrbitingRef = useRef(false);
|
|
663
663
|
const hasAppliedInitial = useRef(false);
|
|
664
664
|
const frameCount = useRef(0);
|
|
665
|
+
const prevIsFlatRef = useRef(isFlat); // Track previous isFlat to detect actual state changes
|
|
665
666
|
// Calculate camera height to fit city in viewport (for top-down view)
|
|
666
667
|
// Formula: height = citySize / (2 * tan(fov/2) * min(1, aspect))
|
|
667
668
|
// Padding factor adds space around the city to match 2D component
|
|
@@ -720,6 +721,12 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
720
721
|
targetZ: 0,
|
|
721
722
|
};
|
|
722
723
|
}, [focusTarget, citySize, isFlat, maxBuildingHeight, calculateFlatCameraHeight]);
|
|
724
|
+
// Capture initial camera position on first render only
|
|
725
|
+
// This prevents the PerspectiveCamera position prop from causing jumps when targetPos changes
|
|
726
|
+
const initialPosRef = useRef(null);
|
|
727
|
+
if (!initialPosRef.current) {
|
|
728
|
+
initialPosRef.current = targetPos;
|
|
729
|
+
}
|
|
723
730
|
// Spring animation for camera movement
|
|
724
731
|
const [{ camX, camY, camZ, lookX, lookY, lookZ }, api] = useSpring(() => ({
|
|
725
732
|
camX: targetPos.x,
|
|
@@ -763,11 +770,30 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
763
770
|
const orbitParamsRef = useRef(null);
|
|
764
771
|
// Track tilt parameters during vertical rotation
|
|
765
772
|
const tiltParamsRef = useRef(null);
|
|
766
|
-
// When
|
|
773
|
+
// When isFlat changes after initial setup, animate to new position
|
|
774
|
+
// We track isFlat explicitly rather than targetPos to avoid spurious animations
|
|
775
|
+
// from aspect ratio changes or other recalculations
|
|
767
776
|
useEffect(() => {
|
|
768
777
|
// Skip the first render - we handle that directly in useFrame
|
|
769
778
|
if (!hasAppliedInitial.current)
|
|
770
779
|
return;
|
|
780
|
+
// Only animate if isFlat actually changed (flat <-> grown transition)
|
|
781
|
+
const isFlatChanged = prevIsFlatRef.current !== isFlat;
|
|
782
|
+
prevIsFlatRef.current = isFlat;
|
|
783
|
+
if (!isFlatChanged) {
|
|
784
|
+
// isFlat didn't change, just update position directly without animation
|
|
785
|
+
// This handles things like focusTarget changes within the same flat/grown state
|
|
786
|
+
api.set({
|
|
787
|
+
camX: targetPos.x,
|
|
788
|
+
camY: targetPos.y,
|
|
789
|
+
camZ: targetPos.z,
|
|
790
|
+
lookX: targetPos.targetX,
|
|
791
|
+
lookY: targetPos.targetY,
|
|
792
|
+
lookZ: targetPos.targetZ,
|
|
793
|
+
});
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
// isFlat changed - animate the transition
|
|
771
797
|
api.start({
|
|
772
798
|
camX: targetPos.x,
|
|
773
799
|
camY: targetPos.y,
|
|
@@ -779,7 +805,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
779
805
|
isAnimatingRef.current = false;
|
|
780
806
|
},
|
|
781
807
|
});
|
|
782
|
-
}, [targetPos, api]);
|
|
808
|
+
}, [targetPos, api, isFlat]);
|
|
783
809
|
// Update camera each frame
|
|
784
810
|
useFrame(() => {
|
|
785
811
|
frameCount.current++;
|
|
@@ -1088,7 +1114,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1088
1114
|
cameraApi = null;
|
|
1089
1115
|
};
|
|
1090
1116
|
}, [resetToInitial, moveTo, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
|
|
1091
|
-
return (_jsxs(_Fragment, { children: [_jsx(PerspectiveCamera, { makeDefault: true, fov: 50, near: 1, far: citySize * 10, position: [
|
|
1117
|
+
return (_jsxs(_Fragment, { children: [_jsx(PerspectiveCamera, { makeDefault: true, fov: 50, near: 1, far: citySize * 10, position: [initialPosRef.current.x, initialPosRef.current.y, initialPosRef.current.z] }), _jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: 0.05, minDistance: 10, maxDistance: citySize * 3, maxPolarAngle: Math.PI / 2.1, target: [initialPosRef.current.targetX, initialPosRef.current.targetY, initialPosRef.current.targetZ] })] }));
|
|
1092
1118
|
}
|
|
1093
1119
|
function InfoPanel({ building }) {
|
|
1094
1120
|
if (!building)
|
package/package.json
CHANGED
|
@@ -1146,6 +1146,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1146
1146
|
const isOrbitingRef = useRef(false);
|
|
1147
1147
|
const hasAppliedInitial = useRef(false);
|
|
1148
1148
|
const frameCount = useRef(0);
|
|
1149
|
+
const prevIsFlatRef = useRef(isFlat); // Track previous isFlat to detect actual state changes
|
|
1149
1150
|
|
|
1150
1151
|
// Calculate camera height to fit city in viewport (for top-down view)
|
|
1151
1152
|
// Formula: height = citySize / (2 * tan(fov/2) * min(1, aspect))
|
|
@@ -1209,6 +1210,13 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1209
1210
|
};
|
|
1210
1211
|
}, [focusTarget, citySize, isFlat, maxBuildingHeight, calculateFlatCameraHeight]);
|
|
1211
1212
|
|
|
1213
|
+
// Capture initial camera position on first render only
|
|
1214
|
+
// This prevents the PerspectiveCamera position prop from causing jumps when targetPos changes
|
|
1215
|
+
const initialPosRef = useRef<typeof targetPos | null>(null);
|
|
1216
|
+
if (!initialPosRef.current) {
|
|
1217
|
+
initialPosRef.current = targetPos;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1212
1220
|
// Spring animation for camera movement
|
|
1213
1221
|
const [{ camX, camY, camZ, lookX, lookY, lookZ }, api] = useSpring(() => ({
|
|
1214
1222
|
camX: targetPos.x,
|
|
@@ -1268,11 +1276,32 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1268
1276
|
azimuthAngle: number; // horizontal angle to maintain
|
|
1269
1277
|
} | null>(null);
|
|
1270
1278
|
|
|
1271
|
-
// When
|
|
1279
|
+
// When isFlat changes after initial setup, animate to new position
|
|
1280
|
+
// We track isFlat explicitly rather than targetPos to avoid spurious animations
|
|
1281
|
+
// from aspect ratio changes or other recalculations
|
|
1272
1282
|
useEffect(() => {
|
|
1273
1283
|
// Skip the first render - we handle that directly in useFrame
|
|
1274
1284
|
if (!hasAppliedInitial.current) return;
|
|
1275
1285
|
|
|
1286
|
+
// Only animate if isFlat actually changed (flat <-> grown transition)
|
|
1287
|
+
const isFlatChanged = prevIsFlatRef.current !== isFlat;
|
|
1288
|
+
prevIsFlatRef.current = isFlat;
|
|
1289
|
+
|
|
1290
|
+
if (!isFlatChanged) {
|
|
1291
|
+
// isFlat didn't change, just update position directly without animation
|
|
1292
|
+
// This handles things like focusTarget changes within the same flat/grown state
|
|
1293
|
+
api.set({
|
|
1294
|
+
camX: targetPos.x,
|
|
1295
|
+
camY: targetPos.y,
|
|
1296
|
+
camZ: targetPos.z,
|
|
1297
|
+
lookX: targetPos.targetX,
|
|
1298
|
+
lookY: targetPos.targetY,
|
|
1299
|
+
lookZ: targetPos.targetZ,
|
|
1300
|
+
});
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// isFlat changed - animate the transition
|
|
1276
1305
|
api.start({
|
|
1277
1306
|
camX: targetPos.x,
|
|
1278
1307
|
camY: targetPos.y,
|
|
@@ -1284,7 +1313,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1284
1313
|
isAnimatingRef.current = false;
|
|
1285
1314
|
},
|
|
1286
1315
|
});
|
|
1287
|
-
}, [targetPos, api]);
|
|
1316
|
+
}, [targetPos, api, isFlat]);
|
|
1288
1317
|
|
|
1289
1318
|
// Update camera each frame
|
|
1290
1319
|
useFrame(() => {
|
|
@@ -1660,7 +1689,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1660
1689
|
fov={50}
|
|
1661
1690
|
near={1}
|
|
1662
1691
|
far={citySize * 10}
|
|
1663
|
-
position={[
|
|
1692
|
+
position={[initialPosRef.current!.x, initialPosRef.current!.y, initialPosRef.current!.z]}
|
|
1664
1693
|
/>
|
|
1665
1694
|
<OrbitControls
|
|
1666
1695
|
ref={controlsRef}
|
|
@@ -1669,7 +1698,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1669
1698
|
minDistance={10}
|
|
1670
1699
|
maxDistance={citySize * 3}
|
|
1671
1700
|
maxPolarAngle={Math.PI / 2.1}
|
|
1672
|
-
target={[
|
|
1701
|
+
target={[initialPosRef.current!.targetX, initialPosRef.current!.targetY, initialPosRef.current!.targetZ]}
|
|
1673
1702
|
/>
|
|
1674
1703
|
</>
|
|
1675
1704
|
);
|
|
@@ -525,3 +525,144 @@ export const ScenarioComparison: StoryObj = {
|
|
|
525
525
|
},
|
|
526
526
|
};
|
|
527
527
|
|
|
528
|
+
/**
|
|
529
|
+
* This story reproduces the exact panel behavior:
|
|
530
|
+
* 1. Start in 2D mode
|
|
531
|
+
* 2. Click "Switch to 3D" button
|
|
532
|
+
* 3. 3D renders flat behind 2D overlay
|
|
533
|
+
* 4. Overlay fades out (100ms delay, 300ms transition)
|
|
534
|
+
* 5. After overlay is gone, buildings grow (400ms delay)
|
|
535
|
+
*
|
|
536
|
+
* This tests the camera initialization issue where the camera
|
|
537
|
+
* might flash to an angled position before settling to flat.
|
|
538
|
+
*/
|
|
539
|
+
export const PanelTransitionTest: StoryObj = {
|
|
540
|
+
render: function RenderPanelTransitionTest() {
|
|
541
|
+
const [viewMode, setViewMode] = useState<'2d' | '3d'>('2d');
|
|
542
|
+
const [isGrown, setIsGrown] = useState(false);
|
|
543
|
+
const [overlayOpacity, setOverlayOpacity] = useState(1);
|
|
544
|
+
const [hideOverlay, setHideOverlay] = useState(true);
|
|
545
|
+
const cityData = authServerCityData as CityData;
|
|
546
|
+
const highlightLayers = createFileColorHighlightLayers(cityData.buildings);
|
|
547
|
+
|
|
548
|
+
// Handle view mode toggle - sets overlay state BEFORE changing viewMode
|
|
549
|
+
const handleToggle = () => {
|
|
550
|
+
if (viewMode === '2d') {
|
|
551
|
+
// Switching from 2D to 3D - show overlay at full opacity BEFORE mode changes
|
|
552
|
+
setOverlayOpacity(1);
|
|
553
|
+
setHideOverlay(false);
|
|
554
|
+
setIsGrown(false);
|
|
555
|
+
setViewMode('3d');
|
|
556
|
+
} else {
|
|
557
|
+
setViewMode('2d');
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// Handle transition timing
|
|
562
|
+
useEffect(() => {
|
|
563
|
+
if (viewMode === '3d') {
|
|
564
|
+
// Timeline:
|
|
565
|
+
// T=0ms: Both 2D and 3D render, 2D at full opacity
|
|
566
|
+
// T=100ms: Start fading overlay
|
|
567
|
+
// T=400ms: Remove overlay, start grow animation
|
|
568
|
+
const fadeTimer = setTimeout(() => {
|
|
569
|
+
setOverlayOpacity(0);
|
|
570
|
+
}, 100);
|
|
571
|
+
|
|
572
|
+
const removeTimer = setTimeout(() => {
|
|
573
|
+
setHideOverlay(true);
|
|
574
|
+
setIsGrown(true);
|
|
575
|
+
}, 400);
|
|
576
|
+
|
|
577
|
+
return () => {
|
|
578
|
+
clearTimeout(fadeTimer);
|
|
579
|
+
clearTimeout(removeTimer);
|
|
580
|
+
};
|
|
581
|
+
} else {
|
|
582
|
+
setIsGrown(false);
|
|
583
|
+
setOverlayOpacity(1);
|
|
584
|
+
setHideOverlay(true);
|
|
585
|
+
}
|
|
586
|
+
}, [viewMode]);
|
|
587
|
+
|
|
588
|
+
return (
|
|
589
|
+
<div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
590
|
+
<div
|
|
591
|
+
style={{
|
|
592
|
+
padding: '12px 16px',
|
|
593
|
+
backgroundColor: '#1f2937',
|
|
594
|
+
borderBottom: '1px solid #374151',
|
|
595
|
+
display: 'flex',
|
|
596
|
+
gap: '16px',
|
|
597
|
+
alignItems: 'center',
|
|
598
|
+
}}
|
|
599
|
+
>
|
|
600
|
+
<button
|
|
601
|
+
onClick={handleToggle}
|
|
602
|
+
style={{
|
|
603
|
+
padding: '8px 16px',
|
|
604
|
+
borderRadius: '6px',
|
|
605
|
+
border: 'none',
|
|
606
|
+
cursor: 'pointer',
|
|
607
|
+
fontSize: '14px',
|
|
608
|
+
fontWeight: 500,
|
|
609
|
+
backgroundColor: '#3b82f6',
|
|
610
|
+
color: '#ffffff',
|
|
611
|
+
}}
|
|
612
|
+
>
|
|
613
|
+
{viewMode === '2d' ? 'Switch to 3D' : 'Switch to 2D'}
|
|
614
|
+
</button>
|
|
615
|
+
<span style={{ color: '#9ca3af', fontSize: '13px' }}>
|
|
616
|
+
viewMode: {viewMode} | isGrown: {String(isGrown)} | hideOverlay: {String(hideOverlay)} | opacity: {overlayOpacity}
|
|
617
|
+
</span>
|
|
618
|
+
</div>
|
|
619
|
+
|
|
620
|
+
<div style={{ flex: 1, backgroundColor: '#0f1419', position: 'relative' }}>
|
|
621
|
+
{/* 3D layer - renders when in 3D mode */}
|
|
622
|
+
{viewMode === '3d' && (
|
|
623
|
+
<FileCity3D
|
|
624
|
+
cityData={cityData}
|
|
625
|
+
highlightLayers={highlightLayers}
|
|
626
|
+
width="100%"
|
|
627
|
+
height="100%"
|
|
628
|
+
isGrown={isGrown}
|
|
629
|
+
animation={{ startFlat: true, autoStartDelay: null }}
|
|
630
|
+
showControls={false}
|
|
631
|
+
backgroundColor="#0f1419"
|
|
632
|
+
/>
|
|
633
|
+
)}
|
|
634
|
+
|
|
635
|
+
{/* 2D overlay - fades out during transition */}
|
|
636
|
+
{(viewMode === '2d' || !hideOverlay) && (
|
|
637
|
+
<div
|
|
638
|
+
style={{
|
|
639
|
+
position: viewMode === '3d' ? 'absolute' : 'relative',
|
|
640
|
+
top: 0,
|
|
641
|
+
left: 0,
|
|
642
|
+
right: 0,
|
|
643
|
+
bottom: 0,
|
|
644
|
+
width: '100%',
|
|
645
|
+
height: '100%',
|
|
646
|
+
zIndex: viewMode === '3d' ? 10 : undefined,
|
|
647
|
+
opacity: viewMode === '2d' ? 1 : overlayOpacity,
|
|
648
|
+
transition: 'opacity 300ms ease-out',
|
|
649
|
+
pointerEvents: viewMode === '2d' ? 'auto' : 'none',
|
|
650
|
+
}}
|
|
651
|
+
>
|
|
652
|
+
<ArchitectureMapHighlightLayers
|
|
653
|
+
cityData={cityData}
|
|
654
|
+
highlightLayers={highlightLayers}
|
|
655
|
+
fullSize={true}
|
|
656
|
+
canvasBackgroundColor="#0f1419"
|
|
657
|
+
defaultBuildingColor="#36454F"
|
|
658
|
+
defaultDirectoryColor="#111827"
|
|
659
|
+
enableZoom={viewMode === '2d'}
|
|
660
|
+
/>
|
|
661
|
+
</div>
|
|
662
|
+
)}
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
);
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
|