@principal-ai/file-city-react 0.5.43 → 0.5.44
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 +35 -1
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +21 -8
- package/dist/components/FileCity3D/index.d.ts +1 -1
- package/dist/components/FileCity3D/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/FileCity3D/FileCity3D.tsx +61 -2
- package/src/components/FileCity3D/index.ts +1 -0
- package/src/stories/HighlightLayersFlatDebug.stories.tsx +319 -0
- package/src/stories/LeaderLineSnippetOverlay3D.stories.tsx +1060 -0
|
@@ -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 {
|
|
@@ -254,6 +272,14 @@ export interface FileCity3DProps {
|
|
|
254
272
|
adaptCameraToBuildings?: boolean;
|
|
255
273
|
/** Base file type color layers (resolved with highlightLayers) */
|
|
256
274
|
fileColorLayers?: HighlightLayer[];
|
|
275
|
+
/**
|
|
276
|
+
* Override the per-building color fallback. When unset (default), buildings
|
|
277
|
+
* not matched by a fill highlight layer are colored by file extension via
|
|
278
|
+
* the built-in file-type palette. Set to a CSS color (e.g. `'#475569'`) to
|
|
279
|
+
* render unmatched buildings in a neutral tone — useful for debug stories
|
|
280
|
+
* that want to isolate highlight-layer rendering.
|
|
281
|
+
*/
|
|
282
|
+
defaultBuildingColor?: string;
|
|
257
283
|
/**
|
|
258
284
|
* Translucent slabs rendered above the city showing scope coverage as
|
|
259
285
|
* elevated planes over the directories they own.
|
|
@@ -280,6 +306,14 @@ export interface FileCity3DProps {
|
|
|
280
306
|
* Memoize this object to avoid unnecessary camera re-mounts.
|
|
281
307
|
*/
|
|
282
308
|
cameraControls?: CameraControlsConfig;
|
|
309
|
+
/**
|
|
310
|
+
* Fires once per R3F render frame with the live camera and canvas size.
|
|
311
|
+
* Use to project world points to canvas pixels for HTML/SVG overlays that
|
|
312
|
+
* need to track buildings as the camera pans / zooms / rotates.
|
|
313
|
+
*
|
|
314
|
+
* Memoize the callback to avoid re-mounting the bridge.
|
|
315
|
+
*/
|
|
316
|
+
onCameraFrame?: OnCameraFrame;
|
|
283
317
|
}
|
|
284
318
|
/**
|
|
285
319
|
* FileCity3D - 3D visualization of codebase structure
|
|
@@ -287,6 +321,6 @@ export interface FileCity3DProps {
|
|
|
287
321
|
* Renders CityData as an interactive 3D city where buildings represent files
|
|
288
322
|
* and their height corresponds to line count or file size.
|
|
289
323
|
*/
|
|
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;
|
|
324
|
+
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
325
|
export default FileCity3D;
|
|
292
326
|
//# sourceMappingURL=FileCity3D.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAOjF,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAEZ,cAAc,IAAI,qBAAqB,EACvC,SAAS,EACT,mBAAmB,EACpB,MAAM,iCAAiC,CAAC;
|
|
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
|
|
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;
|
|
@@ -886,6 +887,18 @@ export function getCameraAngle() {
|
|
|
886
887
|
export function getCameraTilt() {
|
|
887
888
|
return cameraApi?.getCurrentTilt() ?? null;
|
|
888
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Bridge for piping the live camera + canvas size out of the R3F Canvas on
|
|
892
|
+
* every frame. Mounted as a child of `<Canvas>` so it has access to the R3F
|
|
893
|
+
* render loop; runs zero work if no callback is provided.
|
|
894
|
+
*/
|
|
895
|
+
function CameraFrameBridge({ onCameraFrame }) {
|
|
896
|
+
const { camera, size } = useThree();
|
|
897
|
+
useFrame(() => {
|
|
898
|
+
onCameraFrame?.(camera, { width: size.width, height: size.height });
|
|
899
|
+
});
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
889
902
|
const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0, cameraControls, onCameraReady, }) {
|
|
890
903
|
// Use selector to only subscribe to camera, not the entire R3F state
|
|
891
904
|
// This prevents re-renders on pointer movement
|
|
@@ -1679,7 +1692,7 @@ function SelectionRing({ district, centerOffset, color, borderWidth, growProgres
|
|
|
1679
1692
|
const barH = 0.5;
|
|
1680
1693
|
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
1694
|
}
|
|
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, }) {
|
|
1695
|
+
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
1696
|
const centerOffset = useMemo(() => ({
|
|
1684
1697
|
x: (cityData.bounds.minX + cityData.bounds.maxX) / 2,
|
|
1685
1698
|
z: (cityData.bounds.minZ + cityData.bounds.maxZ) / 2,
|
|
@@ -1883,7 +1896,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
1883
1896
|
// Focus color takes priority, then highlight layer color
|
|
1884
1897
|
const districtColor = (isFocused && buildingFocusColor) ? buildingFocusColor : highlightLayerColor;
|
|
1885
1898
|
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 &&
|
|
1899
|
+
}), _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
1900
|
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
1901
|
}
|
|
1889
1902
|
/**
|
|
@@ -1892,7 +1905,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
1892
1905
|
* Renders CityData as an interactive 3D city where buildings represent files
|
|
1893
1906
|
* and their height corresponds to line count or file size.
|
|
1894
1907
|
*/
|
|
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, }) {
|
|
1908
|
+
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
1909
|
const [hoveredBuilding, setHoveredBuilding] = useState(null);
|
|
1897
1910
|
const [internalIsGrown, setInternalIsGrown] = useState(false);
|
|
1898
1911
|
const [cameraReady, setCameraReady] = useState(false);
|
|
@@ -2007,7 +2020,7 @@ export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingC
|
|
|
2007
2020
|
background: backgroundColor,
|
|
2008
2021
|
overflow: 'hidden',
|
|
2009
2022
|
...style,
|
|
2010
|
-
}, children: [
|
|
2023
|
+
}, children: [_jsxs(Canvas, { shadows: true, flat // Disables tone mapping for true colors
|
|
2011
2024
|
: true, style: {
|
|
2012
2025
|
position: 'absolute',
|
|
2013
2026
|
top: 0,
|
|
@@ -2016,6 +2029,6 @@ export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingC
|
|
|
2016
2029
|
height: '100%',
|
|
2017
2030
|
opacity: cameraReady ? 1 : 0,
|
|
2018
2031
|
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) }))] }));
|
|
2032
|
+
}, 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
2033
|
}
|
|
2021
2034
|
export default FileCity3D;
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* FileCity3D - 3D visualization component
|
|
3
3
|
*/
|
|
4
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';
|
|
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,
|
|
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,EACX,aAAa,GACd,MAAM,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -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}
|
|
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;
|
|
@@ -1537,6 +1558,19 @@ export function getCameraTilt() {
|
|
|
1537
1558
|
return cameraApi?.getCurrentTilt() ?? null;
|
|
1538
1559
|
}
|
|
1539
1560
|
|
|
1561
|
+
/**
|
|
1562
|
+
* Bridge for piping the live camera + canvas size out of the R3F Canvas on
|
|
1563
|
+
* every frame. Mounted as a child of `<Canvas>` so it has access to the R3F
|
|
1564
|
+
* render loop; runs zero work if no callback is provided.
|
|
1565
|
+
*/
|
|
1566
|
+
function CameraFrameBridge({ onCameraFrame }: { onCameraFrame?: OnCameraFrame }) {
|
|
1567
|
+
const { camera, size } = useThree();
|
|
1568
|
+
useFrame(() => {
|
|
1569
|
+
onCameraFrame?.(camera, { width: size.width, height: size.height });
|
|
1570
|
+
});
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1540
1574
|
const AnimatedCamera = React.memo(function AnimatedCamera({
|
|
1541
1575
|
citySize,
|
|
1542
1576
|
isFlat,
|
|
@@ -2698,6 +2732,7 @@ interface CitySceneProps {
|
|
|
2698
2732
|
dismissingPanelIds?: ReadonlySet<string>;
|
|
2699
2733
|
onPanelDismissed?: (id: string) => void;
|
|
2700
2734
|
cameraControls?: CameraControlsConfig;
|
|
2735
|
+
defaultBuildingColor?: string;
|
|
2701
2736
|
}
|
|
2702
2737
|
|
|
2703
2738
|
function CityScene({
|
|
@@ -2722,6 +2757,7 @@ function CityScene({
|
|
|
2722
2757
|
dismissingPanelIds,
|
|
2723
2758
|
onPanelDismissed,
|
|
2724
2759
|
cameraControls,
|
|
2760
|
+
defaultBuildingColor,
|
|
2725
2761
|
onCameraReady,
|
|
2726
2762
|
}: CitySceneProps & { onCameraReady?: () => void }) {
|
|
2727
2763
|
const centerOffset = useMemo(
|
|
@@ -3028,6 +3064,7 @@ function CityScene({
|
|
|
3028
3064
|
focusDirectory={buildingFocusDirectory}
|
|
3029
3065
|
highlightLayers={highlightLayers}
|
|
3030
3066
|
isolationMode={isolationMode}
|
|
3067
|
+
defaultBuildingColor={defaultBuildingColor}
|
|
3031
3068
|
/>
|
|
3032
3069
|
|
|
3033
3070
|
<BuildingIcons
|
|
@@ -3144,6 +3181,15 @@ export interface FileCity3DProps {
|
|
|
3144
3181
|
/** Base file type color layers (resolved with highlightLayers) */
|
|
3145
3182
|
fileColorLayers?: HighlightLayer[];
|
|
3146
3183
|
|
|
3184
|
+
/**
|
|
3185
|
+
* Override the per-building color fallback. When unset (default), buildings
|
|
3186
|
+
* not matched by a fill highlight layer are colored by file extension via
|
|
3187
|
+
* the built-in file-type palette. Set to a CSS color (e.g. `'#475569'`) to
|
|
3188
|
+
* render unmatched buildings in a neutral tone — useful for debug stories
|
|
3189
|
+
* that want to isolate highlight-layer rendering.
|
|
3190
|
+
*/
|
|
3191
|
+
defaultBuildingColor?: string;
|
|
3192
|
+
|
|
3147
3193
|
/**
|
|
3148
3194
|
* Translucent slabs rendered above the city showing scope coverage as
|
|
3149
3195
|
* elevated planes over the directories they own.
|
|
@@ -3173,6 +3219,15 @@ export interface FileCity3DProps {
|
|
|
3173
3219
|
* Memoize this object to avoid unnecessary camera re-mounts.
|
|
3174
3220
|
*/
|
|
3175
3221
|
cameraControls?: CameraControlsConfig;
|
|
3222
|
+
|
|
3223
|
+
/**
|
|
3224
|
+
* Fires once per R3F render frame with the live camera and canvas size.
|
|
3225
|
+
* Use to project world points to canvas pixels for HTML/SVG overlays that
|
|
3226
|
+
* need to track buildings as the camera pans / zooms / rotates.
|
|
3227
|
+
*
|
|
3228
|
+
* Memoize the callback to avoid re-mounting the bridge.
|
|
3229
|
+
*/
|
|
3230
|
+
onCameraFrame?: OnCameraFrame;
|
|
3176
3231
|
}
|
|
3177
3232
|
|
|
3178
3233
|
/**
|
|
@@ -3215,7 +3270,9 @@ export function FileCity3D({
|
|
|
3215
3270
|
selectionStyle,
|
|
3216
3271
|
adaptCameraToBuildings = false,
|
|
3217
3272
|
fileColorLayers,
|
|
3273
|
+
defaultBuildingColor,
|
|
3218
3274
|
cameraControls,
|
|
3275
|
+
onCameraFrame,
|
|
3219
3276
|
}: FileCity3DProps) {
|
|
3220
3277
|
const [hoveredBuilding, setHoveredBuilding] = useState<CityBuilding | null>(null);
|
|
3221
3278
|
const [internalIsGrown, setInternalIsGrown] = useState(false);
|
|
@@ -3402,8 +3459,10 @@ export function FileCity3D({
|
|
|
3402
3459
|
dismissingPanelIds={dismissingPanelIds}
|
|
3403
3460
|
onPanelDismissed={onPanelDismissed}
|
|
3404
3461
|
cameraControls={cameraControls}
|
|
3462
|
+
defaultBuildingColor={defaultBuildingColor}
|
|
3405
3463
|
onCameraReady={() => setCameraReady(true)}
|
|
3406
3464
|
/>
|
|
3465
|
+
{onCameraFrame && <CameraFrameBridge onCameraFrame={onCameraFrame} />}
|
|
3407
3466
|
</Canvas>
|
|
3408
3467
|
<InfoPanel building={resolvedSelection.building} />
|
|
3409
3468
|
{showControls && (
|