@principal-ai/file-city-react 0.4.3 → 0.4.4

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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"}
@@ -696,6 +696,8 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
696
696
  }
697
697
  return layers;
698
698
  }, [stableLayers, dynamicLayers, abstractionLayer]);
699
+ // Memoize layer index for O(1) path lookups - only rebuilds when layers change
700
+ const layerIndex = (0, react_1.useMemo)(() => new drawLayeredBuildings_1.LayerIndex(allLayers), [allLayers]);
699
701
  // Memoize abstracted paths lookup - only recalculates when abstraction layer changes
700
702
  const { abstractedPathsSet, abstractedPathLookup } = (0, react_1.useMemo)(() => {
701
703
  const pathsSet = new Set();
@@ -828,9 +830,9 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
828
830
  // Use memoized visible districts and buildings (pre-filtered, doesn't recalculate on hover)
829
831
  // Draw districts with layer support
830
832
  (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
831
- showDirectoryLabels, districtBorderRadius);
833
+ showDirectoryLabels, districtBorderRadius, layerIndex);
832
834
  // Draw buildings with layer support
833
- (0, drawLayeredBuildings_1.drawLayeredBuildings)(ctx, visibleBuildingsMemo, worldToCanvas, scale * zoomState.scale, allLayers, interactionState.hoveredBuilding, resolvedDefaultBuildingColor, showFileNames, resolvedHoverBorderColor, disableOpacityDimming, showFileTypeIcons, buildingBorderRadius);
835
+ (0, drawLayeredBuildings_1.drawLayeredBuildings)(ctx, visibleBuildingsMemo, worldToCanvas, scale * zoomState.scale, allLayers, interactionState.hoveredBuilding, resolvedDefaultBuildingColor, showFileNames, resolvedHoverBorderColor, disableOpacityDimming, showFileTypeIcons, buildingBorderRadius, layerIndex);
834
836
  // Performance monitoring end available for debugging
835
837
  // Performance stats available but not logged to reduce console noise
836
838
  // Uncomment for debugging: render time, buildings/districts counts, layer counts
@@ -863,6 +865,7 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
863
865
  visibleDistrictsMemo,
864
866
  visibleBuildingsMemo,
865
867
  abstractedPathsSet,
868
+ layerIndex,
866
869
  ]);
867
870
  // Optimized hit testing
