@wtdlee/repomap 0.8.0 → 0.9.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/analyzers/index.js +1 -1
- package/dist/chunk-2XZSFAJF.js +20 -0
- package/dist/{chunk-JDM7Y7PX.js → chunk-6AZNHUOB.js} +154 -22
- package/dist/{chunk-OQAXO3X2.js → chunk-NQMJ3QRX.js} +712 -88
- package/dist/{chunk-TNUKDIO7.js → chunk-QDVE7MT3.js} +4 -4
- package/dist/{chunk-GCIRJGW3.js → chunk-WZAAA7DS.js} +125 -177
- package/dist/cli.js +3 -3
- package/dist/generators/assets/docs.css +154 -1
- package/dist/generators/assets/page-map.css +59 -0
- package/dist/generators/assets/rails-map.css +6 -0
- package/dist/generators/index.d.ts +2 -1
- package/dist/generators/index.js +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +1 -1
- package/dist/page-map-generator-HROGGVAQ.js +1 -0
- package/dist/rails-map-generator-DF2YAXW4.js +1 -0
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-HPBPEGHS.js +0 -19
- package/dist/page-map-generator-3GO6GL2P.js +0 -1
- package/dist/rails-map-generator-CAQZUBI6.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {k}from'./chunk-H7VVRHQZ.js';import*as
|
|
2
|
-
\u{1F4C4} Generated: ${t.outputPath}`)),s}generateFromResult(t,e="Rails Application Map"){return this.result=t,this.generateHTML(e)}generateHTML(t){if(!this.result)throw new Error("Analysis not run");let{routes:e,controllers:s,models:a,grpc:l,summary:
|
|
1
|
+
import {k}from'./chunk-H7VVRHQZ.js';import*as c from'fs';import*as d from'path';var i=class{constructor(t){this.rootPath=t;}result=null;async generate(t={}){if(!this.rootPath)throw new Error("Root path required for analysis");let{title:e="Rails Application Map"}=t;this.result=await k(this.rootPath);let s=this.generateHTML(e);return t.outputPath&&(c.writeFileSync(t.outputPath,s),console.log(`
|
|
2
|
+
\u{1F4C4} Generated: ${t.outputPath}`)),s}generateFromResult(t,e="Rails Application Map"){return this.result=t,this.generateHTML(e)}generateHTML(t){if(!this.result)throw new Error("Analysis not run");let{routes:e,controllers:s,models:a,grpc:l,summary:n}=this.result;return `<!DOCTYPE html>
|
|
3
3
|
<html lang="en">
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
@@ -23,25 +23,25 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
23
23
|
<div class="stats-bar">
|
|
24
24
|
<div class="stat active" data-view="routes">
|
|
25
25
|
<div>
|
|
26
|
-
<div class="stat-value">${
|
|
26
|
+
<div class="stat-value">${n.totalRoutes.toLocaleString()}</div>
|
|
27
27
|
<div class="stat-label">Routes</div>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="stat" data-view="controllers">
|
|
31
31
|
<div>
|
|
32
|
-
<div class="stat-value">${
|
|
32
|
+
<div class="stat-value">${n.totalControllers}</div>
|
|
33
33
|
<div class="stat-label">Controllers</div>
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
36
36
|
<div class="stat" data-view="models">
|
|
37
37
|
<div>
|
|
38
|
-
<div class="stat-value">${
|
|
38
|
+
<div class="stat-value">${n.totalModels}</div>
|
|
39
39
|
<div class="stat-label">Models</div>
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
42
42
|
<div class="stat" data-view="grpc">
|
|
43
43
|
<div>
|
|
44
|
-
<div class="stat-value">${
|
|
44
|
+
<div class="stat-value">${n.totalGrpcServices}</div>
|
|
45
45
|
<div class="stat-label">gRPC</div>
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
@@ -62,7 +62,7 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
62
62
|
</div>
|
|
63
63
|
|
|
64
64
|
<div class="sidebar-section namespaces" id="namespaceFilter">
|
|
65
|
-
<div class="sidebar-title">Namespaces (${
|
|
65
|
+
<div class="sidebar-title">Namespaces (${n.namespaces.length})</div>
|
|
66
66
|
<div class="namespace-list">
|
|
67
67
|
<div class="namespace-item active" data-namespace="all">
|
|
68
68
|
<span>All</span>
|
|
@@ -103,6 +103,11 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
103
103
|
let currentView = 'routes';
|
|
104
104
|
let selectedNamespaces = new Set(['all']);
|
|
105
105
|
let selectedMethods = new Set(['all']);
|
|
106
|
+
let selectedControllerFlags = new Set(['all']);
|
|
107
|
+
let selectedModelNamespaces = new Set(['all']);
|
|
108
|
+
let selectedModelFlags = new Set(['all']);
|
|
109
|
+
let selectedGrpcNamespaces = new Set(['all']);
|
|
110
|
+
let selectedGrpcFlags = new Set(['all']);
|
|
106
111
|
let searchQuery = '';
|
|
107
112
|
let routesDisplayCount = 200;
|
|
108
113
|
let controllersDisplayCount = 50;
|
|
@@ -177,68 +182,83 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
177
182
|
});
|
|
178
183
|
});
|
|
179
184
|
|
|
180
|
-
//
|
|
181
|
-
document.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
185
|
+
// Sidebar filter click handler (works even after sidebar re-render)
|
|
186
|
+
document.addEventListener('click', (e) => {
|
|
187
|
+
const target = e.target instanceof Element ? e.target : e.target?.parentElement;
|
|
188
|
+
if (!target) return;
|
|
189
|
+
const item = target.closest('.namespace-item');
|
|
190
|
+
if (!item) return;
|
|
191
|
+
|
|
192
|
+
const filterType = item.dataset.filterType;
|
|
193
|
+
const value = item.dataset.filterValue;
|
|
194
|
+
if (!filterType || value === undefined) return;
|
|
195
|
+
|
|
196
|
+
const multi = e.ctrlKey || e.metaKey;
|
|
197
|
+
|
|
198
|
+
function toggleMulti(setRef, v) {
|
|
199
|
+
if (v === 'all') return new Set(['all']);
|
|
200
|
+
const next = new Set(setRef);
|
|
201
|
+
next.delete('all');
|
|
202
|
+
if (next.has(v)) next.delete(v);
|
|
203
|
+
else next.add(v);
|
|
204
|
+
if (next.size === 0) next.add('all');
|
|
205
|
+
return next;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function toggleSingle(v) {
|
|
209
|
+
return new Set([v]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (filterType === 'routeNamespace') {
|
|
213
|
+
selectedNamespaces = multi ? toggleMulti(selectedNamespaces, value) : toggleSingle(value);
|
|
202
214
|
routesDisplayCount = 200;
|
|
203
215
|
saveStateToUrl();
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Method filter (multi-select with Ctrl/Cmd)
|
|
209
|
-
document.querySelectorAll('.namespace-item[data-method]').forEach(item => {
|
|
210
|
-
item.addEventListener('click', (e) => {
|
|
211
|
-
const method = item.dataset.method;
|
|
212
|
-
if (e.ctrlKey || e.metaKey) {
|
|
213
|
-
if (selectedMethods.has('all')) {
|
|
214
|
-
selectedMethods = new Set([method]);
|
|
215
|
-
} else if (selectedMethods.has(method)) {
|
|
216
|
-
selectedMethods.delete(method);
|
|
217
|
-
if (selectedMethods.size === 0) selectedMethods.add('all');
|
|
218
|
-
} else {
|
|
219
|
-
selectedMethods.add(method);
|
|
220
|
-
}
|
|
221
|
-
} else {
|
|
222
|
-
if (selectedMethods.has(method) && selectedMethods.size === 1) {
|
|
223
|
-
selectedMethods = new Set(['all']);
|
|
224
|
-
} else {
|
|
225
|
-
selectedMethods = new Set([method]);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
updateFilterUI();
|
|
216
|
+
} else if (filterType === 'routeMethod') {
|
|
217
|
+
// Methods behave similarly to namespaces
|
|
218
|
+
selectedMethods = multi ? toggleMulti(selectedMethods, value) : toggleSingle(value);
|
|
229
219
|
routesDisplayCount = 200;
|
|
230
220
|
saveStateToUrl();
|
|
231
|
-
|
|
232
|
-
|
|
221
|
+
} else if (filterType === 'controllerFlag') {
|
|
222
|
+
selectedControllerFlags = multi
|
|
223
|
+
? toggleMulti(selectedControllerFlags, value)
|
|
224
|
+
: toggleSingle(value);
|
|
225
|
+
controllersDisplayCount = 50;
|
|
226
|
+
} else if (filterType === 'modelNamespace') {
|
|
227
|
+
selectedModelNamespaces = multi
|
|
228
|
+
? toggleMulti(selectedModelNamespaces, value)
|
|
229
|
+
: toggleSingle(value);
|
|
230
|
+
modelsDisplayCount = 50;
|
|
231
|
+
} else if (filterType === 'modelFlag') {
|
|
232
|
+
selectedModelFlags = multi ? toggleMulti(selectedModelFlags, value) : toggleSingle(value);
|
|
233
|
+
modelsDisplayCount = 50;
|
|
234
|
+
} else if (filterType === 'grpcNamespace') {
|
|
235
|
+
selectedGrpcNamespaces = multi ? toggleMulti(selectedGrpcNamespaces, value) : toggleSingle(value);
|
|
236
|
+
grpcDisplayCount = 50;
|
|
237
|
+
} else if (filterType === 'grpcFlag') {
|
|
238
|
+
selectedGrpcFlags = multi ? toggleMulti(selectedGrpcFlags, value) : toggleSingle(value);
|
|
239
|
+
grpcDisplayCount = 50;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
updateFilterUI();
|
|
243
|
+
renderMainPanel();
|
|
233
244
|
});
|
|
234
245
|
|
|
235
246
|
function updateFilterUI() {
|
|
236
|
-
document.querySelectorAll('.namespace-item[data-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
247
|
+
document.querySelectorAll('.namespace-item[data-filter-type][data-filter-value]').forEach(item => {
|
|
248
|
+
const t = item.dataset.filterType;
|
|
249
|
+
const v = item.dataset.filterValue;
|
|
250
|
+
if (!t || v === undefined) return;
|
|
251
|
+
|
|
252
|
+
let active = false;
|
|
253
|
+
if (t === 'routeNamespace') active = selectedNamespaces.has(v);
|
|
254
|
+
else if (t === 'routeMethod') active = selectedMethods.has(v);
|
|
255
|
+
else if (t === 'controllerFlag') active = selectedControllerFlags.has(v);
|
|
256
|
+
else if (t === 'modelNamespace') active = selectedModelNamespaces.has(v);
|
|
257
|
+
else if (t === 'modelFlag') active = selectedModelFlags.has(v);
|
|
258
|
+
else if (t === 'grpcNamespace') active = selectedGrpcNamespaces.has(v);
|
|
259
|
+
else if (t === 'grpcFlag') active = selectedGrpcFlags.has(v);
|
|
260
|
+
|
|
261
|
+
item.classList.toggle('active', active);
|
|
242
262
|
});
|
|
243
263
|
}
|
|
244
264
|
|
|
@@ -250,19 +270,10 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
250
270
|
|
|
251
271
|
// Render Functions
|
|
252
272
|
function renderMainPanel() {
|
|
253
|
-
//
|
|
273
|
+
// Update sidebar filters per view (routes/controllers vs models vs grpc)
|
|
254
274
|
const namespaceFilter = document.getElementById('namespaceFilter');
|
|
255
275
|
const methodFilter = document.getElementById('methodFilter');
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (namespaceFilter) {
|
|
259
|
-
namespaceFilter.style.opacity = filtersDisabled ? '0.4' : '1';
|
|
260
|
-
namespaceFilter.style.pointerEvents = filtersDisabled ? 'none' : 'auto';
|
|
261
|
-
}
|
|
262
|
-
if (methodFilter) {
|
|
263
|
-
methodFilter.style.opacity = filtersDisabled ? '0.4' : '1';
|
|
264
|
-
methodFilter.style.pointerEvents = filtersDisabled ? 'none' : 'auto';
|
|
265
|
-
}
|
|
276
|
+
renderSidebarFilters(namespaceFilter, methodFilter);
|
|
266
277
|
|
|
267
278
|
switch (currentView) {
|
|
268
279
|
case 'routes':
|
|
@@ -285,6 +296,289 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
285
296
|
attachEventListeners();
|
|
286
297
|
}
|
|
287
298
|
|
|
299
|
+
function renderSidebarFilters(namespaceFilter, methodFilter) {
|
|
300
|
+
if (!namespaceFilter || !methodFilter) return;
|
|
301
|
+
|
|
302
|
+
function sectionHtml(title, listClass, inner) {
|
|
303
|
+
return (
|
|
304
|
+
'<div class="sidebar-title">' +
|
|
305
|
+
title +
|
|
306
|
+
'</div>' +
|
|
307
|
+
'<div class="' +
|
|
308
|
+
listClass +
|
|
309
|
+
'">' +
|
|
310
|
+
inner +
|
|
311
|
+
'</div>'
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (currentView === 'routes') {
|
|
316
|
+
namespaceFilter.style.opacity = '1';
|
|
317
|
+
namespaceFilter.style.pointerEvents = 'auto';
|
|
318
|
+
methodFilter.style.opacity = '1';
|
|
319
|
+
methodFilter.style.pointerEvents = 'auto';
|
|
320
|
+
|
|
321
|
+
namespaceFilter.innerHTML = sectionHtml(
|
|
322
|
+
'Namespaces',
|
|
323
|
+
'namespace-list',
|
|
324
|
+
renderRouteNamespaceFilters()
|
|
325
|
+
);
|
|
326
|
+
methodFilter.innerHTML = sectionHtml(
|
|
327
|
+
'HTTP Methods',
|
|
328
|
+
'namespace-list methods-list',
|
|
329
|
+
renderRouteMethodFilters()
|
|
330
|
+
);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (currentView === 'controllers') {
|
|
335
|
+
namespaceFilter.style.opacity = '1';
|
|
336
|
+
namespaceFilter.style.pointerEvents = 'auto';
|
|
337
|
+
methodFilter.style.opacity = '1';
|
|
338
|
+
methodFilter.style.pointerEvents = 'auto';
|
|
339
|
+
|
|
340
|
+
namespaceFilter.innerHTML = sectionHtml(
|
|
341
|
+
'Controller Namespaces',
|
|
342
|
+
'namespace-list',
|
|
343
|
+
renderControllerNamespaceFilters()
|
|
344
|
+
);
|
|
345
|
+
methodFilter.innerHTML = sectionHtml(
|
|
346
|
+
'Controller Filters',
|
|
347
|
+
'namespace-list methods-list',
|
|
348
|
+
renderControllerFlagFilters()
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (currentView === 'models') {
|
|
354
|
+
namespaceFilter.style.opacity = '1';
|
|
355
|
+
namespaceFilter.style.pointerEvents = 'auto';
|
|
356
|
+
methodFilter.style.opacity = '1';
|
|
357
|
+
methodFilter.style.pointerEvents = 'auto';
|
|
358
|
+
|
|
359
|
+
namespaceFilter.innerHTML = sectionHtml(
|
|
360
|
+
'Model Namespaces',
|
|
361
|
+
'namespace-list',
|
|
362
|
+
renderModelNamespaceFilters()
|
|
363
|
+
);
|
|
364
|
+
methodFilter.innerHTML = sectionHtml(
|
|
365
|
+
'Model Filters',
|
|
366
|
+
'namespace-list methods-list',
|
|
367
|
+
renderModelFlagFilters()
|
|
368
|
+
);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (currentView === 'grpc') {
|
|
373
|
+
namespaceFilter.style.opacity = '1';
|
|
374
|
+
namespaceFilter.style.pointerEvents = 'auto';
|
|
375
|
+
methodFilter.style.opacity = '1';
|
|
376
|
+
methodFilter.style.pointerEvents = 'auto';
|
|
377
|
+
|
|
378
|
+
namespaceFilter.innerHTML = sectionHtml(
|
|
379
|
+
'gRPC Namespaces',
|
|
380
|
+
'namespace-list',
|
|
381
|
+
renderGrpcNamespaceFilters()
|
|
382
|
+
);
|
|
383
|
+
methodFilter.innerHTML = sectionHtml(
|
|
384
|
+
'gRPC Filters',
|
|
385
|
+
'namespace-list methods-list',
|
|
386
|
+
renderGrpcFlagFilters()
|
|
387
|
+
);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// diagram: keep disabled (no meaningful sidebar filters)
|
|
392
|
+
namespaceFilter.style.opacity = '0.4';
|
|
393
|
+
namespaceFilter.style.pointerEvents = 'none';
|
|
394
|
+
methodFilter.style.opacity = '0.4';
|
|
395
|
+
methodFilter.style.pointerEvents = 'none';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function escapeHtml(s) {
|
|
399
|
+
return String(s)
|
|
400
|
+
.replace(/&/g, '&')
|
|
401
|
+
.replace(/</g, '<')
|
|
402
|
+
.replace(/>/g, '>')
|
|
403
|
+
.replace(/"/g, '"');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function renderFilterItem(label, count, filterType, filterValue, isActive) {
|
|
407
|
+
const safeLabel = escapeHtml(label);
|
|
408
|
+
const safeType = escapeHtml(filterType);
|
|
409
|
+
const safeValue = escapeHtml(filterValue);
|
|
410
|
+
return (
|
|
411
|
+
'<div class="namespace-item ' +
|
|
412
|
+
(isActive ? 'active' : '') +
|
|
413
|
+
'" data-filter-type="' +
|
|
414
|
+
safeType +
|
|
415
|
+
'" data-filter-value="' +
|
|
416
|
+
safeValue +
|
|
417
|
+
'">' +
|
|
418
|
+
'<span>' +
|
|
419
|
+
safeLabel +
|
|
420
|
+
'</span>' +
|
|
421
|
+
'<span class="namespace-count">' +
|
|
422
|
+
count +
|
|
423
|
+
'</span>' +
|
|
424
|
+
'</div>'
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function renderRouteNamespaceFilters() {
|
|
429
|
+
const counts = new Map();
|
|
430
|
+
routes.forEach(r => {
|
|
431
|
+
const ns = r.namespace || '';
|
|
432
|
+
counts.set(ns, (counts.get(ns) || 0) + 1);
|
|
433
|
+
});
|
|
434
|
+
const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
435
|
+
const allCount = routes.length;
|
|
436
|
+
return [
|
|
437
|
+
renderFilterItem('All', allCount, 'routeNamespace', 'all', selectedNamespaces.has('all')),
|
|
438
|
+
...entries.map(([ns, count]) =>
|
|
439
|
+
renderFilterItem(ns || 'root', count, 'routeNamespace', ns, selectedNamespaces.has(ns))
|
|
440
|
+
),
|
|
441
|
+
].join('');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function renderControllerNamespaceFilters() {
|
|
445
|
+
const counts = new Map();
|
|
446
|
+
controllers.forEach(c => {
|
|
447
|
+
const ns = c.namespace || '';
|
|
448
|
+
counts.set(ns, (counts.get(ns) || 0) + 1);
|
|
449
|
+
});
|
|
450
|
+
const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
451
|
+
const allCount = controllers.length;
|
|
452
|
+
return [
|
|
453
|
+
renderFilterItem('All', allCount, 'routeNamespace', 'all', selectedNamespaces.has('all')),
|
|
454
|
+
...entries.map(([ns, count]) =>
|
|
455
|
+
renderFilterItem(ns || 'root', count, 'routeNamespace', ns, selectedNamespaces.has(ns))
|
|
456
|
+
),
|
|
457
|
+
].join('');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function renderRouteMethodFilters() {
|
|
461
|
+
const counts = new Map();
|
|
462
|
+
routes.forEach(r => {
|
|
463
|
+
const m = r.method || 'ALL';
|
|
464
|
+
counts.set(m, (counts.get(m) || 0) + 1);
|
|
465
|
+
});
|
|
466
|
+
const methods = ['ALL', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
467
|
+
const allCount = routes.length;
|
|
468
|
+
return methods.map(m => {
|
|
469
|
+
const label = m === 'ALL' ? 'All' : m;
|
|
470
|
+
const value = m === 'ALL' ? 'all' : m;
|
|
471
|
+
const count = m === 'ALL' ? allCount : (counts.get(m) || 0);
|
|
472
|
+
const active = selectedMethods.has(value);
|
|
473
|
+
return renderFilterItem(label, count, 'routeMethod', value, active);
|
|
474
|
+
}).join('');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function renderControllerFlagFilters() {
|
|
478
|
+
const flags = [
|
|
479
|
+
{
|
|
480
|
+
key: 'json',
|
|
481
|
+
label: 'Renders JSON',
|
|
482
|
+
test: (c) => (c.actions || []).some((a) => a.rendersJson),
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
key: 'redirect',
|
|
486
|
+
label: 'Has Redirect',
|
|
487
|
+
test: (c) => (c.actions || []).some((a) => a.redirectsTo),
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
key: 'private',
|
|
491
|
+
label: 'Has Private Actions',
|
|
492
|
+
test: (c) => (c.actions || []).some((a) => a.visibility === 'private'),
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
const allCount = controllers.length;
|
|
496
|
+
const items = [
|
|
497
|
+
renderFilterItem('All', allCount, 'controllerFlag', 'all', selectedControllerFlags.has('all')),
|
|
498
|
+
];
|
|
499
|
+
flags.forEach(f => {
|
|
500
|
+
const count = controllers.filter(f.test).length;
|
|
501
|
+
items.push(renderFilterItem(f.label, count, 'controllerFlag', f.key, selectedControllerFlags.has(f.key)));
|
|
502
|
+
});
|
|
503
|
+
return items.join('');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function getModelNamespace(model) {
|
|
507
|
+
const p = (model.filePath || '').split('/');
|
|
508
|
+
if (p.length <= 1) return '';
|
|
509
|
+
return p.slice(0, -1).join('/');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function renderModelNamespaceFilters() {
|
|
513
|
+
const counts = new Map();
|
|
514
|
+
models.forEach(m => {
|
|
515
|
+
const ns = getModelNamespace(m);
|
|
516
|
+
counts.set(ns, (counts.get(ns) || 0) + 1);
|
|
517
|
+
});
|
|
518
|
+
const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
519
|
+
const allCount = models.length;
|
|
520
|
+
return [
|
|
521
|
+
renderFilterItem('All', allCount, 'modelNamespace', 'all', selectedModelNamespaces.has('all')),
|
|
522
|
+
...entries.map(([ns, count]) =>
|
|
523
|
+
renderFilterItem(ns || 'root', count, 'modelNamespace', ns, selectedModelNamespaces.has(ns))
|
|
524
|
+
),
|
|
525
|
+
].join('');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function renderModelFlagFilters() {
|
|
529
|
+
const flags = [
|
|
530
|
+
{ key: 'assoc', label: 'Has associations', test: (m) => (m.associations || []).length > 0 },
|
|
531
|
+
{ key: 'valid', label: 'Has validations', test: (m) => (m.validations || []).length > 0 },
|
|
532
|
+
{ key: 'cb', label: 'Has callbacks', test: (m) => (m.callbacks || []).length > 0 },
|
|
533
|
+
{ key: 'concern', label: 'Includes concerns', test: (m) => (m.concerns || []).length > 0 },
|
|
534
|
+
{ key: 'enum', label: 'Has enums', test: (m) => (m.enums || []).length > 0 },
|
|
535
|
+
];
|
|
536
|
+
const allCount = models.length;
|
|
537
|
+
const items = [
|
|
538
|
+
renderFilterItem('All', allCount, 'modelFlag', 'all', selectedModelFlags.has('all')),
|
|
539
|
+
];
|
|
540
|
+
flags.forEach(f => {
|
|
541
|
+
const count = models.filter(f.test).length;
|
|
542
|
+
items.push(renderFilterItem(f.label, count, 'modelFlag', f.key, selectedModelFlags.has(f.key)));
|
|
543
|
+
});
|
|
544
|
+
return items.join('');
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function renderGrpcNamespaceFilters() {
|
|
548
|
+
const counts = new Map();
|
|
549
|
+
grpcServices.forEach(s => {
|
|
550
|
+
const ns = s.namespace || '';
|
|
551
|
+
counts.set(ns, (counts.get(ns) || 0) + 1);
|
|
552
|
+
});
|
|
553
|
+
const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
554
|
+
const allCount = grpcServices.length;
|
|
555
|
+
return [
|
|
556
|
+
renderFilterItem('All', allCount, 'grpcNamespace', 'all', selectedGrpcNamespaces.has('all')),
|
|
557
|
+
...entries.map(([ns, count]) =>
|
|
558
|
+
renderFilterItem(ns || 'root', count, 'grpcNamespace', ns, selectedGrpcNamespaces.has(ns))
|
|
559
|
+
),
|
|
560
|
+
].join('');
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function renderGrpcFlagFilters() {
|
|
564
|
+
const flags = [
|
|
565
|
+
{ key: 'policies', label: 'Has policies', test: (s) => (s.policies || []).length > 0 },
|
|
566
|
+
{ key: 'serializers', label: 'Has serializers', test: (s) => (s.serializers || []).length > 0 },
|
|
567
|
+
{ key: 'concerns', label: 'Includes concerns', test: (s) => (s.concerns || []).length > 0 },
|
|
568
|
+
{ key: 'modelsUsed', label: 'RPC uses models', test: (s) => (s.rpcs || []).some(r => (r.modelsUsed || []).length > 0) },
|
|
569
|
+
{ key: 'servicesUsed', label: 'RPC uses services', test: (s) => (s.rpcs || []).some(r => (r.servicesUsed || []).length > 0) },
|
|
570
|
+
];
|
|
571
|
+
const allCount = grpcServices.length;
|
|
572
|
+
const items = [
|
|
573
|
+
renderFilterItem('All', allCount, 'grpcFlag', 'all', selectedGrpcFlags.has('all')),
|
|
574
|
+
];
|
|
575
|
+
flags.forEach(f => {
|
|
576
|
+
const count = grpcServices.filter(f.test).length;
|
|
577
|
+
items.push(renderFilterItem(f.label, count, 'grpcFlag', f.key, selectedGrpcFlags.has(f.key)));
|
|
578
|
+
});
|
|
579
|
+
return items.join('');
|
|
580
|
+
}
|
|
581
|
+
|
|
288
582
|
function filterRoutes() {
|
|
289
583
|
return routes.filter(route => {
|
|
290
584
|
// Namespace filter (multi-select)
|
|
@@ -355,6 +649,18 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
355
649
|
if (!selectedNamespaces.has('all')) {
|
|
356
650
|
filteredControllers = filteredControllers.filter(c => selectedNamespaces.has(c.namespace || ''));
|
|
357
651
|
}
|
|
652
|
+
if (!selectedControllerFlags.has('all')) {
|
|
653
|
+
filteredControllers = filteredControllers.filter((c) => {
|
|
654
|
+
const rendersJson = (c.actions || []).some((a) => a.rendersJson);
|
|
655
|
+
const hasRedirect = (c.actions || []).some((a) => a.redirectsTo);
|
|
656
|
+
const hasPrivate = (c.actions || []).some((a) => a.visibility === 'private');
|
|
657
|
+
|
|
658
|
+
if (selectedControllerFlags.has('json') && !rendersJson) return false;
|
|
659
|
+
if (selectedControllerFlags.has('redirect') && !hasRedirect) return false;
|
|
660
|
+
if (selectedControllerFlags.has('private') && !hasPrivate) return false;
|
|
661
|
+
return true;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
358
664
|
const displayed = filteredControllers.slice(0, controllersDisplayCount);
|
|
359
665
|
const hasMore = filteredControllers.length > controllersDisplayCount;
|
|
360
666
|
|
|
@@ -406,6 +712,27 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
406
712
|
m.className.toLowerCase().includes(searchQuery)
|
|
407
713
|
);
|
|
408
714
|
}
|
|
715
|
+
// Model namespace filter
|
|
716
|
+
if (!selectedModelNamespaces.has('all')) {
|
|
717
|
+
filteredModels = filteredModels.filter(m => selectedModelNamespaces.has(getModelNamespace(m)));
|
|
718
|
+
}
|
|
719
|
+
// Model flag filter
|
|
720
|
+
if (!selectedModelFlags.has('all')) {
|
|
721
|
+
filteredModels = filteredModels.filter(m => {
|
|
722
|
+
const hasAssoc = (m.associations || []).length > 0;
|
|
723
|
+
const hasValid = (m.validations || []).length > 0;
|
|
724
|
+
const hasCb = (m.callbacks || []).length > 0;
|
|
725
|
+
const hasConcern = (m.concerns || []).length > 0;
|
|
726
|
+
const hasEnum = (m.enums || []).length > 0;
|
|
727
|
+
|
|
728
|
+
if (selectedModelFlags.has('assoc') && !hasAssoc) return false;
|
|
729
|
+
if (selectedModelFlags.has('valid') && !hasValid) return false;
|
|
730
|
+
if (selectedModelFlags.has('cb') && !hasCb) return false;
|
|
731
|
+
if (selectedModelFlags.has('concern') && !hasConcern) return false;
|
|
732
|
+
if (selectedModelFlags.has('enum') && !hasEnum) return false;
|
|
733
|
+
return true;
|
|
734
|
+
});
|
|
735
|
+
}
|
|
409
736
|
const displayed = filteredModels.slice(0, modelsDisplayCount);
|
|
410
737
|
const hasMore = filteredModels.length > modelsDisplayCount;
|
|
411
738
|
|
|
@@ -431,6 +758,7 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
431
758
|
\${hasMore ? \`
|
|
432
759
|
<div class="show-more-container">
|
|
433
760
|
<button class="show-more-btn" onclick="loadMoreModels()">Show More (+50)</button>
|
|
761
|
+
<button class="show-more-btn" onclick="showAllModels()">Show All</button>
|
|
434
762
|
<span class="show-more-count">\${modelsDisplayCount} / \${filteredModels.length}</span>
|
|
435
763
|
</div>
|
|
436
764
|
\` : ''}
|
|
@@ -442,6 +770,11 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
442
770
|
renderMainPanel();
|
|
443
771
|
};
|
|
444
772
|
|
|
773
|
+
window.showAllModels = function() {
|
|
774
|
+
modelsDisplayCount = filteredModels.length;
|
|
775
|
+
renderMainPanel();
|
|
776
|
+
};
|
|
777
|
+
|
|
445
778
|
let grpcDisplayCount = 50;
|
|
446
779
|
let filteredGrpc = grpcServices;
|
|
447
780
|
|
|
@@ -454,6 +787,25 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
454
787
|
(svc.rpcs && svc.rpcs.some(rpc => rpc.name && rpc.name.toLowerCase().includes(searchQuery)))
|
|
455
788
|
);
|
|
456
789
|
}
|
|
790
|
+
if (!selectedGrpcNamespaces.has('all')) {
|
|
791
|
+
filteredGrpc = filteredGrpc.filter(svc => selectedGrpcNamespaces.has(svc.namespace || ''));
|
|
792
|
+
}
|
|
793
|
+
if (!selectedGrpcFlags.has('all')) {
|
|
794
|
+
filteredGrpc = filteredGrpc.filter(svc => {
|
|
795
|
+
const hasPolicies = (svc.policies || []).length > 0;
|
|
796
|
+
const hasSerializers = (svc.serializers || []).length > 0;
|
|
797
|
+
const hasConcerns = (svc.concerns || []).length > 0;
|
|
798
|
+
const usesModels = (svc.rpcs || []).some(r => (r.modelsUsed || []).length > 0);
|
|
799
|
+
const usesServices = (svc.rpcs || []).some(r => (r.servicesUsed || []).length > 0);
|
|
800
|
+
|
|
801
|
+
if (selectedGrpcFlags.has('policies') && !hasPolicies) return false;
|
|
802
|
+
if (selectedGrpcFlags.has('serializers') && !hasSerializers) return false;
|
|
803
|
+
if (selectedGrpcFlags.has('concerns') && !hasConcerns) return false;
|
|
804
|
+
if (selectedGrpcFlags.has('modelsUsed') && !usesModels) return false;
|
|
805
|
+
if (selectedGrpcFlags.has('servicesUsed') && !usesServices) return false;
|
|
806
|
+
return true;
|
|
807
|
+
});
|
|
808
|
+
}
|
|
457
809
|
|
|
458
810
|
const displayedGrpc = filteredGrpc.slice(0, grpcDisplayCount);
|
|
459
811
|
|
|
@@ -524,10 +876,112 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
524
876
|
detailPanel.classList.add('open');
|
|
525
877
|
};
|
|
526
878
|
|
|
527
|
-
|
|
528
|
-
|
|
879
|
+
// Diagram state
|
|
880
|
+
let diagramModelCount = 15;
|
|
881
|
+
let diagramNamespace = 'all';
|
|
882
|
+
let diagramFocusModel = '';
|
|
883
|
+
let diagramDepth = 2;
|
|
884
|
+
|
|
885
|
+
function getNamespaces() {
|
|
886
|
+
const ns = new Set();
|
|
887
|
+
const prefixes = new Map(); // Count common prefixes
|
|
888
|
+
|
|
889
|
+
models.forEach(m => {
|
|
890
|
+
const name = m.name || m.className || '';
|
|
891
|
+
// Check for Ruby namespace (::)
|
|
892
|
+
if (name.includes('::')) {
|
|
893
|
+
ns.add(name.split('::')[0]);
|
|
894
|
+
}
|
|
895
|
+
// Also try to find common prefixes (e.g., User, UserProfile, UserSetting -> User)
|
|
896
|
+
const match = name.match(/^([A-Z][a-z]+)/);
|
|
897
|
+
if (match) {
|
|
898
|
+
const prefix = match[1];
|
|
899
|
+
prefixes.set(prefix, (prefixes.get(prefix) || 0) + 1);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// Add prefixes that have 3+ models as pseudo-namespaces
|
|
904
|
+
prefixes.forEach((count, prefix) => {
|
|
905
|
+
if (count >= 3 && !ns.has(prefix)) {
|
|
906
|
+
ns.add(prefix + '*'); // Mark as prefix-based filter
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
return ['all', ...Array.from(ns).sort()];
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function getModelNames() {
|
|
914
|
+
return models.map(m => m.name || m.className).sort();
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Get related models up to specified depth
|
|
918
|
+
function getRelatedModels(centerModel, depth) {
|
|
919
|
+
const related = new Set([centerModel]);
|
|
920
|
+
const modelMap = new Map();
|
|
921
|
+
models.forEach(m => {
|
|
922
|
+
const name = m.name || m.className;
|
|
923
|
+
modelMap.set(name, m);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
for (let d = 0; d < depth; d++) {
|
|
927
|
+
const currentModels = [...related];
|
|
928
|
+
currentModels.forEach(modelName => {
|
|
929
|
+
const model = modelMap.get(modelName);
|
|
930
|
+
if (!model) return;
|
|
931
|
+
|
|
932
|
+
model.associations.forEach(assoc => {
|
|
933
|
+
const targetName = assoc.className || capitalize(singularize(assoc.name));
|
|
934
|
+
if (modelMap.has(targetName)) {
|
|
935
|
+
related.add(targetName);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// Also find models that reference this model
|
|
940
|
+
models.forEach(m => {
|
|
941
|
+
const mName = m.name || m.className;
|
|
942
|
+
m.associations.forEach(assoc => {
|
|
943
|
+
const targetName = assoc.className || capitalize(singularize(assoc.name));
|
|
944
|
+
if (targetName === modelName) {
|
|
945
|
+
related.add(mName);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return related;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function generateMermaidCode(modelCount, namespace, focusModel, depth) {
|
|
956
|
+
let filteredModels = [...models];
|
|
957
|
+
|
|
958
|
+
// Filter by focus model (takes priority)
|
|
959
|
+
if (focusModel && focusModel !== '') {
|
|
960
|
+
const relatedNames = getRelatedModels(focusModel, depth);
|
|
961
|
+
filteredModels = filteredModels.filter(m => {
|
|
962
|
+
const name = m.name || m.className;
|
|
963
|
+
return relatedNames.has(name);
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
// Filter by namespace
|
|
967
|
+
else if (namespace !== 'all') {
|
|
968
|
+
filteredModels = filteredModels.filter(m => {
|
|
969
|
+
const name = m.name || m.className || '';
|
|
970
|
+
// Handle prefix-based filter (ends with *)
|
|
971
|
+
if (namespace.endsWith('*')) {
|
|
972
|
+
const prefix = namespace.slice(0, -1);
|
|
973
|
+
return name.startsWith(prefix);
|
|
974
|
+
}
|
|
975
|
+
// Handle Ruby namespace (::)
|
|
976
|
+
return name.startsWith(namespace + '::') || name === namespace;
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Sort and limit
|
|
981
|
+
const count = modelCount === 'all' ? filteredModels.length : parseInt(modelCount) || 15;
|
|
982
|
+
const topModels = filteredModels
|
|
529
983
|
.sort((a, b) => b.associations.length - a.associations.length)
|
|
530
|
-
.slice(0,
|
|
984
|
+
.slice(0, count);
|
|
531
985
|
|
|
532
986
|
const modelNames = new Set(topModels.map(m => m.name || m.className));
|
|
533
987
|
let mermaidCode = 'erDiagram\\n';
|
|
@@ -560,12 +1014,163 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
560
1014
|
});
|
|
561
1015
|
}
|
|
562
1016
|
|
|
1017
|
+
return { mermaidCode, modelCount: topModels.length, totalModels: filteredModels.length };
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
window.toggleCustomInput = function() {
|
|
1021
|
+
const countSelect = document.getElementById('model-count-select');
|
|
1022
|
+
const customWrapper = document.getElementById('custom-input-wrapper');
|
|
1023
|
+
if (countSelect.value === 'custom') {
|
|
1024
|
+
customWrapper.style.display = 'flex';
|
|
1025
|
+
document.getElementById('model-count-input').focus();
|
|
1026
|
+
} else {
|
|
1027
|
+
customWrapper.style.display = 'none';
|
|
1028
|
+
document.getElementById('model-count-input').value = '';
|
|
1029
|
+
updateDiagram();
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
window.clearFocusModel = function() {
|
|
1034
|
+
document.getElementById('focus-model-select').value = '';
|
|
1035
|
+
diagramFocusModel = '';
|
|
1036
|
+
updateDiagram();
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
window.updateDiagram = function() {
|
|
1040
|
+
const countInput = document.getElementById('model-count-input');
|
|
1041
|
+
const countSelect = document.getElementById('model-count-select');
|
|
1042
|
+
const nsSelect = document.getElementById('namespace-select');
|
|
1043
|
+
const focusSelect = document.getElementById('focus-model-select');
|
|
1044
|
+
const depthSelect = document.getElementById('depth-select');
|
|
1045
|
+
|
|
1046
|
+
// Get count from input or select
|
|
1047
|
+
let count;
|
|
1048
|
+
if (countSelect && countSelect.value === 'custom') {
|
|
1049
|
+
count = countInput ? countInput.value.trim() || '15' : '15';
|
|
1050
|
+
} else {
|
|
1051
|
+
count = countSelect ? countSelect.value : '15';
|
|
1052
|
+
}
|
|
1053
|
+
diagramModelCount = count;
|
|
1054
|
+
diagramNamespace = nsSelect ? nsSelect.value : 'all';
|
|
1055
|
+
diagramFocusModel = focusSelect ? focusSelect.value : '';
|
|
1056
|
+
diagramDepth = depthSelect ? parseInt(depthSelect.value) || 2 : 2;
|
|
1057
|
+
|
|
1058
|
+
// If focus model is set, disable namespace filter and enable depth
|
|
1059
|
+
if (nsSelect) {
|
|
1060
|
+
nsSelect.disabled = diagramFocusModel !== '';
|
|
1061
|
+
nsSelect.style.opacity = diagramFocusModel !== '' ? '0.5' : '1';
|
|
1062
|
+
}
|
|
1063
|
+
if (depthSelect) {
|
|
1064
|
+
depthSelect.disabled = diagramFocusModel === '';
|
|
1065
|
+
depthSelect.style.opacity = diagramFocusModel !== '' ? '1' : '0.5';
|
|
1066
|
+
const depthLabel = depthSelect.parentElement?.querySelector('span');
|
|
1067
|
+
if (depthLabel) {
|
|
1068
|
+
depthLabel.style.opacity = diagramFocusModel !== '' ? '1' : '0.5';
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const { mermaidCode, modelCount, totalModels } = generateMermaidCode(count, diagramNamespace, diagramFocusModel, diagramDepth);
|
|
1073
|
+
|
|
1074
|
+
// Update diagram - need to recreate SVG
|
|
1075
|
+
const container = document.getElementById('mermaid-container');
|
|
1076
|
+
const diagram = document.getElementById('mermaid-diagram');
|
|
1077
|
+
if (diagram && window.mermaid) {
|
|
1078
|
+
// Remove old SVG
|
|
1079
|
+
const oldSvg = container.querySelector('svg');
|
|
1080
|
+
if (oldSvg) oldSvg.remove();
|
|
1081
|
+
|
|
1082
|
+
// Update mermaid code
|
|
1083
|
+
diagram.textContent = mermaidCode;
|
|
1084
|
+
diagram.removeAttribute('data-processed');
|
|
1085
|
+
diagram.style.display = 'block';
|
|
1086
|
+
|
|
1087
|
+
// Re-render
|
|
1088
|
+
window.mermaid.init(undefined, diagram);
|
|
1089
|
+
setTimeout(() => {
|
|
1090
|
+
initDiagramPanZoom();
|
|
1091
|
+
}, 200);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Update title
|
|
1095
|
+
const title = document.querySelector('.diagram-title-text');
|
|
1096
|
+
if (title) {
|
|
1097
|
+
let filterText = '';
|
|
1098
|
+
if (diagramFocusModel) {
|
|
1099
|
+
filterText = \` around \${diagramFocusModel} (depth \${diagramDepth})\`;
|
|
1100
|
+
} else if (diagramNamespace !== 'all') {
|
|
1101
|
+
filterText = \` in \${diagramNamespace}\`;
|
|
1102
|
+
}
|
|
1103
|
+
title.textContent = \`Model Relationships (\${modelCount}/\${totalModels} models\${filterText})\`;
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
function renderDiagramView() {
|
|
1108
|
+
const namespaces = getNamespaces();
|
|
1109
|
+
const modelNames = getModelNames();
|
|
1110
|
+
const { mermaidCode, modelCount, totalModels } = generateMermaidCode(diagramModelCount, diagramNamespace, diagramFocusModel, diagramDepth);
|
|
1111
|
+
|
|
1112
|
+
let filterText = '';
|
|
1113
|
+
if (diagramFocusModel) {
|
|
1114
|
+
filterText = \` around \${diagramFocusModel} (depth \${diagramDepth})\`;
|
|
1115
|
+
} else if (diagramNamespace !== 'all') {
|
|
1116
|
+
filterText = \` in \${diagramNamespace}\`;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const isCustom = !['15', '30', '50', '100', 'all'].includes(String(diagramModelCount));
|
|
1120
|
+
|
|
563
1121
|
return \`
|
|
564
|
-
<div class="
|
|
565
|
-
<div class="panel-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1122
|
+
<div class="diagram-view-wrapper" style="display:flex;flex-direction:column;height:100%;min-height:0;">
|
|
1123
|
+
<div class="panel-header" style="flex-wrap:wrap;gap:8px;flex-shrink:0;">
|
|
1124
|
+
<div class="panel-title diagram-title-text">Model Relationships (\${modelCount}/\${totalModels} models\${filterText})</div>
|
|
1125
|
+
<div class="diagram-filters" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;font-size:12px;">
|
|
1126
|
+
<label style="display:flex;align-items:center;gap:6px;">
|
|
1127
|
+
<span>Limit:</span>
|
|
1128
|
+
<select id="model-count-select" onchange="toggleCustomInput()" style="padding:6px 10px;border-radius:4px;background:#2d2d2d;color:#fff;border:1px solid #444;min-width:80px;">
|
|
1129
|
+
<option value="15" \${diagramModelCount == 15 ? 'selected' : ''}>15</option>
|
|
1130
|
+
<option value="30" \${diagramModelCount == 30 ? 'selected' : ''}>30</option>
|
|
1131
|
+
<option value="50" \${diagramModelCount == 50 ? 'selected' : ''}>50</option>
|
|
1132
|
+
<option value="100" \${diagramModelCount == 100 ? 'selected' : ''}>100</option>
|
|
1133
|
+
<option value="all" \${diagramModelCount === 'all' ? 'selected' : ''}>All (\${models.length})</option>
|
|
1134
|
+
<option value="custom" \${isCustom ? 'selected' : ''}>Custom...</option>
|
|
1135
|
+
</select>
|
|
1136
|
+
<div id="custom-input-wrapper" style="display:\${isCustom ? 'flex' : 'none'};align-items:center;gap:4px;">
|
|
1137
|
+
<input type="number" id="model-count-input" placeholder="Enter number" min="1" max="\${models.length}"
|
|
1138
|
+
value="\${isCustom ? diagramModelCount : ''}"
|
|
1139
|
+
style="width:100px;padding:6px 10px;border-radius:4px;background:#2d2d2d;color:#fff;border:1px solid #444;"
|
|
1140
|
+
onchange="updateDiagram()" onkeyup="if(event.key==='Enter')updateDiagram()">
|
|
1141
|
+
<button onclick="updateDiagram()" style="padding:6px 12px;border-radius:4px;background:#3b82f6;color:#fff;border:none;cursor:pointer;">Apply</button>
|
|
1142
|
+
</div>
|
|
1143
|
+
</label>
|
|
1144
|
+
<label style="display:flex;align-items:center;gap:6px;">
|
|
1145
|
+
<span>Namespace:</span>
|
|
1146
|
+
<select id="namespace-select" onchange="updateDiagram()" style="padding:6px 10px;border-radius:4px;background:#2d2d2d;color:#fff;border:1px solid #444;\${diagramFocusModel ? 'opacity:0.5;' : ''}" \${diagramFocusModel ? 'disabled' : ''}>
|
|
1147
|
+
<option value="all" \${diagramNamespace === 'all' ? 'selected' : ''}>All</option>
|
|
1148
|
+
\${namespaces.filter(ns => ns !== 'all').map(ns => \`<option value="\${ns}" \${diagramNamespace === ns ? 'selected' : ''}>\${ns}</option>\`).join('')}
|
|
1149
|
+
</select>
|
|
1150
|
+
</label>
|
|
1151
|
+
<label style="display:flex;align-items:center;gap:6px;">
|
|
1152
|
+
<span>Focus:</span>
|
|
1153
|
+
<select id="focus-model-select" onchange="updateDiagram()" style="padding:6px 10px;border-radius:4px;background:#2d2d2d;color:#fff;border:1px solid #444;max-width:150px;">
|
|
1154
|
+
<option value="">None</option>
|
|
1155
|
+
\${modelNames.map(name => \`<option value="\${name}" \${diagramFocusModel === name ? 'selected' : ''}>\${name}</option>\`).join('')}
|
|
1156
|
+
</select>
|
|
1157
|
+
\${diagramFocusModel ? \`<button onclick="clearFocusModel()" style="padding:4px 8px;border-radius:4px;background:#666;color:#fff;border:none;cursor:pointer;" title="Clear focus">\u2715</button>\` : ''}
|
|
1158
|
+
</label>
|
|
1159
|
+
<label style="display:flex;align-items:center;gap:6px;">
|
|
1160
|
+
<span style="opacity:\${diagramFocusModel ? 1 : 0.5}">Depth:</span>
|
|
1161
|
+
<select id="depth-select" onchange="updateDiagram()" \${diagramFocusModel ? '' : 'disabled'} style="padding:6px 10px;border-radius:4px;background:#2d2d2d;color:#fff;border:1px solid #444;opacity:\${diagramFocusModel ? 1 : 0.5}">
|
|
1162
|
+
<option value="1" \${diagramDepth === 1 ? 'selected' : ''}>1</option>
|
|
1163
|
+
<option value="2" \${diagramDepth === 2 ? 'selected' : ''}>2</option>
|
|
1164
|
+
<option value="3" \${diagramDepth === 3 ? 'selected' : ''}>3</option>
|
|
1165
|
+
<option value="4" \${diagramDepth === 4 ? 'selected' : ''}>4</option>
|
|
1166
|
+
<option value="5" \${diagramDepth === 5 ? 'selected' : ''}>5</option>
|
|
1167
|
+
</select>
|
|
1168
|
+
</label>
|
|
1169
|
+
</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
<div class="mermaid-container" id="mermaid-container" style="flex:1;min-height:0;">
|
|
1172
|
+
<pre class="mermaid" id="mermaid-diagram">\${mermaidCode}</pre>
|
|
1173
|
+
</div>
|
|
569
1174
|
</div>
|
|
570
1175
|
\`;
|
|
571
1176
|
}
|
|
@@ -609,6 +1214,20 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
609
1214
|
const svg = container?.querySelector('svg');
|
|
610
1215
|
if (!svg) return;
|
|
611
1216
|
|
|
1217
|
+
// Calculate dynamic max zoom based on SVG size
|
|
1218
|
+
const svgRect = svg.getBoundingClientRect();
|
|
1219
|
+
const containerRect = container.getBoundingClientRect();
|
|
1220
|
+
const svgWidth = svgRect.width || 1000;
|
|
1221
|
+
const svgHeight = svgRect.height || 500;
|
|
1222
|
+
|
|
1223
|
+
// Max zoom: allow reading small text clearly
|
|
1224
|
+
// For very wide diagrams (many models), need much higher zoom
|
|
1225
|
+
const minZoom = 0.01;
|
|
1226
|
+
const maxZoom = Math.max(100, Math.ceil(svgWidth / 20)); // Very aggressive zoom
|
|
1227
|
+
window.diagramMaxZoom = maxZoom;
|
|
1228
|
+
window.diagramMinZoom = minZoom;
|
|
1229
|
+
console.log('Diagram zoom range:', minZoom, '-', maxZoom, 'x (SVG width:', svgWidth, 'px)');
|
|
1230
|
+
|
|
612
1231
|
let scale = 1;
|
|
613
1232
|
let translateX = 0;
|
|
614
1233
|
let translateY = 0;
|
|
@@ -803,11 +1422,13 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
803
1422
|
svg.style.transform = \`translate(\${translateX}px, \${translateY}px) scale(\${scale})\`;
|
|
804
1423
|
}
|
|
805
1424
|
|
|
806
|
-
// Mouse wheel zoom
|
|
1425
|
+
// Mouse wheel zoom (extended range: 0.3x to 10x)
|
|
807
1426
|
container.addEventListener('wheel', (e) => {
|
|
808
1427
|
e.preventDefault();
|
|
809
|
-
|
|
810
|
-
|
|
1428
|
+
// Dynamic step: larger steps at higher zoom levels for faster navigation
|
|
1429
|
+
const step = Math.max(0.1, scale * 0.15);
|
|
1430
|
+
const delta = e.deltaY > 0 ? -step : step;
|
|
1431
|
+
scale = Math.max(minZoom, Math.min(maxZoom, scale + delta));
|
|
811
1432
|
updateTransform();
|
|
812
1433
|
}, { passive: false });
|
|
813
1434
|
|
|
@@ -834,7 +1455,7 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
834
1455
|
e.touches[0].clientY - e.touches[1].clientY
|
|
835
1456
|
);
|
|
836
1457
|
const delta = (dist - lastTouchDist) * 0.01;
|
|
837
|
-
scale = Math.max(
|
|
1458
|
+
scale = Math.max(minZoom, Math.min(maxZoom, scale + delta));
|
|
838
1459
|
lastTouchDist = dist;
|
|
839
1460
|
updateTransform();
|
|
840
1461
|
} else if (e.touches.length === 1 && isDragging) {
|
|
@@ -873,7 +1494,10 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
873
1494
|
|
|
874
1495
|
// Global functions for controls
|
|
875
1496
|
window.diagramZoom = (delta) => {
|
|
876
|
-
|
|
1497
|
+
// Dynamic step based on current scale
|
|
1498
|
+
const step = Math.max(0.2, scale * 0.2);
|
|
1499
|
+
const actualDelta = delta > 0 ? step : -step;
|
|
1500
|
+
scale = Math.max(minZoom, Math.min(maxZoom, scale + actualDelta));
|
|
877
1501
|
updateTransform();
|
|
878
1502
|
};
|
|
879
1503
|
|
|
@@ -1147,5 +1771,5 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as d from'fs';import*as c from'path';
|
|
|
1147
1771
|
`).join("")}
|
|
1148
1772
|
</tbody>
|
|
1149
1773
|
</table>
|
|
1150
|
-
`}highlightParams(t){return t.replace(/:([a-zA-Z_]+)/g,'<span class="param">:$1</span>')}};async function m(){let
|
|
1151
|
-
export{
|
|
1774
|
+
`}highlightParams(t){return t.replace(/:([a-zA-Z_]+)/g,'<span class="param">:$1</span>')}};async function m(){let o=process.argv[2]||process.cwd(),t=process.argv[3]||d.join(o,"rails-map.html");await new i(o).generate({title:"Rails Application Map",outputPath:t});}var p=import.meta.url===`file://${process.argv[1]}`;p&&m().catch(console.error);
|
|
1775
|
+
export{i as a};
|