@principal-ai/file-city-react 0.5.6 → 0.5.7
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/builder/cityDataUtils.js +3 -9
- package/dist/components/ArchitectureMapHighlightLayers.d.ts +1 -2
- package/dist/components/ArchitectureMapHighlightLayers.d.ts.map +1 -1
- package/dist/components/ArchitectureMapHighlightLayers.js +144 -187
- package/dist/components/CityViewWithReactFlow.js +72 -125
- package/dist/components/FileCity3D/FileCity3D.d.ts +1 -1
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +64 -129
- package/dist/components/FileCity3D/index.js +1 -6
- package/dist/hooks/useCodeCityData.js +12 -15
- package/dist/index.js +11 -34
- package/dist/render/client/drawLayeredBuildings.js +9 -16
- package/dist/stories/sample-data.js +7 -11
- package/dist/stories/stress-test-data.js +6 -11
- package/dist/types/react-types.js +1 -2
- package/dist/utils/fileColorHighlightLayers.js +8 -13
- package/dist/utils/fileColorOverrides.js +2 -6
- package/dist/utils/fileTypeIcons.js +5 -10
- package/dist/utils/lucideIconConverter.js +5 -12
- package/package.json +3 -1
- package/dist/config/files.json +0 -1012
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* FileCity3D - 3D visualization of a codebase using React Three Fiber
|
|
4
4
|
*
|
|
@@ -7,49 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Supports animated transition from 2D (flat) to 3D (grown buildings).
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}) : (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
o[k2] = m[k];
|
|
20
|
-
}));
|
|
21
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
-
}) : function(o, v) {
|
|
24
|
-
o["default"] = v;
|
|
25
|
-
});
|
|
26
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
-
var ownKeys = function(o) {
|
|
28
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
-
var ar = [];
|
|
30
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
-
return ar;
|
|
32
|
-
};
|
|
33
|
-
return ownKeys(o);
|
|
34
|
-
};
|
|
35
|
-
return function (mod) {
|
|
36
|
-
if (mod && mod.__esModule) return mod;
|
|
37
|
-
var result = {};
|
|
38
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
-
__setModuleDefault(result, mod);
|
|
40
|
-
return result;
|
|
41
|
-
};
|
|
42
|
-
})();
|
|
43
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.resetCamera = resetCamera;
|
|
45
|
-
exports.FileCity3D = FileCity3D;
|
|
46
|
-
const react_1 = __importStar(require("react"));
|
|
47
|
-
const fiber_1 = require("@react-three/fiber");
|
|
48
|
-
const industry_theme_1 = require("@principal-ade/industry-theme");
|
|
49
|
-
const three_1 = require("@react-spring/three");
|
|
50
|
-
const drei_1 = require("@react-three/drei");
|
|
51
|
-
const file_city_builder_1 = require("@principal-ai/file-city-builder");
|
|
52
|
-
const THREE = __importStar(require("three"));
|
|
10
|
+
import { useMemo, useRef, useState, useEffect, useCallback, } from 'react';
|
|
11
|
+
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
|
12
|
+
import { useTheme } from '@principal-ade/industry-theme';
|
|
13
|
+
import { animated } from '@react-spring/three';
|
|
14
|
+
import { OrbitControls, PerspectiveCamera, Text, RoundedBox, } from '@react-three/drei';
|
|
15
|
+
import { getFileConfig } from '@principal-ai/file-city-builder';
|
|
16
|
+
import * as THREE from 'three';
|
|
53
17
|
const DEFAULT_ANIMATION = {
|
|
54
18
|
startFlat: false,
|
|
55
19
|
autoStartDelay: 500,
|
|
@@ -142,7 +106,7 @@ function getConfigForFile(building) {
|
|
|
142
106
|
matchType: 'filename',
|
|
143
107
|
};
|
|
144
108
|
}
|
|
145
|
-
return
|
|
109
|
+
return getFileConfig(building.path);
|
|
146
110
|
}
|
|
147
111
|
function getColorForFile(building) {
|
|
148
112
|
return getConfigForFile(building).color;
|
|
@@ -170,14 +134,14 @@ function hasActiveHighlights(layers) {
|
|
|
170
134
|
return layers.some((layer) => layer.enabled && layer.items.length > 0);
|
|
171
135
|
}
|
|
172
136
|
// Animated RoundedBox wrapper
|
|
173
|
-
const AnimatedRoundedBox =
|
|
137
|
+
const AnimatedRoundedBox = animated(RoundedBox);
|
|
174
138
|
function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hoveredIndex, growProgress, animationConfig, highlightLayers, isolationMode, hasActiveHighlights, dimOpacity, heightScaling, linearScale, staggerIndices, }) {
|
|
175
|
-
const meshRef =
|
|
176
|
-
const startTimeRef =
|
|
177
|
-
const tempObject =
|
|
178
|
-
const tempColor =
|
|
139
|
+
const meshRef = useRef(null);
|
|
140
|
+
const startTimeRef = useRef(null);
|
|
141
|
+
const tempObject = useMemo(() => new THREE.Object3D(), []);
|
|
142
|
+
const tempColor = useMemo(() => new THREE.Color(), []);
|
|
179
143
|
// Pre-compute building data
|
|
180
|
-
const buildingData =
|
|
144
|
+
const buildingData = useMemo(() => {
|
|
181
145
|
return buildings.map((building, index) => {
|
|
182
146
|
const [width, , depth] = building.dimensions;
|
|
183
147
|
const highlight = getHighlightForPath(building.path, highlightLayers);
|
|
@@ -219,13 +183,13 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
219
183
|
staggerIndices,
|
|
220
184
|
animationConfig.staggerDelay,
|
|
221
185
|
]);
|
|
222
|
-
const visibleBuildings =
|
|
186
|
+
const visibleBuildings = useMemo(() => buildingData.filter((b) => !b.shouldHide), [buildingData]);
|
|
223
187
|
const minHeight = 0.3;
|
|
224
188
|
const baseOffset = 0.2;
|
|
225
189
|
const tension = animationConfig.tension || 120;
|
|
226
190
|
const friction = animationConfig.friction || 14;
|
|
227
191
|
const springDuration = Math.sqrt(1 / (tension * 0.001)) * friction * 20;
|
|
228
|
-
|
|
192
|
+
useEffect(() => {
|
|
229
193
|
if (!meshRef.current)
|
|
230
194
|
return;
|
|
231
195
|
visibleBuildings.forEach((data, instanceIndex) => {
|
|
@@ -251,7 +215,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
251
215
|
minHeight,
|
|
252
216
|
baseOffset,
|
|
253
217
|
]);
|
|
254
|
-
|
|
218
|
+
useFrame(({ clock }) => {
|
|
255
219
|
if (!meshRef.current)
|
|
256
220
|
return;
|
|
257
221
|
if (startTimeRef.current === null && growProgress > 0) {
|
|
@@ -294,7 +258,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
294
258
|
meshRef.current.instanceColor.needsUpdate = true;
|
|
295
259
|
}
|
|
296
260
|
});
|
|
297
|
-
const handlePointerMove =
|
|
261
|
+
const handlePointerMove = useCallback((e) => {
|
|
298
262
|
e.stopPropagation();
|
|
299
263
|
if (e.instanceId !== undefined &&
|
|
300
264
|
e.instanceId < visibleBuildings.length) {
|
|
@@ -302,10 +266,10 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
302
266
|
onHover?.(data.building);
|
|
303
267
|
}
|
|
304
268
|
}, [visibleBuildings, onHover]);
|
|
305
|
-
const handlePointerOut =
|
|
269
|
+
const handlePointerOut = useCallback(() => {
|
|
306
270
|
onHover?.(null);
|
|
307
271
|
}, [onHover]);
|
|
308
|
-
const handleClick =
|
|
272
|
+
const handleClick = useCallback((e) => {
|
|
309
273
|
e.stopPropagation();
|
|
310
274
|
if (e.instanceId !== undefined &&
|
|
311
275
|
e.instanceId < visibleBuildings.length) {
|
|
@@ -315,9 +279,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
315
279
|
}, [visibleBuildings, onClick]);
|
|
316
280
|
if (visibleBuildings.length === 0)
|
|
317
281
|
return null;
|
|
318
|
-
return (
|
|
319
|
-
react_1.default.createElement("boxGeometry", { args: [1, 1, 1] }),
|
|
320
|
-
react_1.default.createElement("meshStandardMaterial", { metalness: 0.1, roughness: 0.35 })));
|
|
282
|
+
return (_jsxs("instancedMesh", { ref: meshRef, args: [undefined, undefined, visibleBuildings.length], onPointerMove: handlePointerMove, onPointerOut: handlePointerOut, onClick: handleClick, frustumCulled: false, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshStandardMaterial", { metalness: 0.1, roughness: 0.35 })] }));
|
|
321
283
|
}
|
|
322
284
|
function DistrictFloor({ district, centerOffset, opacity, }) {
|
|
323
285
|
const { worldBounds } = district;
|
|
@@ -328,21 +290,17 @@ function DistrictFloor({ district, centerOffset, opacity, }) {
|
|
|
328
290
|
const dirName = district.path.split('/').pop() || district.path;
|
|
329
291
|
const pathDepth = district.path.split('/').length;
|
|
330
292
|
const floorY = -5 - pathDepth * 0.1;
|
|
331
|
-
return (
|
|
332
|
-
react_1.default.createElement("lineSegments", { rotation: [-Math.PI / 2, 0, 0], position: [0, floorY, 0], renderOrder: -1 },
|
|
333
|
-
react_1.default.createElement("edgesGeometry", { args: [new THREE.PlaneGeometry(width, depth)], attach: "geometry" }),
|
|
334
|
-
react_1.default.createElement("lineBasicMaterial", { color: "#475569", depthWrite: false })),
|
|
335
|
-
district.label && (react_1.default.createElement(drei_1.Text, { position: [0, 1.5, depth / 2 + 2], rotation: [-Math.PI / 6, 0, 0], fontSize: Math.min(3, width / 6), color: "#cbd5e1", anchorX: "center", anchorY: "middle", outlineWidth: 0.1, outlineColor: "#0f172a" }, dirName))));
|
|
293
|
+
return (_jsxs("group", { position: [centerX, 0, centerZ], children: [_jsxs("lineSegments", { rotation: [-Math.PI / 2, 0, 0], position: [0, floorY, 0], renderOrder: -1, children: [_jsx("edgesGeometry", { args: [new THREE.PlaneGeometry(width, depth)], attach: "geometry" }), _jsx("lineBasicMaterial", { color: "#475569", depthWrite: false })] }), district.label && (_jsx(Text, { position: [0, 1.5, depth / 2 + 2], rotation: [-Math.PI / 6, 0, 0], fontSize: Math.min(3, width / 6), color: "#cbd5e1", anchorX: "center", anchorY: "middle", outlineWidth: 0.1, outlineColor: "#0f172a", children: dirName }))] }));
|
|
336
294
|
}
|
|
337
295
|
let cameraResetFn = null;
|
|
338
|
-
function resetCamera() {
|
|
296
|
+
export function resetCamera() {
|
|
339
297
|
cameraResetFn?.();
|
|
340
298
|
}
|
|
341
299
|
function AnimatedCamera({ citySize, isFlat }) {
|
|
342
|
-
const { camera } =
|
|
300
|
+
const { camera } = useThree();
|
|
343
301
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
344
|
-
const controlsRef =
|
|
345
|
-
const resetToInitial =
|
|
302
|
+
const controlsRef = useRef(null);
|
|
303
|
+
const resetToInitial = useCallback(() => {
|
|
346
304
|
const targetHeight = isFlat ? citySize * 1.5 : citySize * 1.1;
|
|
347
305
|
const targetZ = isFlat ? 0 : citySize * 1.3;
|
|
348
306
|
camera.position.set(0, targetHeight, targetZ);
|
|
@@ -352,18 +310,16 @@ function AnimatedCamera({ citySize, isFlat }) {
|
|
|
352
310
|
controlsRef.current.update();
|
|
353
311
|
}
|
|
354
312
|
}, [isFlat, citySize, camera]);
|
|
355
|
-
|
|
313
|
+
useEffect(() => {
|
|
356
314
|
resetToInitial();
|
|
357
315
|
}, [resetToInitial]);
|
|
358
|
-
|
|
316
|
+
useEffect(() => {
|
|
359
317
|
cameraResetFn = resetToInitial;
|
|
360
318
|
return () => {
|
|
361
319
|
cameraResetFn = null;
|
|
362
320
|
};
|
|
363
321
|
}, [resetToInitial]);
|
|
364
|
-
return (
|
|
365
|
-
react_1.default.createElement(drei_1.PerspectiveCamera, { makeDefault: true, fov: 50, near: 1, far: citySize * 10 }),
|
|
366
|
-
react_1.default.createElement(drei_1.OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: 0.05, minDistance: 10, maxDistance: citySize * 3, maxPolarAngle: Math.PI / 2.1 })));
|
|
322
|
+
return (_jsxs(_Fragment, { children: [_jsx(PerspectiveCamera, { makeDefault: true, fov: 50, near: 1, far: citySize * 10 }), _jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: 0.05, minDistance: 10, maxDistance: citySize * 3, maxPolarAngle: Math.PI / 2.1 })] }));
|
|
367
323
|
}
|
|
368
324
|
function InfoPanel({ building }) {
|
|
369
325
|
if (!building)
|
|
@@ -373,7 +329,7 @@ function InfoPanel({ building }) {
|
|
|
373
329
|
const rawExt = building.fileExtension || building.path.split('.').pop() || '';
|
|
374
330
|
const ext = rawExt.replace(/^\./, '');
|
|
375
331
|
const isCode = isCodeFile(ext);
|
|
376
|
-
return (
|
|
332
|
+
return (_jsxs("div", { style: {
|
|
377
333
|
position: 'absolute',
|
|
378
334
|
bottom: 16,
|
|
379
335
|
left: 16,
|
|
@@ -386,22 +342,13 @@ function InfoPanel({ building }) {
|
|
|
386
342
|
fontFamily: 'monospace',
|
|
387
343
|
maxWidth: 400,
|
|
388
344
|
pointerEvents: 'none',
|
|
389
|
-
} },
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
display: 'flex',
|
|
397
|
-
gap: 12,
|
|
398
|
-
} },
|
|
399
|
-
building.lineCount !== undefined && (react_1.default.createElement("span", null,
|
|
400
|
-
building.lineCount.toLocaleString(),
|
|
401
|
-
" lines")),
|
|
402
|
-
building.size !== undefined && (react_1.default.createElement("span", null,
|
|
403
|
-
(building.size / 1024).toFixed(1),
|
|
404
|
-
" KB")))));
|
|
345
|
+
}, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 4 }, children: fileName }), _jsx("div", { style: { color: '#94a3b8', fontSize: 12 }, children: dirPath }), _jsxs("div", { style: {
|
|
346
|
+
color: '#64748b',
|
|
347
|
+
fontSize: 11,
|
|
348
|
+
marginTop: 4,
|
|
349
|
+
display: 'flex',
|
|
350
|
+
gap: 12,
|
|
351
|
+
}, children: [building.lineCount !== undefined && (_jsxs("span", { children: [building.lineCount.toLocaleString(), " lines"] })), building.size !== undefined && (_jsxs("span", { children: [(building.size / 1024).toFixed(1), " KB"] }))] })] }));
|
|
405
352
|
}
|
|
406
353
|
function ControlsOverlay({ isFlat, onToggle, onResetCamera, }) {
|
|
407
354
|
const buttonStyle = {
|
|
@@ -416,24 +363,22 @@ function ControlsOverlay({ isFlat, onToggle, onResetCamera, }) {
|
|
|
416
363
|
alignItems: 'center',
|
|
417
364
|
gap: 6,
|
|
418
365
|
};
|
|
419
|
-
return (
|
|
366
|
+
return (_jsxs("div", { style: {
|
|
420
367
|
position: 'absolute',
|
|
421
368
|
top: 16,
|
|
422
369
|
right: 16,
|
|
423
370
|
display: 'flex',
|
|
424
371
|
gap: 8,
|
|
425
|
-
} },
|
|
426
|
-
react_1.default.createElement("button", { onClick: onResetCamera, style: buttonStyle }, "Reset View"),
|
|
427
|
-
react_1.default.createElement("button", { onClick: onToggle, style: buttonStyle }, isFlat ? 'Grow to 3D' : 'Flatten to 2D')));
|
|
372
|
+
}, children: [_jsx("button", { onClick: onResetCamera, style: buttonStyle, children: "Reset View" }), _jsx("button", { onClick: onToggle, style: buttonStyle, children: isFlat ? 'Grow to 3D' : 'Flatten to 2D' })] }));
|
|
428
373
|
}
|
|
429
374
|
function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding, growProgress, animationConfig, highlightLayers, isolationMode, dimOpacity, heightScaling, linearScale, }) {
|
|
430
|
-
const centerOffset =
|
|
375
|
+
const centerOffset = useMemo(() => ({
|
|
431
376
|
x: (cityData.bounds.minX + cityData.bounds.maxX) / 2,
|
|
432
377
|
z: (cityData.bounds.minZ + cityData.bounds.maxZ) / 2,
|
|
433
378
|
}), [cityData.bounds]);
|
|
434
379
|
const citySize = Math.max(cityData.bounds.maxX - cityData.bounds.minX, cityData.bounds.maxZ - cityData.bounds.minZ);
|
|
435
|
-
const activeHighlights =
|
|
436
|
-
const staggerIndices =
|
|
380
|
+
const activeHighlights = useMemo(() => hasActiveHighlights(highlightLayers), [highlightLayers]);
|
|
381
|
+
const staggerIndices = useMemo(() => {
|
|
437
382
|
const centerX = (cityData.bounds.minX + cityData.bounds.maxX) / 2;
|
|
438
383
|
const centerZ = (cityData.bounds.minZ + cityData.bounds.maxZ) / 2;
|
|
439
384
|
const withDistance = cityData.buildings.map((b, originalIndex) => ({
|
|
@@ -448,18 +393,12 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
448
393
|
});
|
|
449
394
|
return indices;
|
|
450
395
|
}, [cityData.buildings, cityData.bounds]);
|
|
451
|
-
const hoveredIndex =
|
|
396
|
+
const hoveredIndex = useMemo(() => {
|
|
452
397
|
if (!hoveredBuilding)
|
|
453
398
|
return null;
|
|
454
399
|
return cityData.buildings.findIndex((b) => b.path === hoveredBuilding.path);
|
|
455
400
|
}, [hoveredBuilding, cityData.buildings]);
|
|
456
|
-
return (
|
|
457
|
-
react_1.default.createElement(AnimatedCamera, { citySize: citySize, isFlat: growProgress === 0 }),
|
|
458
|
-
react_1.default.createElement("ambientLight", { intensity: 0.4 }),
|
|
459
|
-
react_1.default.createElement("directionalLight", { position: [citySize, citySize, citySize * 0.5], intensity: 1, castShadow: true, "shadow-mapSize": [2048, 2048] }),
|
|
460
|
-
react_1.default.createElement("directionalLight", { position: [-citySize * 0.5, citySize * 0.5, -citySize * 0.5], intensity: 0.3 }),
|
|
461
|
-
cityData.districts.map((district) => (react_1.default.createElement(DistrictFloor, { key: district.path, district: district, centerOffset: centerOffset, opacity: 1 }))),
|
|
462
|
-
react_1.default.createElement(InstancedBuildings, { buildings: cityData.buildings, centerOffset: centerOffset, onHover: onBuildingHover, onClick: onBuildingClick, hoveredIndex: hoveredIndex, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights, dimOpacity: dimOpacity, heightScaling: heightScaling, linearScale: linearScale, staggerIndices: staggerIndices })));
|
|
401
|
+
return (_jsxs(_Fragment, { children: [_jsx(AnimatedCamera, { citySize: citySize, isFlat: growProgress === 0 }), _jsx("ambientLight", { intensity: 0.4 }), _jsx("directionalLight", { position: [citySize, citySize, citySize * 0.5], intensity: 1, castShadow: true, "shadow-mapSize": [2048, 2048] }), _jsx("directionalLight", { position: [-citySize * 0.5, citySize * 0.5, -citySize * 0.5], intensity: 0.3 }), cityData.districts.map((district) => (_jsx(DistrictFloor, { district: district, centerOffset: centerOffset, opacity: 1 }, district.path))), _jsx(InstancedBuildings, { buildings: cityData.buildings, centerOffset: centerOffset, onHover: onBuildingHover, onClick: onBuildingClick, hoveredIndex: hoveredIndex, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights, dimOpacity: dimOpacity, heightScaling: heightScaling, linearScale: linearScale, staggerIndices: staggerIndices })] }));
|
|
463
402
|
}
|
|
464
403
|
/**
|
|
465
404
|
* FileCity3D - 3D visualization of codebase structure
|
|
@@ -467,17 +406,17 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
467
406
|
* Renders CityData as an interactive 3D city where buildings represent files
|
|
468
407
|
* and their height corresponds to line count or file size.
|
|
469
408
|
*/
|
|
470
|
-
function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls = true, highlightLayers = [], isolationMode = 'transparent', dimOpacity = 0.15, isLoading = false, loadingMessage = 'Loading file city...', emptyMessage = 'No file tree data available', heightScaling = 'logarithmic', linearScale = 0.05, }) {
|
|
471
|
-
const { theme } =
|
|
472
|
-
const [hoveredBuilding, setHoveredBuilding] =
|
|
473
|
-
const [internalIsGrown, setInternalIsGrown] =
|
|
474
|
-
const animationConfig =
|
|
409
|
+
export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls = true, highlightLayers = [], isolationMode = 'transparent', dimOpacity = 0.15, isLoading = false, loadingMessage = 'Loading file city...', emptyMessage = 'No file tree data available', heightScaling = 'logarithmic', linearScale = 0.05, }) {
|
|
410
|
+
const { theme } = useTheme();
|
|
411
|
+
const [hoveredBuilding, setHoveredBuilding] = useState(null);
|
|
412
|
+
const [internalIsGrown, setInternalIsGrown] = useState(false);
|
|
413
|
+
const animationConfig = useMemo(() => ({ ...DEFAULT_ANIMATION, ...animation }), [animation]);
|
|
475
414
|
const isGrown = externalIsGrown !== undefined ? externalIsGrown : internalIsGrown;
|
|
476
415
|
const setIsGrown = (value) => {
|
|
477
416
|
setInternalIsGrown(value);
|
|
478
417
|
onGrowChange?.(value);
|
|
479
418
|
};
|
|
480
|
-
|
|
419
|
+
useEffect(() => {
|
|
481
420
|
if (animationConfig.startFlat && animationConfig.autoStartDelay !== null) {
|
|
482
421
|
const timer = setTimeout(() => {
|
|
483
422
|
setIsGrown(true);
|
|
@@ -494,7 +433,7 @@ function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, c
|
|
|
494
433
|
setIsGrown(!isGrown);
|
|
495
434
|
};
|
|
496
435
|
if (isLoading) {
|
|
497
|
-
return (
|
|
436
|
+
return (_jsx("div", { className: className, style: {
|
|
498
437
|
width,
|
|
499
438
|
height,
|
|
500
439
|
position: 'relative',
|
|
@@ -507,10 +446,10 @@ function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, c
|
|
|
507
446
|
fontFamily: 'system-ui, sans-serif',
|
|
508
447
|
fontSize: 14,
|
|
509
448
|
...style,
|
|
510
|
-
}
|
|
449
|
+
}, children: loadingMessage }));
|
|
511
450
|
}
|
|
512
451
|
if (!cityData || cityData.buildings.length === 0) {
|
|
513
|
-
return (
|
|
452
|
+
return (_jsx("div", { className: className, style: {
|
|
514
453
|
width,
|
|
515
454
|
height,
|
|
516
455
|
position: 'relative',
|
|
@@ -523,25 +462,21 @@ function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, c
|
|
|
523
462
|
fontFamily: 'system-ui, sans-serif',
|
|
524
463
|
fontSize: 14,
|
|
525
464
|
...style,
|
|
526
|
-
}
|
|
465
|
+
}, children: emptyMessage }));
|
|
527
466
|
}
|
|
528
|
-
return (
|
|
467
|
+
return (_jsxs("div", { className: className, style: {
|
|
529
468
|
width,
|
|
530
469
|
height,
|
|
531
470
|
position: 'relative',
|
|
532
471
|
background: theme.colors.background,
|
|
533
472
|
overflow: 'hidden',
|
|
534
473
|
...style,
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
} },
|
|
543
|
-
react_1.default.createElement(CityScene, { cityData: cityData, onBuildingHover: setHoveredBuilding, onBuildingClick: onBuildingClick, hoveredBuilding: hoveredBuilding, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, dimOpacity: dimOpacity, heightScaling: heightScaling, linearScale: linearScale })),
|
|
544
|
-
react_1.default.createElement(InfoPanel, { building: hoveredBuilding }),
|
|
545
|
-
showControls && (react_1.default.createElement(ControlsOverlay, { isFlat: !isGrown, onToggle: handleToggle, onResetCamera: resetCamera }))));
|
|
474
|
+
}, children: [_jsx(Canvas, { shadows: true, style: {
|
|
475
|
+
position: 'absolute',
|
|
476
|
+
top: 0,
|
|
477
|
+
left: 0,
|
|
478
|
+
width: '100%',
|
|
479
|
+
height: '100%',
|
|
480
|
+
}, children: _jsx(CityScene, { cityData: cityData, onBuildingHover: setHoveredBuilding, onBuildingClick: onBuildingClick, hoveredBuilding: hoveredBuilding, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, dimOpacity: dimOpacity, heightScaling: heightScaling, linearScale: linearScale }) }), _jsx(InfoPanel, { building: hoveredBuilding }), showControls && (_jsx(ControlsOverlay, { isFlat: !isGrown, onToggle: handleToggle, onResetCamera: resetCamera }))] }));
|
|
546
481
|
}
|
|
547
|
-
|
|
482
|
+
export default FileCity3D;
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FileCity3D - 3D visualization component
|
|
4
3
|
*/
|
|
5
|
-
|
|
6
|
-
exports.resetCamera = exports.FileCity3D = void 0;
|
|
7
|
-
var FileCity3D_1 = require("./FileCity3D");
|
|
8
|
-
Object.defineProperty(exports, "FileCity3D", { enumerable: true, get: function () { return FileCity3D_1.FileCity3D; } });
|
|
9
|
-
Object.defineProperty(exports, "resetCamera", { enumerable: true, get: function () { return FileCity3D_1.resetCamera; } });
|
|
4
|
+
export { FileCity3D, resetCamera } from './FileCity3D';
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const [cityData, setCityData] = (0, react_1.useState)(null);
|
|
8
|
-
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
9
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { MultiVersionCityBuilder } from '@principal-ai/file-city-builder';
|
|
3
|
+
export function useCodeCityData({ fileSystemTree, autoUpdate = true, }) {
|
|
4
|
+
const [cityData, setCityData] = useState(null);
|
|
5
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
6
|
+
const [error, setError] = useState(null);
|
|
10
7
|
// UI state
|
|
11
|
-
const [highlightedPaths, setHighlightedPaths] =
|
|
12
|
-
const [selectedPaths, setSelectedPaths] =
|
|
13
|
-
const [focusDirectory, setFocusDirectory] =
|
|
8
|
+
const [highlightedPaths, setHighlightedPaths] = useState(new Set());
|
|
9
|
+
const [selectedPaths, setSelectedPaths] = useState(new Set());
|
|
10
|
+
const [focusDirectory, setFocusDirectory] = useState(null);
|
|
14
11
|
// Rebuild city data
|
|
15
|
-
const rebuild =
|
|
12
|
+
const rebuild = useMemo(() => {
|
|
16
13
|
return () => {
|
|
17
14
|
if (!fileSystemTree) {
|
|
18
15
|
setCityData(null);
|
|
@@ -24,7 +21,7 @@ function useCodeCityData({ fileSystemTree, autoUpdate = true, }) {
|
|
|
24
21
|
try {
|
|
25
22
|
// Create a single-version map for the builder
|
|
26
23
|
const versionMap = new Map([['main', fileSystemTree]]);
|
|
27
|
-
const { unionCity } =
|
|
24
|
+
const { unionCity } = MultiVersionCityBuilder.build(versionMap);
|
|
28
25
|
setCityData(unionCity);
|
|
29
26
|
}
|
|
30
27
|
catch (err) {
|
|
@@ -37,7 +34,7 @@ function useCodeCityData({ fileSystemTree, autoUpdate = true, }) {
|
|
|
37
34
|
};
|
|
38
35
|
}, [fileSystemTree]);
|
|
39
36
|
// Auto rebuild when dependencies change
|
|
40
|
-
|
|
37
|
+
useEffect(() => {
|
|
41
38
|
if (autoUpdate) {
|
|
42
39
|
rebuild();
|
|
43
40
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,45 +1,22 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resetCamera = exports.FileCity3D = exports.useTheme = exports.ThemeProvider = exports.CityViewWithReactFlow = exports.useCodeCityData = exports.MultiVersionCityBuilder = exports.drawFileTypeIcon = exports.getFileTypeIcon = exports.extractIconConfig = exports.mergeFileColorConfig = exports.devFileColorOverrides = exports.getFileColorMapping = exports.getDefaultFileColorConfig = exports.createFileColorHighlightLayers = exports.filterCityDataForMultipleDirectories = exports.filterCityDataForSubdirectory = exports.filterCityDataForSelectiveRender = exports.LayerIndex = exports.ArchitectureMapHighlightLayers = void 0;
|
|
4
1
|
// Main component export
|
|
5
|
-
|
|
6
|
-
Object.defineProperty(exports, "ArchitectureMapHighlightLayers", { enumerable: true, get: function () { return ArchitectureMapHighlightLayers_1.ArchitectureMapHighlightLayers; } });
|
|
2
|
+
export { ArchitectureMapHighlightLayers, } from './components/ArchitectureMapHighlightLayers';
|
|
7
3
|
// Layer and rendering types
|
|
8
|
-
|
|
9
|
-
Object.defineProperty(exports, "LayerIndex", { enumerable: true, get: function () { return drawLayeredBuildings_1.LayerIndex; } });
|
|
4
|
+
export { LayerIndex, } from './render/client/drawLayeredBuildings';
|
|
10
5
|
// Utility functions
|
|
11
|
-
|
|
12
|
-
Object.defineProperty(exports, "filterCityDataForSelectiveRender", { enumerable: true, get: function () { return cityDataUtils_1.filterCityDataForSelectiveRender; } });
|
|
13
|
-
Object.defineProperty(exports, "filterCityDataForSubdirectory", { enumerable: true, get: function () { return cityDataUtils_1.filterCityDataForSubdirectory; } });
|
|
14
|
-
Object.defineProperty(exports, "filterCityDataForMultipleDirectories", { enumerable: true, get: function () { return cityDataUtils_1.filterCityDataForMultipleDirectories; } });
|
|
6
|
+
export { filterCityDataForSelectiveRender, filterCityDataForSubdirectory, filterCityDataForMultipleDirectories, } from './builder/cityDataUtils';
|
|
15
7
|
// File color highlight layer utilities
|
|
16
|
-
|
|
17
|
-
Object.defineProperty(exports, "createFileColorHighlightLayers", { enumerable: true, get: function () { return fileColorHighlightLayers_1.createFileColorHighlightLayers; } });
|
|
18
|
-
Object.defineProperty(exports, "getDefaultFileColorConfig", { enumerable: true, get: function () { return fileColorHighlightLayers_1.getDefaultFileColorConfig; } });
|
|
19
|
-
Object.defineProperty(exports, "getFileColorMapping", { enumerable: true, get: function () { return fileColorHighlightLayers_1.getFileColorMapping; } });
|
|
8
|
+
export { createFileColorHighlightLayers, getDefaultFileColorConfig, getFileColorMapping, } from './utils/fileColorHighlightLayers';
|
|
20
9
|
// File color override utilities for development
|
|
21
|
-
|
|
22
|
-
Object.defineProperty(exports, "devFileColorOverrides", { enumerable: true, get: function () { return fileColorOverrides_1.devFileColorOverrides; } });
|
|
23
|
-
Object.defineProperty(exports, "mergeFileColorConfig", { enumerable: true, get: function () { return fileColorOverrides_1.mergeFileColorConfig; } });
|
|
10
|
+
export { devFileColorOverrides, mergeFileColorConfig } from './utils/fileColorOverrides';
|
|
24
11
|
// File type icon utilities
|
|
25
|
-
|
|
26
|
-
Object.defineProperty(exports, "extractIconConfig", { enumerable: true, get: function () { return fileTypeIcons_1.extractIconConfig; } });
|
|
27
|
-
Object.defineProperty(exports, "getFileTypeIcon", { enumerable: true, get: function () { return fileTypeIcons_1.getFileTypeIcon; } });
|
|
28
|
-
Object.defineProperty(exports, "drawFileTypeIcon", { enumerable: true, get: function () { return fileTypeIcons_1.drawFileTypeIcon; } });
|
|
12
|
+
export { extractIconConfig, getFileTypeIcon, drawFileTypeIcon } from './utils/fileTypeIcons';
|
|
29
13
|
// Re-export MultiVersionCityBuilder which was requested
|
|
30
|
-
|
|
31
|
-
Object.defineProperty(exports, "MultiVersionCityBuilder", { enumerable: true, get: function () { return file_city_builder_1.MultiVersionCityBuilder; } });
|
|
14
|
+
export { MultiVersionCityBuilder } from '@principal-ai/file-city-builder';
|
|
32
15
|
// Export the useCodeCityData hook
|
|
33
|
-
|
|
34
|
-
Object.defineProperty(exports, "useCodeCityData", { enumerable: true, get: function () { return useCodeCityData_1.useCodeCityData; } });
|
|
16
|
+
export { useCodeCityData } from './hooks/useCodeCityData';
|
|
35
17
|
// Export React Flow based city view component
|
|
36
|
-
|
|
37
|
-
Object.defineProperty(exports, "CityViewWithReactFlow", { enumerable: true, get: function () { return CityViewWithReactFlow_1.CityViewWithReactFlow; } });
|
|
18
|
+
export { CityViewWithReactFlow, } from './components/CityViewWithReactFlow';
|
|
38
19
|
// Re-export theme utilities for consumers
|
|
39
|
-
|
|
40
|
-
Object.defineProperty(exports, "ThemeProvider", { enumerable: true, get: function () { return industry_theme_1.ThemeProvider; } });
|
|
41
|
-
Object.defineProperty(exports, "useTheme", { enumerable: true, get: function () { return industry_theme_1.useTheme; } });
|
|
20
|
+
export { ThemeProvider, useTheme } from '@principal-ade/industry-theme';
|
|
42
21
|
// 3D visualization component
|
|
43
|
-
|
|
44
|
-
Object.defineProperty(exports, "FileCity3D", { enumerable: true, get: function () { return FileCity3D_1.FileCity3D; } });
|
|
45
|
-
Object.defineProperty(exports, "resetCamera", { enumerable: true, get: function () { return FileCity3D_1.resetCamera; } });
|
|
22
|
+
export { FileCity3D, resetCamera } from './components/FileCity3D';
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.LayerIndex = void 0;
|
|
4
|
-
exports.drawGrid = drawGrid;
|
|
5
|
-
exports.drawLayeredDistricts = drawLayeredDistricts;
|
|
6
|
-
exports.drawLayeredBuildings = drawLayeredBuildings;
|
|
7
|
-
const lucideIconConverter_1 = require("../../utils/lucideIconConverter");
|
|
8
|
-
const fileTypeIcons_1 = require("../../utils/fileTypeIcons");
|
|
1
|
+
import { getLucideIconImage } from '../../utils/lucideIconConverter';
|
|
2
|
+
import { getFileTypeIcon, drawFileTypeIcon } from '../../utils/fileTypeIcons';
|
|
9
3
|
/**
|
|
10
4
|
* LayerIndex provides O(1) path lookups instead of O(n) iteration.
|
|
11
5
|
* This dramatically improves performance with large numbers of layer items.
|
|
12
6
|
*/
|
|
13
|
-
class LayerIndex {
|
|
7
|
+
export class LayerIndex {
|
|
14
8
|
constructor(layers) {
|
|
15
9
|
// Map from exact path to layer items (for file items)
|
|
16
10
|
this.exactIndex = new Map();
|
|
@@ -86,7 +80,6 @@ class LayerIndex {
|
|
|
86
80
|
return sorted;
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
|
-
exports.LayerIndex = LayerIndex;
|
|
90
83
|
// Helper function to draw rounded rectangles
|
|
91
84
|
function drawRoundedRect(ctx, x, y, width, height, radius, fill, stroke) {
|
|
92
85
|
ctx.beginPath();
|
|
@@ -106,7 +99,7 @@ function drawRoundedRect(ctx, x, y, width, height, radius, fill, stroke) {
|
|
|
106
99
|
ctx.stroke();
|
|
107
100
|
}
|
|
108
101
|
// Draw grid helper (copied from original)
|
|
109
|
-
function drawGrid(ctx, width, height, gridSize) {
|
|
102
|
+
export function drawGrid(ctx, width, height, gridSize) {
|
|
110
103
|
ctx.save();
|
|
111
104
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
|
112
105
|
ctx.lineWidth = 1;
|
|
@@ -284,7 +277,7 @@ function renderCoverStrategy(ctx, bounds, layer, item, _scale) {
|
|
|
284
277
|
let img = null;
|
|
285
278
|
// Check for lucideIcon first (new way)
|
|
286
279
|
if (coverOptions.lucideIcon) {
|
|
287
|
-
img =
|
|
280
|
+
img = getLucideIconImage(coverOptions.lucideIcon, '#ffffff', coverOptions.iconSize || 24);
|
|
288
281
|
}
|
|
289
282
|
// Fallback to direct image URL (old way)
|
|
290
283
|
else if (coverOptions.image) {
|
|
@@ -396,7 +389,7 @@ function applyLayerRendering(ctx, bounds, layer, item, scale, borderRadius = 0)
|
|
|
396
389
|
}
|
|
397
390
|
}
|
|
398
391
|
// Draw districts with layer support
|
|
399
|
-
function drawLayeredDistricts(ctx, districts, worldToCanvas, scale, // This includes the zoom scale for text proportionality
|
|
392
|
+
export function drawLayeredDistricts(ctx, districts, worldToCanvas, scale, // This includes the zoom scale for text proportionality
|
|
400
393
|
layers, hoveredDistrict, fullSize, defaultDirectoryColor, layoutConfig, abstractedPaths, // Paths of directories that are abstracted (have covers)
|
|
401
394
|
showDirectoryLabels = true, borderRadius = 0, // Border radius for districts (default: sharp corners)
|
|
402
395
|
layerIndex) {
|
|
@@ -584,7 +577,7 @@ layerIndex) {
|
|
|
584
577
|
});
|
|
585
578
|
}
|
|
586
579
|
// Draw buildings with layer support
|
|
587
|
-
function drawLayeredBuildings(ctx, buildings, worldToCanvas, scale, layers, hoveredBuilding, defaultBuildingColor, showFileNames, hoverBorderColor, disableOpacityDimming, showFileTypeIcons, borderRadius = 0, // Border radius for buildings (default: 0 - sharp corners)
|
|
580
|
+
export function drawLayeredBuildings(ctx, buildings, worldToCanvas, scale, layers, hoveredBuilding, defaultBuildingColor, showFileNames, hoverBorderColor, disableOpacityDimming, showFileTypeIcons, borderRadius = 0, // Border radius for buildings (default: 0 - sharp corners)
|
|
588
581
|
layerIndex, // Optional pre-built index for performance
|
|
589
582
|
iconMap) {
|
|
590
583
|
// Build index once for all buildings (O(n) instead of O(n²))
|
|
@@ -657,7 +650,7 @@ iconMap) {
|
|
|
657
650
|
let iconBottomY = pos.y;
|
|
658
651
|
let hasIcon = false;
|
|
659
652
|
if (showFileTypeIcons && iconMap) {
|
|
660
|
-
const iconConfig =
|
|
653
|
+
const iconConfig = getFileTypeIcon(building.path, iconMap);
|
|
661
654
|
if (iconConfig) {
|
|
662
655
|
hasIcon = true;
|
|
663
656
|
// For wide buildings (wider than tall), shift icon up to make room for text below
|
|
@@ -666,7 +659,7 @@ iconMap) {
|
|
|
666
659
|
const isWide = width > height;
|
|
667
660
|
const iconYOffset = (willShowText && isWide) ? -height * 0.15 : 0;
|
|
668
661
|
const iconY = pos.y + iconYOffset;
|
|
669
|
-
|
|
662
|
+
drawFileTypeIcon(ctx, iconConfig, pos.x, iconY, width, height);
|
|
670
663
|
// Calculate where the icon ends vertically
|
|
671
664
|
const minDimension = Math.min(width, height);
|
|
672
665
|
if (iconConfig.type === 'emoji') {
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSampleCityData = createSampleCityData;
|
|
4
|
-
exports.createSmallSampleCityData = createSmallSampleCityData;
|
|
5
|
-
const file_city_builder_1 = require("@principal-ai/file-city-builder");
|
|
1
|
+
import { CodeCityBuilderWithGrid, buildFileSystemTreeFromFileInfoList, } from '@principal-ai/file-city-builder';
|
|
6
2
|
// Sample file structure representing a typical project
|
|
7
3
|
const sampleFileStructure = [
|
|
8
4
|
// Source files
|
|
@@ -66,13 +62,13 @@ function createFileInfoList(files) {
|
|
|
66
62
|
// Cache the city data to avoid rebuilding on every render
|
|
67
63
|
let cachedCityData = null;
|
|
68
64
|
// Helper function to create sample city data for stories using the real treemap builder
|
|
69
|
-
function createSampleCityData() {
|
|
65
|
+
export function createSampleCityData() {
|
|
70
66
|
if (cachedCityData) {
|
|
71
67
|
return cachedCityData;
|
|
72
68
|
}
|
|
73
69
|
const fileInfos = createFileInfoList(sampleFileStructure);
|
|
74
|
-
const fileTree =
|
|
75
|
-
const builder = new
|
|
70
|
+
const fileTree = buildFileSystemTreeFromFileInfoList(fileInfos, 'sample-project');
|
|
71
|
+
const builder = new CodeCityBuilderWithGrid();
|
|
76
72
|
cachedCityData = builder.buildCityFromFileSystem(fileTree, '', {
|
|
77
73
|
paddingTop: 2,
|
|
78
74
|
paddingBottom: 2,
|
|
@@ -92,13 +88,13 @@ const smallFileStructure = [
|
|
|
92
88
|
];
|
|
93
89
|
let cachedSmallCityData = null;
|
|
94
90
|
// Create a smaller sample for performance testing
|
|
95
|
-
function createSmallSampleCityData() {
|
|
91
|
+
export function createSmallSampleCityData() {
|
|
96
92
|
if (cachedSmallCityData) {
|
|
97
93
|
return cachedSmallCityData;
|
|
98
94
|
}
|
|
99
95
|
const fileInfos = createFileInfoList(smallFileStructure);
|
|
100
|
-
const fileTree =
|
|
101
|
-
const builder = new
|
|
96
|
+
const fileTree = buildFileSystemTreeFromFileInfoList(fileInfos, 'small-sample');
|
|
97
|
+
const builder = new CodeCityBuilderWithGrid();
|
|
102
98
|
cachedSmallCityData = builder.buildCityFromFileSystem(fileTree, '', {
|
|
103
99
|
paddingTop: 2,
|
|
104
100
|
paddingBottom: 2,
|