868
871
  const performHitTest = (0, react_1.useCallback)((canvasX, canvasY) => {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { ArchitectureMapHighlightLayers, type ArchitectureMapHighlightLayersProps, } from './components/ArchitectureMapHighlightLayers';
2
- export { type LayerRenderStrategy, type LayerItem, type HighlightLayer, } from './render/client/drawLayeredBuildings';
2
+ export { type LayerRenderStrategy, type LayerItem, type HighlightLayer, LayerIndex, } from './render/client/drawLayeredBuildings';
3
3
  export { type MapInteractionState, type MapDisplayOptions } from './types/react-types';
4
4
  export { filterCityDataForSelectiveRender, filterCityDataForSubdirectory, filterCityDataForMultipleDirectories, } from './builder/cityDataUtils';
5
5
  export { createFileColorHighlightLayers, getDefaultFileColorConfig, getFileColorMapping, } from './utils/fileColorHighlightLayers';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,8BAA8B,EAC9B,KAAK,mCAAmC,GACzC,MAAM,6CAA6C,CAAC;AAGrD,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,SAAS,EACd,KAAK,cAAc,GACpB,MAAM,sCAAsC,CAAC;AAG9C,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGvF,OAAO,EACL,gCAAgC,EAChC,6BAA6B,EAC7B,oCAAoC,GACrC,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,kCAAkC,CAAC;AAG1C,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,QAAQ,EACR,UAAU,GACX,MAAM,iCAAiC,CAAC;AAGzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAG1E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAG7F,YAAY,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAGhE,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,8BAA8B,EAC9B,KAAK,mCAAmC,GACzC,MAAM,6CAA6C,CAAC;AAGrD,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,UAAU,GACX,MAAM,sCAAsC,CAAC;AAG9C,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGvF,OAAO,EACL,gCAAgC,EAChC,6BAA6B,EAC7B,oCAAoC,GACrC,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,kCAAkC,CAAC;AAG1C,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,QAAQ,EACR,UAAU,GACX,MAAM,iCAAiC,CAAC;AAGzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAG1E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAG7F,YAAY,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAGhE,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useTheme = exports.ThemeProvider = exports.CityViewWithReactFlow = exports.useCodeCityData = exports.MultiVersionCityBuilder = exports.getFileColorMapping = exports.getDefaultFileColorConfig = exports.createFileColorHighlightLayers = exports.filterCityDataForMultipleDirectories = exports.filterCityDataForSubdirectory = exports.filterCityDataForSelectiveRender = exports.ArchitectureMapHighlightLayers = void 0;
3
+ exports.useTheme = exports.ThemeProvider = exports.CityViewWithReactFlow = exports.useCodeCityData = exports.MultiVersionCityBuilder = exports.getFileColorMapping = exports.getDefaultFileColorConfig = exports.createFileColorHighlightLayers = exports.filterCityDataForMultipleDirectories = exports.filterCityDataForSubdirectory = exports.filterCityDataForSelectiveRender = exports.LayerIndex = exports.ArchitectureMapHighlightLayers = void 0;
4
4
  // Main component export
5
5
  var ArchitectureMapHighlightLayers_1 = require("./components/ArchitectureMapHighlightLayers");
6
6
  Object.defineProperty(exports, "ArchitectureMapHighlightLayers", { enumerable: true, get: function () { return ArchitectureMapHighlightLayers_1.ArchitectureMapHighlightLayers; } });
7
+ // Layer and rendering types
8
+ var drawLayeredBuildings_1 = require("./render/client/drawLayeredBuildings");
9
+ Object.defineProperty(exports, "LayerIndex", { enumerable: true, get: function () { return drawLayeredBuildings_1.LayerIndex; } });
7
10
  // Utility functions
8
11
  var cityDataUtils_1 = require("./builder/cityDataUtils");
9
12
  Object.defineProperty(exports, "filterCityDataForSelectiveRender", { enumerable: true, get: function () { return cityDataUtils_1.filterCityDataForSelectiveRender; } });
@@ -32,6 +32,26 @@ export interface HighlightLayer {
32
32
  items: LayerItem[];
33
33
  dynamic?: boolean;
34
34
  }
35
+ /**
36
+ * LayerIndex provides O(1) path lookups instead of O(n) iteration.
37
+ * This dramatically improves performance with large numbers of layer items.
38
+ */
39
+ export declare class LayerIndex {
40
+ private exactIndex;
41
+ private directoryPaths;
42
+ private sortedCache;
43
+ constructor(layers: HighlightLayer[]);
44
+ private buildIndex;
45
+ /**
46
+ * Get all layer items that apply to a given path.
47
+ * For 'exact' mode: only matches the exact path.
48
+ * For 'children' mode: matches exact path OR parent directories that contain this path.
49
+ */
50
+ getItemsForPath(path: string, checkType?: 'exact' | 'children'): Array<{
51
+ layer: HighlightLayer;
52
+ item: LayerItem;
53
+ }>;
54
+ }
35
55
  export declare function drawGrid(ctx: CanvasRenderingContext2D, width: number, height: number, gridSize: number): void;
36
56
  export declare function drawLayeredDistricts(ctx: CanvasRenderingContext2D, districts: CityDistrict[], worldToCanvas: (x: number, z: number) => {
37
57
  x: number;
@@ -43,9 +63,11 @@ layers: HighlightLayer[], hoveredDistrict?: CityDistrict | null, fullSize?: bool
43
63
  paddingLeft: number;
44
64
  paddingRight: number;
45
65
  }, abstractedPaths?: Set<string>, // Paths of directories that are abstracted (have covers)
46
- showDirectoryLabels?: boolean, borderRadius?: number): void;
66
+ showDirectoryLabels?: boolean, borderRadius?: number, // Border radius for districts (default: sharp corners)
67
+ layerIndex?: LayerIndex): void;
47
68
  export declare function drawLayeredBuildings(ctx: CanvasRenderingContext2D, buildings: CityBuilding[], worldToCanvas: (x: number, z: number) => {
48
69
  x: number;
49
70
  y: number;
50
- }, scale: number, layers: HighlightLayer[], hoveredBuilding?: CityBuilding | null, defaultBuildingColor?: string, showFileNames?: boolean, hoverBorderColor?: string, disableOpacityDimming?: boolean, showFileTypeIcons?: boolean, borderRadius?: number): void;
71
+ }, scale: number, layers: HighlightLayer[], hoveredBuilding?: CityBuilding | null, defaultBuildingColor?: string, showFileNames?: boolean, hoverBorderColor?: string, disableOpacityDimming?: boolean, showFileTypeIcons?: boolean, borderRadius?: number, // Border radius for buildings (default: 0 - sharp corners)
72
+ layerIndex?: LayerIndex): void;
51
73
  //# sourceMappingURL=drawLayeredBuildings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"drawLayeredBuildings.d.ts","sourceRoot":"","sources":["../../../src/render/client/drawLayeredBuildings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAG7E,MAAM,MAAM,mBAAmB,GAC3B,QAAQ,GACR,MAAM,GACN,MAAM,GACN,SAAS,GACT,OAAO,GACP,MAAM,GACN,QAAQ,CAAC;AAEb,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,cAAc,CAAC,EAAE,mBAAmB,CAAC;IAErC,YAAY,CAAC,EAAE;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IAEF,YAAY,CAAC,EAAE,CACb,GAAG,EAAE,wBAAwB,EAC7B,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAC/D,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;CACX;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,EAAE,CAAC;IAEnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwED,wBAAgB,QAAQ,CACtB,GAAG,EAAE,wBAAwB,EAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,QAqBjB;AAiVD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,wBAAwB,EAC7B,SAAS,EAAE,YAAY,EAAE,EACzB,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACjE,KAAK,EAAE,MAAM,EAAE,wDAAwD;AACvE,MAAM,EAAE,cAAc,EAAE,EACxB,eAAe,CAAC,EAAE,YAAY,GAAG,IAAI,EACrC,QAAQ,CAAC,EAAE,OAAO,EAClB,qBAAqB,CAAC,EAAE,MAAM,EAC9B,YAAY,CAAC,EAAE;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,EACD,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,yDAAyD;AACxF,mBAAmB,GAAE,OAAc,EACnC,YAAY,GAAE,MAAU,QA6PzB;AA4CD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,wBAAwB,EAC7B,SAAS,EAAE,YAAY,EAAE,EACzB,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACjE,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,EAAE,EACxB,eAAe,CAAC,EAAE,YAAY,GAAG,IAAI,EACrC,oBAAoB,CAAC,EAAE,MAAM,EAC7B,aAAa,CAAC,EAAE,OAAO,EACvB,gBAAgB,CAAC,EAAE,MAAM,EACzB,qBAAqB,CAAC,EAAE,OAAO,EAC/B,iBAAiB,CAAC,EAAE,OAAO,EAC3B,YAAY,GAAE,MAAU,QA2IzB"}
