@principal-ai/file-city-react 0.4.6 → 0.4.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.
@@ -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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"}
@@ -38,6 +38,8 @@ const react_1 = __importStar(require("react"));
38
38
  const industry_theme_1 = require("@principal-ade/industry-theme");
39
39
  const cityDataUtils_1 = require("../builder/cityDataUtils");
40
40
  const drawLayeredBuildings_1 = require("../render/client/drawLayeredBuildings");
41
+ const fileColorHighlightLayers_1 = require("../utils/fileColorHighlightLayers");
42
+ const fileTypeIcons_1 = require("../utils/fileTypeIcons");
41
43
  const DEFAULT_DISPLAY_OPTIONS = {
42
44
  showGrid: false,
43
45
  showConnections: true,
@@ -187,6 +189,11 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
187
189
  const resolvedDefaultBuildingColor = defaultBuildingColor ?? theme.colors.muted;
188
190
  const canvasRef = (0, react_1.useRef)(null);
189
191
  const containerRef = (0, react_1.useRef)(null);
192
+ // Extract icon configuration from file color config
193
+ const iconMap = (0, react_1.useMemo)(() => {
194
+ const colorConfig = (0, fileColorHighlightLayers_1.getDefaultFileColorConfig)();
195
+ return (0, fileTypeIcons_1.extractIconConfig)(colorConfig);
196
+ }, []);
190
197
  const [interactionState, setInteractionState] = (0, react_1.useState)({
191
198
  hoveredDistrict: null,
192
199
  hoveredBuilding: null,
@@ -832,7 +839,8 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
832
839
  (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
833
840
  showDirectoryLabels, districtBorderRadius, layerIndex);
834
841
  // Draw buildings with layer support
835
- (0, drawLayeredBuildings_1.drawLayeredBuildings)(ctx, visibleBuildingsMemo, worldToCanvas, scale * zoomState.scale, allLayers, interactionState.hoveredBuilding, resolvedDefaultBuildingColor, showFileNames, resolvedHoverBorderColor, disableOpacityDimming, showFileTypeIcons, buildingBorderRadius, layerIndex);
842
+ (0, drawLayeredBuildings_1.drawLayeredBuildings)(ctx, visibleBuildingsMemo, worldToCanvas, scale * zoomState.scale, allLayers, interactionState.hoveredBuilding, resolvedDefaultBuildingColor, showFileNames, resolvedHoverBorderColor, disableOpacityDimming, showFileTypeIcons, buildingBorderRadius, layerIndex, // Pre-built index for O(1) lookups
843
+ iconMap);
836
844
  // Performance monitoring end available for debugging
837
845
  // Performance stats available but not logged to reduce console noise
838
846
  // Uncomment for debugging: render time, buildings/districts counts, layer counts
@@ -866,6 +874,7 @@ onHover, buildingBorderRadius = 0, districtBorderRadius = 0, }) {
866
874
  visibleBuildingsMemo,
867
875
  abstractedPathsSet,
868
876
  layerIndex,
877
+ iconMap,
869
878
  ]);
870
879
  // Optimized hit testing
871
880
  const performHitTest = (0, react_1.useCallback)((canvasX, canvasY) => {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,9 @@ export { type LayerRenderStrategy, type LayerItem, type HighlightLayer, LayerInd
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';
6
- export type { ColorLayerConfig, FileSuffixConfig, FileSuffixColorConfig, } from './utils/fileColorHighlightLayers';
6
+ export type { ColorLayerConfig, FileSuffixConfig, FileSuffixColorConfig, FileTypeIconConfig, } from './utils/fileColorHighlightLayers';
7
+ export { devFileColorOverrides, mergeFileColorConfig } from './utils/fileColorOverrides';
8
+ export { extractIconConfig, getFileTypeIcon, drawFileTypeIcon } from './utils/fileTypeIcons';
7
9
  export type { CityData, CityBuilding, CityDistrict, SelectiveRenderOptions, Bounds2D, Position3D, } from '@principal-ai/file-city-builder';
8
10
  export { MultiVersionCityBuilder } from '@principal-ai/file-city-builder';
9
11
  export { useCodeCityData } from './hooks/useCodeCityData';
@@ -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,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"}
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,EACrB,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGzF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG7F,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,6 +1,6 @@
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.LayerIndex = exports.ArchitectureMapHighlightLayers = void 0;
3
+ 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
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; } });
@@ -17,6 +17,15 @@ var fileColorHighlightLayers_1 = require("./utils/fileColorHighlightLayers");
17
17
  Object.defineProperty(exports, "createFileColorHighlightLayers", { enumerable: true, get: function () { return fileColorHighlightLayers_1.createFileColorHighlightLayers; } });
18
18
  Object.defineProperty(exports, "getDefaultFileColorConfig", { enumerable: true, get: function () { return fileColorHighlightLayers_1.getDefaultFileColorConfig; } });
19
19
  Object.defineProperty(exports, "getFileColorMapping", { enumerable: true, get: function () { return fileColorHighlightLayers_1.getFileColorMapping; } });
20
+ // File color override utilities for development
21
+ var fileColorOverrides_1 = require("./utils/fileColorOverrides");
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; } });
24
+ // File type icon utilities
25
+ var fileTypeIcons_1 = require("./utils/fileTypeIcons");
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; } });
20
29
  // Re-export MultiVersionCityBuilder which was requested
21
30
  var file_city_builder_1 = require("@principal-ai/file-city-builder");
22
31
  Object.defineProperty(exports, "MultiVersionCityBuilder", { enumerable: true, get: function () { return file_city_builder_1.MultiVersionCityBuilder; } });
@@ -1,4 +1,5 @@
1
1
  import { CityBuilding, CityDistrict } from '@principal-ai/file-city-builder';
2
+ import { FileTypeIconConfig } from '../../utils/fileColorHighlightLayers';
2
3
  export type LayerRenderStrategy = 'border' | 'fill' | 'glow' | 'pattern' | 'cover' | 'icon' | 'custom';
