@principal-ai/file-city-react 0.4.5 → 0.4.6

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":"CityViewWithReactFlow.d.ts","sourceRoot":"","sources":["../../src/components/CityViewWithReactFlow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+B,MAAM,OAAO,CAAC;AAepD,OAAO,0BAA0B,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAIrE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA+UD,eAAO,MAAM,qBAAqB,sCAA6B,CAAC"}
1
+ {"version":3,"file":"CityViewWithReactFlow.d.ts","sourceRoot":"","sources":["../../src/components/CityViewWithReactFlow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+B,MAAM,OAAO,CAAC;AAepD,OAAO,0BAA0B,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAIrE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAiVD,eAAO,MAAM,qBAAqB,sCAA6B,CAAC"}
@@ -59,6 +59,7 @@ const CellNode = ({ data, selected }) => {
59
59
  }
60
60
  }, [fileTree, cityBuilder]);
61
61
  return (react_1.default.createElement("div", { className: "cell-node", style: {
62
+ boxSizing: 'border-box',
62
63
  background: selected
63
64
  ? `linear-gradient(135deg, ${theme.colors.primary} 0%, ${theme.colors.accent} 100%)`
64
65
  : `linear-gradient(135deg, ${theme.colors.backgroundSecondary} 0%, ${theme.colors.background} 100%)`,
@@ -124,7 +125,7 @@ const CellNode = ({ data, selected }) => {
124
125
  opacity: 1,
125
126
  })),
126
127
  },
127
- ], showGrid: false, showDirectoryLabels: false, canvasBackgroundColor: theme.colors.background, defaultBuildingColor: theme.colors.muted, defaultDirectoryColor: theme.colors.backgroundSecondary, className: "w-full h-full",
128
+ ], showGrid: false, showDirectoryLabels: true, showFileNames: true, canvasBackgroundColor: theme.colors.background, defaultBuildingColor: theme.colors.muted, defaultDirectoryColor: theme.colors.backgroundSecondary, className: "w-full h-full",
128
129
  // Disable interactions to prevent conflicts with React Flow