1
+ {"version":3,"file":"drawLayeredBuildings.d.ts","sourceRoot":"","sources":["../../../src/render/client/drawLayeredBuildings.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LayerIndex = void 0;
3
4
  exports.drawGrid = drawGrid;
4
5
  exports.drawLayeredDistricts = drawLayeredDistricts;
5
6
  exports.drawLayeredBuildings = drawLayeredBuildings;
@@ -20,6 +21,87 @@ function pathMatchesItem(path, item, checkType = 'children') {
20
21
  }
21
22
  }
22
23
  }
24
+ /**
25
+ * LayerIndex provides O(1) path lookups instead of O(n) iteration.
26
+ * This dramatically improves performance with large numbers of layer items.
27
+ */
28
+ class LayerIndex {
29
+ constructor(layers) {
30
+ // Map from exact path to layer items (for file items)
31
+ this.exactIndex = new Map();
32
+ // Sorted list of directory paths for prefix matching
33
+ this.directoryPaths = [];
34
+ // Cache for sorted results by priority
35
+ this.sortedCache = new Map();
36
+ this.buildIndex(layers);
37
+ }
38
+ buildIndex(layers) {
39
+ for (const layer of layers) {
40
+ if (!layer.enabled)
41
+ continue;
42
+ for (const item of layer.items) {
43
+ const entry = { layer, item };
44
+ if (item.type === 'file') {
45
+ // File items: exact match only
46
+ const existing = this.exactIndex.get(item.path);
47
+ if (existing) {
48
+ existing.push(entry);
49
+ }
50
+ else {
51
+ this.exactIndex.set(item.path, [entry]);
52
+ }
53
+ }
54
+ else {
55
+ // Directory items: need prefix matching
56
+ this.directoryPaths.push({ path: item.path, layer, item });
57
+ }
58
+ }
59
+ }
60
+ // Sort directory paths by length (longest first) for correct matching
61
+ this.directoryPaths.sort((a, b) => b.path.length - a.path.length);
62
+ }
63
+ /**
64
+ * Get all layer items that apply to a given path.
65
+ * For 'exact' mode: only matches the exact path.
66
+ * For 'children' mode: matches exact path OR parent directories that contain this path.
67
+ */
68
+ getItemsForPath(path, checkType = 'children') {
69
+ // Check cache first
70
+ const cacheKey = `${path}:${checkType}`;
71
+ const cached = this.sortedCache.get(cacheKey);
72
+ if (cached)
73
+ return cached;
74
+ const matches = [];
75
+ // Check exact matches (file items)
76
+ const exactMatches = this.exactIndex.get(path);
77
+ if (exactMatches) {
78
+ matches.push(...exactMatches);
79
+ }
80
+ // Check directory matches
81
+ if (checkType === 'exact') {
82
+ // For exact mode, only match directories with exact path
83
+ for (const dir of this.directoryPaths) {
84
+ if (dir.path === path) {
85
+ matches.push({ layer: dir.layer, item: dir.item });
86
+ }
87
+ }
88
+ }
89
+ else {
90
+ // For children mode, check if path is inside any directory
91
+ for (const dir of this.directoryPaths) {
92
+ if (path === dir.path || path.startsWith(dir.path + '/')) {
93
+ matches.push({ layer: dir.layer, item: dir.item });
94
+ }
95
+ }
96
+ }
97
+ // Sort by priority (highest first)
98
+ const sorted = matches.sort((a, b) => b.layer.priority - a.layer.priority);
99
+ // Cache the result
100
+ this.sortedCache.set(cacheKey, sorted);
101
+ return sorted;
102
+ }
103
+ }
104
+ exports.LayerIndex = LayerIndex;
23
105
  // Helper function to draw rounded rectangles