3
4
  export interface LayerItem {
4
5
  path: string;
@@ -70,5 +71,6 @@ export declare function drawLayeredBuildings(ctx: CanvasRenderingContext2D, buil
70
71
  x: number;
71
72
  y: number;
72
73
  }, 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)
73
- layerIndex?: LayerIndex): void;
74
+ layerIndex?: LayerIndex, // Optional pre-built index for performance
75
+ iconMap?: Map<string, FileTypeIconConfig>): void;
74
76
  //# sourceMappingURL=drawLayeredBuildings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"drawLayeredBuildings.d.ts","sourceRoot":"","sources":["../../../src/render/client/drawLayeredBuildings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"drawLayeredBuildings.d.ts","sourceRoot":"","sources":["../../../src/render/client/drawLayeredBuildings.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ exports.drawGrid = drawGrid;
5
5
  exports.drawLayeredDistricts = drawLayeredDistricts;
6
6
  exports.drawLayeredBuildings = drawLayeredBuildings;
7
7
  const lucideIconConverter_1 = require("../../utils/lucideIconConverter");
8
+ const fileTypeIcons_1 = require("../../utils/fileTypeIcons");
8
9
  /**
9
10
  * LayerIndex provides O(1) path lookups instead of O(n) iteration.
10
11
  * This dramatically improves performance with large numbers of layer items.
@@ -582,38 +583,10 @@ layerIndex) {
582
583
  } // End of !hasCover check
583
584
  });
584
585
  }
585
- /**
586
- * Draw a React symbol (⚛) at the given position
587
- */
588
- function drawReactSymbol(ctx, x, y, size, color = '#00D8FF', glow = true) {
589
- ctx.save();
590
- // Position and setup
591
- ctx.translate(x, y);
592
- // Glow effect for React symbol
593
- if (glow) {
594
- ctx.shadowColor = color;
595
- ctx.shadowBlur = 8;
596
- }
597
- // Draw the React symbol (⚛)
598
- ctx.fillStyle = color;
599
- ctx.font = `${size}px Arial`;
600
- ctx.textAlign = 'center';
601
- ctx.textBaseline = 'middle';
602
- ctx.fillText('⚛', 0, 0);
603
- ctx.restore();
604
- }
605
- /**
606
- * Check if a file is a React file (JSX/TSX)
607
- */
608
- function isReactFile(fileExtension) {
609
- if (!fileExtension)
610
- return false;
611
- const ext = fileExtension.toLowerCase();
612
- return ext === '.jsx' || ext === '.tsx';
613
- }
614
586
  // Draw buildings with layer support
