@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.
Files changed (41) hide show
  1. package/dist/components/ArchitectureMapHighlightLayers.d.ts.map +1 -1
  2. package/dist/components/ArchitectureMapHighlightLayers.js +10 -1
  3. package/dist/components/CityViewWithReactFlow.d.ts.map +1 -1
  4. package/dist/components/CityViewWithReactFlow.js +2 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +10 -1
  8. package/dist/render/client/drawLayeredBuildings.d.ts +4 -1
  9. package/dist/render/client/drawLayeredBuildings.d.ts.map +1 -1
  10. package/dist/render/client/drawLayeredBuildings.js +29 -82
  11. package/dist/stories/sample-data.d.ts.map +1 -1
  12. package/dist/stories/stress-test-data.d.ts.map +1 -1
  13. package/dist/stories/stress-test-data.js +4 -3
  14. package/dist/utils/fileColorHighlightLayers.d.ts +11 -1
  15. package/dist/utils/fileColorHighlightLayers.d.ts.map +1 -1
  16. package/dist/utils/fileColorHighlightLayers.js +28 -11
  17. package/dist/utils/fileColorOverrides.d.ts +19 -0
  18. package/dist/utils/fileColorOverrides.d.ts.map +1 -0
  19. package/dist/utils/fileColorOverrides.js +52 -0
  20. package/dist/utils/fileTypeIcons.d.ts +16 -0
  21. package/dist/utils/fileTypeIcons.d.ts.map +1 -0
  22. package/dist/utils/fileTypeIcons.js +91 -0
  23. package/dist/utils/lucideIconConverter.d.ts +31 -0
  24. package/dist/utils/lucideIconConverter.d.ts.map +1 -0
  25. package/dist/utils/lucideIconConverter.js +130 -0
  26. package/package.json +4 -2
  27. package/src/components/ArchitectureMapHighlightLayers.tsx +10 -0
  28. package/src/components/CityViewWithReactFlow.tsx +3 -1
  29. package/src/index.ts +7 -0
  30. package/src/render/client/drawLayeredBuildings.ts +33 -104
  31. package/src/stories/AllFileTypes.stories.tsx +389 -0
  32. package/src/stories/ArchitectureMapHighlightLayers.stories.tsx +1 -1
  33. package/src/stories/CityViewWithReactFlow.stories.tsx +1 -0
  34. package/src/stories/StressTest.stories.tsx +0 -4
  35. package/src/stories/sample-data.ts +3 -13
  36. package/src/stories/stress-test-data.ts +6 -15
  37. package/src/utils/fileColorHighlightLayers.ts +44 -8
  38. package/src/utils/fileColorOverrides.ts +54 -0
  39. package/src/utils/fileTypeIcons.ts +116 -0
  40. package/src/utils/lucideIconConverter.tsx +149 -0
  41. 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
+ }