@principal-ai/file-city-react 0.4.1 → 0.4.2
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":"ArchitectureMapHighlightLayers.d.ts","sourceRoot":"","sources":["../../src/components/ArchitectureMapHighlightLayers.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ArchitectureMapHighlightLayers.d.ts","sourceRoot":"","sources":["../../src/components/ArchitectureMapHighlightLayers.tsx"],"names":[],"mappings":"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"}
|
|
@@ -210,6 +210,9 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
|
|
|
210
210
|
const isAnimating = targetZoom !== null;
|
|
211
211
|
// Track the last zoomToPath to detect changes
|
|
212
212
|
const lastZoomToPathRef = (0, react_1.useRef)(null);
|
|
213
|
+
// Throttle ref for hover updates (improves performance with large datasets)
|
|
214
|
+
const lastHoverUpdateRef = (0, react_1.useRef)(0);
|
|
215
|
+
const HOVER_THROTTLE_MS = 16; // ~60fps max for hover updates
|
|
213
216
|
(0, react_1.useEffect)(() => {
|
|
214
217
|
// Reset user interaction state when enableZoom is disabled
|
|
215
218
|
if (!enableZoom) {
|
|
@@ -693,6 +696,66 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
|
|
|
693
696
|
}
|
|
694
697
|
return layers;
|
|
695
698
|
}, [stableLayers, dynamicLayers, abstractionLayer]);
|
|
699
|
+
// Memoize abstracted paths lookup - only recalculates when abstraction layer changes
|
|
700
|
+
const { abstractedPathsSet, abstractedPathLookup } = (0, react_1.useMemo)(() => {
|
|
701
|
+
const pathsSet = new Set();
|
|
702
|
+
const abstractionLayerDef = allLayers.find(l => l.id === 'directory-abstraction');
|
|
703
|
+
if (abstractionLayerDef && abstractionLayerDef.enabled) {
|
|
704
|
+
abstractionLayerDef.items.forEach(item => {
|
|
705
|
+
if (item.type === 'directory') {
|
|
706
|
+
pathsSet.add(item.path);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
abstractedPathsSet: pathsSet,
|
|
712
|
+
abstractedPathLookup: new PathHierarchyLookup(pathsSet),
|
|
713
|
+
};
|
|
714
|
+
}, [allLayers]);
|
|
715
|
+
// Memoize visible districts - only recalculates when data or abstraction changes, NOT on hover
|
|
716
|
+
const visibleDistrictsMemo = (0, react_1.useMemo)(() => {
|
|
717
|
+
if (!filteredCityData)
|
|
718
|
+
return [];
|
|
719
|
+
let districts = abstractedPathLookup.size > 0
|
|
720
|
+
? filteredCityData.districts.filter(district => {
|
|
721
|
+
// Check for root abstraction first
|
|
722
|
+
if (abstractedPathLookup.has('')) {
|
|
723
|
+
return !district.path || district.path === '';
|
|
724
|
+
}
|
|
725
|
+
if (!district.path)
|
|
726
|
+
return true;
|
|
727
|
+
if (abstractedPathLookup.has(district.path))
|
|
728
|
+
return true;
|
|
729
|
+
return !abstractedPathLookup.isChildOfAbstracted(district.path);
|
|
730
|
+
})
|
|
731
|
+
: filteredCityData.districts;
|
|
732
|
+
// If root is abstracted and there's no root district, create one for the cover
|
|
733
|
+
if (abstractedPathsSet.has('')) {
|
|
734
|
+
const hasRootDistrict = districts.some(d => !d.path || d.path === '');
|
|
735
|
+
if (!hasRootDistrict) {
|
|
736
|
+
districts = [
|
|
737
|
+
{
|
|
738
|
+
path: '',
|
|
739
|
+
worldBounds: filteredCityData.bounds,
|
|
740
|
+
fileCount: filteredCityData.buildings.length,
|
|
741
|
+
type: 'directory',
|
|
742
|
+
},
|
|
743
|
+
...districts,
|
|
744
|
+
];
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return districts;
|
|
748
|
+
}, [filteredCityData, abstractedPathLookup, abstractedPathsSet]);
|
|
749
|
+
// Memoize visible buildings - only recalculates when data or abstraction changes, NOT on hover
|
|
750
|
+
const visibleBuildingsMemo = (0, react_1.useMemo)(() => {
|
|
751
|
+
if (!filteredCityData)
|
|
752
|
+
return [];
|
|
753
|
+
return abstractedPathLookup.size > 0
|
|
754
|
+
? filteredCityData.buildings.filter(building => {
|
|
755
|
+
return !abstractedPathLookup.isPathAbstracted(building.path);
|
|
756
|
+
})
|
|
757
|
+
: filteredCityData.buildings;
|
|
758
|
+
}, [filteredCityData, abstractedPathLookup]);
|
|
696
759
|
// Update hit test cache when geometry or abstraction changes
|
|
697
760
|
// Note: We don't depend on zoomState here because:
|
|
698
761
|
// 1. The spatial grid only depends on buildings/districts and abstraction
|
|
@@ -762,64 +825,12 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
|
|
|
762
825
|
y: ((z - coordinateSystemData.bounds.minZ) * scale + offsetZ) * zoomState.scale +
|
|
763
826
|
zoomState.offsetY,
|
|
764
827
|
});
|
|
765
|
-
//
|
|
766
|
-
const abstractedPathsForDistricts = new Set();
|
|
767
|
-
const abstractionLayerForDistricts = allLayers.find(l => l.id === 'directory-abstraction');
|
|
768
|
-
if (abstractionLayerForDistricts && abstractionLayerForDistricts.enabled) {
|
|
769
|
-
abstractionLayerForDistricts.items.forEach(item => {
|
|
770
|
-
if (item.type === 'directory') {
|
|
771
|
-
abstractedPathsForDistricts.add(item.path);
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
// Create PathHierarchyLookup for O(depth) containment checks
|
|
776
|
-
const pathLookup = new PathHierarchyLookup(abstractedPathsForDistricts);
|
|
777
|
-
// Keep abstracted districts (for covers) but filter out their children
|
|
778
|
-
let visibleDistricts = pathLookup.size > 0
|
|
779
|
-
? filteredCityData.districts.filter(district => {
|
|
780
|
-
// Check for root abstraction first
|
|
781
|
-
if (pathLookup.has('')) {
|
|
782
|
-
// If root is abstracted, only show root district
|
|
783
|
-
return !district.path || district.path === '';
|
|
784
|
-
}
|
|
785
|
-
if (!district.path)
|
|
786
|
-
return true; // Keep root
|
|
787
|
-
// Keep the abstracted district itself (we need it for the cover)
|
|
788
|
-
if (pathLookup.has(district.path)) {
|
|
789
|
-
return true;
|
|
790
|
-
}
|
|
791
|
-
// Filter out children of abstracted directories using O(depth) lookup
|
|
792
|
-
return !pathLookup.isChildOfAbstracted(district.path);
|
|
793
|
-
})
|
|
794
|
-
: filteredCityData.districts;
|
|
795
|
-
// If root is abstracted and there's no root district, create one for the cover
|
|
796
|
-
if (abstractedPathsForDistricts.has('')) {
|
|
797
|
-
const hasRootDistrict = visibleDistricts.some(d => !d.path || d.path === '');
|
|
798
|
-
if (!hasRootDistrict) {
|
|
799
|
-
visibleDistricts = [
|
|
800
|
-
{
|
|
801
|
-
path: '',
|
|
802
|
-
worldBounds: filteredCityData.bounds,
|
|
803
|
-
fileCount: filteredCityData.buildings.length, // Total file count
|
|
804
|
-
type: 'directory',
|
|
805
|
-
},
|
|
806
|
-
...visibleDistricts,
|
|
807
|
-
];
|
|
808
|
-
}
|
|
809
|
-
}
|
|
828
|
+
// Use memoized visible districts and buildings (pre-filtered, doesn't recalculate on hover)
|
|
810
829
|
// Draw districts with layer support
|
|
811
|
-
(0, drawLayeredBuildings_1.drawLayeredDistricts)(ctx,
|
|
830
|
+
(0, drawLayeredBuildings_1.drawLayeredDistricts)(ctx, visibleDistrictsMemo, worldToCanvas, scale * zoomState.scale, allLayers, interactionState.hoveredDistrict, fullSize, resolvedDefaultDirectoryColor, filteredCityData.metadata.layoutConfig, abstractedPathsSet, // Pass abstracted paths to skip labels
|
|
812
831
|
showDirectoryLabels, districtBorderRadius);
|
|
813
|
-
// Filter out buildings that are in abstracted directories
|
|
814
|
-
// Reuse the pathLookup created earlier for O(depth) containment checks
|
|
815
|
-
const visibleBuildings = pathLookup.size > 0
|
|
816
|
-
? filteredCityData.buildings.filter(building => {
|
|
817
|
-
// Use PathHierarchyLookup for efficient O(depth) check
|
|
818
|
-
return !pathLookup.isPathAbstracted(building.path);
|
|
819
|
-
})
|
|
820
|
-
: filteredCityData.buildings;
|
|
821
832
|
// Draw buildings with layer support
|
|
822
|
-
(0, drawLayeredBuildings_1.drawLayeredBuildings)(ctx,
|
|
833
|
+
(0, drawLayeredBuildings_1.drawLayeredBuildings)(ctx, visibleBuildingsMemo, worldToCanvas, scale * zoomState.scale, allLayers, interactionState.hoveredBuilding, resolvedDefaultBuildingColor, showFileNames, resolvedHoverBorderColor, disableOpacityDimming, showFileTypeIcons, buildingBorderRadius);
|
|
823
834
|
// Performance monitoring end available for debugging
|
|
824
835
|
// Performance stats available but not logged to reduce console noise
|
|
825
836
|
// Uncomment for debugging: render time, buildings/districts counts, layer counts
|
|
@@ -848,6 +859,10 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
|
|
|
848
859
|
resolvedDefaultBuildingColor,
|
|
849
860
|
districtBorderRadius,
|
|
850
861
|
showFileTypeIcons,
|
|
862
|
+
// Memoized values for performance (don't recalculate on hover)
|
|
863
|
+
visibleDistrictsMemo,
|
|
864
|
+
visibleBuildingsMemo,
|
|
865
|
+
abstractedPathsSet,
|
|
851
866
|
]);
|
|
852
867
|
// Optimized hit testing
|
|
853
868
|
const performHitTest = (0, react_1.useCallback)((canvasX, canvasY) => {
|
|
@@ -974,6 +989,12 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
|
|
|
974
989
|
const handleMouseMoveInternal = (0, react_1.useCallback)((e) => {
|
|
975
990
|
if (!canvasRef.current || !containerRef.current || !filteredCityData || zoomState.isDragging)
|
|
976
991
|
return;
|
|
992
|
+
// Throttle hover updates to improve performance with large datasets
|
|
993
|
+
const now = performance.now();
|
|
994
|
+
if (now - lastHoverUpdateRef.current < HOVER_THROTTLE_MS) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
lastHoverUpdateRef.current = now;
|
|
977
998
|
// Get the container rect for mouse position
|
|
978
999
|
const containerRect = containerRef.current.getBoundingClientRect();
|
|
979
1000
|
// Get mouse position relative to the container
|
package/package.json
CHANGED
|
@@ -358,6 +358,10 @@ function ArchitectureMapHighlightLayersInner({
|
|
|
358
358
|
// Track the last zoomToPath to detect changes
|
|
359
359
|
const lastZoomToPathRef = useRef<string | null>(null);
|
|
360
360
|
|
|
361
|
+
// Throttle ref for hover updates (improves performance with large datasets)
|
|
362
|
+
const lastHoverUpdateRef = useRef<number>(0);
|
|
363
|
+
const HOVER_THROTTLE_MS = 16; // ~60fps max for hover updates
|
|
364
|
+
|
|
361
365
|
useEffect(() => {
|
|
362
366
|
// Reset user interaction state when enableZoom is disabled
|
|
363
367
|
if (!enableZoom) {
|
|
@@ -1003,6 +1007,70 @@ function ArchitectureMapHighlightLayersInner({
|
|
|
1003
1007
|
return layers;
|
|
1004
1008
|
}, [stableLayers, dynamicLayers, abstractionLayer]);
|
|
1005
1009
|
|
|
1010
|
+
// Memoize abstracted paths lookup - only recalculates when abstraction layer changes
|
|
1011
|
+
const { abstractedPathsSet, abstractedPathLookup } = useMemo(() => {
|
|
1012
|
+
const pathsSet = new Set<string>();
|
|
1013
|
+
const abstractionLayerDef = allLayers.find(l => l.id === 'directory-abstraction');
|
|
1014
|
+
if (abstractionLayerDef && abstractionLayerDef.enabled) {
|
|
1015
|
+
abstractionLayerDef.items.forEach(item => {
|
|
1016
|
+
if (item.type === 'directory') {
|
|
1017
|
+
pathsSet.add(item.path);
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
return {
|
|
1022
|
+
abstractedPathsSet: pathsSet,
|
|
1023
|
+
abstractedPathLookup: new PathHierarchyLookup(pathsSet),
|
|
1024
|
+
};
|
|
1025
|
+
}, [allLayers]);
|
|
1026
|
+
|
|
1027
|
+
// Memoize visible districts - only recalculates when data or abstraction changes, NOT on hover
|
|
1028
|
+
const visibleDistrictsMemo = useMemo(() => {
|
|
1029
|
+
if (!filteredCityData) return [];
|
|
1030
|
+
|
|
1031
|
+
let districts =
|
|
1032
|
+
abstractedPathLookup.size > 0
|
|
1033
|
+
? filteredCityData.districts.filter(district => {
|
|
1034
|
+
// Check for root abstraction first
|
|
1035
|
+
if (abstractedPathLookup.has('')) {
|
|
1036
|
+
return !district.path || district.path === '';
|
|
1037
|
+
}
|
|
1038
|
+
if (!district.path) return true;
|
|
1039
|
+
if (abstractedPathLookup.has(district.path)) return true;
|
|
1040
|
+
return !abstractedPathLookup.isChildOfAbstracted(district.path);
|
|
1041
|
+
})
|
|
1042
|
+
: filteredCityData.districts;
|
|
1043
|
+
|
|
1044
|
+
// If root is abstracted and there's no root district, create one for the cover
|
|
1045
|
+
if (abstractedPathsSet.has('')) {
|
|
1046
|
+
const hasRootDistrict = districts.some(d => !d.path || d.path === '');
|
|
1047
|
+
if (!hasRootDistrict) {
|
|
1048
|
+
districts = [
|
|
1049
|
+
{
|
|
1050
|
+
path: '',
|
|
1051
|
+
worldBounds: filteredCityData.bounds,
|
|
1052
|
+
fileCount: filteredCityData.buildings.length,
|
|
1053
|
+
type: 'directory' as const,
|
|
1054
|
+
},
|
|
1055
|
+
...districts,
|
|
1056
|
+
];
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
return districts;
|
|
1061
|
+
}, [filteredCityData, abstractedPathLookup, abstractedPathsSet]);
|
|
1062
|
+
|
|
1063
|
+
// Memoize visible buildings - only recalculates when data or abstraction changes, NOT on hover
|
|
1064
|
+
const visibleBuildingsMemo = useMemo(() => {
|
|
1065
|
+
if (!filteredCityData) return [];
|
|
1066
|
+
|
|
1067
|
+
return abstractedPathLookup.size > 0
|
|
1068
|
+
? filteredCityData.buildings.filter(building => {
|
|
1069
|
+
return !abstractedPathLookup.isPathAbstracted(building.path);
|
|
1070
|
+
})
|
|
1071
|
+
: filteredCityData.buildings;
|
|
1072
|
+
}, [filteredCityData, abstractedPathLookup]);
|
|
1073
|
+
|
|
1006
1074
|
// Update hit test cache when geometry or abstraction changes
|
|
1007
1075
|
// Note: We don't depend on zoomState here because:
|
|
1008
1076
|
// 1. The spatial grid only depends on buildings/districts and abstraction
|
|
@@ -1106,63 +1174,11 @@ function ArchitectureMapHighlightLayersInner({
|
|
|
1106
1174
|
zoomState.offsetY,
|
|
1107
1175
|
});
|
|
1108
1176
|
|
|
1109
|
-
//
|
|
1110
|
-
const abstractedPathsForDistricts = new Set<string>();
|
|
1111
|
-
const abstractionLayerForDistricts = allLayers.find(l => l.id === 'directory-abstraction');
|
|
1112
|
-
if (abstractionLayerForDistricts && abstractionLayerForDistricts.enabled) {
|
|
1113
|
-
abstractionLayerForDistricts.items.forEach(item => {
|
|
1114
|
-
if (item.type === 'directory') {
|
|
1115
|
-
abstractedPathsForDistricts.add(item.path);
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
// Create PathHierarchyLookup for O(depth) containment checks
|
|
1121
|
-
const pathLookup = new PathHierarchyLookup(abstractedPathsForDistricts);
|
|
1122
|
-
|
|
1123
|
-
// Keep abstracted districts (for covers) but filter out their children
|
|
1124
|
-
let visibleDistricts =
|
|
1125
|
-
pathLookup.size > 0
|
|
1126
|
-
? filteredCityData.districts.filter(district => {
|
|
1127
|
-
// Check for root abstraction first
|
|
1128
|
-
if (pathLookup.has('')) {
|
|
1129
|
-
// If root is abstracted, only show root district
|
|
1130
|
-
return !district.path || district.path === '';
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
if (!district.path) return true; // Keep root
|
|
1134
|
-
|
|
1135
|
-
// Keep the abstracted district itself (we need it for the cover)
|
|
1136
|
-
if (pathLookup.has(district.path)) {
|
|
1137
|
-
return true;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
// Filter out children of abstracted directories using O(depth) lookup
|
|
1141
|
-
return !pathLookup.isChildOfAbstracted(district.path);
|
|
1142
|
-
})
|
|
1143
|
-
: filteredCityData.districts;
|
|
1144
|
-
|
|
1145
|
-
// If root is abstracted and there's no root district, create one for the cover
|
|
1146
|
-
if (abstractedPathsForDistricts.has('')) {
|
|
1147
|
-
const hasRootDistrict = visibleDistricts.some(d => !d.path || d.path === '');
|
|
1148
|
-
|
|
1149
|
-
if (!hasRootDistrict) {
|
|
1150
|
-
visibleDistricts = [
|
|
1151
|
-
{
|
|
1152
|
-
path: '',
|
|
1153
|
-
worldBounds: filteredCityData.bounds,
|
|
1154
|
-
fileCount: filteredCityData.buildings.length, // Total file count
|
|
1155
|
-
type: 'directory',
|
|
1156
|
-
},
|
|
1157
|
-
...visibleDistricts,
|
|
1158
|
-
];
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1177
|
+
// Use memoized visible districts and buildings (pre-filtered, doesn't recalculate on hover)
|
|
1162
1178
|
// Draw districts with layer support
|
|
1163
1179
|
drawLayeredDistricts(
|
|
1164
1180
|
ctx,
|
|
1165
|
-
|
|
1181
|
+
visibleDistrictsMemo,
|
|
1166
1182
|
worldToCanvas,
|
|
1167
1183
|
scale * zoomState.scale,
|
|
1168
1184
|
allLayers,
|
|
@@ -1170,25 +1186,15 @@ function ArchitectureMapHighlightLayersInner({
|
|
|
1170
1186
|
fullSize,
|
|
1171
1187
|
resolvedDefaultDirectoryColor,
|
|
1172
1188
|
filteredCityData.metadata.layoutConfig,
|
|
1173
|
-
|
|
1189
|
+
abstractedPathsSet, // Pass abstracted paths to skip labels
|
|
1174
1190
|
showDirectoryLabels,
|
|
1175
1191
|
districtBorderRadius,
|
|
1176
1192
|
);
|
|
1177
1193
|
|
|
1178
|
-
// Filter out buildings that are in abstracted directories
|
|
1179
|
-
// Reuse the pathLookup created earlier for O(depth) containment checks
|
|
1180
|
-
const visibleBuildings =
|
|
1181
|
-
pathLookup.size > 0
|
|
1182
|
-
? filteredCityData.buildings.filter(building => {
|
|
1183
|
-
// Use PathHierarchyLookup for efficient O(depth) check
|
|
1184
|
-
return !pathLookup.isPathAbstracted(building.path);
|
|
1185
|
-
})
|
|
1186
|
-
: filteredCityData.buildings;
|
|
1187
|
-
|
|
1188
1194
|
// Draw buildings with layer support
|
|
1189
1195
|
drawLayeredBuildings(
|
|
1190
1196
|
ctx,
|
|
1191
|
-
|
|
1197
|
+
visibleBuildingsMemo,
|
|
1192
1198
|
worldToCanvas,
|
|
1193
1199
|
scale * zoomState.scale,
|
|
1194
1200
|
allLayers,
|
|
@@ -1229,6 +1235,10 @@ function ArchitectureMapHighlightLayersInner({
|
|
|
1229
1235
|
resolvedDefaultBuildingColor,
|
|
1230
1236
|
districtBorderRadius,
|
|
1231
1237
|
showFileTypeIcons,
|
|
1238
|
+
// Memoized values for performance (don't recalculate on hover)
|
|
1239
|
+
visibleDistrictsMemo,
|
|
1240
|
+
visibleBuildingsMemo,
|
|
1241
|
+
abstractedPathsSet,
|
|
1232
1242
|
]);
|
|
1233
1243
|
|
|
1234
1244
|
// Optimized hit testing
|
|
@@ -1406,6 +1416,13 @@ function ArchitectureMapHighlightLayersInner({
|
|
|
1406
1416
|
if (!canvasRef.current || !containerRef.current || !filteredCityData || zoomState.isDragging)
|
|
1407
1417
|
return;
|
|
1408
1418
|
|
|
1419
|
+
// Throttle hover updates to improve performance with large datasets
|
|
1420
|
+
const now = performance.now();
|
|
1421
|
+
if (now - lastHoverUpdateRef.current < HOVER_THROTTLE_MS) {
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
lastHoverUpdateRef.current = now;
|
|
1425
|
+
|
|
1409
1426
|
// Get the container rect for mouse position
|
|
1410
1427
|
const containerRect = containerRef.current.getBoundingClientRect();
|
|
1411
1428
|
// Get mouse position relative to the container
|