@principal-ai/file-city-react 0.4.8 → 0.5.1
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/render/client/drawLayeredBuildings.d.ts +2 -35
- package/dist/render/client/drawLayeredBuildings.d.ts.map +1 -1
- package/dist/render/client/drawLayeredBuildings.js +17 -5
- package/package.json +2 -2
- package/src/render/client/drawLayeredBuildings.ts +26 -52
- package/src/stories/AllFileTypes.stories.tsx +5 -3
- package/src/stories/ArchitectureMapGridLayout.stories.tsx +1 -1
- package/src/stories/ArchitectureMapHighlightLayers.stories.tsx +2 -3
- package/src/stories/CityViewWithReactFlow.stories.tsx +1 -1
- package/src/stories/StressTest.stories.tsx +1 -1
|
@@ -1,39 +1,6 @@
|
|
|
1
|
-
import { CityBuilding, CityDistrict } from '@principal-ai/file-city-builder';
|
|
1
|
+
import { CityBuilding, CityDistrict, LayerItem, LayerRenderStrategy, HighlightLayer } from '@principal-ai/file-city-builder';
|
|
2
2
|
import { FileTypeIconConfig } from '../../utils/fileColorHighlightLayers';
|
|
3
|
-
export type
|
|
4
|
-
export interface LayerItem {
|
|
5
|
-
path: string;
|
|
6
|
-
type: 'file' | 'directory';
|
|
7
|
-
renderStrategy?: LayerRenderStrategy;
|
|
8
|
-
coverOptions?: {
|
|
9
|
-
opacity?: number;
|
|
10
|
-
image?: string;
|
|
11
|
-
text?: string;
|
|
12
|
-
textSize?: number;
|
|
13
|
-
backgroundColor?: string;
|
|
14
|
-
borderRadius?: number;
|
|
15
|
-
icon?: string;
|
|
16
|
-
iconSize?: number;
|
|
17
|
-
lucideIcon?: string;
|
|
18
|
-
};
|
|
19
|
-
customRender?: (ctx: CanvasRenderingContext2D, bounds: {
|
|
20
|
-
x: number;
|
|
21
|
-
y: number;
|
|
22
|
-
width: number;
|
|
23
|
-
height: number;
|
|
24
|
-
}, scale: number) => void;
|
|
25
|
-
}
|
|
26
|
-
export interface HighlightLayer {
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
enabled: boolean;
|
|
30
|
-
color: string;
|
|
31
|
-
opacity?: number;
|
|
32
|
-
borderWidth?: number;
|
|
33
|
-
priority: number;
|
|
34
|
-
items: LayerItem[];
|
|
35
|
-
dynamic?: boolean;
|
|
36
|
-
}
|
|
3
|
+
export type { LayerItem, LayerRenderStrategy, HighlightLayer };
|
|
37
4
|
/**
|
|
38
5
|
* LayerIndex provides O(1) path lookups instead of O(n) iteration.
|
|
39
6
|
* This dramatically improves performance with large numbers of layer items.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawLayeredBuildings.d.ts","sourceRoot":"","sources":["../../../src/render/client/drawLayeredBuildings.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"drawLayeredBuildings.d.ts","sourceRoot":"","sources":["../../../src/render/client/drawLayeredBuildings.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,mBAAmB,EACnB,cAAc,EACf,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAI1E,YAAY,EAAE,SAAS,EAAE,mBAAmB,EAAE,cAAc,EAAE,CAAC;AAE/D;;;GAGG;AACH,qBAAa,UAAU;IAErB,OAAO,CAAC,UAAU,CAA6E;IAE/F,OAAO,CAAC,cAAc,CAAuE;IAE7F,OAAO,CAAC,WAAW,CAA6E;gBAEpF,MAAM,EAAE,cAAc,EAAE;IAIpC,OAAO,CAAC,UAAU;IA0BlB;;;;OAIG;IACH,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,OAAO,GAAG,UAAuB,GAC3C,KAAK,CAAC;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC;CAuCrD;AA8BD,wBAAgB,QAAQ,CACtB,GAAG,EAAE,wBAAwB,EAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,QAqBjB;AA0VD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,wBAAwB,EAC7B,SAAS,EAAE,YAAY,EAAE,EACzB,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACjE,KAAK,EAAE,MAAM,EAAE,wDAAwD;AACvE,MAAM,EAAE,cAAc,EAAE,EACxB,eAAe,CAAC,EAAE,YAAY,GAAG,IAAI,EACrC,QAAQ,CAAC,EAAE,OAAO,EAClB,qBAAqB,CAAC,EAAE,MAAM,EAC9B,YAAY,CAAC,EAAE;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,EACD,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,yDAAyD;AACxF,mBAAmB,GAAE,OAAc,EACnC,YAAY,GAAE,MAAU,EAAE,uDAAuD;AACjF,UAAU,CAAC,EAAE,UAAU,QAgQxB;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,wBAAwB,EAC7B,SAAS,EAAE,YAAY,EAAE,EACzB,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACjE,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,EAAE,EACxB,eAAe,CAAC,EAAE,YAAY,GAAG,IAAI,EACrC,oBAAoB,CAAC,EAAE,MAAM,EAC7B,aAAa,CAAC,EAAE,OAAO,EACvB,gBAAgB,CAAC,EAAE,MAAM,EACzB,qBAAqB,CAAC,EAAE,OAAO,EAC/B,iBAAiB,CAAC,EAAE,OAAO,EAC3B,YAAY,GAAE,MAAU,EAAE,2DAA2D;AACrF,UAAU,CAAC,EAAE,UAAU,EAAE,2CAA2C;AACpE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,QA0K1C"}
|
|
@@ -655,12 +655,16 @@ iconMap) {
|
|
|
655
655
|
// Draw file type icon if enabled and icon map is provided
|
|
656
656
|
// Track icon size for positioning file name below
|
|
657
657
|
let iconBottomY = pos.y;
|
|
658
|
+
let hasIcon = false;
|
|
658
659
|
if (showFileTypeIcons && iconMap) {
|
|
659
660
|
const iconConfig = (0, fileTypeIcons_1.getFileTypeIcon)(building.path, iconMap);
|
|
660
661
|
if (iconConfig) {
|
|
662
|
+
hasIcon = true;
|
|
661
663
|
// For wide buildings (wider than tall), shift icon up to make room for text below
|
|
664
|
+
// Only shift if text will actually be rendered
|
|
665
|
+
const willShowText = showFileNames && width > 100 && height > 30;
|
|
662
666
|
const isWide = width > height;
|
|
663
|
-
const iconYOffset = isWide ? -height * 0.15 : 0;
|
|
667
|
+
const iconYOffset = (willShowText && isWide) ? -height * 0.15 : 0;
|
|
664
668
|
const iconY = pos.y + iconYOffset;
|
|
665
669
|
(0, fileTypeIcons_1.drawFileTypeIcon)(ctx, iconConfig, pos.x, iconY, width, height);
|
|
666
670
|
// Calculate where the icon ends vertically
|
|
@@ -691,13 +695,21 @@ iconMap) {
|
|
|
691
695
|
const fontSize = Math.min(30, Math.max(10, Math.floor(Math.min(width, height) * 0.3)));
|
|
692
696
|
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
693
697
|
ctx.textAlign = 'center';
|
|
694
|
-
ctx.textBaseline = 'top'; // Changed from 'middle' to 'top'
|
|
695
698
|
const textMetrics = ctx.measureText(fileName);
|
|
696
699
|
const textWidth = textMetrics.width;
|
|
697
700
|
if (textWidth < width - 8) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
+
let textY;
|
|
702
|
+
if (hasIcon) {
|
|
703
|
+
// Position text below icon
|
|
704
|
+
ctx.textBaseline = 'top';
|
|
705
|
+
const spacing = 4;
|
|
706
|
+
textY = iconBottomY + spacing;
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
// Center text vertically when no icon
|
|
710
|
+
ctx.textBaseline = 'middle';
|
|
711
|
+
textY = pos.y;
|
|
712
|
+
}
|
|
701
713
|
// Text
|
|
702
714
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
|
|
703
715
|
ctx.fillText(fileName, pos.x, textY);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principal-ai/file-city-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "React components for File City visualization",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@principal-ade/industry-theme": "^0.1.3",
|
|
16
16
|
"@principal-ai/alexandria-core-library": "^0.1.36",
|
|
17
|
-
"@principal-ai/file-city-builder": "^0.
|
|
17
|
+
"@principal-ai/file-city-builder": "^0.4.0",
|
|
18
18
|
"lucide-react": "^0.563.0",
|
|
19
19
|
"reactflow": "^11.11.4"
|
|
20
20
|
},
|
|
@@ -1,54 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CityBuilding,
|
|
3
|
+
CityDistrict,
|
|
4
|
+
LayerItem,
|
|
5
|
+
LayerRenderStrategy,
|
|
6
|
+
HighlightLayer,
|
|
7
|
+
} from '@principal-ai/file-city-builder';
|
|
2
8
|
import { getLucideIconImage } from '../../utils/lucideIconConverter';
|
|
3
9
|
import { FileTypeIconConfig } from '../../utils/fileColorHighlightLayers';
|
|
4
10
|
import { getFileTypeIcon, drawFileTypeIcon } from '../../utils/fileTypeIcons';
|
|
5
11
|
|
|
6
|
-
//
|
|
7
|
-
export type LayerRenderStrategy
|
|
8
|
-
| 'border'
|
|
9
|
-
| 'fill'
|
|
10
|
-
| 'glow'
|
|
11
|
-
| 'pattern'
|
|
12
|
-
| 'cover'
|
|
13
|
-
| 'icon'
|
|
14
|
-
| 'custom';
|
|
15
|
-
|
|
16
|
-
export interface LayerItem {
|
|
17
|
-
path: string;
|
|
18
|
-
type: 'file' | 'directory';
|
|
19
|
-
renderStrategy?: LayerRenderStrategy;
|
|
20
|
-
// Cover-specific options
|
|
21
|
-
coverOptions?: {
|
|
22
|
-
opacity?: number;
|
|
23
|
-
image?: string;
|
|
24
|
-
text?: string;
|
|
25
|
-
textSize?: number;
|
|
26
|
-
backgroundColor?: string;
|
|
27
|
-
borderRadius?: number;
|
|
28
|
-
icon?: string;
|
|
29
|
-
iconSize?: number;
|
|
30
|
-
lucideIcon?: string;
|
|
31
|
-
};
|
|
32
|
-
// Custom render function
|
|
33
|
-
customRender?: (
|
|
34
|
-
ctx: CanvasRenderingContext2D,
|
|
35
|
-
bounds: { x: number; y: number; width: number; height: number },
|
|
36
|
-
scale: number,
|
|
37
|
-
) => void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface HighlightLayer {
|
|
41
|
-
id: string;
|
|
42
|
-
name: string;
|
|
43
|
-
enabled: boolean;
|
|
44
|
-
color: string;
|
|
45
|
-
opacity?: number;
|
|
46
|
-
borderWidth?: number;
|
|
47
|
-
priority: number; // Higher priority layers render on top
|
|
48
|
-
items: LayerItem[];
|
|
49
|
-
// Performance optimization - mark frequently changing layers as dynamic
|
|
50
|
-
dynamic?: boolean; // If true, this layer changes frequently (e.g., hover effects)
|
|
51
|
-
}
|
|
12
|
+
// Re-export for backward compatibility
|
|
13
|
+
export type { LayerItem, LayerRenderStrategy, HighlightLayer };
|
|
52
14
|
|
|
53
15
|
/**
|
|
54
16
|
* LayerIndex provides O(1) path lookups instead of O(n) iteration.
|
|
@@ -932,12 +894,16 @@ export function drawLayeredBuildings(
|
|
|
932
894
|
// Draw file type icon if enabled and icon map is provided
|
|
933
895
|
// Track icon size for positioning file name below
|
|
934
896
|
let iconBottomY = pos.y;
|
|
897
|
+
let hasIcon = false;
|
|
935
898
|
if (showFileTypeIcons && iconMap) {
|
|
936
899
|
const iconConfig = getFileTypeIcon(building.path, iconMap);
|
|
937
900
|
if (iconConfig) {
|
|
901
|
+
hasIcon = true;
|
|
938
902
|
// For wide buildings (wider than tall), shift icon up to make room for text below
|
|
903
|
+
// Only shift if text will actually be rendered
|
|
904
|
+
const willShowText = showFileNames && width > 100 && height > 30;
|
|
939
905
|
const isWide = width > height;
|
|
940
|
-
const iconYOffset = isWide ? -height * 0.15 : 0;
|
|
906
|
+
const iconYOffset = (willShowText && isWide) ? -height * 0.15 : 0;
|
|
941
907
|
const iconY = pos.y + iconYOffset;
|
|
942
908
|
|
|
943
909
|
drawFileTypeIcon(ctx, iconConfig, pos.x, iconY, width, height);
|
|
@@ -971,15 +937,23 @@ export function drawLayeredBuildings(
|
|
|
971
937
|
const fontSize = Math.min(30, Math.max(10, Math.floor(Math.min(width, height) * 0.3)));
|
|
972
938
|
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
973
939
|
ctx.textAlign = 'center';
|
|
974
|
-
ctx.textBaseline = 'top'; // Changed from 'middle' to 'top'
|
|
975
940
|
|
|
976
941
|
const textMetrics = ctx.measureText(fileName);
|
|
977
942
|
const textWidth = textMetrics.width;
|
|
978
943
|
|
|
979
944
|
if (textWidth < width - 8) {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
945
|
+
let textY: number;
|
|
946
|
+
|
|
947
|
+
if (hasIcon) {
|
|
948
|
+
// Position text below icon
|
|
949
|
+
ctx.textBaseline = 'top';
|
|
950
|
+
const spacing = 4;
|
|
951
|
+
textY = iconBottomY + spacing;
|
|
952
|
+
} else {
|
|
953
|
+
// Center text vertically when no icon
|
|
954
|
+
ctx.textBaseline = 'middle';
|
|
955
|
+
textY = pos.y;
|
|
956
|
+
}
|
|
983
957
|
|
|
984
958
|
// Text
|
|
985
959
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
|
|
@@ -11,7 +11,7 @@ const meta = {
|
|
|
11
11
|
layout: 'fullscreen',
|
|
12
12
|
},
|
|
13
13
|
decorators: [
|
|
14
|
-
Story => (
|
|
14
|
+
(Story: React.ComponentType) => (
|
|
15
15
|
<div style={{ width: '100vw', height: '100vh', backgroundColor: '#1a1a1a' }}>
|
|
16
16
|
<Story />
|
|
17
17
|
</div>
|
|
@@ -199,7 +199,7 @@ function createAllFileTypesCityData() {
|
|
|
199
199
|
};
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
const fileTree = buildFileSystemTreeFromFileInfoList(fileInfos);
|
|
202
|
+
const fileTree = buildFileSystemTreeFromFileInfoList(fileInfos, 'all-file-types');
|
|
203
203
|
const builder = new CodeCityBuilderWithGrid();
|
|
204
204
|
return builder.buildCityFromFileSystem(fileTree, '', {
|
|
205
205
|
paddingTop: 2,
|
|
@@ -230,6 +230,8 @@ export const AllFileTypesWithColors: Story = {
|
|
|
230
230
|
enableZoom={true}
|
|
231
231
|
buildingBorderRadius={2}
|
|
232
232
|
districtBorderRadius={4}
|
|
233
|
+
showFileTypeIcons={true}
|
|
234
|
+
showFileNames={true}
|
|
233
235
|
/>
|
|
234
236
|
<div
|
|
235
237
|
style={{
|
|
@@ -288,7 +290,7 @@ export const TestFilesWithIcons: Story = {
|
|
|
288
290
|
};
|
|
289
291
|
});
|
|
290
292
|
|
|
291
|
-
const fileTree = buildFileSystemTreeFromFileInfoList(fileInfos);
|
|
293
|
+
const fileTree = buildFileSystemTreeFromFileInfoList(fileInfos, 'test-files');
|
|
292
294
|
const builder = new CodeCityBuilderWithGrid();
|
|
293
295
|
const cityData = builder.buildCityFromFileSystem(fileTree, '', {
|
|
294
296
|
paddingTop: 2,
|
|
@@ -12,7 +12,7 @@ const meta = {
|
|
|
12
12
|
layout: 'fullscreen',
|
|
13
13
|
},
|
|
14
14
|
decorators: [
|
|
15
|
-
Story => (
|
|
15
|
+
(Story: React.ComponentType) => (
|
|
16
16
|
<div style={{ width: '100vw', height: '100vh', backgroundColor: '#1a1a1a' }}>
|
|
17
17
|
<Story />
|
|
18
18
|
</div>
|
|
@@ -200,7 +200,6 @@ export const WithAbstractionLayer: Story = {
|
|
|
200
200
|
color: '#1e40af',
|
|
201
201
|
priority: 0,
|
|
202
202
|
items: [],
|
|
203
|
-
// @ts-expect-error - abstraction layer specific properties
|
|
204
203
|
abstractionLayer: true,
|
|
205
204
|
abstractionConfig: {
|
|
206
205
|
maxZoomLevel: 2.0,
|
|
@@ -208,7 +207,7 @@ export const WithAbstractionLayer: Story = {
|
|
|
208
207
|
backgroundColor: '#1e40af',
|
|
209
208
|
allowRootAbstraction: false,
|
|
210
209
|
},
|
|
211
|
-
},
|
|
210
|
+
} as HighlightLayer,
|
|
212
211
|
],
|
|
213
212
|
fullSize: true,
|
|
214
213
|
enableZoom: true,
|