@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 targetPos changes after initial, animate to new position
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: [targetPos.x, targetPos.y, targetPos.z] }), _jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: 0.05, minDistance: 10, maxDistance: citySize * 3, maxPolarAngle: Math.PI / 2.1, target: [targetPos.targetX, targetPos.targetY, targetPos.targetZ] })] }));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/file-city-react",
3
- "version": "0.5.26",
3
+ "version": "0.5.28",
4
4
  "type": "module",
5
5
  "description": "React components for File City visualization",
6
6
  "main": "dist/index.js",
@@ -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 targetPos changes after initial, animate to new position
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={[targetPos.x, targetPos.y, targetPos.z]}
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={[targetPos.targetX, targetPos.targetY, targetPos.targetZ]}
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
+