@principal-ai/file-city-react 0.4.5 → 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.
- package/dist/components/ArchitectureMapHighlightLayers.d.ts.map +1 -1
- package/dist/components/ArchitectureMapHighlightLayers.js +10 -1
- package/dist/components/CityViewWithReactFlow.d.ts.map +1 -1
- package/dist/components/CityViewWithReactFlow.js +2 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/render/client/drawLayeredBuildings.d.ts +4 -1
- package/dist/render/client/drawLayeredBuildings.d.ts.map +1 -1
- package/dist/render/client/drawLayeredBuildings.js +29 -82
- 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 +11 -1
- package/dist/utils/fileColorHighlightLayers.d.ts.map +1 -1
- package/dist/utils/fileColorHighlightLayers.js +28 -11
- package/dist/utils/fileColorOverrides.d.ts +19 -0
- package/dist/utils/fileColorOverrides.d.ts.map +1 -0
- package/dist/utils/fileColorOverrides.js +52 -0
- package/dist/utils/fileTypeIcons.d.ts +16 -0
- package/dist/utils/fileTypeIcons.d.ts.map +1 -0
- package/dist/utils/fileTypeIcons.js +91 -0
- package/dist/utils/lucideIconConverter.d.ts +31 -0
- package/dist/utils/lucideIconConverter.d.ts.map +1 -0
- package/dist/utils/lucideIconConverter.js +130 -0
- package/package.json +4 -2
- package/src/components/ArchitectureMapHighlightLayers.tsx +10 -0
- package/src/components/CityViewWithReactFlow.tsx +3 -1
- package/src/index.ts +7 -0
- package/src/render/client/drawLayeredBuildings.ts +33 -104
- package/src/stories/AllFileTypes.stories.tsx +389 -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 +44 -8
- package/src/utils/fileColorOverrides.ts +54 -0
- package/src/utils/fileTypeIcons.ts +116 -0
- package/src/utils/lucideIconConverter.tsx +149 -0
- package/src/config/files.json +0 -1012
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Cache for converted SVG data URLs
|
|
2
|
+
const svgCache = new Map<string, string>();
|
|
3
|
+
|
|
4
|
+
// Cache for loaded Image objects (to prevent flashing)
|
|
5
|
+
const imageCache = new Map<string, HTMLImageElement>();
|
|
6
|
+
|
|
7
|
+
// SVG path data for Lucide icons
|
|
8
|
+
// Source: https://github.com/lucide-icons/lucide
|
|
9
|
+
const ICON_PATHS: Record<string, string> = {
|
|
10
|
+
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"/>',
|
|
11
|
+
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"/>',
|
|
12
|
+
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"/>',
|
|
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
|
+
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
|
+
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"/>',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert a Lucide icon name to an SVG data URL that can be used in Canvas
|
|
31
|
+
* @param iconName - The name of the Lucide icon (e.g., "TestTube", "FileCode")
|
|
32
|
+
* @param color - The color to apply to the icon (default: "white")
|
|
33
|
+
* @param size - The size of the icon (default: 24)
|
|
34
|
+
* @returns SVG data URL string or null if icon not found
|
|
35
|
+
*/
|
|
36
|
+
export function lucideIconToDataUrl(
|
|
37
|
+
iconName: string,
|
|
38
|
+
color: string = 'white',
|
|
39
|
+
size: number = 24,
|
|
40
|
+
): string | null {
|
|
41
|
+
// Create cache key
|
|
42
|
+
const cacheKey = `${iconName}-${color}-${size}`;
|
|
43
|
+
|
|
44
|
+
// Check cache first
|
|
45
|
+
if (svgCache.has(cacheKey)) {
|
|
46
|
+
const cached = svgCache.get(cacheKey);
|
|
47
|
+
if (cached) {
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get the icon path data
|
|
53
|
+
const pathData = ICON_PATHS[iconName];
|
|
54
|
+
|
|
55
|
+
if (!pathData) {
|
|
56
|
+
console.warn(`[lucideIconConverter] Icon "${iconName}" not found. Available icons:`, Object.keys(ICON_PATHS));
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Create SVG string
|
|
62
|
+
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>`;
|
|
63
|
+
|
|
64
|
+
// Convert to data URL
|
|
65
|
+
const encodedSvg = encodeURIComponent(svgString);
|
|
66
|
+
const dataUrl = `data:image/svg+xml,${encodedSvg}`;
|
|
67
|
+
|
|
68
|
+
// Cache the result
|
|
69
|
+
svgCache.set(cacheKey, dataUrl);
|
|
70
|
+
|
|
71
|
+
return dataUrl;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(`[lucideIconConverter] Error converting icon "${iconName}":`, error);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Preload commonly used icons to improve performance
|
|
80
|
+
* Call this on app initialization
|
|
81
|
+
*/
|
|
82
|
+
export function preloadCommonIcons() {
|
|
83
|
+
const commonIcons = [
|
|
84
|
+
'TestTube',
|
|
85
|
+
'FileCode',
|
|
86
|
+
'FileText',
|
|
87
|
+
'File',
|
|
88
|
+
'Folder',
|
|
89
|
+
'Image',
|
|
90
|
+
'Database',
|
|
91
|
+
'Settings',
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
commonIcons.forEach(iconName => {
|
|
95
|
+
lucideIconToDataUrl(iconName);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get a cached Image object for an icon (prevents flashing on redraws)
|
|
101
|
+
* @param iconName - The name of the Lucide icon
|
|
102
|
+
* @param color - The color to apply to the icon (default: "white")
|
|
103
|
+
* @param size - The size of the icon (default: 24)
|
|
104
|
+
* @returns HTMLImageElement or null if icon not found
|
|
105
|
+
*/
|
|
106
|
+
export function getLucideIconImage(
|
|
107
|
+
iconName: string,
|
|
108
|
+
color: string = 'white',
|
|
109
|
+
size: number = 24,
|
|
110
|
+
): HTMLImageElement | null {
|
|
111
|
+
const cacheKey = `${iconName}-${color}-${size}`;
|
|
112
|
+
|
|
113
|
+
// Return cached image if available
|
|
114
|
+
if (imageCache.has(cacheKey)) {
|
|
115
|
+
const cached = imageCache.get(cacheKey);
|
|
116
|
+
if (cached) {
|
|
117
|
+
return cached;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get the data URL
|
|
122
|
+
const dataUrl = lucideIconToDataUrl(iconName, color, size);
|
|
123
|
+
if (!dataUrl) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create and cache the image
|
|
128
|
+
const img = new Image();
|
|
129
|
+
img.src = dataUrl;
|
|
130
|
+
imageCache.set(cacheKey, img);
|
|
131
|
+
|
|
132
|
+
return img;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Clear the SVG cache (useful for memory management)
|
|
137
|
+
*/
|
|
138
|
+
export function clearIconCache() {
|
|
139
|
+
svgCache.clear();
|
|
140
|
+
imageCache.clear();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get available Lucide icon names
|
|
145
|
+
* @returns Array of all available icon names
|
|
146
|
+
*/
|
|
147
|
+
export function getAvailableIcons(): string[] {
|
|
148
|
+
return Object.keys(ICON_PATHS);
|
|
149
|
+
}
|