@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.
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import React from 'react';
10
10
  import type { CityData, CityBuilding, CityDistrict, HighlightLayer as BuilderHighlightLayer, LayerItem, LayerRenderStrategy } from '@principal-ai/file-city-builder';
11
+ import * as THREE from 'three';
11
12
  import type { ThreeElements } from '@react-three/fiber';
12
13
  declare module 'react' {
13
14
  namespace JSX {
@@ -24,6 +25,23 @@ export interface SelectionStyle {
24
25
  /** Ring border width in world units. Default: 2. */
25
26
  borderWidth?: number;
26
27
  }
28
+ /**
29
+ * Per-frame camera callback signature. Fires once per R3F render frame from
30
+ * inside the Canvas, so projection done in the callback is in lockstep with
31
+ * the city render — useful for HTML/SVG overlays that need to track world
32
+ * positions (e.g. leader lines anchored to buildings).
33
+ *
34
+ * The callback receives the live `THREE.Camera` and the current canvas
35
+ * size in CSS pixels. To project a world point to canvas-local pixels:
36
+ *
37
+ * const v = new THREE.Vector3(x, y, z).project(camera);
38
+ * const px = (v.x * 0.5 + 0.5) * size.width;
39
+ * const py = (v.y * -0.5 + 0.5) * size.height;
40
+ */
41
+ export type OnCameraFrame = (camera: THREE.Camera, size: {
42
+ width: number;
43
+ height: number;
44
+ }) => void;
27
45
  /** What to do with non-highlighted buildings */
28
46
  export type IsolationMode = 'none' | 'transparent' | 'collapse' | 'hide';
29
47
  export interface AnimationConfig {
@@ -125,6 +143,13 @@ export interface CameraControlsConfig {
125
143
  export declare const DEFAULT_CAMERA_CONTROLS: Required<Omit<CameraControlsConfig, 'panSpeed' | 'rotateSpeed' | 'zoomSpeed'>> & Pick<CameraControlsConfig, 'panSpeed' | 'rotateSpeed' | 'zoomSpeed'>;
126
144
  export declare function resetCamera(): void;
127
145
  export declare function moveCameraTo(x: number, z: number, size?: number): void;
146
+ /**
147
+ * Position the camera straight overhead a target at a specific height (flat
148
+ * top-down view). Use to fit the city inside a sub-rect of the canvas — e.g.
149
+ * when overlays cover part of the viewport. Larger `height` = city appears
150
+ * smaller; smaller `height` = zoomed in.
151
+ */
152
+ export declare function setCameraFlatView(x: number, z: number, height: number, options?: RotateOptions): void;
128
153
  /**
129
154
  * Set the camera's look-at target (center point for orbiting).
130
155
  * Camera maintains its current distance and angles relative to the new target.
@@ -254,6 +279,14 @@ export interface FileCity3DProps {
254
279
  adaptCameraToBuildings?: boolean;
255
280
  /** Base file type color layers (resolved with highlightLayers) */
256
281
  fileColorLayers?: HighlightLayer[];
282
+ /**
283
+ * Override the per-building color fallback. When unset (default), buildings
284
+ * not matched by a fill highlight layer are colored by file extension via
285
+ * the built-in file-type palette. Set to a CSS color (e.g. `'#475569'`) to
286
+ * render unmatched buildings in a neutral tone — useful for debug stories
287
+ * that want to isolate highlight-layer rendering.
288
+ */
289
+ defaultBuildingColor?: string;
257
290
  /**
258
291
  * Translucent slabs rendered above the city showing scope coverage as
259
292
  * elevated planes over the directories they own.
@@ -280,6 +313,14 @@ export interface FileCity3DProps {
280
313
  * Memoize this object to avoid unnecessary camera re-mounts.
281
314
  */
282
315
  cameraControls?: CameraControlsConfig;
316
+ /**
317
+ * Fires once per R3F render frame with the live camera and canvas size.
318
+ * Use to project world points to canvas pixels for HTML/SVG overlays that
319
+ * need to track buildings as the camera pans / zooms / rotates.
320
+ *
321
+ * Memoize the callback to avoid re-mounting the bridge.
322
+ */
323
+ onCameraFrame?: OnCameraFrame;
283
324
  }
284
325
  /**
285
326
  * FileCity3D - 3D visualization of codebase structure
@@ -287,6 +328,6 @@ export interface FileCity3DProps {
287
328
  * Renders CityData as an interactive 3D city where buildings represent files
288
329
  * and their height corresponds to line count or file size.
289
330
  */
290
- export declare function FileCity3D({ cityData, width, height, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity, isLoading, loadingMessage, emptyMessage, heightScaling, linearScale, flatPatterns, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor, textColor, selectedBuilding, selectedPath, selectionStyle, adaptCameraToBuildings, fileColorLayers, cameraControls, }: FileCity3DProps): import("react/jsx-runtime").JSX.Element;
331
+ export declare function FileCity3D({ cityData, width, height, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity, isLoading, loadingMessage, emptyMessage, heightScaling, linearScale, flatPatterns, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor, textColor, selectedBuilding, selectedPath, selectionStyle, adaptCameraToBuildings, fileColorLayers, defaultBuildingColor, cameraControls, onCameraFrame, }: FileCity3DProps): import("react/jsx-runtime").JSX.Element;
291
332
  export default FileCity3D;
292
333
  //# sourceMappingURL=FileCity3D.d.ts.map
@@ -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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"}
@@ -430,7 +430,7 @@ function BorderHighlights({ buildings, centerOffset, highlightLayers, growProgre
430
430
  });
431
431
  if (borderEdgeData.length === 0)
432
432
  return null;
433
- return (_jsxs("instancedMesh", { ref: meshRef, args: [undefined, undefined, borderEdgeData.length], frustumCulled: false, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshBasicMaterial", { transparent: true, opacity: 0.9, vertexColors: true })] }));
433
+ return (_jsxs("instancedMesh", { ref: meshRef, args: [undefined, undefined, borderEdgeData.length], frustumCulled: false, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshBasicMaterial", { transparent: true, opacity: 0.9 })] }));
434
434
  }
435
435
  // Helper to check if a path is inside a directory
436
436
  function isPathInDirectory(path, directory) {
@@ -438,7 +438,7 @@ function isPathInDirectory(path, directory) {
438
438
  return true;
439
439
  return path === directory || path.startsWith(directory + '/');
440
440
  }
441
- function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hoveredIndex, selectedIndex, growProgress, animationConfig, heightScaling, linearScale, flatPatterns, staggerIndices, focusDirectory, highlightLayers, isolationMode, }) {
441
+ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hoveredIndex, selectedIndex, growProgress, animationConfig, heightScaling, linearScale, flatPatterns, staggerIndices, focusDirectory, highlightLayers, isolationMode, defaultBuildingColor, }) {
442
442
  const meshRef = useRef(null);
443
443
  const startTimeRef = useRef(null);
444
444
  const tempObject = useMemo(() => new THREE.Object3D(), []);
@@ -515,7 +515,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
515
515
  // Get all layer matches and find first fill match for building color
516
516
  const matches = getLayerMatchesForPath(building.path, highlightLayers);
517
517
  const fillMatch = matches.find(m => m.renderStrategy === 'fill');
518
- const color = fillMatch?.color ?? getColorForFile(building);
518
+ const color = fillMatch?.color ?? defaultBuildingColor ?? getColorForFile(building);
519
519
  const x = building.position.x - centerOffset.x;
520
520
  const z = building.position.z - centerOffset.z;
521
521
  const staggerIndex = staggerIndices[index] ?? index;
@@ -541,6 +541,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
541
541
  staggerIndices,
542
542
  animationConfig.staggerDelay,
543
543
  highlightLayers,
544
+ defaultBuildingColor,
544
545
  ]);
545
546
  const minHeight = 0.3;
546
547
  const baseOffset = 0.2;
@@ -817,6 +818,15 @@ export function resetCamera() {
817
818
  export function moveCameraTo(x, z, size) {
818
819
  cameraApi?.moveTo(x, z, size);
819
820
  }
821
+ /**
822
+ * Position the camera straight overhead a target at a specific height (flat
823
+ * top-down view). Use to fit the city inside a sub-rect of the canvas — e.g.
824
+ * when overlays cover part of the viewport. Larger `height` = city appears
825
+ * smaller; smaller `height` = zoomed in.
826
+ */
827
+ export function setCameraFlatView(x, z, height, options) {
828
+ cameraApi?.setFlatView(x, z, height, options);
829
+ }
820
830
  /**
821
831
  * Set the camera's look-at target (center point for orbiting).
822
832
  * Camera maintains its current distance and angles relative to the new target.
@@ -886,6 +896,18 @@ export function getCameraAngle() {
886
896
  export function getCameraTilt() {
887
897
  return cameraApi?.getCurrentTilt() ?? null;
888
898
  }
899
+ /**
900
+ * Bridge for piping the live camera + canvas size out of the R3F Canvas on
901
+ * every frame. Mounted as a child of `<Canvas>` so it has access to the R3F
902
+ * render loop; runs zero work if no callback is provided.
903
+ */
904
+ function CameraFrameBridge({ onCameraFrame }) {
905
+ const { camera, size } = useThree();
906
+ useFrame(() => {
907
+ onCameraFrame?.(camera, { width: size.width, height: size.height });
908
+ });
909
+ return null;
910
+ }
889
911
  const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0, cameraControls, onCameraReady, }) {
890
912
  // Use selector to only subscribe to camera, not the entire R3F state
891
913
  // This prevents re-renders on pointer movement
@@ -1219,6 +1241,36 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
1219
1241
  lookZ: z,
1220
1242
  });
1221
1243
  }, [citySize, api]);
1244
+ // Position the camera directly above a target at a given height (flat /
1245
+ // top-down view). Useful for fitting the city into a visible sub-rect of
1246
+ // the canvas — raise `height` to make the city appear smaller, lower it to
1247
+ // zoom in. Pairs with `setTarget` for pan-only changes.
1248
+ const setFlatView = useCallback((x, z, height, options) => {
1249
+ // MapControls (drag/wheel) mutates camera.position and controls.target
1250
+ // directly without touching our spring. If we don't resync first, a
1251
+ // call here can no-op when the spring's stored values happen to equal
1252
+ // the requested target even though the visible camera is elsewhere.
1253
+ if (controlsRef.current) {
1254
+ api.set({
1255
+ camX: camera.position.x,
1256
+ camY: camera.position.y,
1257
+ camZ: camera.position.z,
1258
+ lookX: controlsRef.current.target.x,
1259
+ lookY: controlsRef.current.target.y,
1260
+ lookZ: controlsRef.current.target.z,
1261
+ });
1262
+ }
1263
+ const config = options?.duration ? { duration: options.duration } : undefined;
1264
+ api.start({
1265
+ camX: x,
1266
+ camY: height,
1267
+ camZ: z + 0.001, // tiny offset to avoid gimbal lock when looking straight down
1268
+ lookX: x,
1269
+ lookY: 0,
1270
+ lookZ: z,
1271
+ ...(config ? { config } : {}),
1272
+ });
1273
+ }, [api, camera]);
1222
1274
  // Set camera target (look-at point), maintaining current distance and angles
1223
1275
  const setTarget = useCallback((x, y, z, options) => {
1224
1276
  // Get current offset from target
@@ -1409,6 +1461,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
1409
1461
  cameraApi = {
1410
1462
  reset: resetToInitial,
1411
1463
  moveTo,
1464
+ setFlatView,
1412
1465
  setTarget,
1413
1466
  rotateTo,
1414
1467
  rotateBy,
@@ -1422,7 +1475,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
1422
1475
  return () => {
1423
1476
  cameraApi = null;
1424
1477
  };
1425
- }, [resetToInitial, moveTo, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
1478
+ }, [resetToInitial, moveTo, setFlatView, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
1426
1479
  // Custom wheel handler for wheel === 'pan'. We disable MapControls' built-in
1427
1480
  // zoom (otherwise it competes with our handler) and handle both axes here:
1428
1481
  // ctrl/⌘+wheel = zoom (matches trackpad pinch), plain wheel = pan along the
@@ -1679,7 +1732,7 @@ function SelectionRing({ district, centerOffset, color, borderWidth, growProgres
1679
1732
  const barH = 0.5;
1680
1733
  return (_jsxs("group", { position: [cx, y, cz], children: [_jsxs("mesh", { position: [0, 0, -d / 2], renderOrder: 20, children: [_jsx("boxGeometry", { args: [w + t, barH, t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] }), _jsxs("mesh", { position: [0, 0, d / 2], renderOrder: 20, children: [_jsx("boxGeometry", { args: [w + t, barH, t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] }), _jsxs("mesh", { position: [-w / 2, 0, 0], renderOrder: 20, children: [_jsx("boxGeometry", { args: [t, barH, d + t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] }), _jsxs("mesh", { position: [w / 2, 0, 0], renderOrder: 20, children: [_jsx("boxGeometry", { args: [t, barH, d + t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] })] }));
1681
1734
  }
1682
- function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding, selectedBuilding, selectedDistrict, selectionStyle, growProgress, animationConfig, highlightLayers, isolationMode, heightScaling, linearScale, flatPatterns, focusDirectory, focusColor, adaptCameraToBuildings = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, cameraControls, onCameraReady, }) {
1735
+ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding, selectedBuilding, selectedDistrict, selectionStyle, growProgress, animationConfig, highlightLayers, isolationMode, heightScaling, linearScale, flatPatterns, focusDirectory, focusColor, adaptCameraToBuildings = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, cameraControls, defaultBuildingColor, onCameraReady, }) {
1683
1736
  const centerOffset = useMemo(() => ({
1684
1737
  x: (cityData.bounds.minX + cityData.bounds.maxX) / 2,
1685
1738
  z: (cityData.bounds.minZ + cityData.bounds.maxZ) / 2,
@@ -1883,7 +1936,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
1883
1936
  // Focus color takes priority, then highlight layer color
1884
1937
  const districtColor = (isFocused && buildingFocusColor) ? buildingFocusColor : highlightLayerColor;
1885
1938
  return (_jsx(DistrictFloor, { district: district, centerOffset: centerOffset, opacity: 1, highlightColor: districtColor, growProgress: growProgress }, district.path));
1886
- }), _jsx(InstancedBuildings, { buildings: cityData.buildings, centerOffset: centerOffset, onHover: onBuildingHover, onClick: onBuildingClick, hoveredIndex: hoveredIndex, selectedIndex: selectedIndex, growProgress: growProgress, animationConfig: animationConfig, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, staggerIndices: staggerIndices, focusDirectory: buildingFocusDirectory, highlightLayers: highlightLayers, isolationMode: isolationMode }), _jsx(BuildingIcons, { buildings: cityData.buildings, centerOffset: centerOffset, growProgress: growProgress, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights }), growProgress === 0 &&
1939
+ }), _jsx(InstancedBuildings, { buildings: cityData.buildings, centerOffset: centerOffset, onHover: onBuildingHover, onClick: onBuildingClick, hoveredIndex: hoveredIndex, selectedIndex: selectedIndex, growProgress: growProgress, animationConfig: animationConfig, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, staggerIndices: staggerIndices, focusDirectory: buildingFocusDirectory, highlightLayers: highlightLayers, isolationMode: isolationMode, defaultBuildingColor: defaultBuildingColor }), _jsx(BuildingIcons, { buildings: cityData.buildings, centerOffset: centerOffset, growProgress: growProgress, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights }), growProgress === 0 &&
1887
1940
  elevatedScopePanels?.map(panel => (_jsx(ElevatedScopePanelMesh, { panel: panel, centerOffset: centerOffset, dismissing: dismissingPanelIds?.has(panel.id) ?? false, onDismissed: onPanelDismissed }, panel.id))), selectedDistrict && (_jsx(SelectionRing, { district: selectedDistrict, centerOffset: centerOffset, color: selectionStyle?.color ?? '#facc15', borderWidth: selectionStyle?.borderWidth ?? 2, growProgress: growProgress }))] }));
1888
1941
  }
1889
1942
  /**
@@ -1892,7 +1945,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
1892
1945
  * Renders CityData as an interactive 3D city where buildings represent files
1893
1946
  * and their height corresponds to line count or file size.
1894
1947
  */
1895
- export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity = 0.15, isLoading = false, loadingMessage = 'Loading file city...', emptyMessage = 'No file tree data available', heightScaling = 'linear', linearScale = 1, flatPatterns = DEFAULT_FLAT_PATTERNS, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor = '#0f172a', textColor = '#94a3b8', selectedBuilding = null, selectedPath = null, selectionStyle, adaptCameraToBuildings = false, fileColorLayers, cameraControls, }) {
1948
+ export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity = 0.15, isLoading = false, loadingMessage = 'Loading file city...', emptyMessage = 'No file tree data available', heightScaling = 'linear', linearScale = 1, flatPatterns = DEFAULT_FLAT_PATTERNS, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor = '#0f172a', textColor = '#94a3b8', selectedBuilding = null, selectedPath = null, selectionStyle, adaptCameraToBuildings = false, fileColorLayers, defaultBuildingColor, cameraControls, onCameraFrame, }) {
1896
1949
  const [hoveredBuilding, setHoveredBuilding] = useState(null);
1897
1950
  const [internalIsGrown, setInternalIsGrown] = useState(false);
1898
1951
  const [cameraReady, setCameraReady] = useState(false);
@@ -2007,7 +2060,7 @@ export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingC
2007
2060
  background: backgroundColor,
2008
2061
  overflow: 'hidden',
2009
2062
  ...style,
2010
- }, children: [_jsx(Canvas, { shadows: true, flat // Disables tone mapping for true colors
2063
+ }, children: [_jsxs(Canvas, { shadows: true, flat // Disables tone mapping for true colors
2011
2064
  : true, style: {
2012
2065
  position: 'absolute',
2013
2066
  top: 0,
@@ -2016,6 +2069,6 @@ export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingC
2016
2069
  height: '100%',
2017
2070
  opacity: cameraReady ? 1 : 0,
2018
2071
  transition: 'opacity 0.1s ease-in',
2019
- }, children: _jsx(CityScene, { cityData: cityData, onBuildingHover: handleBuildingHover, onBuildingClick: onBuildingClick, hoveredBuilding: hoveredBuilding, selectedBuilding: resolvedSelection.building, selectedDistrict: resolvedSelection.district, selectionStyle: selectionStyle, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, focusDirectory: focusDirectory, focusColor: focusColor, adaptCameraToBuildings: adaptCameraToBuildings, elevatedScopePanels: elevatedScopePanels, dismissingPanelIds: dismissingPanelIds, onPanelDismissed: onPanelDismissed, cameraControls: cameraControls, onCameraReady: () => setCameraReady(true) }) }), _jsx(InfoPanel, { building: resolvedSelection.building }), showControls && (_jsx(ControlsOverlay, { isFlat: !isGrown, onToggle: handleToggle, onResetCamera: resetCamera, onLookDown: () => tiltCameraTo(0) }))] }));
2072
+ }, children: [_jsx(CityScene, { cityData: cityData, onBuildingHover: handleBuildingHover, onBuildingClick: onBuildingClick, hoveredBuilding: hoveredBuilding, selectedBuilding: resolvedSelection.building, selectedDistrict: resolvedSelection.district, selectionStyle: selectionStyle, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, focusDirectory: focusDirectory, focusColor: focusColor, adaptCameraToBuildings: adaptCameraToBuildings, elevatedScopePanels: elevatedScopePanels, dismissingPanelIds: dismissingPanelIds, onPanelDismissed: onPanelDismissed, cameraControls: cameraControls, defaultBuildingColor: defaultBuildingColor, onCameraReady: () => setCameraReady(true) }), onCameraFrame && _jsx(CameraFrameBridge, { onCameraFrame: onCameraFrame })] }), _jsx(InfoPanel, { building: resolvedSelection.building }), showControls && (_jsx(ControlsOverlay, { isFlat: !isGrown, onToggle: handleToggle, onResetCamera: resetCamera, onLookDown: () => tiltCameraTo(0) }))] }));
2020
2073
  }