24
106
  function drawRoundedRect(ctx, x, y, width, height, radius, fill, stroke) {
25
107
  ctx.beginPath();
@@ -339,12 +421,15 @@ function applyLayerRendering(ctx, bounds, layer, item, scale, borderRadius = 0)
339
421
  // Draw districts with layer support
340
422
  function drawLayeredDistricts(ctx, districts, worldToCanvas, scale, // This includes the zoom scale for text proportionality
341
423
  layers, hoveredDistrict, fullSize, defaultDirectoryColor, layoutConfig, abstractedPaths, // Paths of directories that are abstracted (have covers)
342
- showDirectoryLabels = true, borderRadius = 0) {
424
+ showDirectoryLabels = true, borderRadius = 0, // Border radius for districts (default: sharp corners)
425
+ layerIndex) {
426
+ // Build index once for all districts (O(n) instead of O(n²))
427
+ const index = layerIndex || new LayerIndex(layers);
343
428
  districts.forEach(district => {
344
429
  const districtPath = district.path || '';
345
430
  const isRoot = !districtPath || districtPath === '';
346
431
  // Check if this root district has layer matches (like covers) - if so, render it
347
- const rootLayerMatches = getLayerItemsForPath(districtPath, layers, 'exact');
432
+ const rootLayerMatches = index.getItemsForPath(districtPath, 'exact');
348
433
  const hasLayerRendering = rootLayerMatches.length > 0;
349
434
  // Skip root districts unless they have layer rendering (covers, highlights, etc.)
350
435
  if (isRoot && !hasLayerRendering)
@@ -371,7 +456,7 @@ showDirectoryLabels = true, borderRadius = 0) {
371
456
  let opacity = 0.3;
372
457
  let borderOpacity = 0.6;
373
458
  // Check if district has layer highlighting - use exact matching for districts
374
- const layerMatches = getLayerItemsForPath(districtPath, layers, 'exact');
459
+ const layerMatches = index.getItemsForPath(districtPath, 'exact');
375
460
  const hasLayerHighlight = layerMatches.length > 0;
376
461
  if (hasLayerHighlight) {
377
462
  opacity = 0.5;
@@ -551,7 +636,10 @@ function isReactFile(fileExtension) {
551
636
  return ext === '.jsx' || ext === '.tsx';
552
637
  }
553
638
  // Draw buildings with layer support
554
- function drawLayeredBuildings(ctx, buildings, worldToCanvas, scale, layers, hoveredBuilding, defaultBuildingColor, showFileNames, hoverBorderColor, disableOpacityDimming, showFileTypeIcons, borderRadius = 0) {
639
+ function drawLayeredBuildings(ctx, buildings, worldToCanvas, scale, layers, hoveredBuilding, defaultBuildingColor, showFileNames, hoverBorderColor, disableOpacityDimming, showFileTypeIcons, borderRadius = 0, // Border radius for buildings (default: 0 - sharp corners)
640
+ layerIndex) {
641
+ // Build index once for all buildings (O(n) instead of O(n²))
642
+ const index = layerIndex || new LayerIndex(layers);
555
643
  buildings.forEach(building => {
556
644
  const pos = worldToCanvas(building.position.x, building.position.z);
557
645
  // Calculate building dimensions
@@ -572,7 +660,7 @@ function drawLayeredBuildings(ctx, buildings, worldToCanvas, scale, layers, hove
572
660
  height: height,
573
661
  };
574
662
  // Get layer matches for this building - only check file items, not parent directories
575
- const layerMatches = getLayerItemsForPath(building.path, layers).filter(match => match.item.type === 'file'); // Only apply file-specific highlights to buildings
663
+ const layerMatches = index.getItemsForPath(building.path).filter(match => match.item.type === 'file'); // Only apply file-specific highlights to buildings
576
664
  const hasLayerHighlight = layerMatches.length > 0;
577
665
  const isHovered = hoveredBuilding === building;
578
666
  // Building color
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/file-city-react",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "React components for File City visualization",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,6 +12,7 @@ import {
12
12
  drawGrid,
13
13
  HighlightLayer,
14
14
  LayerItem,
15
+ LayerIndex,
15
16
  } from '../render/client/drawLayeredBuildings';
16
17
  import {
17
18
  CityData,
@@ -1007,6 +1008,9 @@ function ArchitectureMapHighlightLayersInner({
1007
1008
  return layers;
1008
1009
  }, [stableLayers, dynamicLayers, abstractionLayer]);
1009
1010
 
1011
+ // Memoize layer index for O(1) path lookups - only rebuilds when layers change
1012
+ const layerIndex = useMemo(() => new LayerIndex(allLayers), [allLayers]);
1013
+
1010
1014
  // Memoize abstracted paths lookup - only recalculates when abstraction layer changes
1011
1015
  const { abstractedPathsSet, abstractedPathLookup } = useMemo(() => {
1012
1016
  const pathsSet = new Set<string>();
@@ -1189,6 +1193,7 @@ function ArchitectureMapHighlightLayersInner({
1189
1193
  abstractedPathsSet, // Pass abstracted paths to skip labels
1190
1194
  showDirectoryLabels,
1191
1195
  districtBorderRadius,
1196
+ layerIndex, // Pre-built index for O(1) lookups
1192
1197
  );
1193
1198
 
1194
1199
  // Draw buildings with layer support
@@ -1205,6 +1210,7 @@ function ArchitectureMapHighlightLayersInner({
1205
1210
  disableOpacityDimming,
1206
1211
  showFileTypeIcons,
1207
1212
  buildingBorderRadius,
1213
+ layerIndex, // Pre-built index for O(1) lookups
1208
1214
  );
1209
1215
 
1210
1216
  // Performance monitoring end available for debugging
@@ -1239,6 +1245,7 @@ function ArchitectureMapHighlightLayersInner({
1239
1245
  visibleDistrictsMemo,
1240
1246
  visibleBuildingsMemo,
1241
1247
  abstractedPathsSet,
1248
+ layerIndex,
1242
1249
  ]);
1243
1250
 
1244
1251
  // Optimized hit testing
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  type LayerRenderStrategy,
10
10
  type LayerItem,
11
11
  type HighlightLayer,
12
+ LayerIndex,
12
13
  } from './render/client/drawLayeredBuildings';
13
14
 
14
15
  // React-specific types
@@ -66,6 +66,97 @@ function pathMatchesItem(
66
66
  }
67
67
  }
68
68
 
69
+ /**
70
+ * LayerIndex provides O(1) path lookups instead of O(n) iteration.
71
+ * This dramatically improves performance with large numbers of layer items.
72
+ */
73
+ export class LayerIndex {
74
+ // Map from exact path to layer items (for file items)
75
+ private exactIndex: Map<string, Array<{ layer: HighlightLayer; item: LayerItem }>> = new Map();
76
+ // Sorted list of directory paths for prefix matching
77
+ private directoryPaths: Array<{ path: string; layer: HighlightLayer; item: LayerItem }> = [];
78
+ // Cache for sorted results by priority
79
+ private sortedCache: Map<string, Array<{ layer: HighlightLayer; item: LayerItem }>> = new Map();
80
+
81
+ constructor(layers: HighlightLayer[]) {
82
+ this.buildIndex(layers);
83
+ }
84
+
85
+ private buildIndex(layers: HighlightLayer[]) {
86
+ for (const layer of layers) {
87
+ if (!layer.enabled) continue;
88
+
89
+ for (const item of layer.items) {
90
+ const entry = { layer, item };
91
+
92
+ if (item.type === 'file') {
93
+ // File items: exact match only
94
+ const existing = this.exactIndex.get(item.path);
95
+ if (existing) {
96
+ existing.push(entry);
97
+ } else {
98
+ this.exactIndex.set(item.path, [entry]);
99
+ }
100
+ } else {
101
+ // Directory items: need prefix matching
102
+ this.directoryPaths.push({ path: item.path, layer, item });
103
+ }
104
+ }
105
+ }
106
+
107
+ // Sort directory paths by length (longest first) for correct matching
108
+ this.directoryPaths.sort((a, b) => b.path.length - a.path.length);
109
+ }
110
+
111
+ /**
112
+ * Get all layer items that apply to a given path.
113
+ * For 'exact' mode: only matches the exact path.
114
+ * For 'children' mode: matches exact path OR parent directories that contain this path.
115
+ */
116
+ getItemsForPath(
117
+ path: string,
118
+ checkType: 'exact' | 'children' = 'children',
119
+ ): Array<{ layer: HighlightLayer; item: LayerItem }> {
120
+ // Check cache first
121
+ const cacheKey = `${path}:${checkType}`;
122
+ const cached = this.sortedCache.get(cacheKey);
123
+ if (cached) return cached;
124
+
125
+ const matches: Array<{ layer: HighlightLayer; item: LayerItem }> = [];
126
+
127
+ // Check exact matches (file items)
128
+ const exactMatches = this.exactIndex.get(path);
129
+ if (exactMatches) {
130
+ matches.push(...exactMatches);
131
+ }
132
+
133
+ // Check directory matches
134
+ if (checkType === 'exact') {
135
+ // For exact mode, only match directories with exact path
136
+ for (const dir of this.directoryPaths) {
137
+ if (dir.path === path) {
138
+ matches.push({ layer: dir.layer, item: dir.item });
139
+ }
140
+ }
141
+ } else {
142
+ // For children mode, check if path is inside any directory
143
+ for (const dir of this.directoryPaths) {
144
+ if (path === dir.path || path.startsWith(dir.path + '/')) {
145
+ matches.push({ layer: dir.layer, item: dir.item });
146
+ }
147
+ }
148
+ }
149
+
150
+ // Sort by priority (highest first)
151
+ const sorted = matches.sort((a, b) => b.layer.priority - a.layer.priority);
152
+
153
+ // Cache the result
154
+ this.sortedCache.set(cacheKey, sorted);
155
+
156
+ return sorted;
157
+ }
158
+ }
159
+
69
160
  // Helper function to draw rounded rectangles
70
161
  function drawRoundedRect(
71
162
  ctx: CanvasRenderingContext2D,
@@ -496,13 +587,17 @@ export function drawLayeredDistricts(
496
587
  abstractedPaths?: Set<string>, // Paths of directories that are abstracted (have covers)
497
588
  showDirectoryLabels: boolean = true,
498
589
  borderRadius: number = 0, // Border radius for districts (default: sharp corners)
590
+ layerIndex?: LayerIndex, // Optional pre-built index for performance
499
591
  ) {
592
+ // Build index once for all districts (O(n) instead of O(n²))
593
+ const index = layerIndex || new LayerIndex(layers);
594
+
500
595
  districts.forEach(district => {
501
596
  const districtPath = district.path || '';
502
597
  const isRoot = !districtPath || districtPath === '';
503
598
 
504
599
  // Check if this root district has layer matches (like covers) - if so, render it
505
- const rootLayerMatches = getLayerItemsForPath(districtPath, layers, 'exact');
600
+ const rootLayerMatches = index.getItemsForPath(districtPath, 'exact');
506
601
  const hasLayerRendering = rootLayerMatches.length > 0;
507
602
 
508
603
  // Skip root districts unless they have layer rendering (covers, highlights, etc.)
@@ -536,7 +631,7 @@ export function drawLayeredDistricts(
536
631
  let borderOpacity = 0.6;
537
632
 
538
633
  // Check if district has layer highlighting - use exact matching for districts
539
- const layerMatches = getLayerItemsForPath(districtPath, layers, 'exact');
634
+ const layerMatches = index.getItemsForPath(districtPath, 'exact');
540
635
  const hasLayerHighlight = layerMatches.length > 0;
541
636
 
542
637
  if (hasLayerHighlight) {
@@ -805,7 +900,11 @@ export function drawLayeredBuildings(
805
900
  disableOpacityDimming?: boolean,
806
901
  showFileTypeIcons?: boolean,
807
902
  borderRadius: number = 0, // Border radius for buildings (default: 0 - sharp corners)
903
+ layerIndex?: LayerIndex, // Optional pre-built index for performance
808
904
  ) {
905
+ // Build index once for all buildings (O(n) instead of O(n²))
906
+ const index = layerIndex || new LayerIndex(layers);
907
+
809
908
  buildings.forEach(building => {
810
909
  const pos = worldToCanvas(building.position.x, building.position.z);
811
910
 
@@ -832,7 +931,7 @@ export function drawLayeredBuildings(
832
931
  };
833
932
 
834
933
  // Get layer matches for this building - only check file items, not parent directories
835
- const layerMatches = getLayerItemsForPath(building.path, layers).filter(
934
+ const layerMatches = index.getItemsForPath(building.path).filter(
836
935
  match => match.item.type === 'file',
837
936
  ); // Only apply file-specific highlights to buildings
838
937
  const hasLayerHighlight = layerMatches.length > 0;