archrip 0.1.7 → 0.1.9
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/schema/architecture.schema.json +1 -1
- package/dist/templates/slash-commands/claude/archrip-scan.md +26 -11
- package/dist/templates/slash-commands/codex/archrip-scan.md +26 -11
- package/dist/templates/slash-commands/gemini/archrip-scan.md +26 -11
- package/dist/viewer-template/package.json +1 -0
- package/dist/viewer-template/src/App.tsx +6 -5
- package/dist/viewer-template/src/components/DetailPanel.tsx +247 -169
- package/dist/viewer-template/src/components/nodes/GroupNode.tsx +62 -0
- package/dist/viewer-template/src/data/loader.ts +8 -2
- package/dist/viewer-template/src/hooks/useArchitecture.ts +3 -1
- package/dist/viewer-template/src/hooks/useDepthFilter.ts +169 -15
- package/dist/viewer-template/src/hooks/useUseCaseFilter.ts +21 -4
- package/dist/viewer-template/src/types.ts +48 -8
- package/dist/viewer-template/src/utils/layout.ts +202 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ArchNodeData, UseCase } from '../types.ts';
|
|
2
|
-
import { getCategoryColors, getCategoryLabel } from '../types.ts';
|
|
2
|
+
import { getCategoryColors, getCategoryLabel, isGroupNode } from '../types.ts';
|
|
3
3
|
|
|
4
4
|
interface DetailPanelProps {
|
|
5
5
|
data: ArchNodeData;
|
|
@@ -48,192 +48,270 @@ export function DetailPanel({ data, useCases, onClose, onUseCaseClick }: DetailP
|
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
50
|
<div className="p-4 space-y-5 text-sm">
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
{isGroupNode(data) ? (
|
|
52
|
+
<>
|
|
53
|
+
{/* Group node: show contained nodes */}
|
|
54
|
+
<Section title="Contained Nodes">
|
|
55
|
+
<div className="space-y-2">
|
|
56
|
+
{data.memberNodes?.map((member) => (
|
|
57
|
+
<div
|
|
58
|
+
key={member.id}
|
|
59
|
+
className="rounded-lg p-3 border"
|
|
60
|
+
style={{
|
|
61
|
+
background: 'var(--color-surface-secondary)',
|
|
62
|
+
borderColor: 'var(--color-border-primary)',
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<div className="font-semibold text-sm" style={{ color: 'var(--color-content-primary)' }}>
|
|
66
|
+
{member.label}
|
|
67
|
+
</div>
|
|
68
|
+
{member.description && (
|
|
69
|
+
<div className="text-xs mt-0.5" style={{ color: 'var(--color-content-secondary)' }}>
|
|
70
|
+
{member.description}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
{member.filePath && (
|
|
74
|
+
<div className="text-xs mt-1">
|
|
75
|
+
{member.sourceUrl ? (
|
|
76
|
+
<a
|
|
77
|
+
href={member.sourceUrl}
|
|
78
|
+
target="_blank"
|
|
79
|
+
rel="noopener noreferrer"
|
|
80
|
+
className="underline break-all"
|
|
81
|
+
style={{ color: 'var(--color-interactive-primary)' }}
|
|
82
|
+
>
|
|
83
|
+
{member.filePath}
|
|
84
|
+
</a>
|
|
85
|
+
) : (
|
|
86
|
+
<code
|
|
87
|
+
className="px-1 py-0.5 rounded break-all"
|
|
88
|
+
style={{ color: 'var(--color-content-tertiary)', background: 'var(--color-surface-primary)' }}
|
|
89
|
+
>
|
|
90
|
+
{member.filePath}
|
|
91
|
+
</code>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
</Section>
|
|
57
99
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
{/* Use Cases for group */}
|
|
101
|
+
{data.useCases.length > 0 && (
|
|
102
|
+
<Section title="Use Cases">
|
|
103
|
+
<div className="flex flex-wrap gap-1.5">
|
|
104
|
+
{data.useCases.map((ucId) => {
|
|
105
|
+
const ucName = useCases.find(uc => uc.id === ucId)?.name ?? ucId;
|
|
106
|
+
return (
|
|
107
|
+
<button
|
|
108
|
+
key={ucId}
|
|
109
|
+
onClick={() => onUseCaseClick(ucId)}
|
|
110
|
+
className="px-2 py-1 rounded text-xs transition-colors cursor-pointer border"
|
|
111
|
+
style={{
|
|
112
|
+
background: 'var(--color-surface-secondary)',
|
|
113
|
+
color: 'var(--color-interactive-primary)',
|
|
114
|
+
borderColor: 'var(--color-border-primary)',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{ucName}
|
|
118
|
+
</button>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
</Section>
|
|
123
|
+
)}
|
|
124
|
+
</>
|
|
125
|
+
) : (
|
|
126
|
+
<>
|
|
127
|
+
{/* Description */}
|
|
128
|
+
{data.description && (
|
|
129
|
+
<Section title="Description">
|
|
130
|
+
<p style={{ color: 'var(--color-content-secondary)' }}>{data.description}</p>
|
|
131
|
+
</Section>
|
|
78
132
|
)}
|
|
79
|
-
</Section>
|
|
80
|
-
)}
|
|
81
|
-
|
|
82
|
-
{/* Implements */}
|
|
83
|
-
{data.implements && (
|
|
84
|
-
<Section title="Implements">
|
|
85
|
-
<code
|
|
86
|
-
className="px-1.5 py-0.5 rounded text-xs"
|
|
87
|
-
style={{ color: 'var(--cat-port-text)', background: 'var(--cat-port-bg)' }}
|
|
88
|
-
>
|
|
89
|
-
{data.implements}
|
|
90
|
-
</code>
|
|
91
|
-
</Section>
|
|
92
|
-
)}
|
|
93
|
-
|
|
94
|
-
{/* External Service */}
|
|
95
|
-
{data.externalService && (
|
|
96
|
-
<Section title="External Service">
|
|
97
|
-
<span style={{ color: 'var(--color-content-secondary)' }}>{data.externalService}</span>
|
|
98
|
-
</Section>
|
|
99
|
-
)}
|
|
100
133
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
{/* Source Link */}
|
|
135
|
+
{data.filePath && (
|
|
136
|
+
<Section title="Source">
|
|
137
|
+
{data.sourceUrl ? (
|
|
138
|
+
<a
|
|
139
|
+
href={data.sourceUrl}
|
|
140
|
+
target="_blank"
|
|
141
|
+
rel="noopener noreferrer"
|
|
142
|
+
className="underline break-all"
|
|
143
|
+
style={{ color: 'var(--color-interactive-primary)' }}
|
|
144
|
+
>
|
|
145
|
+
{data.filePath}
|
|
146
|
+
</a>
|
|
147
|
+
) : (
|
|
148
|
+
<code
|
|
149
|
+
className="px-1.5 py-0.5 rounded text-xs break-all"
|
|
150
|
+
style={{ color: 'var(--color-content-secondary)', background: 'var(--color-surface-secondary)' }}
|
|
151
|
+
>
|
|
152
|
+
{data.filePath}
|
|
153
|
+
</code>
|
|
154
|
+
)}
|
|
155
|
+
</Section>
|
|
156
|
+
)}
|
|
120
157
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<div className="flex flex-wrap gap-1.5">
|
|
125
|
-
{data.methods.map((m) => (
|
|
158
|
+
{/* Implements */}
|
|
159
|
+
{data.implements && (
|
|
160
|
+
<Section title="Implements">
|
|
126
161
|
<code
|
|
127
|
-
key={m}
|
|
128
162
|
className="px-1.5 py-0.5 rounded text-xs"
|
|
129
|
-
style={{
|
|
163
|
+
style={{ color: 'var(--cat-port-text)', background: 'var(--cat-port-bg)' }}
|
|
130
164
|
>
|
|
131
|
-
{
|
|
165
|
+
{data.implements}
|
|
132
166
|
</code>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
</Section>
|
|
136
|
-
)}
|
|
167
|
+
</Section>
|
|
168
|
+
)}
|
|
137
169
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
<td className="p-1.5" style={{ color: 'var(--color-content-tertiary)' }}>{col.index ?? '-'}</td>
|
|
165
|
-
</tr>
|
|
166
|
-
))}
|
|
167
|
-
</tbody>
|
|
168
|
-
</table>
|
|
169
|
-
</div>
|
|
170
|
+
{/* External Service */}
|
|
171
|
+
{data.externalService && (
|
|
172
|
+
<Section title="External Service">
|
|
173
|
+
<span style={{ color: 'var(--color-content-secondary)' }}>{data.externalService}</span>
|
|
174
|
+
</Section>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
{/* Routes */}
|
|
178
|
+
{data.routes && data.routes.length > 0 && (
|
|
179
|
+
<Section title="Routes">
|
|
180
|
+
<div className="space-y-1">
|
|
181
|
+
{data.routes.map((route) => {
|
|
182
|
+
const [method, ...pathParts] = route.split(' ');
|
|
183
|
+
const path = pathParts.join(' ');
|
|
184
|
+
return (
|
|
185
|
+
<div key={route} className="font-mono text-xs">
|
|
186
|
+
<span className={`inline-block w-14 font-bold ${methodColor(method ?? '')}`}>
|
|
187
|
+
{method}
|
|
188
|
+
</span>
|
|
189
|
+
<span style={{ color: 'var(--color-content-secondary)' }}>{path}</span>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
})}
|
|
193
|
+
</div>
|
|
194
|
+
</Section>
|
|
195
|
+
)}
|
|
170
196
|
|
|
171
|
-
{/*
|
|
172
|
-
{data.
|
|
173
|
-
<
|
|
174
|
-
<div className="
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
key={k}
|
|
197
|
+
{/* Methods */}
|
|
198
|
+
{data.methods && data.methods.length > 0 && (
|
|
199
|
+
<Section title="Methods">
|
|
200
|
+
<div className="flex flex-wrap gap-1.5">
|
|
201
|
+
{data.methods.map((m) => (
|
|
202
|
+
<code
|
|
203
|
+
key={m}
|
|
179
204
|
className="px-1.5 py-0.5 rounded text-xs"
|
|
180
|
-
style={{ background: 'var(--
|
|
205
|
+
style={{ background: 'var(--color-surface-secondary)', color: 'var(--color-content-secondary)' }}
|
|
181
206
|
>
|
|
182
|
-
{
|
|
183
|
-
</
|
|
207
|
+
{m}()
|
|
208
|
+
</code>
|
|
184
209
|
))}
|
|
185
210
|
</div>
|
|
186
|
-
</
|
|
187
|
-
)
|
|
211
|
+
</Section>
|
|
212
|
+
)}
|
|
188
213
|
|
|
189
|
-
{/*
|
|
190
|
-
{data.schema
|
|
191
|
-
<
|
|
192
|
-
<div className="
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
214
|
+
{/* Schema */}
|
|
215
|
+
{data.schema && (
|
|
216
|
+
<Section title={`Table: ${data.schema.tableName}`}>
|
|
217
|
+
<div className="overflow-x-auto">
|
|
218
|
+
<table className="w-full text-xs border-collapse">
|
|
219
|
+
<thead>
|
|
220
|
+
<tr style={{ background: 'var(--color-surface-secondary)' }}>
|
|
221
|
+
<th className="text-left p-1.5 font-semibold" style={{ borderBottom: '1px solid var(--color-border-primary)' }}>Column</th>
|
|
222
|
+
<th className="text-left p-1.5 font-semibold" style={{ borderBottom: '1px solid var(--color-border-primary)' }}>Type</th>
|
|
223
|
+
<th className="text-left p-1.5 font-semibold" style={{ borderBottom: '1px solid var(--color-border-primary)' }}>Null</th>
|
|
224
|
+
<th className="text-left p-1.5 font-semibold" style={{ borderBottom: '1px solid var(--color-border-primary)' }}>Key</th>
|
|
225
|
+
</tr>
|
|
226
|
+
</thead>
|
|
227
|
+
<tbody>
|
|
228
|
+
{data.schema.columns.map((col) => (
|
|
229
|
+
<tr key={col.name} style={{ borderBottom: '1px solid var(--color-border-primary)' }}>
|
|
230
|
+
<td className="p-1.5 font-mono" style={{ color: 'var(--color-content-primary)' }}>
|
|
231
|
+
{col.name}
|
|
232
|
+
{col.foreignKey && (
|
|
233
|
+
<span className="ml-1" style={{ color: 'var(--color-interactive-primary)' }} title={`FK: ${col.foreignKey.table}.${col.foreignKey.column}${col.foreignKey.onDelete ? ` (${col.foreignKey.onDelete})` : ''}`}>
|
|
234
|
+
FK
|
|
235
|
+
</span>
|
|
236
|
+
)}
|
|
237
|
+
</td>
|
|
238
|
+
<td className="p-1.5" style={{ color: 'var(--color-content-secondary)' }}>{col.type}</td>
|
|
239
|
+
<td className="p-1.5">{col.nullable ? <span className="text-yellow-600 dark:text-yellow-400">YES</span> : '-'}</td>
|
|
240
|
+
<td className="p-1.5" style={{ color: 'var(--color-content-tertiary)' }}>{col.index ?? '-'}</td>
|
|
241
|
+
</tr>
|
|
242
|
+
))}
|
|
243
|
+
</tbody>
|
|
244
|
+
</table>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{/* Enum Values */}
|
|
248
|
+
{data.schema.enumValues && Object.entries(data.schema.enumValues).map(([field, values]) => (
|
|
249
|
+
<div key={field} className="mt-2">
|
|
250
|
+
<div className="text-xs font-semibold mb-1" style={{ color: 'var(--color-content-secondary)' }}>{field} values:</div>
|
|
251
|
+
<div className="flex flex-wrap gap-1">
|
|
252
|
+
{Object.entries(values).map(([k, v]) => (
|
|
253
|
+
<span
|
|
254
|
+
key={k}
|
|
255
|
+
className="px-1.5 py-0.5 rounded text-xs"
|
|
256
|
+
style={{ background: 'var(--cat-controller-bg)', color: 'var(--cat-controller-text)' }}
|
|
257
|
+
>
|
|
258
|
+
{k}={v}
|
|
259
|
+
</span>
|
|
260
|
+
))}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
))}
|
|
264
|
+
|
|
265
|
+
{/* Indexes */}
|
|
266
|
+
{data.schema.indexes && data.schema.indexes.length > 0 && (
|
|
267
|
+
<div className="mt-2">
|
|
268
|
+
<div className="text-xs font-semibold mb-1" style={{ color: 'var(--color-content-secondary)' }}>Indexes:</div>
|
|
269
|
+
<ul className="text-xs space-y-0.5" style={{ color: 'var(--color-content-secondary)' }}>
|
|
270
|
+
{data.schema.indexes.map((idx) => (
|
|
271
|
+
<li key={idx} className="font-mono">{idx}</li>
|
|
272
|
+
))}
|
|
273
|
+
</ul>
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
</Section>
|
|
199
277
|
)}
|
|
200
|
-
</Section>
|
|
201
|
-
)}
|
|
202
278
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
279
|
+
{/* SQL Examples */}
|
|
280
|
+
{data.sqlExamples && data.sqlExamples.length > 0 && (
|
|
281
|
+
<Section title="SQL Examples">
|
|
282
|
+
{data.sqlExamples.map((sql) => (
|
|
283
|
+
<pre key={sql} className="bg-gray-900 text-green-400 p-2 rounded text-xs overflow-x-auto mb-1.5 whitespace-pre-wrap">
|
|
284
|
+
{sql}
|
|
285
|
+
</pre>
|
|
286
|
+
))}
|
|
287
|
+
</Section>
|
|
288
|
+
)}
|
|
213
289
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
290
|
+
{/* Use Cases */}
|
|
291
|
+
{data.useCases.length > 0 && (
|
|
292
|
+
<Section title="Use Cases">
|
|
293
|
+
<div className="flex flex-wrap gap-1.5">
|
|
294
|
+
{data.useCases.map((ucId) => {
|
|
295
|
+
const ucName = useCases.find(uc => uc.id === ucId)?.name ?? ucId;
|
|
296
|
+
return (
|
|
297
|
+
<button
|
|
298
|
+
key={ucId}
|
|
299
|
+
onClick={() => onUseCaseClick(ucId)}
|
|
300
|
+
className="px-2 py-1 rounded text-xs transition-colors cursor-pointer border"
|
|
301
|
+
style={{
|
|
302
|
+
background: 'var(--color-surface-secondary)',
|
|
303
|
+
color: 'var(--color-interactive-primary)',
|
|
304
|
+
borderColor: 'var(--color-border-primary)',
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
{ucName}
|
|
308
|
+
</button>
|
|
309
|
+
);
|
|
310
|
+
})}
|
|
311
|
+
</div>
|
|
312
|
+
</Section>
|
|
313
|
+
)}
|
|
314
|
+
</>
|
|
237
315
|
)}
|
|
238
316
|
</div>
|
|
239
317
|
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Handle, Position } from '@xyflow/react';
|
|
2
|
+
import type { NodeProps } from '@xyflow/react';
|
|
3
|
+
import type { ArchFlowNode } from '../../types.ts';
|
|
4
|
+
import { getCategoryColors, getCategoryIcon } from '../../types.ts';
|
|
5
|
+
|
|
6
|
+
export function GroupNode({ data, selected }: NodeProps<ArchFlowNode>) {
|
|
7
|
+
const d = data;
|
|
8
|
+
const colors = getCategoryColors(d.category);
|
|
9
|
+
const icon = getCategoryIcon(d.category);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<Handle type="target" position={Position.Top} style={{ background: colors.border }} />
|
|
14
|
+
<div
|
|
15
|
+
style={{
|
|
16
|
+
background: colors.bg,
|
|
17
|
+
border: `2px dashed ${selected ? 'var(--color-border-focus)' : colors.border}`,
|
|
18
|
+
borderRadius: 12,
|
|
19
|
+
padding: '8px 12px',
|
|
20
|
+
minWidth: 160,
|
|
21
|
+
maxWidth: 220,
|
|
22
|
+
cursor: 'pointer',
|
|
23
|
+
boxShadow: selected ? 'var(--shadow-node-selected)' : 'var(--shadow-node)',
|
|
24
|
+
transition: 'box-shadow 0.15s, border-color 0.15s',
|
|
25
|
+
position: 'relative',
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
{/* Count badge */}
|
|
29
|
+
{d.memberCount != null && (
|
|
30
|
+
<div
|
|
31
|
+
style={{
|
|
32
|
+
position: 'absolute',
|
|
33
|
+
top: -8,
|
|
34
|
+
right: -8,
|
|
35
|
+
background: colors.border,
|
|
36
|
+
color: '#fff',
|
|
37
|
+
fontSize: 10,
|
|
38
|
+
fontWeight: 700,
|
|
39
|
+
width: 20,
|
|
40
|
+
height: 20,
|
|
41
|
+
borderRadius: '50%',
|
|
42
|
+
display: 'flex',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
justifyContent: 'center',
|
|
45
|
+
lineHeight: 1,
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{d.memberCount}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
|
|
52
|
+
<div style={{ fontSize: 11, color: colors.text, opacity: 0.7, marginBottom: 2 }}>
|
|
53
|
+
{icon} {d.category.toUpperCase()}
|
|
54
|
+
</div>
|
|
55
|
+
<div style={{ fontSize: 13, fontWeight: 600, color: colors.text, lineHeight: 1.3, wordBreak: 'break-word' }}>
|
|
56
|
+
{d.label}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<Handle type="source" position={Position.Bottom} style={{ background: colors.border }} />
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Edge } from '@xyflow/react';
|
|
2
2
|
import type { ArchFlowNode, ArchNodeData, UseCase, TableSchema, DepthLevel } from '../types.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { computeDepths } from '../types.ts';
|
|
4
4
|
|
|
5
5
|
interface RawArchData {
|
|
6
6
|
version: string;
|
|
@@ -10,6 +10,7 @@ interface RawArchData {
|
|
|
10
10
|
language?: string;
|
|
11
11
|
framework?: string;
|
|
12
12
|
sourceUrl?: string;
|
|
13
|
+
layout?: string;
|
|
13
14
|
};
|
|
14
15
|
nodes: RawNode[];
|
|
15
16
|
edges: RawEdge[];
|
|
@@ -55,6 +56,7 @@ export interface LoadedArchitecture {
|
|
|
55
56
|
nodes: ArchFlowNode[];
|
|
56
57
|
edges: Edge[];
|
|
57
58
|
useCases: UseCase[];
|
|
59
|
+
layoutType: 'dagre' | 'concentric';
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
function resolveSourceUrl(template: string | undefined, filePath: string): string {
|
|
@@ -83,6 +85,8 @@ export async function loadArchitecture(): Promise<LoadedArchitecture> {
|
|
|
83
85
|
const layout = raw._layout ?? {};
|
|
84
86
|
const sourceUrlTemplate = raw.project.sourceUrl;
|
|
85
87
|
|
|
88
|
+
const depthMap = computeDepths(raw.nodes);
|
|
89
|
+
|
|
86
90
|
// Convert raw nodes to React Flow nodes
|
|
87
91
|
const nodes: ArchFlowNode[] = raw.nodes.map((n) => {
|
|
88
92
|
const pos = layout[n.id] ?? { x: 0, y: 0 };
|
|
@@ -91,10 +95,11 @@ export async function loadArchitecture(): Promise<LoadedArchitecture> {
|
|
|
91
95
|
const data: ArchNodeData = {
|
|
92
96
|
label: n.label,
|
|
93
97
|
category: n.category,
|
|
94
|
-
depth: (n.depth ??
|
|
98
|
+
depth: (n.depth ?? depthMap.get(n.layer) ?? 1) as DepthLevel,
|
|
95
99
|
description: n.description ?? '',
|
|
96
100
|
filePath: n.filePath ?? '',
|
|
97
101
|
sourceUrl: resolveSourceUrl(sourceUrlTemplate, n.filePath ?? ''),
|
|
102
|
+
layer: n.layer,
|
|
98
103
|
methods: n.methods,
|
|
99
104
|
routes: n.routes,
|
|
100
105
|
useCases: n.useCases ?? [],
|
|
@@ -136,5 +141,6 @@ export async function loadArchitecture(): Promise<LoadedArchitecture> {
|
|
|
136
141
|
nodes,
|
|
137
142
|
edges,
|
|
138
143
|
useCases,
|
|
144
|
+
layoutType: raw.project.layout === 'concentric' ? 'concentric' as const : 'dagre' as const,
|
|
139
145
|
};
|
|
140
146
|
}
|
|
@@ -10,6 +10,7 @@ export function useArchitecture() {
|
|
|
10
10
|
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
|
11
11
|
const [useCases, setUseCases] = useState<UseCase[]>([]);
|
|
12
12
|
const [projectName, setProjectName] = useState('Architecture Viewer');
|
|
13
|
+
const [layoutType, setLayoutType] = useState<'dagre' | 'concentric'>('dagre');
|
|
13
14
|
const [loading, setLoading] = useState(true);
|
|
14
15
|
const [error, setError] = useState<string | null>(null);
|
|
15
16
|
|
|
@@ -20,6 +21,7 @@ export function useArchitecture() {
|
|
|
20
21
|
setEdges(arch.edges);
|
|
21
22
|
setUseCases(arch.useCases);
|
|
22
23
|
setProjectName(arch.projectName);
|
|
24
|
+
setLayoutType(arch.layoutType);
|
|
23
25
|
setLoading(false);
|
|
24
26
|
})
|
|
25
27
|
.catch((err: unknown) => {
|
|
@@ -28,5 +30,5 @@ export function useArchitecture() {
|
|
|
28
30
|
});
|
|
29
31
|
}, [setNodes, setEdges]);
|
|
30
32
|
|
|
31
|
-
return { nodes, edges, useCases, projectName, loading, error, setNodes, setEdges, onNodesChange, onEdgesChange };
|
|
33
|
+
return { nodes, edges, useCases, projectName, layoutType, loading, error, setNodes, setEdges, onNodesChange, onEdgesChange };
|
|
32
34
|
}
|