129
130
  onFileClick: undefined }))) : (react_1.default.createElement("div", { style: {
130
131
  display: 'flex',
@@ -13,6 +13,7 @@ export interface LayerItem {
13
13
  borderRadius?: number;
14
14
  icon?: string;
15
15
  iconSize?: number;
16
+ lucideIcon?: string;
16
17
  };
17
18
  customRender?: (ctx: CanvasRenderingContext2D, bounds: {
18
19
  x: number;
@@ -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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"}
@@ -4,23 +4,7 @@ exports.LayerIndex = void 0;
4
4
  exports.drawGrid = drawGrid;
5
5
  exports.drawLayeredDistricts = drawLayeredDistricts;
6
6
  exports.drawLayeredBuildings = drawLayeredBuildings;
7
- // Helper to check if a path matches a layer item
8
- function pathMatchesItem(path, item, checkType = 'children') {
9
- if (item.type === 'file') {
10
- return path === item.path;
11
- }
12
- else {
13
- // Directory match
14
- if (checkType === 'exact') {
15
- // Only match the directory itself, not its children
16
- return path === item.path;
17
- }
18
- else {
19
- // Match directory and all its children (original behavior)
20
- return path === item.path || path.startsWith(item.path + '/');
21
- }
22
- }
23
- }
7
+ const lucideIconConverter_1 = require("../../utils/lucideIconConverter");
24
8
  /**
25
9
  * LayerIndex provides O(1) path lookups instead of O(n) iteration.
26
10
  * This dramatically improves performance with large numbers of layer items.
@@ -120,21 +104,6 @@ function drawRoundedRect(ctx, x, y, width, height, radius, fill, stroke) {
120
104
  if (stroke)
121
105
  ctx.stroke();
122
106
  }
123
- // Get all layer items that apply to a given path
124
- function getLayerItemsForPath(path, layers, checkType = 'children') {
125
- const matches = [];
126
- for (const layer of layers) {
127
- if (!layer.enabled)
128
- continue;
129
- for (const item of layer.items) {
130
- if (pathMatchesItem(path, item, checkType)) {
131
- matches.push({ layer, item });
132
- }
133
- }
134
- }
135
- // Sort by priority (highest first)
136
- return matches.sort((a, b) => b.layer.priority - a.layer.priority);
137
- }
138
107
  // Draw grid helper (copied from original)
139
108
  function drawGrid(ctx, width, height, gridSize) {
140
109
  ctx.save();
@@ -310,20 +279,27 @@ function renderCoverStrategy(ctx, bounds, layer, item, _scale) {
310
279
  ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
311
280
  // Reset alpha for text/icon
312
281
  ctx.globalAlpha = 1;
313
- // Image/SVG (takes precedence over text icon)
314
- if (coverOptions.image) {
315
- const img = new Image();
316
- img.onload = () => {
317
- const imageSize = coverOptions.iconSize || Math.min(bounds.width, bounds.height) * 0.4;
318
- const imageX = bounds.x + bounds.width / 2 - imageSize / 2;
319
- const imageY = coverOptions.text
320
- ? bounds.y + bounds.height * 0.25
321
- : bounds.y + bounds.height / 2 - imageSize / 2;
322
- ctx.drawImage(img, imageX, imageY, imageSize, imageSize);
323
- };
282
+ // Get image for rendering
283
+ let img = null;
284
+ // Check for lucideIcon first (new way)
285
+ if (coverOptions.lucideIcon) {
286
+ img = (0, lucideIconConverter_1.getLucideIconImage)(coverOptions.lucideIcon, '#ffffff', coverOptions.iconSize || 24);
287
+ }
288
+ // Fallback to direct image URL (old way)
289
+ else if (coverOptions.image) {
290
+ img = new Image();
324
291
  img.src = coverOptions.image;
325
292
  }
326
- // Text Icon (fallback if no image)
293
+ // Draw the image if we have one
294
+ if (img) {
295
+ const imageSize = coverOptions.iconSize || Math.min(bounds.width, bounds.height) * 0.4;
296
+ const imageX = bounds.x + bounds.width / 2 - imageSize / 2;
297
+ const imageY = coverOptions.text
298
+ ? bounds.y + bounds.height * 0.25
299
+ : bounds.y + bounds.height / 2 - imageSize / 2;
300
+ ctx.drawImage(img, imageX, imageY, imageSize, imageSize);
301
+ }
302
+ // Text Icon (fallback if no image or lucideIcon)
327
303
  else if (coverOptions.icon) {
328
304
  const iconSize = coverOptions.iconSize || Math.min(bounds.width, bounds.height) * 0.3;
329
305
  ctx.font = `${iconSize}px Arial`;
@@ -703,8 +679,10 @@ layerIndex) {
703
679
  ctx.strokeRect(bounds.x - 1, bounds.y - 1, bounds.width + 2, bounds.height + 2);
704
680
  }
705
681
  }
706
- // Draw React symbol for JSX/TSX files (only if enabled)
707
- if (showFileTypeIcons && isReactFile(building.fileExtension)) {
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) {
708
686
  // Position React symbol centered in the building
709
687
  // Size is 75% of the smaller dimension
710
688
  const reactSize = Math.min(width, height) * 0.75;
@@ -1 +1 @@
1
- {"version":3,"file":"sample-data.d.ts","sourceRoot":"","sources":["../../src/stories/sample-data.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAGT,MAAM,iCAAiC,CAAC;AAqFzC,wBAAgB,oBAAoB,IAAI,QAAQ,CAmB/C;AAaD,wBAAgB,yBAAyB,IAAI,QAAQ,CAmBpD"}
1
+ {"version":3,"file":"sample-data.d.ts","sourceRoot":"","sources":["../../src/stories/sample-data.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAGT,MAAM,iCAAiC,CAAC;AA2EzC,wBAAgB,oBAAoB,IAAI,QAAQ,CAmB/C;AAaD,wBAAgB,yBAAyB,IAAI,QAAQ,CAmBpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"stress-test-data.d.ts","sourceRoot":"","sources":["../../src/stories/stress-test-data.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAGT,MAAM,iCAAiC,CAAC;AA2CzC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAoElE;AAoBD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,GAAE,MAAa,EAAE,QAAQ,GAAE,OAAc,GAAG,QAAQ,CAwBrG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
1
+ {"version":3,"file":"stress-test-data.d.ts","sourceRoot":"","sources":["../../src/stories/stress-test-data.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAGT,MAAM,iCAAiC,CAAC;AA+BzC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAoElE;AAoBD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,GAAE,MAAa,EAAE,QAAQ,GAAE,OAAc,GAAG,QAAQ,CA2BrG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
@@ -6,8 +6,6 @@ exports.clearStressTestCache = clearStressTestCache;
6
6
  const file_city_builder_1 = require("@principal-ai/file-city-builder");
7
7
  // Common file extensions for realistic distribution
8
8
  const FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.css', '.json', '.md', '.test.ts', '.spec.tsx'];
9
- // Top-level source directories
10
- const TOP_LEVEL_DIRS = ['src', 'lib', 'packages', 'modules'];
11
9
  // Second-level directories (domain areas)
12
10
  const DOMAIN_DIRS = ['components', 'utils', 'services', 'hooks', 'types', 'helpers', 'core', 'api', 'features', 'pages'];
13
11
  // Third-level directories (categories within domains)
@@ -120,7 +118,10 @@ const stressTestCache = new Map();
120
118
  */
121
119
  function createStressTestCityData(fileCount = 8000, useCache = true) {
122
120
  if (useCache && stressTestCache.has(fileCount)) {
123
- return stressTestCache.get(fileCount);
121
+ const cached = stressTestCache.get(fileCount);
122
+ if (cached) {
123
+ return cached;
124
+ }
124
125
  }
125
126
  const filePaths = generateLargeFilePaths(fileCount);
126
127
  const fileInfos = createFileInfoList(filePaths);
@@ -14,6 +14,7 @@ export interface ColorLayerConfig {
14
14
  borderRadius?: number;
15
15
  icon?: string;
16
16
  iconSize?: number;
17
+ lucideIcon?: string;
17
18
  };
18
19
  customRender?: (ctx: CanvasRenderingContext2D, bounds: {
19
20
  x: number;
@@ -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;KACnB,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,CAiRlB;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;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,12 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.createFileColorHighlightLayers = createFileColorHighlightLayers;
7
4
  exports.getDefaultFileColorConfig = getDefaultFileColorConfig;
8
5
  exports.getFileColorMapping = getFileColorMapping;
9
- const files_json_1 = __importDefault(require("../config/files.json"));
6
+ const file_city_builder_1 = require("@principal-ai/file-city-builder");
10
7
  /**
11
8
  * Creates highlight layers for files based on file extension configurations.
12
9
  *
@@ -41,7 +38,7 @@ function createFileColorHighlightLayers(files, config) {
41
38
  return [];
42
39
  }
43
40
  // Use provided config or fall back to default from files.json
44
- const colorConfig = config || files_json_1.default;
41
+ const colorConfig = config || file_city_builder_1.defaultFileColorConfig;
45
42
  const { suffixConfigs, defaultConfig: defaultFileConfig, includeUnmatched = true } = colorConfig;
46
43
  // Validation
47
44
  if (!suffixConfigs || typeof suffixConfigs !== 'object') {
@@ -56,7 +53,6 @@ function createFileColorHighlightLayers(files, config) {
56
53
  const filePath = file.path;
57
54
  const lastSlash = filePath.lastIndexOf('/');
58
55
  const fileName = lastSlash === -1 ? filePath : filePath.substring(lastSlash + 1);
59
- const lastDot = fileName.lastIndexOf('.');
60
56
  // Check for exact filename match first (e.g., LICENSE, Makefile)
61
57
  if (suffixConfigs[fileName]) {
62
58
  if (!filesBySuffix.has(fileName)) {
@@ -68,6 +64,7 @@ function createFileColorHighlightLayers(files, config) {
68
64
  }
69
65
  return;
70
66
  }
67
+ const lastDot = fileName.lastIndexOf('.');
71
68
  if (lastDot === -1 || lastDot === fileName.length - 1) {
72
69
  // No extension or ends with dot
73
70
  if (includeUnmatched) {
@@ -75,7 +72,24 @@ function createFileColorHighlightLayers(files, config) {
75
72
  }
76
73
  return;
77
74
  }
78
- const extension = fileName.substring(lastDot).toLowerCase();
75
+ // Check for compound extensions first (e.g., .test.ts, .spec.tsx, .d.ts)
76
+ // Look for patterns like .test.ts, .spec.js, etc.
77
+ let extension = null;
78
+ const lowerFileName = fileName.toLowerCase();
79
+ // Sort suffixes by length (longest first) to match compound extensions before simple ones
80
+ // e.g., .test.ts should be checked before .ts
81
+ const sortedSuffixes = Object.keys(suffixConfigs).sort((a, b) => b.length - a.length);
82
+ // Check if any suffix config matches as a suffix of the filename
83
+ for (const suffix of sortedSuffixes) {
84
+ if (lowerFileName.endsWith(suffix)) {
85
+ extension = suffix;
86
+ break;
87
+ }
88
+ }
89
+ // Fallback to simple extension if no compound match found
90
+ if (!extension) {
91
+ extension = fileName.substring(lastDot).toLowerCase();
92
+ }
79
93
  if (suffixConfigs[extension]) {
80
94
  if (!filesBySuffix.has(extension)) {
81
95
  filesBySuffix.set(extension, []);
@@ -265,7 +279,7 @@ function createFileColorHighlightLayers(files, config) {
265
279
  * This returns the configuration loaded from files.json.
266
280
  */
267
281
  function getDefaultFileColorConfig() {
268
- return files_json_1.default;
282
+ return file_city_builder_1.defaultFileColorConfig;
269
283
  }
270
284
  /**
271
285
  * Get a simple color mapping from the configuration.
@@ -275,7 +289,7 @@ function getDefaultFileColorConfig() {
275
289
  * @returns Record mapping file extensions to hex color strings
276
290
  */
277
291
  function getFileColorMapping(config) {
278
- const colorConfig = config || files_json_1.default;
292
+ const colorConfig = config || file_city_builder_1.defaultFileColorConfig;
279
293
  return Object.entries(colorConfig.suffixConfigs).reduce((acc, [extension, suffixConfig]) => {
280
294
  acc[extension] = suffixConfig.primary.color;
281
295
  return acc;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Convert a Lucide icon name to an SVG data URL that can be used in Canvas
3
+ * @param iconName - The name of the Lucide icon (e.g., "TestTube", "FileCode")
4
+ * @param color - The color to apply to the icon (default: "white")
5
+ * @param size - The size of the icon (default: 24)
6
+ * @returns SVG data URL string or null if icon not found
7
+ */
8
+ export declare function lucideIconToDataUrl(iconName: string, color?: string, size?: number): string | null;
9
+ /**
10
+ * Preload commonly used icons to improve performance
11
+ * Call this on app initialization
12
+ */
13
+ export declare function preloadCommonIcons(): void;
14
+ /**
15
+ * Get a cached Image object for an icon (prevents flashing on redraws)
16
+ * @param iconName - The name of the Lucide icon
17
+ * @param color - The color to apply to the icon (default: "white")
18
+ * @param size - The size of the icon (default: 24)
19
+ * @returns HTMLImageElement or null if icon not found
20
+ */
21
+ export declare function getLucideIconImage(iconName: string, color?: string, size?: number): HTMLImageElement | null;
22
+ /**
23
+ * Clear the SVG cache (useful for memory management)
24
+ */
25
+ export declare function clearIconCache(): void;
26
+ /**
27
+ * Get available Lucide icon names
28
+ * @returns Array of all available icon names
29
+ */
30
+ export declare function getAvailableIcons(): string[];
31
+ //# sourceMappingURL=lucideIconConverter.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lucideIconToDataUrl = lucideIconToDataUrl;
4
+ exports.preloadCommonIcons = preloadCommonIcons;
5
+ exports.getLucideIconImage = getLucideIconImage;
6
+ exports.clearIconCache = clearIconCache;
7
+ exports.getAvailableIcons = getAvailableIcons;
8
+ // Cache for converted SVG data URLs
9
+ const svgCache = new Map();
10
+ // Cache for loaded Image objects (to prevent flashing)
11
+ const imageCache = new Map();
12
+ // SVG path data for Lucide icons
13
+ // Source: https://github.com/lucide-icons/lucide
14
+ const ICON_PATHS = {
15
+ TestTube: '<path d="M14.5 2v17.5c0 1.4-1.1 2.5-2.5 2.5s-2.5-1.1-2.5-2.5V2"/><path d="M8.5 2h7"/><path d="M14.5 16h-5"/>',
16
+ FlaskConical: '<path d="M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2"/><path d="M8.5 2h7"/><path d="M7 16h10"/>',
17
+ FileCode: '<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 13-2 2 2 2"/><path d="m14 17 2-2-2-2"/>',
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
+ 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
+ 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
+ };
22
+ /**
23
+ * Convert a Lucide icon name to an SVG data URL that can be used in Canvas
24
+ * @param iconName - The name of the Lucide icon (e.g., "TestTube", "FileCode")
25
+ * @param color - The color to apply to the icon (default: "white")
26
+ * @param size - The size of the icon (default: 24)
27
+ * @returns SVG data URL string or null if icon not found
28
+ */
29
+ function lucideIconToDataUrl(iconName, color = 'white', size = 24) {
30
+ // Create cache key
31
+ const cacheKey = `${iconName}-${color}-${size}`;
32
+ // Check cache first
33
+ if (svgCache.has(cacheKey)) {
34
+ const cached = svgCache.get(cacheKey);
35
+ if (cached) {
36
+ return cached;
37
+ }
38
+ }
39
+ // Get the icon path data
40
+ const pathData = ICON_PATHS[iconName];
41
+ if (!pathData) {
42
+ console.warn(`[lucideIconConverter] Icon "${iconName}" not found. Available icons:`, Object.keys(ICON_PATHS));
43
+ return null;
44
+ }
45
+ try {
46
+ // Create SVG string
47
+ const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${pathData}</svg>`;
48
+ // Convert to data URL
49
+ const encodedSvg = encodeURIComponent(svgString);
50
+ const dataUrl = `data:image/svg+xml,${encodedSvg}`;
51
+ // Cache the result
52
+ svgCache.set(cacheKey, dataUrl);
53
+ return dataUrl;
54
+ }
55
+ catch (error) {
56
+ console.error(`[lucideIconConverter] Error converting icon "${iconName}":`, error);
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Preload commonly used icons to improve performance
62
+ * Call this on app initialization
63
+ */
64
+ function preloadCommonIcons() {
65
+ const commonIcons = [
66
+ 'TestTube',
67
+ 'FileCode',
68
+ 'FileText',
69
+ 'File',
70
+ 'Folder',
71
+ 'Image',
72
+ 'Database',
73
+ 'Settings',
74
+ ];
75
+ commonIcons.forEach(iconName => {
76
+ lucideIconToDataUrl(iconName);
77
+ });
78
+ }
79
+ /**
80
+ * Get a cached Image object for an icon (prevents flashing on redraws)
81
+ * @param iconName - The name of the Lucide icon
82
+ * @param color - The color to apply to the icon (default: "white")
83
+ * @param size - The size of the icon (default: 24)
84
+ * @returns HTMLImageElement or null if icon not found
85
+ */
86
+ function getLucideIconImage(iconName, color = 'white', size = 24) {
87
+ const cacheKey = `${iconName}-${color}-${size}`;
88
+ // Return cached image if available
89
+ if (imageCache.has(cacheKey)) {
90
+ const cached = imageCache.get(cacheKey);
91
+ if (cached) {
92
+ return cached;
93
+ }
94
+ }
95
+ // Get the data URL
96
+ const dataUrl = lucideIconToDataUrl(iconName, color, size);
97
+ if (!dataUrl) {
98
+ return null;
99
+ }
100
+ // Create and cache the image
101
+ const img = new Image();
102
+ img.src = dataUrl;
103
+ imageCache.set(cacheKey, img);
104
+ return img;
105
+ }
106
+ /**
107
+ * Clear the SVG cache (useful for memory management)
108
+ */
109
+ function clearIconCache() {
110
+ svgCache.clear();
111
+ imageCache.clear();
112
+ }
113
+ /**
114
+ * Get available Lucide icon names
115
+ * @returns Array of all available icon names
116
+ */
117
+ function getAvailableIcons() {
118
+ return Object.keys(ICON_PATHS);
119
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/file-city-react",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
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,8 @@
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.0",
17
+ "@principal-ai/file-city-builder": "^0.3.2",
18
+ "lucide-react": "^0.563.0",
18
19
  "reactflow": "^11.11.4"
19
20
  },
20
21
  "peerDependencies": {
@@ -25,6 +26,7 @@
25
26
  "@storybook/addon-links": "^10.1.2",
26
27
  "@storybook/react-vite": "^10.1.2",
27
28
  "@types/react": "^19.0.0",
29
+ "@types/react-dom": "^19.2.3",
28
30
  "react": "^19.1.1",
29
31
  "react-dom": "^19.1.1",
30
32
  "storybook": "^10.1.2",
@@ -61,6 +61,7 @@ const CellNode: React.FC<NodeProps<CellNodeData>> = ({ data, selected }) => {
61
61
  <div
62
62
  className="cell-node"
63
63
  style={{
64
+ boxSizing: 'border-box',
64
65
  background: selected
65
66
  ? `linear-gradient(135deg, ${theme.colors.primary} 0%, ${theme.colors.accent} 100%)`
66
67
  : `linear-gradient(135deg, ${theme.colors.backgroundSecondary} 0%, ${theme.colors.background} 100%)`,
@@ -147,7 +148,8 @@ const CellNode: React.FC<NodeProps<CellNodeData>> = ({ data, selected }) => {
147
148
  },
148
149
  ]}
149
150
  showGrid={false}
150
- showDirectoryLabels={false}
151
+ showDirectoryLabels={true}
152
+ showFileNames={true}
151
153
  canvasBackgroundColor={theme.colors.background}
152
154
  defaultBuildingColor={theme.colors.muted}
153
155
  defaultDirectoryColor={theme.colors.backgroundSecondary}
@@ -1,4 +1,5 @@
1
1
  import { CityBuilding, CityDistrict } from '@principal-ai/file-city-builder';
2
+ import { getLucideIconImage } from '../../utils/lucideIconConverter';
2
3
 
3
4
  // Layer types and interfaces
4
5
  export type LayerRenderStrategy =
@@ -24,6 +25,7 @@ export interface LayerItem {
24
25
  borderRadius?: number;
25
26
  icon?: string;
26
27
  iconSize?: number;
28
+ lucideIcon?: string;
27
29
  };
28
30
  // Custom render function
29
31
  customRender?: (
@@ -46,26 +48,6 @@ export interface HighlightLayer {
46
48
  dynamic?: boolean; // If true, this layer changes frequently (e.g., hover effects)
47
49
  }
48
50
 
49
- // Helper to check if a path matches a layer item
50
- function pathMatchesItem(
51
- path: string,
52
- item: LayerItem,
53
- checkType: 'exact' | 'children' = 'children',
54
- ): boolean {
55
- if (item.type === 'file') {
56
- return path === item.path;
57
- } else {
58
- // Directory match
59
- if (checkType === 'exact') {
60
- // Only match the directory itself, not its children
61
- return path === item.path;
62
- } else {
63
- // Match directory and all its children (original behavior)
64
- return path === item.path || path.startsWith(item.path + '/');
65
- }
66
- }
67
- }
68
-
69
51
  /**
70
52
  * LayerIndex provides O(1) path lookups instead of O(n) iteration.
71
53
  * This dramatically improves performance with large numbers of layer items.
@@ -184,28 +166,6 @@ function drawRoundedRect(
184
166
  if (stroke) ctx.stroke();
185
167
  }
186
168
 
187
- // Get all layer items that apply to a given path
188
- function getLayerItemsForPath(
189
- path: string,
190
- layers: HighlightLayer[],
191
- checkType: 'exact' | 'children' = 'children',
192
- ): Array<{ layer: HighlightLayer; item: LayerItem }> {
193
- const matches: Array<{ layer: HighlightLayer; item: LayerItem }> = [];
194
-
195
- for (const layer of layers) {
196
- if (!layer.enabled) continue;
197
-
198
- for (const item of layer.items) {
199
- if (pathMatchesItem(path, item, checkType)) {
200
- matches.push({ layer, item });
201
- }
202
- }
203
- }
204
-
205
- // Sort by priority (highest first)
206
- return matches.sort((a, b) => b.layer.priority - a.layer.priority);
207
- }
208
-
209
169
  // Draw grid helper (copied from original)
210
170
  export function drawGrid(
211
171
  ctx: CanvasRenderingContext2D,
@@ -439,21 +399,30 @@ function renderCoverStrategy(
439
399
  // Reset alpha for text/icon
440
400
  ctx.globalAlpha = 1;
441
401
 
442
- // Image/SVG (takes precedence over text icon)
443
- if (coverOptions.image) {
444
- const img = new Image();
445
- img.onload = () => {
446
- const imageSize = coverOptions.iconSize || Math.min(bounds.width, bounds.height) * 0.4;
447
- const imageX = bounds.x + bounds.width / 2 - imageSize / 2;
448
- const imageY = coverOptions.text
449
- ? bounds.y + bounds.height * 0.25
450
- : bounds.y + bounds.height / 2 - imageSize / 2;
451
-
452
- ctx.drawImage(img, imageX, imageY, imageSize, imageSize);
453
- };
402
+ // Get image for rendering
403
+ let img: HTMLImageElement | null = null;
404
+
405
+ // Check for lucideIcon first (new way)
406
+ if (coverOptions.lucideIcon) {
407
+ img = getLucideIconImage(coverOptions.lucideIcon, '#ffffff', coverOptions.iconSize || 24);
408
+ }
409
+ // Fallback to direct image URL (old way)
410
+ else if (coverOptions.image) {
411
+ img = new Image();
454
412
  img.src = coverOptions.image;
455
413
  }
456
- // Text Icon (fallback if no image)
414
+
415
+ // Draw the image if we have one
416
+ if (img) {
417
+ const imageSize = coverOptions.iconSize || Math.min(bounds.width, bounds.height) * 0.4;
418
+ const imageX = bounds.x + bounds.width / 2 - imageSize / 2;
419
+ const imageY = coverOptions.text
420
+ ? bounds.y + bounds.height * 0.25
421
+ : bounds.y + bounds.height / 2 - imageSize / 2;
422
+
423
+ ctx.drawImage(img, imageX, imageY, imageSize, imageSize);
424
+ }
425
+ // Text Icon (fallback if no image or lucideIcon)
457
426
  else if (coverOptions.icon) {
458
427
  const iconSize = coverOptions.iconSize || Math.min(bounds.width, bounds.height) * 0.3;
459
428
  ctx.font = `${iconSize}px Arial`;
@@ -998,8 +967,10 @@ export function drawLayeredBuildings(
998
967
  }
999
968
  }
1000
969
 
1001
- // Draw React symbol for JSX/TSX files (only if enabled)
1002
- if (showFileTypeIcons && isReactFile(building.fileExtension)) {
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) {
1003
974
  // Position React symbol centered in the building
1004
975
  // Size is 75% of the smaller dimension
1005
976
  const reactSize = Math.min(width, height) * 0.75;