@principal-ai/file-city-react 0.3.0

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 (51) hide show
  1. package/dist/builder/cityDataUtils.d.ts +15 -0
  2. package/dist/builder/cityDataUtils.d.ts.map +1 -0
  3. package/dist/builder/cityDataUtils.js +348 -0
  4. package/dist/components/ArchitectureMapHighlightLayers.d.ts +63 -0
  5. package/dist/components/ArchitectureMapHighlightLayers.d.ts.map +1 -0
  6. package/dist/components/ArchitectureMapHighlightLayers.js +1040 -0
  7. package/dist/components/CityViewWithReactFlow.d.ts +14 -0
  8. package/dist/components/CityViewWithReactFlow.d.ts.map +1 -0
  9. package/dist/components/CityViewWithReactFlow.js +266 -0
  10. package/dist/config/files.json +996 -0
  11. package/dist/hooks/useCodeCityData.d.ts +21 -0
  12. package/dist/hooks/useCodeCityData.d.ts.map +1 -0
  13. package/dist/hooks/useCodeCityData.js +57 -0
  14. package/dist/index.d.ts +14 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +29 -0
  17. package/dist/render/client/drawLayeredBuildings.d.ts +51 -0
  18. package/dist/render/client/drawLayeredBuildings.d.ts.map +1 -0
  19. package/dist/render/client/drawLayeredBuildings.js +650 -0
  20. package/dist/stories/ArchitectureMapGridLayout.stories.d.ts +73 -0
  21. package/dist/stories/ArchitectureMapGridLayout.stories.d.ts.map +1 -0
  22. package/dist/stories/ArchitectureMapGridLayout.stories.js +345 -0
  23. package/dist/stories/ArchitectureMapHighlightLayers.stories.d.ts +78 -0
  24. package/dist/stories/ArchitectureMapHighlightLayers.stories.d.ts.map +1 -0
  25. package/dist/stories/ArchitectureMapHighlightLayers.stories.js +270 -0
  26. package/dist/stories/CityViewWithReactFlow.stories.d.ts +24 -0
  27. package/dist/stories/CityViewWithReactFlow.stories.d.ts.map +1 -0
  28. package/dist/stories/CityViewWithReactFlow.stories.js +778 -0
  29. package/dist/stories/sample-data.d.ts +4 -0
  30. package/dist/stories/sample-data.d.ts.map +1 -0
  31. package/dist/stories/sample-data.js +268 -0
  32. package/dist/types/react-types.d.ts +17 -0
  33. package/dist/types/react-types.d.ts.map +1 -0
  34. package/dist/types/react-types.js +4 -0
  35. package/dist/utils/fileColorHighlightLayers.d.ts +86 -0
  36. package/dist/utils/fileColorHighlightLayers.d.ts.map +1 -0
  37. package/dist/utils/fileColorHighlightLayers.js +283 -0
  38. package/package.json +49 -0
  39. package/src/builder/cityDataUtils.ts +430 -0
  40. package/src/components/ArchitectureMapHighlightLayers.tsx +1518 -0
  41. package/src/components/CityViewWithReactFlow.tsx +365 -0
  42. package/src/config/files.json +996 -0
  43. package/src/hooks/useCodeCityData.ts +82 -0
  44. package/src/index.ts +64 -0
  45. package/src/render/client/drawLayeredBuildings.ts +946 -0
  46. package/src/stories/ArchitectureMapGridLayout.stories.tsx +410 -0
  47. package/src/stories/ArchitectureMapHighlightLayers.stories.tsx +312 -0
  48. package/src/stories/CityViewWithReactFlow.stories.tsx +787 -0
  49. package/src/stories/sample-data.ts +301 -0
  50. package/src/types/react-types.ts +18 -0
  51. package/src/utils/fileColorHighlightLayers.ts +378 -0