615
587
  function drawLayeredBuildings(ctx, buildings, worldToCanvas, scale, layers, hoveredBuilding, defaultBuildingColor, showFileNames, hoverBorderColor, disableOpacityDimming, showFileTypeIcons, borderRadius = 0, // Border radius for buildings (default: 0 - sharp corners)
616
- layerIndex) {
588
+ layerIndex, // Optional pre-built index for performance
589
+ iconMap) {
617
590
  // Build index once for all buildings (O(n) instead of O(n²))
618
591
  const index = layerIndex || new LayerIndex(layers);
619
592
  buildings.forEach(building => {
@@ -679,16 +652,12 @@ layerIndex) {
679
652
  ctx.strokeRect(bounds.x - 1, bounds.y - 1, bounds.width + 2, bounds.height + 2);
680
653
  }
681
654
  }
682
- // Draw React symbol for JSX/TSX files (only if enabled and not a test file)
683
- // Test files have their own Lucide icons, so skip React symbol for them
684
- const isTestFile = building.path.includes('.test.') || building.path.includes('.spec.');
685
- if (showFileTypeIcons && isReactFile(building.fileExtension) && !isTestFile) {
686
- // Position React symbol centered in the building
687
- // Size is 75% of the smaller dimension
688
- const reactSize = Math.min(width, height) * 0.75;
689
- const reactX = pos.x;
690
- const reactY = pos.y;
691
- drawReactSymbol(ctx, reactX, reactY, reactSize);
655
+ // Draw file type icon if enabled and icon map is provided
656
+ if (showFileTypeIcons && iconMap) {
657
+ const iconConfig = (0, fileTypeIcons_1.getFileTypeIcon)(building.path, iconMap);
658
+ if (iconConfig) {
659
+ (0, fileTypeIcons_1.drawFileTypeIcon)(ctx, iconConfig, pos.x, pos.y, width, height);
660
+ }
692
661
  }
693
662
  // Draw filename if enabled
694
663
  if (showFileNames && width > 100 && height > 30) {
@@ -23,9 +23,18 @@ export interface ColorLayerConfig {
23
23
  height: number;
24
24
  }, scale: number) => void;
25
25
  }
26
+ export interface FileTypeIconConfig {
27
+ type: 'emoji' | 'lucide';
28
+ name: string;
29
+ color?: string;
30
+ backgroundColor?: string;
31
+ glow?: boolean;
32
+ size?: number;
33
+ }
26
34
  export interface FileSuffixConfig {
27
35
  primary: ColorLayerConfig;
28
36
  secondary?: ColorLayerConfig;
37
+ icon?: FileTypeIconConfig;
29
38
  displayName?: string;
30
39
  description?: string;
31
40
  category?: string;
@@ -73,7 +82,7 @@ export declare function createFileColorHighlightLayers(files: Array<{
73
82
  }> | null | undefined, config?: FileSuffixColorConfig): HighlightLayer[];
74
83
  /**
75
84
  * Get the default file color configuration.
76
- * This returns the configuration loaded from files.json.
85
+ * This returns the configuration loaded from files.json merged with local dev overrides.
77
86
  */
78
87
  export declare function getDefaultFileColorConfig(): FileSuffixColorConfig;
79
88
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"fileColorHighlightLayers.d.ts","sourceRoot":"","sources":["../../src/utils/fileColorHighlightLayers.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EAEd,mBAAmB,EACpB,MAAM,uCAAuC,CAAC;AAG/C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,mBAAmB,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,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;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,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,gBAAgB;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,gBAAgB,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EACjD,MAAM,CAAC,EAAE,qBAAqB,GAC7B,cAAc,EAAE,CAqSlB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,qBAAqB,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM1F"}
1
+ {"version":3,"file":"fileColorHighlightLayers.d.ts","sourceRoot":"","sources":["../../src/utils/fileColorHighlightLayers.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EAEd,mBAAmB,EACpB,MAAM,uCAAuC,CAAC;AAI/C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,mBAAmB,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,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;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,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,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,gBAAgB,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EACjD,MAAM,CAAC,EAAE,qBAAqB,GAC7B,cAAc,EAAE,CAuSlB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,qBAAqB,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ1F"}
@@ -4,6 +4,7 @@ exports.createFileColorHighlightLayers = createFileColorHighlightLayers;
4
4
  exports.getDefaultFileColorConfig = getDefaultFileColorConfig;
5
5
  exports.getFileColorMapping = getFileColorMapping;
6
6
  const file_city_builder_1 = require("@principal-ai/file-city-builder");
7
+ const fileColorOverrides_1 = require("./fileColorOverrides");
7
8
  /**
8
9
  * Creates highlight layers for files based on file extension configurations.
9
10
  *
@@ -37,8 +38,9 @@ function createFileColorHighlightLayers(files, config) {
37
38
  if (!files || files.length === 0) {
38
39
  return [];
39
40
  }
40
- // Use provided config or fall back to default from files.json
41
- const colorConfig = config || file_city_builder_1.defaultFileColorConfig;
41
+ // Use provided config or fall back to default merged with dev overrides
42
+ const colorConfig = config ||
43
+ (0, fileColorOverrides_1.mergeFileColorConfig)(file_city_builder_1.defaultFileColorConfig, fileColorOverrides_1.devFileColorOverrides);
42
44
  const { suffixConfigs, defaultConfig: defaultFileConfig, includeUnmatched = true } = colorConfig;
43
45
  // Validation
44
46
  if (!suffixConfigs || typeof suffixConfigs !== 'object') {
@@ -276,10 +278,10 @@ function createFileColorHighlightLayers(files, config) {
276
278
  }
277
279
  /**
278
280
  * Get the default file color configuration.
279
- * This returns the configuration loaded from files.json.
281
+ * This returns the configuration loaded from files.json merged with local dev overrides.
280
282
  */
281
283
  function getDefaultFileColorConfig() {
282
- return file_city_builder_1.defaultFileColorConfig;
284
+ return (0, fileColorOverrides_1.mergeFileColorConfig)(file_city_builder_1.defaultFileColorConfig, fileColorOverrides_1.devFileColorOverrides);
283
285
  }
284
286
  /**
285
287
  * Get a simple color mapping from the configuration.
@@ -289,7 +291,8 @@ function getDefaultFileColorConfig() {
289
291
  * @returns Record mapping file extensions to hex color strings
290
292
  */
291
293
  function getFileColorMapping(config) {
292
- const colorConfig = config || file_city_builder_1.defaultFileColorConfig;
294
+ const colorConfig = config ||
295
+ (0, fileColorOverrides_1.mergeFileColorConfig)(file_city_builder_1.defaultFileColorConfig, fileColorOverrides_1.devFileColorOverrides);
293
296
  return Object.entries(colorConfig.suffixConfigs).reduce((acc, [extension, suffixConfig]) => {
294
297
  acc[extension] = suffixConfig.primary.color;
295
298
  return acc;
@@ -0,0 +1,19 @@
1
+ import { FileSuffixColorConfig } from './fileColorHighlightLayers';
2
+ /**
3
+ * Development overrides for file color configuration.
4
+ * Add new file extensions here during development, then move them to
5
+ * @principal-ai/file-city-builder/src/config/files.json when ready to publish.
6
+ *
7
+ * These overrides will merge with (and take precedence over) the default config.
8
+ */
9
+ export declare const devFileColorOverrides: Partial<FileSuffixColorConfig>;
10
+ /**
11
+ * Merges the default file color configuration with local development overrides.
12
+ * Overrides take precedence over defaults.
13
+ *
14
+ * @param defaultConfig - The default configuration from the builder package
15
+ * @param overrides - Local development overrides
16
+ * @returns Merged configuration with overrides applied
17
+ */
18
+ export declare function mergeFileColorConfig(defaultConfig: FileSuffixColorConfig, overrides: Partial<FileSuffixColorConfig>): FileSuffixColorConfig;
19
+ //# sourceMappingURL=fileColorOverrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileColorOverrides.d.ts","sourceRoot":"","sources":["../../src/utils/fileColorOverrides.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,OAAO,CAAC,qBAAqB,CAoBhE,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,qBAAqB,EACpC,SAAS,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACxC,qBAAqB,CAWvB"}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.devFileColorOverrides = void 0;
4
+ exports.mergeFileColorConfig = mergeFileColorConfig;
5
+ /**
6
+ * Development overrides for file color configuration.
7
+ * Add new file extensions here during development, then move them to
8
+ * @principal-ai/file-city-builder/src/config/files.json when ready to publish.
9
+ *
10
+ * These overrides will merge with (and take precedence over) the default config.
11
+ */
12
+ exports.devFileColorOverrides = {
13
+ suffixConfigs: {
14
+ // Add your new file extensions here during development
15
+ "package.json": {
16
+ primary: {
17
+ color: "#CB3837",
18
+ renderStrategy: "fill",
19
+ opacity: 1.0,
20
+ },
21
+ icon: {
22
+ type: "lucide",
23
+ name: "Package",
24
+ color: "#ffffff",
25
+ size: 0.6,
26
+ },
27
+ displayName: "Package",
28
+ description: "npm package.json files",
29
+ category: "Configuration",
30
+ },
31
+ },
32
+ };
33
+ /**
34
+ * Merges the default file color configuration with local development overrides.
35
+ * Overrides take precedence over defaults.
36
+ *
37
+ * @param defaultConfig - The default configuration from the builder package
38
+ * @param overrides - Local development overrides
39
+ * @returns Merged configuration with overrides applied
40
+ */
41
+ function mergeFileColorConfig(defaultConfig, overrides) {
42
+ return {
43
+ ...defaultConfig,
44
+ ...overrides,
45
+ suffixConfigs: {
46
+ ...defaultConfig.suffixConfigs,
47
+ ...(overrides.suffixConfigs || {}),
48
+ },
49
+ // Keep default config's defaultConfig unless explicitly overridden
50
+ ...(overrides.defaultConfig && { defaultConfig: overrides.defaultConfig }),
51
+ };
52
+ }
@@ -0,0 +1,16 @@
1
+ import { FileSuffixColorConfig, FileTypeIconConfig } from './fileColorHighlightLayers';
2
+ /**
3
+ * Extract icon configurations from file color config
4
+ * This creates a map of file extensions to their icon configs
5
+ */
6
+ export declare function extractIconConfig(colorConfig: FileSuffixColorConfig): Map<string, FileTypeIconConfig>;
7
+ /**
8
+ * Get icon configuration for a file path
9
+ * Uses same matching logic as file color system
10
+ */
11
+ export declare function getFileTypeIcon(filePath: string, iconMap: Map<string, FileTypeIconConfig>): FileTypeIconConfig | null;
12
+ /**
13
+ * Draw a file type icon on the canvas
14
+ */
15
+ export declare function drawFileTypeIcon(ctx: CanvasRenderingContext2D, icon: FileTypeIconConfig, x: number, y: number, buildingWidth: number, buildingHeight: number): void;
16
+ //# sourceMappingURL=fileTypeIcons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileTypeIcons.d.ts","sourceRoot":"","sources":["../../src/utils/fileTypeIcons.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGvF;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,qBAAqB,GACjC,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAUjC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,GACvC,kBAAkB,GAAG,IAAI,CA0B3B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,kBAAkB,EACxB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,QAkDvB"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractIconConfig = extractIconConfig;
4
+ exports.getFileTypeIcon = getFileTypeIcon;
5
+ exports.drawFileTypeIcon = drawFileTypeIcon;
6
+ const lucideIconConverter_1 = require("./lucideIconConverter");
7
+ /**
8
+ * Extract icon configurations from file color config
9
+ * This creates a map of file extensions to their icon configs
10
+ */
11
+ function extractIconConfig(colorConfig) {
12
+ const iconMap = new Map();
13
+ Object.entries(colorConfig.suffixConfigs).forEach(([suffix, config]) => {
14
+ if (config.icon) {
15
+ iconMap.set(suffix, config.icon);
16
+ }
17
+ });
18
+ return iconMap;
19
+ }
20
+ /**
21
+ * Get icon configuration for a file path
22
+ * Uses same matching logic as file color system
23
+ */
24
+ function getFileTypeIcon(filePath, iconMap) {
25
+ const lastSlash = filePath.lastIndexOf('/');
26
+ const fileName = lastSlash === -1 ? filePath : filePath.substring(lastSlash + 1);
27
+ // Check exact filename match first (e.g., "package.json")
28
+ if (iconMap.has(fileName)) {
29
+ return iconMap.get(fileName) || null;
30
+ }
31
+ const lastDot = fileName.lastIndexOf('.');
32
+ if (lastDot === -1 || lastDot === fileName.length - 1) {
33
+ // No extension or ends with dot
34
+ return null;
35
+ }
36
+ // Check compound extensions (longest first) - same as fileColorHighlightLayers
37
+ const sortedExtensions = Array.from(iconMap.keys()).sort((a, b) => b.length - a.length);
38
+ const lowerFileName = fileName.toLowerCase();
39
+ for (const ext of sortedExtensions) {
40
+ if (ext.startsWith('.') && lowerFileName.endsWith(ext)) {
41
+ return iconMap.get(ext) || null;
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * Draw a file type icon on the canvas
48
+ */
49
+ function drawFileTypeIcon(ctx, icon, x, y, buildingWidth, buildingHeight) {
50
+ ctx.save();
51
+ if (icon.type === 'emoji') {
52
+ // Calculate size as percentage of building
53
+ const sizeScale = icon.size || 0.75;
54
+ const emojiSize = Math.min(buildingWidth, buildingHeight) * sizeScale;
55
+ // Glow effect
56
+ if (icon.glow) {
57
+ ctx.shadowColor = icon.color || '#00D8FF';
58
+ ctx.shadowBlur = 8;
59
+ }
60
+ ctx.fillStyle = icon.color || '#ffffff';
61
+ ctx.font = `${emojiSize}px Arial`;
62
+ ctx.textAlign = 'center';
63
+ ctx.textBaseline = 'middle';
64
+ ctx.fillText(icon.name, x, y);
65
+ }
66
+ else if (icon.type === 'lucide') {
67
+ // Calculate size as percentage of building (same as emoji)
68
+ const sizeScale = icon.size || 0.5;
69
+ const actualIconSize = Math.min(buildingWidth, buildingHeight) * sizeScale;
70
+ // Round to nearest 4px to reduce cache misses and prevent flickering
71
+ const roundedIconSize = Math.round(actualIconSize / 4) * 4;
72
+ const svgSize = Math.max(16, Math.min(roundedIconSize, 64)); // Clamp between 16-64px for SVG generation
73
+ // Optional background circle
74
+ if (icon.backgroundColor) {
75
+ const bgRadius = actualIconSize * 0.7;
76
+ ctx.fillStyle = icon.backgroundColor;
77
+ ctx.beginPath();
78
+ ctx.arc(x, y, bgRadius, 0, Math.PI * 2);
79
+ ctx.fill();
80
+ }
81
+ // Draw Lucide icon - use rounded size for cache, scale to actual size
82
+ const img = (0, lucideIconConverter_1.getLucideIconImage)(icon.name, icon.color || '#ffffff', svgSize);
83
+ if (img) {
84
+ const iconX = x - actualIconSize / 2;
85
+ const iconY = y - actualIconSize / 2;
86
+ // Scale the cached icon to actual size
87
+ ctx.drawImage(img, iconX, iconY, actualIconSize, actualIconSize);
88
+ }
89
+ }
90
+ ctx.restore();
91
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"lucideIconConverter.d.ts","sourceRoot":"","sources":["../../src/utils/lucideIconConverter.tsx"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAgB,EACvB,IAAI,GAAE,MAAW,GAChB,MAAM,GAAG,IAAI,CAoCf;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,SAejC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAgB,EACvB,IAAI,GAAE,MAAW,GAChB,gBAAgB,GAAG,IAAI,CAuBzB;AAED;;GAEG;AACH,wBAAgB,cAAc,SAG7B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C"}
1
+ {"version":3,"file":"lucideIconConverter.d.ts","sourceRoot":"","sources":["../../src/utils/lucideIconConverter.tsx"],"names":[],"mappings":"AA4BA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAgB,EACvB,IAAI,GAAE,MAAW,GAChB,MAAM,GAAG,IAAI,CAoCf;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,SAejC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAgB,EACvB,IAAI,GAAE,MAAW,GAChB,gBAAgB,GAAG,IAAI,CAuBzB;AAED;;GAEG;AACH,wBAAgB,cAAc,SAG7B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C"}
@@ -18,6 +18,17 @@ const ICON_PATHS = {
18
18
  FileText: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>',
19
19
  File: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
20
20
  Folder: '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
21
+ Package: '<path d="m7.5 4.27 9 5.15"/><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
22
+ BookOpen: '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>',
23
+ BookText: '<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/><path d="M8 7h6"/><path d="M8 11h8"/>',
24
+ ScrollText: '<path d="M8 21h12a2 2 0 0 0 2-2v-2H10v2a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v3h4"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M15 8h-5"/><path d="M15 12h-5"/>',
25
+ Settings: '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
26
+ Home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
27
+ Lock: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
28
+ GitBranch: '<line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>',
29
+ EyeOff: '<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/>',
30
+ Key: '<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/>',
31
+ Atom: '<circle cx="12" cy="12" r="1"/><path d="M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5Z"/><path d="M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5Z"/>',
21
32
  };
22
33
  /**
23
34
  * Convert a Lucide icon name to an SVG data URL that can be used in Canvas
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/file-city-react",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "React components for File City visualization",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@principal-ade/industry-theme": "^0.1.3",
16
16
  "@principal-ai/alexandria-core-library": "^0.1.36",
17
- "@principal-ai/file-city-builder": "^0.3.2",
17
+ "@principal-ai/file-city-builder": "^0.3.3",
18
18
  "lucide-react": "^0.563.0",
19
19
  "reactflow": "^11.11.4"
20
20
  },
@@ -21,6 +21,8 @@ import {
21
21
  SelectiveRenderOptions,
22
22
  } from '@principal-ai/file-city-builder';
23
23
  import { MapInteractionState, MapDisplayOptions } from '../types/react-types';
24
+ import { getDefaultFileColorConfig } from '../utils/fileColorHighlightLayers';
25
+ import { extractIconConfig } from '../utils/fileTypeIcons';
24
26
 
25
27
  const DEFAULT_DISPLAY_OPTIONS: MapDisplayOptions = {
26
28
  showGrid: false,
@@ -326,6 +328,12 @@ function ArchitectureMapHighlightLayersInner({
326
328
  const canvasRef = useRef<HTMLCanvasElement>(null);
327
329
  const containerRef = useRef<HTMLDivElement>(null);
328
330
 
331
+ // Extract icon configuration from file color config
332
+ const iconMap = useMemo(() => {
333
+ const colorConfig = getDefaultFileColorConfig();
334
+ return extractIconConfig(colorConfig);
335
+ }, []);
336
+
329
337
  const [interactionState, setInteractionState] = useState<MapInteractionState>({
330
338
  hoveredDistrict: null,
331
339
  hoveredBuilding: null,
@@ -1211,6 +1219,7 @@ function ArchitectureMapHighlightLayersInner({
1211
1219
  showFileTypeIcons,
1212
1220
  buildingBorderRadius,
1213
1221
  layerIndex, // Pre-built index for O(1) lookups
1222
+ iconMap, // Icon configuration map for file type icons
1214
1223
  );
1215
1224
 
1216
1225
  // Performance monitoring end available for debugging
@@ -1246,6 +1255,7 @@ function ArchitectureMapHighlightLayersInner({
1246
1255
  visibleBuildingsMemo,
1247
1256
  abstractedPathsSet,
1248
1257
  layerIndex,
1258
+ iconMap,
1249
1259
  ]);
1250
1260
 
1251
1261
  // Optimized hit testing
package/src/index.ts CHANGED
@@ -33,8 +33,15 @@ export type {
33
33
  ColorLayerConfig,
34
34
  FileSuffixConfig,
35
35
  FileSuffixColorConfig,
36
+ FileTypeIconConfig,
36
37
  } from './utils/fileColorHighlightLayers';
37
38
 
39
+ // File color override utilities for development
40
+ export { devFileColorOverrides, mergeFileColorConfig } from './utils/fileColorOverrides';
41
+
42
+ // File type icon utilities
43
+ export { extractIconConfig, getFileTypeIcon, drawFileTypeIcon } from './utils/fileTypeIcons';
44
+
38
45
  // Re-export commonly used types from builder
39
46
  export type {
40
47
  CityData,
@@ -1,5 +1,7 @@
1
1
  import { CityBuilding, CityDistrict } from '@principal-ai/file-city-builder';
2
2
  import { getLucideIconImage } from '../../utils/lucideIconConverter';
3
+ import { FileTypeIconConfig } from '../../utils/fileColorHighlightLayers';
4
+ import { getFileTypeIcon, drawFileTypeIcon } from '../../utils/fileTypeIcons';
3
5
 
4
6
  // Layer types and interfaces
5
7
  export type LayerRenderStrategy =
@@ -814,47 +816,6 @@ export function drawLayeredDistricts(
814
816
  });
815
817
  }
816
818
 
817
- /**
818
- * Draw a React symbol (⚛) at the given position
819
- */
820
- function drawReactSymbol(
821
- ctx: CanvasRenderingContext2D,
822
- x: number,
823
- y: number,
824
- size: number,
825
- color: string = '#00D8FF',
826
- glow: boolean = true,
827
- ) {
828
- ctx.save();
829
-
830
- // Position and setup
831
- ctx.translate(x, y);
832
-
833
- // Glow effect for React symbol
834
- if (glow) {
835
- ctx.shadowColor = color;
836
- ctx.shadowBlur = 8;
837
- }
838
-
839
- // Draw the React symbol (⚛)
840
- ctx.fillStyle = color;
841
- ctx.font = `${size}px Arial`;
842
- ctx.textAlign = 'center';
843
- ctx.textBaseline = 'middle';
844
- ctx.fillText('⚛', 0, 0);
845
-
846
- ctx.restore();
847
- }
848
-
849
- /**
850
- * Check if a file is a React file (JSX/TSX)
851
- */
852
- function isReactFile(fileExtension?: string): boolean {
853
- if (!fileExtension) return false;
854
- const ext = fileExtension.toLowerCase();
855
- return ext === '.jsx' || ext === '.tsx';
856
- }
857
-
858
819
  // Draw buildings with layer support
859
820
  export function drawLayeredBuildings(
860
821
  ctx: CanvasRenderingContext2D,
@@ -870,6 +831,7 @@ export function drawLayeredBuildings(
870
831
  showFileTypeIcons?: boolean,
871
832
  borderRadius: number = 0, // Border radius for buildings (default: 0 - sharp corners)
872
833
  layerIndex?: LayerIndex, // Optional pre-built index for performance
834
+ iconMap?: Map<string, FileTypeIconConfig>, // Optional icon configuration map
873
835
  ) {
874
836
  // Build index once for all buildings (O(n) instead of O(n²))
875
837
  const index = layerIndex || new LayerIndex(layers);
@@ -967,16 +929,12 @@ export function drawLayeredBuildings(
967
929
  }
968
930
  }
969
931
 
970
- // Draw React symbol for JSX/TSX files (only if enabled and not a test file)
971
- // Test files have their own Lucide icons, so skip React symbol for them
972
- const isTestFile = building.path.includes('.test.') || building.path.includes('.spec.');
973
- if (showFileTypeIcons && isReactFile(building.fileExtension) && !isTestFile) {
974
- // Position React symbol centered in the building
975
- // Size is 75% of the smaller dimension
976
- const reactSize = Math.min(width, height) * 0.75;
977
- const reactX = pos.x;
978
- const reactY = pos.y;
979
- drawReactSymbol(ctx, reactX, reactY, reactSize);
932
+ // Draw file type icon if enabled and icon map is provided
933
+ if (showFileTypeIcons && iconMap) {
934
+ const iconConfig = getFileTypeIcon(building.path, iconMap);
935
+ if (iconConfig) {
936
+ drawFileTypeIcon(ctx, iconConfig, pos.x, pos.y, width, height);
937
+ }
980
938
  }
981
939
 
982
940
  // Draw filename if enabled
@@ -26,7 +26,10 @@ type Story = StoryObj<typeof meta>;
26
26
  const allFileTypes = [
27
27
  // Frontend
28
28
  { path: 'frontend/typescript/index.ts', size: 1500 },
29
+ { path: 'frontend/typescript/index.tsx', size: 1700 },
29
30
  { path: 'frontend/typescript/App.tsx', size: 3200 },
31
+ { path: 'frontend/javascript/index.js', size: 1300 },
32
+ { path: 'frontend/javascript/index.jsx', size: 1500 },
30
33
  { path: 'frontend/javascript/script.js', size: 1200 },
31
34
  { path: 'frontend/javascript/Component.jsx', size: 1800 },
32
35
  { path: 'frontend/html/index.html', size: 2000 },
@@ -71,7 +74,15 @@ const allFileTypes = [
71
74
  { path: 'tests/specs/Form.spec.jsx', size: 2000 },
72
75
  { path: 'tests/snapshots/component.snap', size: 3500 },
73
76
 
77
+ // Storybook - Component documentation
78
+ { path: 'stories/Button.stories.ts', size: 2200 },
79
+ { path: 'stories/Card.stories.tsx', size: 2600 },
80
+ { path: 'stories/Input.stories.js', size: 1800 },
81
+ { path: 'stories/Modal.stories.jsx', size: 2400 },
82
+
74
83
  // Data & Config
84
+ { path: 'config/package.json', size: 2400 },
85
+ { path: 'config/tsconfig.json', size: 1800 },
75
86
  { path: 'config/data.json', size: 1200 },
76
87
  { path: 'config/settings.yaml', size: 1000 },
77
88
  { path: 'config/app.yml', size: 950 },
@@ -81,6 +92,7 @@ const allFileTypes = [
81
92
 
82
93
  // Documentation
83
94
  { path: 'docs/README.md', size: 4200 },
95
+ { path: 'docs/CHANGELOG.md', size: 3600 },
84
96
  { path: 'docs/API.mdx', size: 3800 },
85
97
  { path: 'docs/notes.txt', size: 600 },
86
98
 
@@ -128,11 +140,26 @@ const allFileTypes = [
128
140
 
129
141
  // Lock files
130
142
  { path: 'package-lock.json', size: 256000 },
131
- { path: 'Cargo.lock', size: 45000 },
132
143
  { path: 'yarn.lock', size: 128000 },
144
+ { path: 'pnpm-lock.yaml', size: 180000 },
145
+ { path: 'bun.lockb', size: 95000 },
146
+ { path: 'Cargo.lock', size: 45000 },
147
+ { path: 'Gemfile.lock', size: 32000 },
148
+ { path: 'poetry.lock', size: 78000 },
149
+ { path: 'composer.lock', size: 156000 },
150
+ { path: 'go.sum', size: 28000 },
133
151
 
134
152
  // Config files (exact names)
153
+ { path: '.env', size: 450 },
154
+ { path: '.env.example', size: 380 },
155
+ { path: '.env.local', size: 420 },
156
+ { path: '.env.development', size: 400 },
157
+ { path: '.env.production', size: 460 },
158
+ { path: '.env.test', size: 340 },
135
159
  { path: '.gitignore', size: 600 },
160
+ { path: '.husky/pre-commit', size: 450 },
161
+ { path: '.husky/commit-msg', size: 380 },
162
+ { path: '.husky/pre-push', size: 420 },
136
163
  { path: '.dockerignore', size: 400 },
137
164
  { path: '.npmignore', size: 300 },
138
165
  { path: '.eslintignore', size: 250 },
@@ -195,7 +222,7 @@ export const AllFileTypesWithColors: Story = {
195
222
  <ArchitectureMapHighlightLayers
196
223
  cityData={cityData}
197
224
  highlightLayers={highlightLayers}
198
- showLayerControls={true}
225
+ showLayerControls={false}
199
226
  fullSize={true}
200
227
  canvasBackgroundColor="#0a0a0a"
201
228
  defaultBuildingColor="#36454F"
@@ -4,6 +4,7 @@ import {
4
4
  LayerItem,
5
5
  LayerRenderStrategy,
6
6
  } from '../render/client/drawLayeredBuildings';
7
+ import { devFileColorOverrides, mergeFileColorConfig } from './fileColorOverrides';
7
8
 
8
9
  // Type definitions for the color configuration
9
10
  export interface ColorLayerConfig {
@@ -30,9 +31,19 @@ export interface ColorLayerConfig {
30
31
  ) => void;
31
32
  }
32
33
 
34
+ export interface FileTypeIconConfig {
35
+ type: 'emoji' | 'lucide';
36
+ name: string; // emoji character or Lucide icon name
37
+ color?: string;
38
+ backgroundColor?: string;
39
+ glow?: boolean;
40
+ size?: number; // Scale factor (0-1) relative to building size - default: 0.75 for emoji, 0.5 for lucide
41
+ }
42
+
33
43
  export interface FileSuffixConfig {
34
44
  primary: ColorLayerConfig;
35
45
  secondary?: ColorLayerConfig;
46
+ icon?: FileTypeIconConfig; // Optional icon configuration independent of render strategy
36
47
  displayName?: string;
37
48
  description?: string;
38
49
  category?: string;
@@ -85,8 +96,10 @@ export function createFileColorHighlightLayers(
85
96
  return [];
86
97
  }
87
98
 
88
- // Use provided config or fall back to default from files.json
89
- const colorConfig = config || (defaultFileColorConfig as FileSuffixColorConfig);
99
+ // Use provided config or fall back to default merged with dev overrides
100
+ const colorConfig =
101
+ config ||
102
+ mergeFileColorConfig(defaultFileColorConfig as FileSuffixColorConfig, devFileColorOverrides);
90
103
 
91
104
  const { suffixConfigs, defaultConfig: defaultFileConfig, includeUnmatched = true } = colorConfig;
92
105
 
@@ -377,10 +390,10 @@ export function createFileColorHighlightLayers(
377
390
 
378
391
  /**
379
392
  * Get the default file color configuration.
380
- * This returns the configuration loaded from files.json.
393
+ * This returns the configuration loaded from files.json merged with local dev overrides.
381
394
  */
382
395
  export function getDefaultFileColorConfig(): FileSuffixColorConfig {
383
- return defaultFileColorConfig as FileSuffixColorConfig;
396
+ return mergeFileColorConfig(defaultFileColorConfig as FileSuffixColorConfig, devFileColorOverrides);
384
397
  }
385
398
 
386
399
  /**
@@ -391,7 +404,9 @@ export function getDefaultFileColorConfig(): FileSuffixColorConfig {
391
404
  * @returns Record mapping file extensions to hex color strings
392
405
  */
393
406
  export function getFileColorMapping(config?: FileSuffixColorConfig): Record<string, string> {
394
- const colorConfig = config || (defaultFileColorConfig as FileSuffixColorConfig);
407
+ const colorConfig =
408
+ config ||
409
+ mergeFileColorConfig(defaultFileColorConfig as FileSuffixColorConfig, devFileColorOverrides);
395
410
  return Object.entries(colorConfig.suffixConfigs).reduce((acc, [extension, suffixConfig]) => {
396
411
  acc[extension] = suffixConfig.primary.color;
397
412
  return acc;
@@ -0,0 +1,54 @@
1
+ import { FileSuffixColorConfig } from './fileColorHighlightLayers';
2
+
3
+ /**
4
+ * Development overrides for file color configuration.
5
+ * Add new file extensions here during development, then move them to
6
+ * @principal-ai/file-city-builder/src/config/files.json when ready to publish.
7
+ *
8
+ * These overrides will merge with (and take precedence over) the default config.
9
+ */
10
+ export const devFileColorOverrides: Partial<FileSuffixColorConfig> = {
11
+ suffixConfigs: {
12
+ // Add your new file extensions here during development
13
+ "package.json": {
14
+ primary: {
15
+ color: "#CB3837",
16
+ renderStrategy: "fill",
17
+ opacity: 1.0,
18
+ },
19
+ icon: {
20
+ type: "lucide",
21
+ name: "Package",
22
+ color: "#ffffff",
23
+ size: 0.6,
24
+ },
25
+ displayName: "Package",
26
+ description: "npm package.json files",
27
+ category: "Configuration",
28
+ },
29
+ },
30
+ };
31
+
32
+ /**
33
+ * Merges the default file color configuration with local development overrides.
34
+ * Overrides take precedence over defaults.
35
+ *
36
+ * @param defaultConfig - The default configuration from the builder package
37
+ * @param overrides - Local development overrides
38
+ * @returns Merged configuration with overrides applied
39
+ */
40
+ export function mergeFileColorConfig(
41
+ defaultConfig: FileSuffixColorConfig,
42
+ overrides: Partial<FileSuffixColorConfig>,
43
+ ): FileSuffixColorConfig {
44
+ return {
45
+ ...defaultConfig,
46
+ ...overrides,
47
+ suffixConfigs: {
48
+ ...defaultConfig.suffixConfigs,
49
+ ...(overrides.suffixConfigs || {}),
50
+ },
51
+ // Keep default config's defaultConfig unless explicitly overridden
52
+ ...(overrides.defaultConfig && { defaultConfig: overrides.defaultConfig }),
53
+ };
54
+ }
@@ -0,0 +1,116 @@
1
+ import { FileSuffixColorConfig, FileTypeIconConfig } from './fileColorHighlightLayers';
2
+ import { getLucideIconImage } from './lucideIconConverter';
3
+
4
+ /**
5
+ * Extract icon configurations from file color config
6
+ * This creates a map of file extensions to their icon configs
7
+ */
8
+ export function extractIconConfig(
9
+ colorConfig: FileSuffixColorConfig,
10
+ ): Map<string, FileTypeIconConfig> {
11
+ const iconMap = new Map<string, FileTypeIconConfig>();
12
+
13
+ Object.entries(colorConfig.suffixConfigs).forEach(([suffix, config]) => {
14
+ if (config.icon) {
15
+ iconMap.set(suffix, config.icon);
16
+ }
17
+ });
18
+
19
+ return iconMap;
20
+ }
21
+
22
+ /**
23
+ * Get icon configuration for a file path
24
+ * Uses same matching logic as file color system
25
+ */
26
+ export function getFileTypeIcon(
27
+ filePath: string,
28
+ iconMap: Map<string, FileTypeIconConfig>,
29
+ ): FileTypeIconConfig | null {
30
+ const lastSlash = filePath.lastIndexOf('/');
31
+ const fileName = lastSlash === -1 ? filePath : filePath.substring(lastSlash + 1);
32
+
33
+ // Check exact filename match first (e.g., "package.json")
34
+ if (iconMap.has(fileName)) {
35
+ return iconMap.get(fileName) || null;
36
+ }
37
+
38
+ const lastDot = fileName.lastIndexOf('.');
39
+ if (lastDot === -1 || lastDot === fileName.length - 1) {
40
+ // No extension or ends with dot
41
+ return null;
42
+ }
43
+
44
+ // Check compound extensions (longest first) - same as fileColorHighlightLayers
45
+ const sortedExtensions = Array.from(iconMap.keys()).sort((a, b) => b.length - a.length);
46
+
47
+ const lowerFileName = fileName.toLowerCase();
48
+ for (const ext of sortedExtensions) {
49
+ if (ext.startsWith('.') && lowerFileName.endsWith(ext)) {
50
+ return iconMap.get(ext) || null;
51
+ }
52
+ }
53
+
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Draw a file type icon on the canvas
59
+ */
60
+ export function drawFileTypeIcon(
61
+ ctx: CanvasRenderingContext2D,
62
+ icon: FileTypeIconConfig,
63
+ x: number,
64
+ y: number,
65
+ buildingWidth: number,
66
+ buildingHeight: number,
67
+ ) {
68
+ ctx.save();
69
+
70
+ if (icon.type === 'emoji') {
71
+ // Calculate size as percentage of building
72
+ const sizeScale = icon.size || 0.75;
73
+ const emojiSize = Math.min(buildingWidth, buildingHeight) * sizeScale;
74
+
75
+ // Glow effect
76
+ if (icon.glow) {
77
+ ctx.shadowColor = icon.color || '#00D8FF';
78
+ ctx.shadowBlur = 8;
79
+ }
80
+
81
+ ctx.fillStyle = icon.color || '#ffffff';
82
+ ctx.font = `${emojiSize}px Arial`;
83
+ ctx.textAlign = 'center';
84
+ ctx.textBaseline = 'middle';
85
+ ctx.fillText(icon.name, x, y);
86
+ } else if (icon.type === 'lucide') {
87
+ // Calculate size as percentage of building (same as emoji)
88
+ const sizeScale = icon.size || 0.5;
89
+ const actualIconSize = Math.min(buildingWidth, buildingHeight) * sizeScale;
90
+
91
+ // Round to nearest 4px to reduce cache misses and prevent flickering
92
+ const roundedIconSize = Math.round(actualIconSize / 4) * 4;
93
+ const svgSize = Math.max(16, Math.min(roundedIconSize, 64)); // Clamp between 16-64px for SVG generation
94
+
95
+ // Optional background circle
96
+ if (icon.backgroundColor) {
97
+ const bgRadius = actualIconSize * 0.7;
98
+ ctx.fillStyle = icon.backgroundColor;
99
+ ctx.beginPath();
100
+ ctx.arc(x, y, bgRadius, 0, Math.PI * 2);
101
+ ctx.fill();
102
+ }
103
+
104
+ // Draw Lucide icon - use rounded size for cache, scale to actual size
105
+ const img = getLucideIconImage(icon.name, icon.color || '#ffffff', svgSize);
106
+
107
+ if (img) {
108
+ const iconX = x - actualIconSize / 2;
109
+ const iconY = y - actualIconSize / 2;
110
+ // Scale the cached icon to actual size
111
+ ctx.drawImage(img, iconX, iconY, actualIconSize, actualIconSize);
112
+ }
113
+ }
114
+
115
+ ctx.restore();
116
+ }
@@ -13,6 +13,17 @@ const ICON_PATHS: Record<string, string> = {
13
13
  FileText: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>',
14
14
  File: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
15
15
  Folder: '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
16
+ Package: '<path d="m7.5 4.27 9 5.15"/><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
17
+ BookOpen: '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>',
18
+ BookText: '<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/><path d="M8 7h6"/><path d="M8 11h8"/>',
19
+ ScrollText: '<path d="M8 21h12a2 2 0 0 0 2-2v-2H10v2a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v3h4"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M15 8h-5"/><path d="M15 12h-5"/>',
20
+ Settings: '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
21
+ Home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
22
+ Lock: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
23
+ GitBranch: '<line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>',
24
+ EyeOff: '<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/>',
25
+ Key: '<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/>',
26
+ Atom: '<circle cx="12" cy="12" r="1"/><path d="M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5Z"/><path d="M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5Z"/>',
16
27
  };
17
28
 
18
29
  /**