2021
2074
  export default FileCity3D;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * FileCity3D - 3D visualization component
3
3
  */
4
- export { FileCity3D, resetCamera, getCameraAngle, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
5
- export type { FileCity3DProps, AnimationConfig, HighlightLayer, LayerItem, LayerRenderStrategy, IsolationMode, HeightScaling, FlatPattern, ElevatedScopePanel, SelectionStyle, CityData, CityBuilding, CityDistrict, CameraControlsConfig, MouseDragAction, TouchOneAction, TouchTwoAction, WheelAction, } from './FileCity3D';
4
+ export { FileCity3D, resetCamera, getCameraAngle, getCameraPosition, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraFlatView, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
5
+ export type { FileCity3DProps, AnimationConfig, HighlightLayer, LayerItem, LayerRenderStrategy, IsolationMode, HeightScaling, FlatPattern, ElevatedScopePanel, SelectionStyle, CityData, CityBuilding, CityDistrict, CameraControlsConfig, MouseDragAction, TouchOneAction, TouchTwoAction, WheelAction, OnCameraFrame, } from './FileCity3D';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,GACZ,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,UAAU,EACV,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC"}
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * FileCity3D - 3D visualization component
3
3
  */
4
- export { FileCity3D, resetCamera, getCameraAngle, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
4
+ export { FileCity3D, resetCamera, getCameraAngle, getCameraPosition, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraFlatView, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/file-city-react",
3
- "version": "0.5.43",
3
+ "version": "0.5.45",
4
4
  "type": "module",
5
5
  "description": "React components for File City visualization",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,7 @@
30
30
  "@principal-ade/industry-theme": "^0.1.19",
31
31
  "@principal-ai/file-city-builder": "^0.4.5",
32
32
  "@principal-ai/principal-view-core": "^0.28.5",
33
+ "@principal-ai/principal-view-react": "^0.15.6",
33
34
  "@react-spring/three": "^10.0.3",
34
35
  "@react-three/drei": "^10.0.6",
35
36
  "@react-three/fiber": "^9.1.2",
@@ -49,6 +49,24 @@ export interface SelectionStyle {
49
49
  borderWidth?: number;
50
50
  }
51
51
 
52
+ /**
53
+ * Per-frame camera callback signature. Fires once per R3F render frame from
54
+ * inside the Canvas, so projection done in the callback is in lockstep with
55
+ * the city render — useful for HTML/SVG overlays that need to track world
56
+ * positions (e.g. leader lines anchored to buildings).
57
+ *
58
+ * The callback receives the live `THREE.Camera` and the current canvas
59
+ * size in CSS pixels. To project a world point to canvas-local pixels:
60
+ *
61
+ * const v = new THREE.Vector3(x, y, z).project(camera);
62
+ * const px = (v.x * 0.5 + 0.5) * size.width;
63
+ * const py = (v.y * -0.5 + 0.5) * size.height;
64
+ */
65
+ export type OnCameraFrame = (
66
+ camera: THREE.Camera,
67
+ size: { width: number; height: number },
68
+ ) => void;
69
+
52
70
  /** What to do with non-highlighted buildings */
53
71
  export type IsolationMode =
54
72
  | 'none' // Show all buildings normally
@@ -718,7 +736,7 @@ function BorderHighlights({
718
736
  return (
719
737
  <instancedMesh ref={meshRef} args={[undefined, undefined, borderEdgeData.length]} frustumCulled={false}>
720
738
  <boxGeometry args={[1, 1, 1]} />
721
- <meshBasicMaterial transparent opacity={0.9} vertexColors />
739
+ <meshBasicMaterial transparent opacity={0.9} />
722
740
  </instancedMesh>
723
741
  );
724
742
  }
@@ -743,6 +761,7 @@ interface InstancedBuildingsProps {
743
761
  focusDirectory: string | null;
744
762
  highlightLayers: HighlightLayer[];
745
763
  isolationMode: IsolationMode;
764
+ defaultBuildingColor?: string;
746
765
  }
747
766
 
748
767
  // Helper to check if a path is inside a directory
@@ -767,6 +786,7 @@ function InstancedBuildings({
767
786
  focusDirectory,
768
787
  highlightLayers,
769
788
  isolationMode,
789
+ defaultBuildingColor,
770
790
  }: InstancedBuildingsProps) {
771
791
  const meshRef = useRef<THREE.InstancedMesh>(null);
772
792
  const startTimeRef = useRef<number | null>(null);
@@ -852,7 +872,7 @@ function InstancedBuildings({
852
872
  // Get all layer matches and find first fill match for building color
853
873
  const matches = getLayerMatchesForPath(building.path, highlightLayers);
854
874
  const fillMatch = matches.find(m => m.renderStrategy === 'fill');
855
- const color = fillMatch?.color ?? getColorForFile(building);
875
+ const color = fillMatch?.color ?? defaultBuildingColor ?? getColorForFile(building);
856
876
 
857
877
  const x = building.position.x - centerOffset.x;
858
878
  const z = building.position.z - centerOffset.z;
@@ -881,6 +901,7 @@ function InstancedBuildings({
881
901
  staggerIndices,
882
902
  animationConfig.staggerDelay,
883
903
  highlightLayers,
904
+ defaultBuildingColor,
884
905
  ]);
885
906
 
886
907
  const minHeight = 0.3;
@@ -1432,6 +1453,7 @@ function touchTwoAction(action: TouchTwoAction): number | undefined {
1432
1453
  interface CameraApi {
1433
1454
  reset: () => void;
1434
1455
  moveTo: (x: number, z: number, size?: number) => void;
1456
+ setFlatView: (x: number, z: number, height: number, options?: RotateOptions) => void;
1435
1457
  setTarget: (x: number, y: number, z: number, options?: RotateOptions) => void;
1436
1458
  rotateTo: (angleOrDirection: number | 'north' | 'south' | 'east' | 'west', options?: RotateOptions) => void;
1437
1459
  rotateBy: (degrees: number, options?: RotateOptions) => void;
@@ -1453,6 +1475,21 @@ export function moveCameraTo(x: number, z: number, size?: number) {
1453
1475
  cameraApi?.moveTo(x, z, size);
1454
1476
  }
1455
1477
 
1478
+ /**
1479
+ * Position the camera straight overhead a target at a specific height (flat
1480
+ * top-down view). Use to fit the city inside a sub-rect of the canvas — e.g.
1481
+ * when overlays cover part of the viewport. Larger `height` = city appears
1482
+ * smaller; smaller `height` = zoomed in.
1483
+ */
1484
+ export function setCameraFlatView(
1485
+ x: number,
1486
+ z: number,
1487
+ height: number,
1488
+ options?: RotateOptions,
1489
+ ) {
1490
+ cameraApi?.setFlatView(x, z, height, options);
1491
+ }
1492
+
1456
1493
  /**
1457
1494
  * Set the camera's look-at target (center point for orbiting).
1458
1495
  * Camera maintains its current distance and angles relative to the new target.
@@ -1537,6 +1574,19 @@ export function getCameraTilt() {
1537
1574
  return cameraApi?.getCurrentTilt() ?? null;
1538
1575
  }
1539
1576
 
1577
+ /**
1578
+ * Bridge for piping the live camera + canvas size out of the R3F Canvas on
1579
+ * every frame. Mounted as a child of `<Canvas>` so it has access to the R3F
1580
+ * render loop; runs zero work if no callback is provided.
1581
+ */
1582
+ function CameraFrameBridge({ onCameraFrame }: { onCameraFrame?: OnCameraFrame }) {
1583
+ const { camera, size } = useThree();
1584
+ useFrame(() => {
1585
+ onCameraFrame?.(camera, { width: size.width, height: size.height });
1586
+ });
1587
+ return null;
1588
+ }
1589
+
1540
1590
  const AnimatedCamera = React.memo(function AnimatedCamera({
1541
1591
  citySize,
1542
1592
  isFlat,
@@ -1934,6 +1984,40 @@ const AnimatedCamera = React.memo(function AnimatedCamera({
1934
1984
  });
1935
1985
  }, [citySize, api]);
1936
1986
 
1987
+ // Position the camera directly above a target at a given height (flat /
1988
+ // top-down view). Useful for fitting the city into a visible sub-rect of
1989
+ // the canvas — raise `height` to make the city appear smaller, lower it to
1990
+ // zoom in. Pairs with `setTarget` for pan-only changes.
1991
+ const setFlatView = useCallback(
1992
+ (x: number, z: number, height: number, options?: RotateOptions) => {
1993
+ // MapControls (drag/wheel) mutates camera.position and controls.target
1994
+ // directly without touching our spring. If we don't resync first, a
1995
+ // call here can no-op when the spring's stored values happen to equal
1996
+ // the requested target even though the visible camera is elsewhere.
1997
+ if (controlsRef.current) {
1998
+ api.set({
1999
+ camX: camera.position.x,
2000
+ camY: camera.position.y,
2001
+ camZ: camera.position.z,
2002
+ lookX: controlsRef.current.target.x,
2003
+ lookY: controlsRef.current.target.y,
2004
+ lookZ: controlsRef.current.target.z,
2005
+ });
2006
+ }
2007
+ const config = options?.duration ? { duration: options.duration } : undefined;
2008
+ api.start({
2009
+ camX: x,
2010
+ camY: height,
2011
+ camZ: z + 0.001, // tiny offset to avoid gimbal lock when looking straight down
2012
+ lookX: x,
2013
+ lookY: 0,
2014
+ lookZ: z,
2015
+ ...(config ? { config } : {}),
2016
+ });
2017
+ },
2018
+ [api, camera],
2019
+ );
2020
+
1937
2021
  // Set camera target (look-at point), maintaining current distance and angles
1938
2022
  const setTarget = useCallback((x: number, y: number, z: number, options?: RotateOptions) => {
1939
2023
  // Get current offset from target
@@ -2167,6 +2251,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({
2167
2251
  cameraApi = {
2168
2252
  reset: resetToInitial,
2169
2253
  moveTo,
2254
+ setFlatView,
2170
2255
  setTarget,
2171
2256
  rotateTo,
2172
2257
  rotateBy,
@@ -2180,7 +2265,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({
2180
2265
  return () => {
2181
2266
  cameraApi = null;
2182
2267
  };
2183
- }, [resetToInitial, moveTo, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
2268
+ }, [resetToInitial, moveTo, setFlatView, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
2184
2269
 
2185
2270
  // Custom wheel handler for wheel === 'pan'. We disable MapControls' built-in
2186
2271
  // zoom (otherwise it competes with our handler) and handle both axes here:
@@ -2698,6 +2783,7 @@ interface CitySceneProps {
2698
2783
  dismissingPanelIds?: ReadonlySet<string>;
2699
2784
  onPanelDismissed?: (id: string) => void;
2700
2785
  cameraControls?: CameraControlsConfig;
2786
+ defaultBuildingColor?: string;
2701
2787
  }
2702
2788
 
2703
2789
  function CityScene({
@@ -2722,6 +2808,7 @@ function CityScene({
2722
2808
  dismissingPanelIds,
2723
2809
  onPanelDismissed,
2724
2810
  cameraControls,
2811
+ defaultBuildingColor,
2725
2812
  onCameraReady,
2726
2813
  }: CitySceneProps & { onCameraReady?: () => void }) {
2727
2814
  const centerOffset = useMemo(
@@ -3028,6 +3115,7 @@ function CityScene({
3028
3115
  focusDirectory={buildingFocusDirectory}
3029
3116
  highlightLayers={highlightLayers}
3030
3117
  isolationMode={isolationMode}
3118
+ defaultBuildingColor={defaultBuildingColor}
3031
3119
  />
3032
3120
 
3033
3121
  <BuildingIcons
@@ -3144,6 +3232,15 @@ export interface FileCity3DProps {
3144
3232
  /** Base file type color layers (resolved with highlightLayers) */
3145
3233
  fileColorLayers?: HighlightLayer[];
3146
3234
 
3235
+ /**
3236
+ * Override the per-building color fallback. When unset (default), buildings
3237
+ * not matched by a fill highlight layer are colored by file extension via
3238
+ * the built-in file-type palette. Set to a CSS color (e.g. `'#475569'`) to
3239
+ * render unmatched buildings in a neutral tone — useful for debug stories
3240
+ * that want to isolate highlight-layer rendering.
3241
+ */
3242
+ defaultBuildingColor?: string;
3243
+
3147
3244
  /**
3148
3245
  * Translucent slabs rendered above the city showing scope coverage as
3149
3246
  * elevated planes over the directories they own.
@@ -3173,6 +3270,15 @@ export interface FileCity3DProps {
3173
3270
  * Memoize this object to avoid unnecessary camera re-mounts.
3174
3271
  */
3175
3272
  cameraControls?: CameraControlsConfig;
3273
+
3274
+ /**
3275
+ * Fires once per R3F render frame with the live camera and canvas size.
3276
+ * Use to project world points to canvas pixels for HTML/SVG overlays that
3277
+ * need to track buildings as the camera pans / zooms / rotates.
3278
+ *
3279
+ * Memoize the callback to avoid re-mounting the bridge.
3280
+ */
3281
+ onCameraFrame?: OnCameraFrame;
3176
3282
  }
3177
3283
 
3178
3284
  /**
@@ -3215,7 +3321,9 @@ export function FileCity3D({
3215
3321
  selectionStyle,
3216
3322
  adaptCameraToBuildings = false,
3217
3323
  fileColorLayers,
3324
+ defaultBuildingColor,
3218
3325
  cameraControls,
3326
+ onCameraFrame,
3219
3327
  }: FileCity3DProps) {
3220
3328
  const [hoveredBuilding, setHoveredBuilding] = useState<CityBuilding | null>(null);
3221
3329
  const [internalIsGrown, setInternalIsGrown] = useState(false);
@@ -3402,8 +3510,10 @@ export function FileCity3D({
3402
3510
  dismissingPanelIds={dismissingPanelIds}
3403
3511
  onPanelDismissed={onPanelDismissed}
3404
3512
  cameraControls={cameraControls}
3513
+ defaultBuildingColor={defaultBuildingColor}
3405
3514
  onCameraReady={() => setCameraReady(true)}
3406
3515
  />
3516
+ {onCameraFrame && <CameraFrameBridge onCameraFrame={onCameraFrame} />}
3407
3517
  </Canvas>
3408
3518
  <InfoPanel building={resolvedSelection.building} />
3409
3519
  {showControls && (
@@ -6,6 +6,7 @@ export {
6
6
  FileCity3D,
7
7
  resetCamera,
8
8
  getCameraAngle,
9
+ getCameraPosition,
9
10
  getCameraTarget,
10
11
  getCameraTilt,
11
12
  rotateCameraTo,
@@ -13,6 +14,7 @@ export {
13
14
  tiltCameraTo,
14
15
  tiltCameraBy,
15
16
  moveCameraTo,
17
+ setCameraFlatView,
16
18
  setCameraTarget,
17
19
  DEFAULT_FLAT_PATTERNS,
18
20
  DEFAULT_CAMERA_CONTROLS,
@@ -36,4 +38,5 @@ export type {
36
38
  TouchOneAction,
37
39
  TouchTwoAction,
38
40
  WheelAction,
41
+ OnCameraFrame,
39
42
  } from './FileCity3D';