@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.
- package/dist/components/CityViewWithReactFlow.d.ts.map +1 -1
- package/dist/components/CityViewWithReactFlow.js +2 -1
- package/dist/render/client/drawLayeredBuildings.d.ts +1 -0
- package/dist/render/client/drawLayeredBuildings.d.ts.map +1 -1
- package/dist/render/client/drawLayeredBuildings.js +24 -46
- package/dist/stories/sample-data.d.ts.map +1 -1
- package/dist/stories/stress-test-data.d.ts.map +1 -1
- package/dist/stories/stress-test-data.js +4 -3
- package/dist/utils/fileColorHighlightLayers.d.ts +1 -0
- package/dist/utils/fileColorHighlightLayers.d.ts.map +1 -1
- package/dist/utils/fileColorHighlightLayers.js +23 -9
- package/dist/utils/lucideIconConverter.d.ts +31 -0
- package/dist/utils/lucideIconConverter.d.ts.map +1 -0
- package/dist/utils/lucideIconConverter.js +119 -0
- package/package.json +4 -2
- package/src/components/CityViewWithReactFlow.tsx +3 -1
- package/src/render/client/drawLayeredBuildings.ts +28 -57
- package/src/stories/AllFileTypes.stories.tsx +362 -0
- package/src/stories/ArchitectureMapHighlightLayers.stories.tsx +1 -1
- package/src/stories/CityViewWithReactFlow.stories.tsx +1 -0
- package/src/stories/StressTest.stories.tsx +0 -4
- package/src/stories/sample-data.ts +3 -13
- package/src/stories/stress-test-data.ts +6 -15
- package/src/utils/fileColorHighlightLayers.ts +27 -6
- package/src/utils/lucideIconConverter.tsx +138 -0
- package/src/config/files.json +0 -1012
|
@@ -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;
|
|
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:
|
|
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',
|
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
-
|
|
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);
|
|
@@ -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;
|
|
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
|
|
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 ||
|
|
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
|
-
|
|
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
|
|
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 ||
|
|
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.
|
|
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.
|
|
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={
|
|
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
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|