@@ -0,0 +1,365 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import ReactFlow, {
3
+ Node,
4
+ Edge,
5
+ ReactFlowProvider,
6
+ Controls,
7
+ Background,
8
+ MiniMap,
9
+ NodeProps,
10
+ Handle,
11
+ Position,
12
+ useNodesState,
13
+ useEdgesState,
14
+ BackgroundVariant,
15
+ } from 'reactflow';
16
+ import 'reactflow/dist/style.css';
17
+ import { useTheme } from '@principal-ade/industry-theme';
18
+ import { FileTree } from '@principal-ai/repository-abstraction';
19
+ import { CodebaseView } from '@principal-ai/alexandria-core-library';
20
+ import { GridLayoutManager, CodeCityBuilderWithGrid } from '@principal-ai/file-city-builder';
21
+ import { ArchitectureMapHighlightLayers } from './ArchitectureMapHighlightLayers';
22
+
23
+ export interface CityViewWithReactFlowProps {
24
+ fileTree: FileTree;
25
+ gridConfig?: CodebaseView;
26
+ onCellClick?: (cellId: string) => void;
27
+ cellWidth?: number;
28
+ cellHeight?: number;
29
+ cellSpacing?: number;
30
+ }
31
+
32
+ interface CellNodeData {
33
+ label: string;
34
+ fileTree: FileTree;
35
+ fileCount: number;
36
+ directoryCount: number;
37
+ coordinates: [number, number];
38
+ }
39
+
40
+ const CellNode: React.FC<NodeProps<CellNodeData>> = ({ data, selected }) => {
41
+ const { label, fileTree, fileCount, directoryCount } = data;
42
+ const { theme } = useTheme();
43
+
44
+ // Build city data for this cell's file tree
45
+ const cityBuilder = useMemo(() => new CodeCityBuilderWithGrid(), []);
46
+ const cityData = useMemo(() => {
47
+ if (!fileTree || !fileTree.root || fileTree.root.children.length === 0) {
48
+ return null;
49
+ }
50
+
51
+ try {
52
+ const result = cityBuilder.buildCityFromFileSystem(fileTree);
53
+ return result;
54
+ } catch (error) {
55
+ // Silently fail for production
56
+ return null;
57
+ }
58
+ }, [fileTree, cityBuilder]);
59
+
60
+ return (
61
+ <div
62
+ className="cell-node"
63
+ style={{
64
+ background: selected
65
+ ? `linear-gradient(135deg, ${theme.colors.primary} 0%, ${theme.colors.accent} 100%)`
66
+ : `linear-gradient(135deg, ${theme.colors.backgroundSecondary} 0%, ${theme.colors.background} 100%)`,
67
+ borderRadius: `${theme.radii[2]}px`,
68
+ padding: `${theme.space[3]}px`,
69
+ width: '450px',
70
+ height: '450px',
71
+ border: selected ? `3px solid ${theme.colors.accent}` : `2px solid ${theme.colors.border}`,
72
+ boxShadow: selected
73
+ ? `0 10px 25px ${theme.colors.accent}30`
74
+ : '0 4px 6px rgba(0, 0, 0, 0.1)',
75
+ display: 'flex',
76
+ flexDirection: 'column',
77
+ transition: 'all 0.3s ease',
78
+ }}
79
+ >
80
+ <Handle type="target" position={Position.Top} style={{ background: theme.colors.accent }} />
81
+
82
+ {/* Header with title and stats */}
83
+ <div
84
+ style={{
85
+ marginBottom: `${theme.space[2]}px`,
86
+ paddingBottom: `${theme.space[2]}px`,
87
+ borderBottom: `1px solid ${theme.colors.border}`,
88
+ }}
89
+ >
90
+ <h3
91
+ style={{
92
+ margin: `0 0 ${theme.space[1]}px 0`,
93
+ fontSize: `${theme.fontSizes[2]}px`,
94
+ fontWeight: theme.fontWeights.bold,
95
+ fontFamily: theme.fonts.heading,
96
+ color: theme.colors.text,
97
+ }}
98
+ >
99
+ {label}
100
+ </h3>
101
+ <div
102
+ style={{
103
+ fontSize: `${theme.fontSizes[0]}px`,
104
+ fontFamily: theme.fonts.body,
105
+ color: theme.colors.textSecondary,
106
+ display: 'flex',
107
+ gap: `${theme.space[3]}px`,
108
+ }}
109
+ >
110
+ <span>{fileCount} files</span>
111
+ <span>{directoryCount} directories</span>
112
+ </div>
113
+ </div>
114
+
115
+ {/* 3D City Visualization */}
116
+ <div
117
+ style={{
118
+ position: 'relative',
119
+ background: theme.colors.background,
120
+ borderRadius: `${theme.radii[1]}px`,
121
+ overflow: 'hidden',
122
+ width: '350px',
123
+ height: '350px',
124
+ }}
125
+ >
126
+ {cityData ? (
127
+ <div
128
+ style={{ width: '100%', height: '100%', position: 'relative', pointerEvents: 'none' }}
129
+ >
130
+ <ArchitectureMapHighlightLayers
131
+ cityData={cityData}
132
+ highlightLayers={[
133
+ {
134
+ id: 'typescript',
135
+ name: 'TypeScript Files',
136
+ enabled: true,
137
+ color: theme.colors.success,
138
+ opacity: 0.8,
139
+ priority: 1,
140
+ items: cityData.buildings
141
+ .filter(b => b.path.endsWith('.ts') || b.path.endsWith('.tsx'))
142
+ .map(b => ({
143
+ path: b.path,
144
+ type: 'file' as const,
145
+ opacity: 1,
146
+ })),
147
+ },
148
+ ]}
149
+ showGrid={false}
150
+ showDirectoryLabels={false}
151
+ canvasBackgroundColor={theme.colors.background}
152
+ defaultBuildingColor={theme.colors.muted}
153
+ defaultDirectoryColor={theme.colors.backgroundSecondary}
154
+ className="w-full h-full"
155
+ // Disable interactions to prevent conflicts with React Flow
156
+ onFileClick={undefined}
157
+ />
158
+ </div>
159
+ ) : (
160
+ <div
161
+ style={{
162
+ display: 'flex',
163
+ alignItems: 'center',
164
+ justifyContent: 'center',
165
+ height: '100%',
166
+ color: theme.colors.textMuted,
167
+ fontSize: `${theme.fontSizes[1]}px`,
168
+ fontFamily: theme.fonts.body,
169
+ }}
170
+ >
171
+ Empty cell
172
+ </div>
173
+ )}
174
+ </div>
175
+
176
+ <Handle
177
+ type="source"
178
+ position={Position.Bottom}
179
+ style={{ background: theme.colors.accent }}
180
+ />
181
+ </div>
182
+ );
183
+ };
184
+
185
+ const nodeTypes = {
186
+ cellNode: CellNode,
187
+ };
188
+
189
+ const CityViewWithReactFlowInner: React.FC<CityViewWithReactFlowProps> = ({
190
+ fileTree,
191
+ gridConfig,
192
+ onCellClick,
193
+ cellWidth = 450,
194
+ cellHeight = 350,
195
+ cellSpacing = 100,
196
+ }) => {
197
+ const { theme } = useTheme();
198
+
199
+ const defaultGridConfig: CodebaseView = {
200
+ id: 'default',
201
+ version: '1.0',
202
+ name: 'Default Grid',
203
+ description: 'Default single-cell grid layout',
204
+ overviewPath: 'README.md',
205
+ category: 'default',
206
+ displayOrder: 0,
207
+ referenceGroups: {
208
+ main: {
209
+ files: ['*'],
210
+ coordinates: [0, 0],
211
+ },
212
+ },
213
+ metadata: {
214
+ ui: {
215
+ enabled: true,
216
+ rows: 1,
217
+ cols: 1,
218
+ },
219
+ },
220
+ };
221
+
222
+ const config = gridConfig || defaultGridConfig;
223
+
224
+ const { nodes, edges } = useMemo(() => {
225
+ const gridManager = new GridLayoutManager();
226
+ const { rows, cols } = gridManager.getGridDimensions(config);
227
+
228
+ const gridTrees = gridManager.splitTreeIntoGrid(fileTree, config);
229
+
230
+ const generatedNodes: Node[] = [];
231
+ const generatedEdges: Edge[] = [];
232
+
233
+ // Create cell nodes
234
+ gridTrees.forEach((cellTree: FileTree, cellKey: string) => {
235
+ const [row, col] = cellKey.split(',').map(Number);
236
+
237
+ // Find cell name from config
238
+ let cellName = 'Cell';
239
+ for (const [name, cellConfig] of Object.entries(config.referenceGroups)) {
240
+ if (cellConfig.coordinates[0] === row && cellConfig.coordinates[1] === col) {
241
+ cellName = name;
242
+ break;
243
+ }
244
+ }
245
+
246
+ const cellNode: Node<CellNodeData> = {
247
+ id: `cell-${row}-${col}`,
248
+ type: 'cellNode',
249
+ position: {
250
+ x: col * (cellWidth + cellSpacing),
251
+ y: row * (cellHeight + cellSpacing),
252
+ },
253
+ data: {
254
+ label: cellName,
255
+ fileTree: cellTree,
256
+ fileCount: cellTree.stats.totalFiles,
257
+ directoryCount: cellTree.stats.totalDirectories,
258
+ coordinates: [row, col],
259
+ },
260
+ };
261
+
262
+ generatedNodes.push(cellNode);
263
+ });
264
+
265
+ // Create subtle grid connections (optional)
266
+ for (let row = 0; row < rows; row++) {
267
+ for (let col = 0; col < cols; col++) {
268
+ // Only add edges if both nodes exist (non-empty cells)
269
+ const currentNode = generatedNodes.find(n => n.id === `cell-${row}-${col}`);
270
+ if (!currentNode) continue;
271
+
272
+ // Horizontal connections
273
+ if (col < cols - 1) {
274
+ const rightNode = generatedNodes.find(n => n.id === `cell-${row}-${col + 1}`);
275
+ if (rightNode) {
276
+ generatedEdges.push({
277
+ id: `edge-${row}-${col}-to-${row}-${col + 1}`,
278
+ source: `cell-${row}-${col}`,
279
+ target: `cell-${row}-${col + 1}`,
280
+ type: 'straight',
281
+ animated: false,
282
+ style: {
283
+ stroke: `${theme.colors.border}40`,
284
+ strokeWidth: 1,
285
+ strokeDasharray: '5 10',
286
+ },
287
+ });
288
+ }
289
+ }
290
+
291
+ // Vertical connections
292
+ if (row < rows - 1) {
293
+ const bottomNode = generatedNodes.find(n => n.id === `cell-${row + 1}-${col}`);
294
+ if (bottomNode) {
295
+ generatedEdges.push({
296
+ id: `edge-${row}-${col}-to-${row + 1}-${col}`,
297
+ source: `cell-${row}-${col}`,
298
+ target: `cell-${row + 1}-${col}`,
299
+ type: 'straight',
300
+ animated: false,
301
+ style: {
302
+ stroke: `${theme.colors.border}40`,
303
+ strokeWidth: 1,
304
+ strokeDasharray: '5 10',
305
+ },
306
+ });
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ return { nodes: generatedNodes, edges: generatedEdges };
313
+ }, [fileTree, config, cellWidth, cellHeight, cellSpacing, theme]);
314
+
315
+ const [nodesState, , onNodesChange] = useNodesState(nodes);
316
+ const [edgesState, , onEdgesChange] = useEdgesState(edges);
317
+
318
+ const onNodeClick = useCallback(
319
+ (_event: React.MouseEvent, node: Node) => {
320
+ if (node.type === 'cellNode' && onCellClick) {
321
+ onCellClick(node.id);
322
+ }
323
+ },
324
+ [onCellClick],
325
+ );
326
+
327
+ return (
328
+ <div style={{ width: '100%', height: '100%', background: theme.colors.background }}>
329
+ <ReactFlowProvider>
330
+ <ReactFlow
331
+ nodes={nodesState}
332
+ edges={edgesState}
333
+ onNodesChange={onNodesChange}
334
+ onEdgesChange={onEdgesChange}
335
+ onNodeClick={onNodeClick}
336
+ nodeTypes={nodeTypes}
337
+ fitView
338
+ fitViewOptions={{ padding: 0.2 }}
339
+ attributionPosition="bottom-left"
340
+ defaultViewport={{ x: 0, y: 0, zoom: 0.8 }}
341
+ minZoom={0.2}
342
+ maxZoom={2}
343
+ >
344
+ <Background
345
+ variant={BackgroundVariant.Dots}
346
+ gap={30}
347
+ size={1}
348
+ color={`${theme.colors.border}40`}
349
+ />
350
+ <Controls />
351
+ <MiniMap
352
+ nodeColor={() => theme.colors.primary}
353
+ style={{
354
+ backgroundColor: theme.colors.backgroundSecondary,
355
+ border: `1px solid ${theme.colors.border}`,
356
+ }}
357
+ maskColor="rgba(0, 0, 0, 0.6)"
358
+ />
359
+ </ReactFlow>
360
+ </ReactFlowProvider>
361
+ </div>
362
+ );
363
+ };
364
+
365
+ export const CityViewWithReactFlow = CityViewWithReactFlowInner;