@principal-ai/file-city-react 0.5.13 → 0.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAMjF,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAEb,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,QAAQ,OAAO,CAAC;IAErB,UAAU,GAAG,CAAC;QAEZ,UAAU,iBAAkB,SAAQ,aAAa;SAAG;KACrD;CACF;AAGD,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;AAGrD,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED,gDAAgD;AAChD,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,aAAa,GACb,UAAU,GACV,MAAM,CAAC;AAGX,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wCAAwC;AACxC,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"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"}
|
|
@@ -287,15 +287,16 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
287
287
|
return;
|
|
288
288
|
buildings.forEach((building, index) => {
|
|
289
289
|
let shouldCollapse = false;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
290
|
+
const isInFocusDirectory = focusDirectory
|
|
291
|
+
? isPathInDirectory(building.path, focusDirectory)
|
|
292
|
+
: true; // No focusDirectory means all are "in focus"
|
|
293
|
+
const isHighlighted = hasActiveHighlightLayers
|
|
294
|
+
? getHighlightForPath(building.path, highlightLayers) !== null
|
|
295
|
+
: true; // No highlights means all are "highlighted"
|
|
296
|
+
// Collapse if outside BOTH focusDirectory AND highlightLayers
|
|
297
|
+
// (only when collapse mode is active)
|
|
298
|
+
if (focusDirectory || (hasActiveHighlightLayers && isolationMode === 'collapse')) {
|
|
299
|
+
shouldCollapse = !isInFocusDirectory && !isHighlighted;
|
|
299
300
|
}
|
|
300
301
|
targetMultipliersRef.current[index] = shouldCollapse ? 0.05 : 1;
|
|
301
302
|
});
|
|
@@ -569,12 +570,12 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
|
|
|
569
570
|
const { camera } = useThree();
|
|
570
571
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
571
572
|
const controlsRef = useRef(null);
|
|
572
|
-
const prevTargetRef = useRef(null);
|
|
573
573
|
const isAnimatingRef = useRef(false);
|
|
574
|
-
|
|
574
|
+
const hasAppliedInitial = useRef(false);
|
|
575
|
+
const frameCount = useRef(0);
|
|
576
|
+
// Compute target camera position
|
|
575
577
|
const targetPos = useMemo(() => {
|
|
576
578
|
if (focusTarget) {
|
|
577
|
-
// Position camera to look at focus target
|
|
578
579
|
const distance = Math.max(focusTarget.size * 2, 50);
|
|
579
580
|
const height = Math.max(focusTarget.size * 1.5, 40);
|
|
580
581
|
return {
|
|
@@ -586,9 +587,9 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
|
|
|
586
587
|
targetZ: focusTarget.z,
|
|
587
588
|
};
|
|
588
589
|
}
|
|
589
|
-
// Default
|
|
590
|
-
const targetHeight =
|
|
591
|
-
const targetZ =
|
|
590
|
+
// Default overview
|
|
591
|
+
const targetHeight = citySize * 1.1;
|
|
592
|
+
const targetZ = citySize * 1.3;
|
|
592
593
|
return {
|
|
593
594
|
x: 0,
|
|
594
595
|
y: targetHeight,
|
|
@@ -597,11 +598,9 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
|
|
|
597
598
|
targetY: 0,
|
|
598
599
|
targetZ: 0,
|
|
599
600
|
};
|
|
600
|
-
}, [focusTarget,
|
|
601
|
-
// Create a stable key to detect when target actually changes
|
|
602
|
-
const targetKey = `${targetPos.x},${targetPos.y},${targetPos.z},${targetPos.targetX},${targetPos.targetZ}`;
|
|
601
|
+
}, [focusTarget, citySize]);
|
|
603
602
|
// Spring animation for camera movement
|
|
604
|
-
const { camX, camY, camZ, lookX, lookY, lookZ } = useSpring({
|
|
603
|
+
const [{ camX, camY, camZ, lookX, lookY, lookZ }, api] = useSpring(() => ({
|
|
605
604
|
camX: targetPos.x,
|
|
606
605
|
camY: targetPos.y,
|
|
607
606
|
camZ: targetPos.z,
|
|
@@ -609,44 +608,74 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
|
|
|
609
608
|
lookY: targetPos.targetY,
|
|
610
609
|
lookZ: targetPos.targetZ,
|
|
611
610
|
config: { tension: 60, friction: 20 },
|
|
612
|
-
immediate: prevTargetRef.current === null, // Skip animation on first render
|
|
613
611
|
onStart: () => {
|
|
614
612
|
isAnimatingRef.current = true;
|
|
615
613
|
},
|
|
616
614
|
onRest: () => {
|
|
617
615
|
isAnimatingRef.current = false;
|
|
618
616
|
},
|
|
619
|
-
});
|
|
620
|
-
//
|
|
617
|
+
}));
|
|
618
|
+
// When targetPos changes after initial, animate to new position
|
|
621
619
|
useEffect(() => {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
620
|
+
// Skip the first render - we handle that directly in useFrame
|
|
621
|
+
if (!hasAppliedInitial.current)
|
|
622
|
+
return;
|
|
623
|
+
api.start({
|
|
624
|
+
camX: targetPos.x,
|
|
625
|
+
camY: targetPos.y,
|
|
626
|
+
camZ: targetPos.z,
|
|
627
|
+
lookX: targetPos.targetX,
|
|
628
|
+
lookY: targetPos.targetY,
|
|
629
|
+
lookZ: targetPos.targetZ,
|
|
630
|
+
onRest: () => {
|
|
631
|
+
isAnimatingRef.current = false;
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
}, [targetPos, api]);
|
|
635
|
+
// Update camera each frame
|
|
625
636
|
useFrame(() => {
|
|
637
|
+
frameCount.current++;
|
|
638
|
+
// Skip first 2 frames to ensure OrbitControls is fully initialized
|
|
639
|
+
if (frameCount.current < 3)
|
|
640
|
+
return;
|
|
626
641
|
if (!controlsRef.current)
|
|
627
642
|
return;
|
|
628
|
-
//
|
|
629
|
-
if (
|
|
643
|
+
// Set initial position: apply camera position directly (no spring animation)
|
|
644
|
+
if (!hasAppliedInitial.current) {
|
|
645
|
+
camera.position.set(targetPos.x, targetPos.y, targetPos.z);
|
|
646
|
+
controlsRef.current.target.set(targetPos.targetX, targetPos.targetY, targetPos.targetZ);
|
|
647
|
+
controlsRef.current.update();
|
|
648
|
+
// Sync spring to this position so future animations start from here
|
|
649
|
+
api.set({
|
|
650
|
+
camX: targetPos.x,
|
|
651
|
+
camY: targetPos.y,
|
|
652
|
+
camZ: targetPos.z,
|
|
653
|
+
lookX: targetPos.targetX,
|
|
654
|
+
lookY: targetPos.targetY,
|
|
655
|
+
lookZ: targetPos.targetZ,
|
|
656
|
+
});
|
|
657
|
+
hasAppliedInitial.current = true;
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
// Subsequent frames: only update during animations
|
|
661
|
+
if (isAnimatingRef.current) {
|
|
630
662
|
camera.position.set(camX.get(), camY.get(), camZ.get());
|
|
631
663
|
controlsRef.current.target.set(lookX.get(), lookY.get(), lookZ.get());
|
|
632
664
|
controlsRef.current.update();
|
|
633
665
|
}
|
|
634
666
|
});
|
|
635
667
|
const resetToInitial = useCallback(() => {
|
|
636
|
-
const targetHeight =
|
|
637
|
-
const targetZ =
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
resetToInitial();
|
|
648
|
-
}
|
|
649
|
-
}, [resetToInitial, focusTarget]);
|
|
668
|
+
const targetHeight = citySize * 1.1;
|
|
669
|
+
const targetZ = citySize * 1.3;
|
|
670
|
+
api.start({
|
|
671
|
+
camX: 0,
|
|
672
|
+
camY: targetHeight,
|
|
673
|
+
camZ: targetZ,
|
|
674
|
+
lookX: 0,
|
|
675
|
+
lookY: 0,
|
|
676
|
+
lookZ: 0,
|
|
677
|
+
});
|
|
678
|
+
}, [citySize, api]);
|
|
650
679
|
useEffect(() => {
|
|
651
680
|
cameraResetFn = resetToInitial;
|
|
652
681
|
return () => {
|
package/package.json
CHANGED
|
@@ -491,15 +491,18 @@ function InstancedBuildings({
|
|
|
491
491
|
buildings.forEach((building, index) => {
|
|
492
492
|
let shouldCollapse = false;
|
|
493
493
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
494
|
+
const isInFocusDirectory = focusDirectory
|
|
495
|
+
? isPathInDirectory(building.path, focusDirectory)
|
|
496
|
+
: true; // No focusDirectory means all are "in focus"
|
|
497
|
+
|
|
498
|
+
const isHighlighted = hasActiveHighlightLayers
|
|
499
|
+
? getHighlightForPath(building.path, highlightLayers) !== null
|
|
500
|
+
: true; // No highlights means all are "highlighted"
|
|
501
|
+
|
|
502
|
+
// Collapse if outside BOTH focusDirectory AND highlightLayers
|
|
503
|
+
// (only when collapse mode is active)
|
|
504
|
+
if (focusDirectory || (hasActiveHighlightLayers && isolationMode === 'collapse')) {
|
|
505
|
+
shouldCollapse = !isInFocusDirectory && !isHighlighted;
|
|
503
506
|
}
|
|
504
507
|
|
|
505
508
|
targetMultipliersRef.current![index] = shouldCollapse ? 0.05 : 1;
|
|
@@ -996,13 +999,13 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
|
|
|
996
999
|
const { camera } = useThree();
|
|
997
1000
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
998
1001
|
const controlsRef = useRef<any>(null);
|
|
999
|
-
const prevTargetRef = useRef<string | null>(null);
|
|
1000
1002
|
const isAnimatingRef = useRef(false);
|
|
1003
|
+
const hasAppliedInitial = useRef(false);
|
|
1004
|
+
const frameCount = useRef(0);
|
|
1001
1005
|
|
|
1002
|
-
//
|
|
1006
|
+
// Compute target camera position
|
|
1003
1007
|
const targetPos = useMemo(() => {
|
|
1004
1008
|
if (focusTarget) {
|
|
1005
|
-
// Position camera to look at focus target
|
|
1006
1009
|
const distance = Math.max(focusTarget.size * 2, 50);
|
|
1007
1010
|
const height = Math.max(focusTarget.size * 1.5, 40);
|
|
1008
1011
|
return {
|
|
@@ -1014,9 +1017,9 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
|
|
|
1014
1017
|
targetZ: focusTarget.z,
|
|
1015
1018
|
};
|
|
1016
1019
|
}
|
|
1017
|
-
// Default
|
|
1018
|
-
const targetHeight =
|
|
1019
|
-
const targetZ =
|
|
1020
|
+
// Default overview
|
|
1021
|
+
const targetHeight = citySize * 1.1;
|
|
1022
|
+
const targetZ = citySize * 1.3;
|
|
1020
1023
|
return {
|
|
1021
1024
|
x: 0,
|
|
1022
1025
|
y: targetHeight,
|
|
@@ -1025,13 +1028,10 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
|
|
|
1025
1028
|
targetY: 0,
|
|
1026
1029
|
targetZ: 0,
|
|
1027
1030
|
};
|
|
1028
|
-
}, [focusTarget,
|
|
1029
|
-
|
|
1030
|
-
// Create a stable key to detect when target actually changes
|
|
1031
|
-
const targetKey = `${targetPos.x},${targetPos.y},${targetPos.z},${targetPos.targetX},${targetPos.targetZ}`;
|
|
1031
|
+
}, [focusTarget, citySize]);
|
|
1032
1032
|
|
|
1033
1033
|
// Spring animation for camera movement
|
|
1034
|
-
const { camX, camY, camZ, lookX, lookY, lookZ } = useSpring({
|
|
1034
|
+
const [{ camX, camY, camZ, lookX, lookY, lookZ }, api] = useSpring(() => ({
|
|
1035
1035
|
camX: targetPos.x,
|
|
1036
1036
|
camY: targetPos.y,
|
|
1037
1037
|
camZ: targetPos.z,
|
|
@@ -1039,26 +1039,62 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
|
|
|
1039
1039
|
lookY: targetPos.targetY,
|
|
1040
1040
|
lookZ: targetPos.targetZ,
|
|
1041
1041
|
config: { tension: 60, friction: 20 },
|
|
1042
|
-
immediate: prevTargetRef.current === null, // Skip animation on first render
|
|
1043
1042
|
onStart: () => {
|
|
1044
1043
|
isAnimatingRef.current = true;
|
|
1045
1044
|
},
|
|
1046
1045
|
onRest: () => {
|
|
1047
1046
|
isAnimatingRef.current = false;
|
|
1048
1047
|
},
|
|
1049
|
-
});
|
|
1048
|
+
}));
|
|
1050
1049
|
|
|
1051
|
-
//
|
|
1050
|
+
// When targetPos changes after initial, animate to new position
|
|
1052
1051
|
useEffect(() => {
|
|
1053
|
-
|
|
1054
|
-
|
|
1052
|
+
// Skip the first render - we handle that directly in useFrame
|
|
1053
|
+
if (!hasAppliedInitial.current) return;
|
|
1054
|
+
|
|
1055
|
+
api.start({
|
|
1056
|
+
camX: targetPos.x,
|
|
1057
|
+
camY: targetPos.y,
|
|
1058
|
+
camZ: targetPos.z,
|
|
1059
|
+
lookX: targetPos.targetX,
|
|
1060
|
+
lookY: targetPos.targetY,
|
|
1061
|
+
lookZ: targetPos.targetZ,
|
|
1062
|
+
onRest: () => {
|
|
1063
|
+
isAnimatingRef.current = false;
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
}, [targetPos, api]);
|
|
1055
1067
|
|
|
1056
|
-
// Update camera each frame
|
|
1068
|
+
// Update camera each frame
|
|
1057
1069
|
useFrame(() => {
|
|
1070
|
+
frameCount.current++;
|
|
1071
|
+
|
|
1072
|
+
// Skip first 2 frames to ensure OrbitControls is fully initialized
|
|
1073
|
+
if (frameCount.current < 3) return;
|
|
1058
1074
|
if (!controlsRef.current) return;
|
|
1059
1075
|
|
|
1060
|
-
//
|
|
1061
|
-
if (
|
|
1076
|
+
// Set initial position: apply camera position directly (no spring animation)
|
|
1077
|
+
if (!hasAppliedInitial.current) {
|
|
1078
|
+
camera.position.set(targetPos.x, targetPos.y, targetPos.z);
|
|
1079
|
+
controlsRef.current.target.set(targetPos.targetX, targetPos.targetY, targetPos.targetZ);
|
|
1080
|
+
controlsRef.current.update();
|
|
1081
|
+
|
|
1082
|
+
// Sync spring to this position so future animations start from here
|
|
1083
|
+
api.set({
|
|
1084
|
+
camX: targetPos.x,
|
|
1085
|
+
camY: targetPos.y,
|
|
1086
|
+
camZ: targetPos.z,
|
|
1087
|
+
lookX: targetPos.targetX,
|
|
1088
|
+
lookY: targetPos.targetY,
|
|
1089
|
+
lookZ: targetPos.targetZ,
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
hasAppliedInitial.current = true;
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Subsequent frames: only update during animations
|
|
1097
|
+
if (isAnimatingRef.current) {
|
|
1062
1098
|
camera.position.set(camX.get(), camY.get(), camZ.get());
|
|
1063
1099
|
controlsRef.current.target.set(lookX.get(), lookY.get(), lookZ.get());
|
|
1064
1100
|
controlsRef.current.update();
|
|
@@ -1066,23 +1102,18 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
|
|
|
1066
1102
|
});
|
|
1067
1103
|
|
|
1068
1104
|
const resetToInitial = useCallback(() => {
|
|
1069
|
-
const targetHeight =
|
|
1070
|
-
const targetZ =
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
useEffect(() => {
|
|
1082
|
-
if (!focusTarget) {
|
|
1083
|
-
resetToInitial();
|
|
1084
|
-
}
|
|
1085
|
-
}, [resetToInitial, focusTarget]);
|
|
1105
|
+
const targetHeight = citySize * 1.1;
|
|
1106
|
+
const targetZ = citySize * 1.3;
|
|
1107
|
+
|
|
1108
|
+
api.start({
|
|
1109
|
+
camX: 0,
|
|
1110
|
+
camY: targetHeight,
|
|
1111
|
+
camZ: targetZ,
|
|
1112
|
+
lookX: 0,
|
|
1113
|
+
lookY: 0,
|
|
1114
|
+
lookZ: 0,
|
|
1115
|
+
});
|
|
1116
|
+
}, [citySize, api]);
|
|
1086
1117
|
|
|
1087
1118
|
useEffect(() => {
|
|
1088
1119
|
cameraResetFn = resetToInitial;
|