modviz 0.1.2
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/README.md +261 -0
- package/dist/client/_shell.html +0 -0
- package/dist/client/android-chrome-192x192.png +0 -0
- package/dist/client/android-chrome-512x512.png +0 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/app-Sjrldkrg.css +2 -0
- package/dist/client/assets/button-aOWckyNs.js +1 -0
- package/dist/client/assets/check-C0EQe2S8.js +1 -0
- package/dist/client/assets/chevron-down-DrspihmT.js +1 -0
- package/dist/client/assets/chevron-right-DIJHr8AN.js +1 -0
- package/dist/client/assets/colors-CQoWjU5E.js +1 -0
- package/dist/client/assets/command-kkF7_wdz.js +45 -0
- package/dist/client/assets/compare-K6jVFsiI.js +1 -0
- package/dist/client/assets/compare-TOnoe1EP.js +2 -0
- package/dist/client/assets/configure-DnlSnhtN.js +1 -0
- package/dist/client/assets/explorer-C7NclVKg.js +2 -0
- package/dist/client/assets/explorer-Xu2X6XXF.js +1 -0
- package/dist/client/assets/external-link-B9eNA-li.js +1 -0
- package/dist/client/assets/flamegraph-CRVZSAlj.js +13 -0
- package/dist/client/assets/floating-ui.dom-DLIT5tPE.js +1 -0
- package/dist/client/assets/formatting-CiC0SYI8.js +1 -0
- package/dist/client/assets/graph-6Vr74V1k.js +2 -0
- package/dist/client/assets/graph-CVzypIGU.js +2 -0
- package/dist/client/assets/graph-command-menu-D2MoVT2B.js +4 -0
- package/dist/client/assets/graph-command-menu-VWiiW3qy.css +1 -0
- package/dist/client/assets/hierarchy-C8xxGb_u.js +2 -0
- package/dist/client/assets/hierarchy-iO7d4oSK.js +2 -0
- package/dist/client/assets/import-display-D-jRyyjM.js +5 -0
- package/dist/client/assets/imports-CPggnrs-.js +2 -0
- package/dist/client/assets/imports-CodbPyUJ.js +1 -0
- package/dist/client/assets/index-Dj_rhLdR.js +12 -0
- package/dist/client/assets/input-BCFMF0aR.js +1 -0
- package/dist/client/assets/jsx-runtime-DWSWI4JT.js +1 -0
- package/dist/client/assets/lazyRouteComponent-PTSyFp1J.js +1 -0
- package/dist/client/assets/loading-state-CyC_hrTF.js +1 -0
- package/dist/client/assets/modviz-data-BiRqoDI5.js +1 -0
- package/dist/client/assets/modviz-layout-Do93E-IB.js +1 -0
- package/dist/client/assets/modviz-sigma-Xl8qHaxK.js +312 -0
- package/dist/client/assets/portal-BgAm3V3j.js +1 -0
- package/dist/client/assets/routes-DBtN8hrZ.js +1 -0
- package/dist/client/assets/schemas-B4zfTepZ.js +39 -0
- package/dist/client/assets/search-BYHxNrYn.js +1 -0
- package/dist/client/assets/search-params-BaZRBvGI.js +1 -0
- package/dist/client/assets/setup-view-j1o0TuZz.js +1 -0
- package/dist/client/assets/summary-D703Zh3x.js +1 -0
- package/dist/client/assets/tooltip-B1VDU9HG.js +1 -0
- package/dist/client/assets/trace-B67CM5s2.js +2 -0
- package/dist/client/assets/trace-Bwwdw3AM.js +1 -0
- package/dist/client/assets/treemap-BZf2shzY.js +5 -0
- package/dist/client/assets/treemap-Csroy8Gy.js +2 -0
- package/dist/client/assets/utils-DkkZd0ys.js +1 -0
- package/dist/client/favicon-16x16.png +0 -0
- package/dist/client/favicon-32x32.png +0 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/site.webmanifest +19 -0
- package/dist/mod/cli-options.js +225 -0
- package/dist/mod/cli.js +519 -0
- package/dist/mod/index.js +3 -0
- package/dist/mod/llm-analysis.js +29 -0
- package/dist/mod/llm-output.js +742 -0
- package/dist/mod/module-graph-plugins.js +60 -0
- package/dist/mod/production-server.js +103 -0
- package/dist/mod/runtime-host.js +217 -0
- package/dist/mod/snapshot-history.js +73 -0
- package/dist/mod/types.js +3 -0
- package/dist/server/assets/__23tanstack-start-plugin-adapters-3QxJs4a0.js +5 -0
- package/dist/server/assets/_tanstack-start-manifest_v-DMytuIue.js +188 -0
- package/dist/server/assets/button-Bqnnid5i.js +41 -0
- package/dist/server/assets/colors-DhAxrYua.js +100 -0
- package/dist/server/assets/command-SdxShIbL.js +138 -0
- package/dist/server/assets/compare-BFMiiUsB.js +562 -0
- package/dist/server/assets/compare-CpOqTpYu.js +10 -0
- package/dist/server/assets/configure-Bvd45DTI.js +288 -0
- package/dist/server/assets/explorer-C7dODpSv.js +379 -0
- package/dist/server/assets/explorer-CpSb0JTa.js +20 -0
- package/dist/server/assets/flamegraph-CdW-VG6I.js +198 -0
- package/dist/server/assets/formatting-iDlL4tA-.js +4 -0
- package/dist/server/assets/graph-C1G9H5O4.js +438 -0
- package/dist/server/assets/graph-DAGFGioS.js +45 -0
- package/dist/server/assets/graph-command-menu-BV5GtOWx.js +249 -0
- package/dist/server/assets/hierarchy-B4K-Zfn9.js +16 -0
- package/dist/server/assets/hierarchy-BGpWSG-f.js +104 -0
- package/dist/server/assets/import-display-BVIOWcsm.js +124 -0
- package/dist/server/assets/imports-B6JBDl_h.js +379 -0
- package/dist/server/assets/imports-BGe5tZJT.js +28 -0
- package/dist/server/assets/input-C5r-hBix.js +19 -0
- package/dist/server/assets/loading-state-CrvCWTtw.js +23 -0
- package/dist/server/assets/modviz-data-CUyTorv0.js +197 -0
- package/dist/server/assets/modviz-layout-BAH2ogse.js +253 -0
- package/dist/server/assets/modviz-server-DoMlAyFW.js +195 -0
- package/dist/server/assets/modviz-sigma-XYxARWqd.js +1441 -0
- package/dist/server/assets/rolldown-runtime-rSIU-vHC.js +13 -0
- package/dist/server/assets/router-DYJ-zDbU.js +353 -0
- package/dist/server/assets/routes-DInCacpY.js +244 -0
- package/dist/server/assets/search-params-BNApPgkX.js +26 -0
- package/dist/server/assets/setup-view-DjI49Iqr.js +91 -0
- package/dist/server/assets/start-Ba3KII43.js +4 -0
- package/dist/server/assets/summary-z3lXkLCQ.js +208 -0
- package/dist/server/assets/tooltip-Ck0DDfF7.js +24 -0
- package/dist/server/assets/trace-ColKOf9g.js +16 -0
- package/dist/server/assets/trace-eVs-hIZO.js +578 -0
- package/dist/server/assets/treemap-BbZ9M4GF.js +17 -0
- package/dist/server/assets/treemap-CrgWFoCF.js +912 -0
- package/dist/server/assets/utils-BQZm0uva.js +8 -0
- package/dist/server/server.js +5259 -0
- package/dist/shared/modviz-compare.js +120 -0
- package/dist/shared/modviz-trace.js +244 -0
- package/package.json +135 -0
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
import { t as Button } from "./button-Bqnnid5i.js";
|
|
2
|
+
import { i as TooltipTrigger, r as TooltipContent, t as Tooltip } from "./tooltip-Ck0DDfF7.js";
|
|
3
|
+
import { c as getExternalPackageName, d as getWorkspacePackageNames, f as isModvizBundleReady, m as useModvizBundle, u as getNodeScope } from "./modviz-data-CUyTorv0.js";
|
|
4
|
+
import { t as colors } from "./colors-DhAxrYua.js";
|
|
5
|
+
import { t as Route$1 } from "./treemap-BbZ9M4GF.js";
|
|
6
|
+
import { t as ModvizLayout } from "./modviz-layout-BAH2ogse.js";
|
|
7
|
+
import { t as SetupView } from "./setup-view-DjI49Iqr.js";
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { Link } from "@tanstack/react-router";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { ChevronLeft, ExternalLink, FolderTree, Info } from "lucide-react";
|
|
12
|
+
import { Portal } from "@ark-ui/react/portal";
|
|
13
|
+
import { layoutWithLines, prepareWithSegments } from "@chenglou/pretext";
|
|
14
|
+
//#region src/utils/treemap.ts
|
|
15
|
+
var ROOT_COLOR = "#94a3b8";
|
|
16
|
+
var createTreeNode = (id, label, kind, parentId, color = ROOT_COLOR) => ({
|
|
17
|
+
id,
|
|
18
|
+
label,
|
|
19
|
+
kind,
|
|
20
|
+
color,
|
|
21
|
+
parentId,
|
|
22
|
+
children: [],
|
|
23
|
+
size: 0,
|
|
24
|
+
moduleCount: 0,
|
|
25
|
+
totalIncoming: 0,
|
|
26
|
+
totalOutgoing: 0,
|
|
27
|
+
totalNamedImports: 0
|
|
28
|
+
});
|
|
29
|
+
var getModuleWeight = (node) => Math.max(1, node.importees.length + node.importedBy.length);
|
|
30
|
+
var getNodeColor = (node, colorMap) => {
|
|
31
|
+
const colorKey = node.cluster ?? node.package?.name ?? node.type ?? node.path;
|
|
32
|
+
return colorMap.get(colorKey) ?? ROOT_COLOR;
|
|
33
|
+
};
|
|
34
|
+
var getWorkspaceSegments = (node) => {
|
|
35
|
+
const segments = node.path.split(/[\\/]/).filter(Boolean);
|
|
36
|
+
return segments.length > 0 ? segments : [node.name];
|
|
37
|
+
};
|
|
38
|
+
var getExternalSegments = (node, packageName) => {
|
|
39
|
+
const segments = node.path.split(/[\\/]/).filter(Boolean);
|
|
40
|
+
const nodeModulesIndex = segments.lastIndexOf("node_modules");
|
|
41
|
+
if (nodeModulesIndex === -1) return [packageName, ...segments];
|
|
42
|
+
const scopeOrName = segments[nodeModulesIndex + 1];
|
|
43
|
+
const isScoped = Boolean(scopeOrName?.startsWith("@"));
|
|
44
|
+
const rest = segments.slice(nodeModulesIndex + (isScoped ? 3 : 2));
|
|
45
|
+
return rest.length > 0 ? [packageName, ...rest] : [packageName, node.name];
|
|
46
|
+
};
|
|
47
|
+
var ensureChild = (parent, nodesById, id, label, kind, color) => {
|
|
48
|
+
const existing = nodesById.get(id);
|
|
49
|
+
if (existing) return existing;
|
|
50
|
+
const child = createTreeNode(id, label, kind, parent.id, color);
|
|
51
|
+
parent.children.push(child);
|
|
52
|
+
nodesById.set(id, child);
|
|
53
|
+
return child;
|
|
54
|
+
};
|
|
55
|
+
var aggregateTree = (node) => {
|
|
56
|
+
if (node.kind === "module" && node.sourceNode) {
|
|
57
|
+
node.size = getModuleWeight(node.sourceNode);
|
|
58
|
+
node.moduleCount = 1;
|
|
59
|
+
node.totalIncoming = node.sourceNode.importedBy.length;
|
|
60
|
+
node.totalOutgoing = node.sourceNode.importees.length;
|
|
61
|
+
node.totalNamedImports = node.sourceNode.imports.length;
|
|
62
|
+
return node;
|
|
63
|
+
}
|
|
64
|
+
for (const child of node.children) {
|
|
65
|
+
aggregateTree(child);
|
|
66
|
+
node.size += child.size;
|
|
67
|
+
node.moduleCount += child.moduleCount;
|
|
68
|
+
node.totalIncoming += child.totalIncoming;
|
|
69
|
+
node.totalOutgoing += child.totalOutgoing;
|
|
70
|
+
node.totalNamedImports += child.totalNamedImports;
|
|
71
|
+
}
|
|
72
|
+
node.children.sort((left, right) => right.size - left.size || left.label.localeCompare(right.label));
|
|
73
|
+
if (node.children[0]) node.color = node.children[0].color;
|
|
74
|
+
return node;
|
|
75
|
+
};
|
|
76
|
+
var buildTreemapModel = (nodes, workspacePackageNames, colorMap) => {
|
|
77
|
+
const root = createTreeNode("root", "All Modules", "root", null, ROOT_COLOR);
|
|
78
|
+
const nodesById = new Map([[root.id, root]]);
|
|
79
|
+
const workspaceRoot = createTreeNode("scope:workspace", "Workspace", "scope", root.id, "#2563eb");
|
|
80
|
+
const externalRoot = createTreeNode("scope:external", "External", "scope", root.id, "#0f766e");
|
|
81
|
+
root.children.push(workspaceRoot, externalRoot);
|
|
82
|
+
nodesById.set(workspaceRoot.id, workspaceRoot);
|
|
83
|
+
nodesById.set(externalRoot.id, externalRoot);
|
|
84
|
+
for (const node of nodes) {
|
|
85
|
+
const scope = getNodeScope(node, workspacePackageNames);
|
|
86
|
+
const parentRoot = scope === "workspace" ? workspaceRoot : externalRoot;
|
|
87
|
+
const packageName = scope === "external" ? getExternalPackageName(node) : null;
|
|
88
|
+
const segments = scope === "workspace" ? getWorkspaceSegments(node) : getExternalSegments(node, packageName ?? "external");
|
|
89
|
+
const groupSegments = segments.slice(0, -1);
|
|
90
|
+
const leafLabel = segments.at(-1) ?? node.name;
|
|
91
|
+
const color = getNodeColor(node, colorMap);
|
|
92
|
+
let currentParent = parentRoot;
|
|
93
|
+
const keyParts = [scope];
|
|
94
|
+
for (const [index, segment] of groupSegments.entries()) {
|
|
95
|
+
keyParts.push(segment);
|
|
96
|
+
const groupKind = scope === "external" && index === 0 ? "package" : "folder";
|
|
97
|
+
const id = `${groupKind}:${keyParts.join("/")}`;
|
|
98
|
+
currentParent = ensureChild(currentParent, nodesById, id, segment, groupKind, color);
|
|
99
|
+
}
|
|
100
|
+
const leafId = `module:${node.path}`;
|
|
101
|
+
const leaf = ensureChild(currentParent, nodesById, leafId, leafLabel, "module", color);
|
|
102
|
+
leaf.sourceNode = node;
|
|
103
|
+
leaf.sourcePath = node.path;
|
|
104
|
+
}
|
|
105
|
+
aggregateTree(root);
|
|
106
|
+
root.children = root.children.filter((child) => child.moduleCount > 0);
|
|
107
|
+
return {
|
|
108
|
+
root,
|
|
109
|
+
nodesById
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
var getTreemapAncestors = (node, nodesById) => {
|
|
113
|
+
const ancestors = [];
|
|
114
|
+
let current = node;
|
|
115
|
+
while (current) {
|
|
116
|
+
ancestors.unshift(current);
|
|
117
|
+
current = current.parentId ? nodesById.get(current.parentId) : void 0;
|
|
118
|
+
}
|
|
119
|
+
return ancestors;
|
|
120
|
+
};
|
|
121
|
+
var collectTreemapModules = (node) => {
|
|
122
|
+
if (node.kind === "module") return [node];
|
|
123
|
+
return node.children.flatMap((child) => collectTreemapModules(child));
|
|
124
|
+
};
|
|
125
|
+
var partitionTreemap = (nodes, x, y, width, height) => {
|
|
126
|
+
if (nodes.length === 0 || width <= 0 || height <= 0) return [];
|
|
127
|
+
if (nodes.length === 1) return [{
|
|
128
|
+
id: nodes[0].id,
|
|
129
|
+
x,
|
|
130
|
+
y,
|
|
131
|
+
width,
|
|
132
|
+
height,
|
|
133
|
+
node: nodes[0]
|
|
134
|
+
}];
|
|
135
|
+
const total = nodes.reduce((sum, node) => sum + node.size, 0);
|
|
136
|
+
if (total <= 0) return [];
|
|
137
|
+
let splitIndex = 1;
|
|
138
|
+
let firstGroupSize = nodes[0].size;
|
|
139
|
+
while (splitIndex < nodes.length - 1 && firstGroupSize < total / 2) {
|
|
140
|
+
firstGroupSize += nodes[splitIndex].size;
|
|
141
|
+
splitIndex += 1;
|
|
142
|
+
}
|
|
143
|
+
const firstGroup = nodes.slice(0, splitIndex);
|
|
144
|
+
const secondGroup = nodes.slice(splitIndex);
|
|
145
|
+
if (secondGroup.length === 0) return nodes.map((node, index) => ({
|
|
146
|
+
id: node.id,
|
|
147
|
+
x: width >= height ? x + width / nodes.length * index : x,
|
|
148
|
+
y: width >= height ? y : y + height / nodes.length * index,
|
|
149
|
+
width: width >= height ? width / nodes.length : width,
|
|
150
|
+
height: width >= height ? height : height / nodes.length,
|
|
151
|
+
node
|
|
152
|
+
}));
|
|
153
|
+
if (width >= height) {
|
|
154
|
+
const firstWidth = width * (firstGroupSize / total);
|
|
155
|
+
return [...partitionTreemap(firstGroup, x, y, firstWidth, height), ...partitionTreemap(secondGroup, x + firstWidth, y, width - firstWidth, height)];
|
|
156
|
+
}
|
|
157
|
+
const firstHeight = height * (firstGroupSize / total);
|
|
158
|
+
return [...partitionTreemap(firstGroup, x, y, width, firstHeight), ...partitionTreemap(secondGroup, x, y + firstHeight, width, height - firstHeight)];
|
|
159
|
+
};
|
|
160
|
+
var layoutTreemap = (nodes, x, y, width, height) => partitionTreemap([...nodes].sort((left, right) => right.size - left.size), x, y, width, height);
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/routes/treemap.tsx?tsr-split=component
|
|
163
|
+
var TREEMAP_PAGE_SIZE = 18;
|
|
164
|
+
var TREEMAP_LAYOUT_WIDTH = 1e3;
|
|
165
|
+
var TREEMAP_LAYOUT_HEIGHT = 600;
|
|
166
|
+
var INLINE_LABEL_HORIZONTAL_PADDING = 28;
|
|
167
|
+
var INLINE_LABEL_TOP_OFFSET = 36;
|
|
168
|
+
var INLINE_LABEL_BOTTOM_PADDING = 14;
|
|
169
|
+
var INLINE_LABEL_MIN_WIDTH = 56;
|
|
170
|
+
var INLINE_LABEL_MIN_HEIGHT = 42;
|
|
171
|
+
var INLINE_LABEL_STACK_GAP = 4;
|
|
172
|
+
var INLINE_LABEL_SECTION_GAP = 12;
|
|
173
|
+
var INLINE_LABEL_TITLE_MAX_LINES = 3;
|
|
174
|
+
var INLINE_LABEL_CAPTION_MAX_LINES = 2;
|
|
175
|
+
var INLINE_LABEL_HINT_MAX_LINES = 2;
|
|
176
|
+
var TITLE_LINE_HEIGHT = 18;
|
|
177
|
+
var CAPTION_LINE_HEIGHT = 16;
|
|
178
|
+
var HINT_LINE_HEIGHT = 14;
|
|
179
|
+
var TITLE_FONT = "600 14px ui-sans-serif, system-ui, sans-serif";
|
|
180
|
+
var CAPTION_FONT = "400 12px ui-sans-serif, system-ui, sans-serif";
|
|
181
|
+
var HINT_FONT = "400 11px ui-sans-serif, system-ui, sans-serif";
|
|
182
|
+
var formatCount = (value, singular, plural = `${singular}s`) => `${value} ${value === 1 ? singular : plural}`;
|
|
183
|
+
var getNodeTitle = (node) => {
|
|
184
|
+
if (node.kind === "module") return node.sourcePath ?? node.label;
|
|
185
|
+
return node.label;
|
|
186
|
+
};
|
|
187
|
+
var getNodeSubtitle = (node) => {
|
|
188
|
+
if (node.kind === "module" && node.sourceNode) return `${node.sourceNode.type}${node.sourceNode.cluster ? ` • ${node.sourceNode.cluster}` : ""}`;
|
|
189
|
+
if (node.kind === "package") return `${formatCount(node.moduleCount, "module")} in package`;
|
|
190
|
+
if (node.kind === "scope") return `${formatCount(node.moduleCount, "module")} in ${node.label.toLowerCase()}`;
|
|
191
|
+
return `${formatCount(node.moduleCount, "module")} across this branch`;
|
|
192
|
+
};
|
|
193
|
+
var getRectCaption = (node) => {
|
|
194
|
+
if (node.kind === "module") return `${node.totalOutgoing} out • ${node.totalIncoming} in`;
|
|
195
|
+
return `${formatCount(node.moduleCount, "module")}`;
|
|
196
|
+
};
|
|
197
|
+
var getRectNumberVisibility = (width, height) => width > 32 && height > 24;
|
|
198
|
+
var isDenseTailRect = (rect) => {
|
|
199
|
+
if (rect.inlineLabel) return false;
|
|
200
|
+
return rect.width * rect.height < 900 || rect.width < 28 || rect.height < 28;
|
|
201
|
+
};
|
|
202
|
+
var getOverlayStyle = (x, y, width, height) => ({
|
|
203
|
+
left: `${x / TREEMAP_LAYOUT_WIDTH * 100}%`,
|
|
204
|
+
top: `${y / TREEMAP_LAYOUT_HEIGHT * 100}%`,
|
|
205
|
+
width: `${width / TREEMAP_LAYOUT_WIDTH * 100}%`,
|
|
206
|
+
height: `${height / TREEMAP_LAYOUT_HEIGHT * 100}%`
|
|
207
|
+
});
|
|
208
|
+
var getTextLines = (text, font, maxWidth, lineHeight) => layoutWithLines(prepareWithSegments(text, font), Math.max(1, maxWidth), lineHeight);
|
|
209
|
+
var getInlineLabelLayout = (node, width, height) => {
|
|
210
|
+
const availableWidth = width - INLINE_LABEL_HORIZONTAL_PADDING;
|
|
211
|
+
const availableHeight = height - INLINE_LABEL_TOP_OFFSET - INLINE_LABEL_BOTTOM_PADDING;
|
|
212
|
+
if (availableWidth < INLINE_LABEL_MIN_WIDTH || availableHeight < INLINE_LABEL_MIN_HEIGHT) return null;
|
|
213
|
+
const titleLayout = getTextLines(node.label, TITLE_FONT, availableWidth, TITLE_LINE_HEIGHT);
|
|
214
|
+
if (titleLayout.lineCount === 0 || titleLayout.lineCount > INLINE_LABEL_TITLE_MAX_LINES) return null;
|
|
215
|
+
const captionLayout = getTextLines(getRectCaption(node), CAPTION_FONT, availableWidth, CAPTION_LINE_HEIGHT);
|
|
216
|
+
if (captionLayout.lineCount === 0 || captionLayout.lineCount > INLINE_LABEL_CAPTION_MAX_LINES) return null;
|
|
217
|
+
const hintText = node.kind === "module" ? null : "Click to zoom deeper";
|
|
218
|
+
const hintLayout = hintText ? getTextLines(hintText, HINT_FONT, availableWidth, HINT_LINE_HEIGHT) : null;
|
|
219
|
+
if (hintLayout && hintLayout.lineCount > INLINE_LABEL_HINT_MAX_LINES) return null;
|
|
220
|
+
if (titleLayout.height + INLINE_LABEL_STACK_GAP + captionLayout.height + (hintLayout ? INLINE_LABEL_SECTION_GAP + hintLayout.height : 0) > availableHeight) return null;
|
|
221
|
+
return {
|
|
222
|
+
titleLines: titleLayout.lines.map((line) => line.text),
|
|
223
|
+
captionLines: captionLayout.lines.map((line) => line.text),
|
|
224
|
+
hintLines: hintLayout ? hintLayout.lines.map((line) => line.text) : []
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
var isNodeInBranch = (node, branchId, nodesById) => {
|
|
228
|
+
let current = node;
|
|
229
|
+
while (current) {
|
|
230
|
+
if (current.id === branchId) return true;
|
|
231
|
+
current = current.parentId ? nodesById.get(current.parentId) ?? null : null;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
};
|
|
235
|
+
var getExplorerSearchForNode = (node, preservedModule) => {
|
|
236
|
+
const selected = preservedModule?.kind === "module" ? preservedModule.sourcePath ?? "" : "";
|
|
237
|
+
if (node.kind === "module" && node.sourcePath) return {
|
|
238
|
+
selected: node.sourcePath,
|
|
239
|
+
q: "",
|
|
240
|
+
scope: node.sourcePath.includes("node_modules") ? "external" : "workspace"
|
|
241
|
+
};
|
|
242
|
+
if (node.kind === "scope") return {
|
|
243
|
+
selected,
|
|
244
|
+
q: "",
|
|
245
|
+
scope: node.id === "scope:external" ? "external" : "workspace"
|
|
246
|
+
};
|
|
247
|
+
const [, encodedPath = ""] = node.id.split(":");
|
|
248
|
+
const [scope, ...segments] = encodedPath.split("/");
|
|
249
|
+
return {
|
|
250
|
+
selected,
|
|
251
|
+
q: segments.join("/"),
|
|
252
|
+
scope: scope === "external" ? "external" : "workspace"
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
function TreemapRoute() {
|
|
256
|
+
const bundle = useModvizBundle();
|
|
257
|
+
const search = Route$1.useSearch();
|
|
258
|
+
const navigate = Route$1.useNavigate();
|
|
259
|
+
const [hoveredNodeId, setHoveredNodeId] = useState(null);
|
|
260
|
+
if (!isModvizBundleReady(bundle)) return /* @__PURE__ */ jsx(ModvizLayout, {
|
|
261
|
+
projectTitle: bundle.projectTitle,
|
|
262
|
+
title: "Treemap",
|
|
263
|
+
description: "Package and module area map for wide snapshot exploration.",
|
|
264
|
+
children: /* @__PURE__ */ jsx(SetupView, { bundle })
|
|
265
|
+
});
|
|
266
|
+
const workspacePackageNames = useMemo(() => getWorkspacePackageNames(bundle.graph), [bundle.graph]);
|
|
267
|
+
const clusterColors = useMemo(() => {
|
|
268
|
+
const colorMap = /* @__PURE__ */ new Map();
|
|
269
|
+
bundle.graph.nodes.forEach((node) => {
|
|
270
|
+
const colorKey = node.cluster ?? node.package?.name ?? node.type ?? node.path;
|
|
271
|
+
if (!colorMap.has(colorKey)) colorMap.set(colorKey, colors.list[colorMap.size] ?? colors.deterministic(colorKey));
|
|
272
|
+
});
|
|
273
|
+
return colorMap;
|
|
274
|
+
}, [bundle.graph.nodes]);
|
|
275
|
+
const treemap = useMemo(() => buildTreemapModel(bundle.graph.nodes, workspacePackageNames, clusterColors), [
|
|
276
|
+
bundle.graph.nodes,
|
|
277
|
+
workspacePackageNames,
|
|
278
|
+
clusterColors
|
|
279
|
+
]);
|
|
280
|
+
const focusNodeId = search.focus || "root";
|
|
281
|
+
const activeNodeId = search.selected || focusNodeId;
|
|
282
|
+
const updateTreemapSearch = (patch, options) => {
|
|
283
|
+
navigate({
|
|
284
|
+
replace: options?.replace ?? false,
|
|
285
|
+
resetScroll: false,
|
|
286
|
+
search: (previous) => ({
|
|
287
|
+
...previous,
|
|
288
|
+
...patch
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
const resolvedFocusNode = treemap.nodesById.get(focusNodeId) ?? treemap.root;
|
|
293
|
+
const resolvedActiveNode = treemap.nodesById.get(activeNodeId) ?? resolvedFocusNode;
|
|
294
|
+
const breadcrumbs = useMemo(() => getTreemapAncestors(resolvedFocusNode, treemap.nodesById), [resolvedFocusNode, treemap.nodesById]);
|
|
295
|
+
const parentNode = resolvedFocusNode.parentId ? treemap.nodesById.get(resolvedFocusNode.parentId) ?? null : null;
|
|
296
|
+
const hoveredNode = hoveredNodeId ? treemap.nodesById.get(hoveredNodeId) ?? null : null;
|
|
297
|
+
const preservedExplorerModule = useMemo(() => {
|
|
298
|
+
if (hoveredNode?.kind === "module" && isNodeInBranch(hoveredNode, resolvedFocusNode.id, treemap.nodesById)) return hoveredNode;
|
|
299
|
+
if (resolvedActiveNode.kind === "module" && isNodeInBranch(resolvedActiveNode, resolvedFocusNode.id, treemap.nodesById)) return resolvedActiveNode;
|
|
300
|
+
return null;
|
|
301
|
+
}, [
|
|
302
|
+
hoveredNode,
|
|
303
|
+
resolvedActiveNode,
|
|
304
|
+
resolvedFocusNode.id,
|
|
305
|
+
treemap.nodesById
|
|
306
|
+
]);
|
|
307
|
+
const explorerSearch = useMemo(() => getExplorerSearchForNode(resolvedFocusNode, preservedExplorerModule), [preservedExplorerModule, resolvedFocusNode]);
|
|
308
|
+
const visibleNodes = resolvedFocusNode.children.length > 0 ? resolvedFocusNode.children : [resolvedFocusNode];
|
|
309
|
+
const sortedVisibleNodes = useMemo(() => [...visibleNodes].sort((left, right) => right.size - left.size), [visibleNodes]);
|
|
310
|
+
const paginationEnabled = search.paginate;
|
|
311
|
+
const totalPages = Math.max(1, Math.ceil(sortedVisibleNodes.length / TREEMAP_PAGE_SIZE));
|
|
312
|
+
const pageIndex = paginationEnabled ? Math.min(search.page, totalPages - 1) : 0;
|
|
313
|
+
const pageStart = paginationEnabled ? pageIndex * TREEMAP_PAGE_SIZE : 0;
|
|
314
|
+
const pagedVisibleNodes = useMemo(() => paginationEnabled ? sortedVisibleNodes.slice(pageStart, pageStart + TREEMAP_PAGE_SIZE) : sortedVisibleNodes, [
|
|
315
|
+
pageStart,
|
|
316
|
+
paginationEnabled,
|
|
317
|
+
sortedVisibleNodes
|
|
318
|
+
]);
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
if (!paginationEnabled || search.page === pageIndex) return;
|
|
321
|
+
updateTreemapSearch({ page: pageIndex }, { replace: true });
|
|
322
|
+
}, [
|
|
323
|
+
pageIndex,
|
|
324
|
+
paginationEnabled,
|
|
325
|
+
search.page
|
|
326
|
+
]);
|
|
327
|
+
const rectangles = useMemo(() => layoutTreemap(pagedVisibleNodes, 0, 0, TREEMAP_LAYOUT_WIDTH, TREEMAP_LAYOUT_HEIGHT).map((rect, index) => ({
|
|
328
|
+
...rect,
|
|
329
|
+
legendIndex: pageStart + index + 1,
|
|
330
|
+
inlineLabel: getInlineLabelLayout(rect.node, rect.width, rect.height)
|
|
331
|
+
})), [pagedVisibleNodes, pageStart]);
|
|
332
|
+
const denseTailGrid = useMemo(() => {
|
|
333
|
+
if (paginationEnabled) return null;
|
|
334
|
+
const denseTailRects = rectangles.filter(isDenseTailRect);
|
|
335
|
+
if (denseTailRects.length < 8) return null;
|
|
336
|
+
const minX = Math.min(...denseTailRects.map((rect) => rect.x));
|
|
337
|
+
const minY = Math.min(...denseTailRects.map((rect) => rect.y));
|
|
338
|
+
const maxX = Math.max(...denseTailRects.map((rect) => rect.x + rect.width));
|
|
339
|
+
const maxY = Math.max(...denseTailRects.map((rect) => rect.y + rect.height));
|
|
340
|
+
const bounds = {
|
|
341
|
+
x: minX,
|
|
342
|
+
y: minY,
|
|
343
|
+
width: maxX - minX,
|
|
344
|
+
height: maxY - minY
|
|
345
|
+
};
|
|
346
|
+
const padding = 6;
|
|
347
|
+
const gap = 4;
|
|
348
|
+
const minTileSize = 18;
|
|
349
|
+
const availableWidth = Math.max(minTileSize, bounds.width - padding * 2);
|
|
350
|
+
const availableHeight = Math.max(minTileSize, bounds.height - padding * 2);
|
|
351
|
+
const maxColumns = Math.max(1, Math.floor((availableWidth + gap) / (minTileSize + gap)));
|
|
352
|
+
const maxRows = Math.max(1, Math.floor((availableHeight + gap) / (minTileSize + gap)));
|
|
353
|
+
const capacity = Math.max(1, maxColumns * maxRows);
|
|
354
|
+
const hiddenCount = Math.max(0, denseTailRects.length - capacity);
|
|
355
|
+
const visibleRects = hiddenCount > 0 ? denseTailRects.slice(0, capacity - 1) : denseTailRects.slice(0, capacity);
|
|
356
|
+
const tileCount = visibleRects.length + (hiddenCount > 0 ? 1 : 0);
|
|
357
|
+
const columns = Math.min(maxColumns, Math.max(1, Math.ceil(Math.sqrt(tileCount))));
|
|
358
|
+
const rows = Math.max(1, Math.ceil(tileCount / columns));
|
|
359
|
+
const tileSize = Math.floor(Math.min((availableWidth - gap * (columns - 1)) / columns, (availableHeight - gap * (rows - 1)) / rows));
|
|
360
|
+
const tiles = [];
|
|
361
|
+
visibleRects.forEach((rect, index) => {
|
|
362
|
+
const column = index % columns;
|
|
363
|
+
const row = Math.floor(index / columns);
|
|
364
|
+
tiles.push({
|
|
365
|
+
id: rect.node.id,
|
|
366
|
+
label: getNodeTitle(rect.node),
|
|
367
|
+
caption: getRectCaption(rect.node),
|
|
368
|
+
color: rect.node.color,
|
|
369
|
+
legendIndex: rect.legendIndex,
|
|
370
|
+
x: padding + column * (tileSize + gap),
|
|
371
|
+
y: padding + row * (tileSize + gap),
|
|
372
|
+
size: tileSize
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
if (hiddenCount > 0) {
|
|
376
|
+
const index = visibleRects.length;
|
|
377
|
+
const column = index % columns;
|
|
378
|
+
const row = Math.floor(index / columns);
|
|
379
|
+
tiles.push({
|
|
380
|
+
id: "dense-tail-more",
|
|
381
|
+
label: `+${hiddenCount} more`,
|
|
382
|
+
caption: "Enable pagination to reveal every tiny node",
|
|
383
|
+
color: "rgba(15,23,42,0.84)",
|
|
384
|
+
legendIndex: 0,
|
|
385
|
+
x: padding + column * (tileSize + gap),
|
|
386
|
+
y: padding + row * (tileSize + gap),
|
|
387
|
+
size: tileSize
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
bounds,
|
|
392
|
+
tiles,
|
|
393
|
+
hiddenCount
|
|
394
|
+
};
|
|
395
|
+
}, [paginationEnabled, rectangles]);
|
|
396
|
+
const previewNode = hoveredNode ?? resolvedActiveNode;
|
|
397
|
+
const topModules = useMemo(() => collectTreemapModules(resolvedActiveNode).sort((left, right) => right.size - left.size).slice(0, 6), [resolvedActiveNode]);
|
|
398
|
+
const pageForNodeId = (nodeId) => {
|
|
399
|
+
const index = sortedVisibleNodes.findIndex((node) => node.id === nodeId);
|
|
400
|
+
if (index < 0) return 0;
|
|
401
|
+
return Math.floor(index / TREEMAP_PAGE_SIZE);
|
|
402
|
+
};
|
|
403
|
+
const handleRectClick = (node) => {
|
|
404
|
+
updateTreemapSearch({ selected: node.id });
|
|
405
|
+
if (node.children.length > 0) updateTreemapSearch({
|
|
406
|
+
focus: node.id,
|
|
407
|
+
page: 0,
|
|
408
|
+
selected: node.id
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
const handleDenseTileClick = (nodeId) => {
|
|
412
|
+
updateTreemapSearch({
|
|
413
|
+
paginate: true,
|
|
414
|
+
page: pageForNodeId(nodeId),
|
|
415
|
+
selected: nodeId
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
const handleNavigateTo = (nodeId) => {
|
|
419
|
+
updateTreemapSearch({
|
|
420
|
+
focus: nodeId,
|
|
421
|
+
page: 0,
|
|
422
|
+
selected: nodeId
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
const pageEnd = Math.min(sortedVisibleNodes.length, pageStart + TREEMAP_PAGE_SIZE);
|
|
426
|
+
return /* @__PURE__ */ jsx(ModvizLayout, {
|
|
427
|
+
projectTitle: bundle.projectTitle,
|
|
428
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
429
|
+
className: "space-y-4",
|
|
430
|
+
children: [
|
|
431
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
432
|
+
className: "flex flex-wrap items-center gap-3",
|
|
433
|
+
children: [
|
|
434
|
+
/* @__PURE__ */ jsx("h1", {
|
|
435
|
+
className: "text-2xl font-bold text-slate-900 dark:text-slate-100",
|
|
436
|
+
children: "Treemap Visualization"
|
|
437
|
+
}),
|
|
438
|
+
parentNode && /* @__PURE__ */ jsxs(Button, {
|
|
439
|
+
variant: "outline",
|
|
440
|
+
size: "sm",
|
|
441
|
+
onClick: () => handleNavigateTo(parentNode.id),
|
|
442
|
+
className: "gap-2",
|
|
443
|
+
children: [/* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" }), "Up one level"]
|
|
444
|
+
}),
|
|
445
|
+
resolvedFocusNode.id !== treemap.root.id && /* @__PURE__ */ jsx(Button, {
|
|
446
|
+
variant: "outline",
|
|
447
|
+
size: "sm",
|
|
448
|
+
onClick: () => handleNavigateTo(treemap.root.id),
|
|
449
|
+
children: "Reset view"
|
|
450
|
+
}),
|
|
451
|
+
/* @__PURE__ */ jsx(Button, {
|
|
452
|
+
asChild: true,
|
|
453
|
+
variant: "outline",
|
|
454
|
+
size: "sm",
|
|
455
|
+
children: /* @__PURE__ */ jsxs(Link, {
|
|
456
|
+
to: "/explorer",
|
|
457
|
+
search: explorerSearch,
|
|
458
|
+
children: [/* @__PURE__ */ jsx(ExternalLink, { className: "size-4" }), "Open current view in explorer"]
|
|
459
|
+
})
|
|
460
|
+
})
|
|
461
|
+
]
|
|
462
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
463
|
+
className: "mt-2 text-slate-600 dark:text-slate-400",
|
|
464
|
+
children: "Size reflects total direct dependency edges for each branch or file. Click folders or packages to drill in, then click files to inspect the hotspot."
|
|
465
|
+
})] }),
|
|
466
|
+
/* @__PURE__ */ jsxs("div", {
|
|
467
|
+
className: "rounded-[24px] border border-slate-200/70 bg-white/90 p-6 shadow-[0_16px_50px_-32px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70",
|
|
468
|
+
children: [
|
|
469
|
+
/* @__PURE__ */ jsxs("div", {
|
|
470
|
+
className: "mb-4 flex flex-wrap items-center gap-2 text-sm text-slate-500 dark:text-slate-400",
|
|
471
|
+
children: [/* @__PURE__ */ jsx(FolderTree, { className: "size-4" }), breadcrumbs.map((node, index) => /* @__PURE__ */ jsxs("button", {
|
|
472
|
+
type: "button",
|
|
473
|
+
onClick: () => handleNavigateTo(node.id),
|
|
474
|
+
className: "rounded-full px-2 py-1 transition hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-900 dark:hover:text-slate-100",
|
|
475
|
+
children: [index > 0 ? "/ " : "", node.label]
|
|
476
|
+
}, node.id))]
|
|
477
|
+
}),
|
|
478
|
+
/* @__PURE__ */ jsxs("div", {
|
|
479
|
+
className: "mb-4 flex flex-wrap items-start justify-between gap-3 rounded-[20px] border border-slate-200/70 bg-slate-50/90 px-4 py-3 dark:border-slate-800 dark:bg-slate-900/70",
|
|
480
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [
|
|
481
|
+
/* @__PURE__ */ jsx("p", {
|
|
482
|
+
className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400",
|
|
483
|
+
children: hoveredNode ? "Hover preview" : "Selection preview"
|
|
484
|
+
}),
|
|
485
|
+
/* @__PURE__ */ jsx("p", {
|
|
486
|
+
className: "mt-1 text-sm font-semibold text-slate-900 dark:text-slate-100",
|
|
487
|
+
children: getNodeTitle(previewNode)
|
|
488
|
+
}),
|
|
489
|
+
/* @__PURE__ */ jsx("p", {
|
|
490
|
+
className: "mt-1 text-xs text-slate-500 dark:text-slate-400",
|
|
491
|
+
children: getNodeSubtitle(previewNode)
|
|
492
|
+
})
|
|
493
|
+
] }), /* @__PURE__ */ jsxs("div", {
|
|
494
|
+
className: "text-right text-xs text-slate-500 dark:text-slate-400",
|
|
495
|
+
children: [/* @__PURE__ */ jsx("p", { children: getRectCaption(previewNode) }), /* @__PURE__ */ jsx("p", {
|
|
496
|
+
className: "mt-1",
|
|
497
|
+
children: formatCount(previewNode.totalNamedImports, "import statement")
|
|
498
|
+
})]
|
|
499
|
+
})]
|
|
500
|
+
}),
|
|
501
|
+
/* @__PURE__ */ jsxs("div", {
|
|
502
|
+
className: "mb-4 flex min-h-[84px] flex-wrap items-center justify-between gap-3 rounded-[20px] border border-slate-200/70 bg-white/80 px-4 py-3 text-sm dark:border-slate-800 dark:bg-slate-950/40",
|
|
503
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
504
|
+
className: "font-medium text-slate-900 dark:text-slate-100",
|
|
505
|
+
children: paginationEnabled ? totalPages > 1 ? "Dense level detected" : "Single-page level" : "Global overview enabled"
|
|
506
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
507
|
+
className: "text-slate-500 dark:text-slate-400",
|
|
508
|
+
children: paginationEnabled ? totalPages > 1 ? `Showing ${pageStart + 1}-${pageEnd} of ${sortedVisibleNodes.length} nodes so each page stays readable.` : `Showing all ${sortedVisibleNodes.length} nodes on a single page.` : `Showing all ${sortedVisibleNodes.length} nodes at once for a full branch overview.`
|
|
509
|
+
})] }), /* @__PURE__ */ jsxs("div", {
|
|
510
|
+
className: "flex items-center gap-2",
|
|
511
|
+
children: [
|
|
512
|
+
/* @__PURE__ */ jsx(Button, {
|
|
513
|
+
variant: "outline",
|
|
514
|
+
size: "sm",
|
|
515
|
+
onClick: () => updateTreemapSearch({
|
|
516
|
+
paginate: !paginationEnabled,
|
|
517
|
+
page: 0
|
|
518
|
+
}, { replace: true }),
|
|
519
|
+
children: paginationEnabled ? "Show global overview" : "Enable pagination"
|
|
520
|
+
}),
|
|
521
|
+
/* @__PURE__ */ jsx(Button, {
|
|
522
|
+
variant: "outline",
|
|
523
|
+
size: "sm",
|
|
524
|
+
onClick: () => updateTreemapSearch({ page: Math.max(0, pageIndex - 1) }, { replace: true }),
|
|
525
|
+
disabled: !paginationEnabled || totalPages <= 1 || pageIndex === 0,
|
|
526
|
+
children: "Previous page"
|
|
527
|
+
}),
|
|
528
|
+
/* @__PURE__ */ jsx("span", {
|
|
529
|
+
className: "min-w-24 px-2 text-center text-xs font-medium text-slate-500 dark:text-slate-400",
|
|
530
|
+
children: paginationEnabled ? `Page ${pageIndex + 1} / ${totalPages}` : "All nodes visible"
|
|
531
|
+
}),
|
|
532
|
+
/* @__PURE__ */ jsx(Button, {
|
|
533
|
+
variant: "outline",
|
|
534
|
+
size: "sm",
|
|
535
|
+
onClick: () => updateTreemapSearch({ page: Math.min(totalPages - 1, pageIndex + 1) }, { replace: true }),
|
|
536
|
+
disabled: !paginationEnabled || totalPages <= 1 || pageIndex >= totalPages - 1,
|
|
537
|
+
children: "Next page"
|
|
538
|
+
})
|
|
539
|
+
]
|
|
540
|
+
})]
|
|
541
|
+
}),
|
|
542
|
+
/* @__PURE__ */ jsxs("div", {
|
|
543
|
+
className: "relative aspect-[5/3] w-full overflow-hidden rounded-[20px] border border-slate-200 bg-slate-50 dark:border-slate-700 dark:bg-slate-900/60",
|
|
544
|
+
children: [/* @__PURE__ */ jsx("svg", {
|
|
545
|
+
viewBox: `0 0 ${TREEMAP_LAYOUT_WIDTH} ${TREEMAP_LAYOUT_HEIGHT}`,
|
|
546
|
+
className: "absolute inset-0 h-full w-full",
|
|
547
|
+
children: rectangles.map((rect) => {
|
|
548
|
+
if (denseTailGrid && isDenseTailRect(rect)) return null;
|
|
549
|
+
const isActive = rect.node.id === resolvedActiveNode.id;
|
|
550
|
+
const isHovered = rect.node.id === hoveredNodeId;
|
|
551
|
+
return /* @__PURE__ */ jsxs("g", {
|
|
552
|
+
onClick: () => handleRectClick(rect.node),
|
|
553
|
+
className: "cursor-pointer transition-opacity hover:opacity-90",
|
|
554
|
+
onMouseEnter: () => setHoveredNodeId(rect.node.id),
|
|
555
|
+
onMouseLeave: () => setHoveredNodeId((current) => current === rect.node.id ? null : current),
|
|
556
|
+
children: [
|
|
557
|
+
/* @__PURE__ */ jsxs("title", { children: [
|
|
558
|
+
"#",
|
|
559
|
+
rect.legendIndex,
|
|
560
|
+
" ",
|
|
561
|
+
getNodeTitle(rect.node),
|
|
562
|
+
`\n${getNodeSubtitle(rect.node)}`,
|
|
563
|
+
`\n${getRectCaption(rect.node)}`
|
|
564
|
+
] }),
|
|
565
|
+
/* @__PURE__ */ jsx("rect", {
|
|
566
|
+
x: rect.x + 2,
|
|
567
|
+
y: rect.y + 2,
|
|
568
|
+
width: Math.max(0, rect.width - 4),
|
|
569
|
+
height: Math.max(0, rect.height - 4),
|
|
570
|
+
fill: rect.node.color,
|
|
571
|
+
stroke: isActive || isHovered ? "#0f172a" : "rgba(255,255,255,0.92)",
|
|
572
|
+
strokeWidth: isActive ? 4 : isHovered ? 3 : 2,
|
|
573
|
+
rx: 10,
|
|
574
|
+
opacity: rect.node.kind === "module" ? .9 : .84
|
|
575
|
+
}),
|
|
576
|
+
getRectNumberVisibility(rect.width, rect.height) && /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
|
|
577
|
+
x: rect.x + 10,
|
|
578
|
+
y: rect.y + 10,
|
|
579
|
+
width: Math.min(34, Math.max(24, rect.width - 20)),
|
|
580
|
+
height: 20,
|
|
581
|
+
rx: 10,
|
|
582
|
+
fill: "rgba(15,23,42,0.72)"
|
|
583
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
584
|
+
x: rect.x + 10 + Math.min(34, Math.max(24, rect.width - 20)) / 2,
|
|
585
|
+
y: rect.y + 24,
|
|
586
|
+
textAnchor: "middle",
|
|
587
|
+
fontSize: "11",
|
|
588
|
+
fontWeight: "700",
|
|
589
|
+
fill: "white",
|
|
590
|
+
children: ["#", rect.legendIndex]
|
|
591
|
+
})] }),
|
|
592
|
+
rect.inlineLabel && /* @__PURE__ */ jsx("foreignObject", {
|
|
593
|
+
x: rect.x + 14,
|
|
594
|
+
y: rect.y + 36,
|
|
595
|
+
width: Math.max(0, rect.width - 28),
|
|
596
|
+
height: Math.max(0, rect.height - 50),
|
|
597
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
598
|
+
className: "flex h-full flex-col justify-between overflow-hidden text-white",
|
|
599
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
600
|
+
className: "space-y-0 text-sm font-semibold",
|
|
601
|
+
style: { lineHeight: `${TITLE_LINE_HEIGHT}px` },
|
|
602
|
+
children: rect.inlineLabel.titleLines.map((line, index) => /* @__PURE__ */ jsx("p", {
|
|
603
|
+
className: "overflow-hidden text-ellipsis whitespace-nowrap",
|
|
604
|
+
children: line
|
|
605
|
+
}, `${rect.id}-title-${index}`))
|
|
606
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
607
|
+
className: "mt-1 space-y-0 text-xs opacity-90",
|
|
608
|
+
style: { lineHeight: `${CAPTION_LINE_HEIGHT}px` },
|
|
609
|
+
children: rect.inlineLabel.captionLines.map((line, index) => /* @__PURE__ */ jsx("p", {
|
|
610
|
+
className: "overflow-hidden text-ellipsis whitespace-nowrap",
|
|
611
|
+
children: line
|
|
612
|
+
}, `${rect.id}-caption-${index}`))
|
|
613
|
+
})] }), rect.inlineLabel.hintLines.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
614
|
+
className: "space-y-0 text-[11px] opacity-80",
|
|
615
|
+
style: { lineHeight: `${HINT_LINE_HEIGHT}px` },
|
|
616
|
+
children: rect.inlineLabel.hintLines.map((line, index) => /* @__PURE__ */ jsx("p", {
|
|
617
|
+
className: "overflow-hidden text-ellipsis whitespace-nowrap",
|
|
618
|
+
children: line
|
|
619
|
+
}, `${rect.id}-hint-${index}`))
|
|
620
|
+
})]
|
|
621
|
+
})
|
|
622
|
+
})
|
|
623
|
+
]
|
|
624
|
+
}, rect.id);
|
|
625
|
+
})
|
|
626
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
627
|
+
className: "pointer-events-none absolute inset-0",
|
|
628
|
+
children: [rectangles.filter((rect) => !rect.inlineLabel).filter((rect) => !(denseTailGrid && isDenseTailRect(rect))).map((rect) => /* @__PURE__ */ jsx("div", {
|
|
629
|
+
className: "pointer-events-auto absolute",
|
|
630
|
+
style: getOverlayStyle(rect.x + 2, rect.y + 2, Math.max(0, rect.width - 4), Math.max(0, rect.height - 4)),
|
|
631
|
+
children: /* @__PURE__ */ jsxs(Tooltip, {
|
|
632
|
+
lazyMount: true,
|
|
633
|
+
openDelay: 70,
|
|
634
|
+
closeDelay: 0,
|
|
635
|
+
children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
636
|
+
asChild: true,
|
|
637
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
638
|
+
type: "button",
|
|
639
|
+
className: "block h-full w-full rounded-[10px]",
|
|
640
|
+
onClick: () => handleRectClick(rect.node),
|
|
641
|
+
onMouseEnter: () => setHoveredNodeId(rect.node.id),
|
|
642
|
+
onMouseLeave: () => setHoveredNodeId((current) => current === rect.node.id ? null : current),
|
|
643
|
+
"aria-label": `Open ${getNodeTitle(rect.node)}`
|
|
644
|
+
})
|
|
645
|
+
}), /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(TooltipContent, {
|
|
646
|
+
className: "max-w-80",
|
|
647
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
648
|
+
className: "space-y-1",
|
|
649
|
+
children: [
|
|
650
|
+
/* @__PURE__ */ jsxs("p", {
|
|
651
|
+
className: "font-semibold",
|
|
652
|
+
children: [
|
|
653
|
+
"#",
|
|
654
|
+
rect.legendIndex,
|
|
655
|
+
" ",
|
|
656
|
+
getNodeTitle(rect.node)
|
|
657
|
+
]
|
|
658
|
+
}),
|
|
659
|
+
/* @__PURE__ */ jsx("p", {
|
|
660
|
+
className: "text-xs opacity-80",
|
|
661
|
+
children: getNodeSubtitle(rect.node)
|
|
662
|
+
}),
|
|
663
|
+
/* @__PURE__ */ jsx("p", {
|
|
664
|
+
className: "text-xs opacity-80",
|
|
665
|
+
children: getRectCaption(rect.node)
|
|
666
|
+
})
|
|
667
|
+
]
|
|
668
|
+
})
|
|
669
|
+
}) })]
|
|
670
|
+
})
|
|
671
|
+
}, `${rect.id}-overlay`)), denseTailGrid && /* @__PURE__ */ jsx("div", {
|
|
672
|
+
className: "pointer-events-auto absolute",
|
|
673
|
+
style: getOverlayStyle(denseTailGrid.bounds.x, denseTailGrid.bounds.y, denseTailGrid.bounds.width, denseTailGrid.bounds.height),
|
|
674
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
675
|
+
className: "relative h-full w-full rounded-[16px] border border-white/75 bg-white/18 p-1 shadow-[0_12px_32px_-22px_rgba(15,23,42,0.55)] backdrop-blur-[1px] dark:border-slate-700/80 dark:bg-slate-950/12",
|
|
676
|
+
children: denseTailGrid.tiles.map((tile) => {
|
|
677
|
+
if (tile.id === "dense-tail-more") return /* @__PURE__ */ jsxs("button", {
|
|
678
|
+
type: "button",
|
|
679
|
+
onClick: () => updateTreemapSearch({
|
|
680
|
+
paginate: true,
|
|
681
|
+
page: 0
|
|
682
|
+
}, { replace: true }),
|
|
683
|
+
className: "absolute flex items-center justify-center rounded-[10px] text-[10px] font-semibold text-white shadow-sm transition hover:scale-[1.03]",
|
|
684
|
+
style: {
|
|
685
|
+
left: tile.x,
|
|
686
|
+
top: tile.y,
|
|
687
|
+
width: tile.size,
|
|
688
|
+
height: tile.size,
|
|
689
|
+
backgroundColor: tile.color
|
|
690
|
+
},
|
|
691
|
+
children: ["+", denseTailGrid.hiddenCount]
|
|
692
|
+
}, tile.id);
|
|
693
|
+
return /* @__PURE__ */ jsxs(Tooltip, {
|
|
694
|
+
lazyMount: true,
|
|
695
|
+
openDelay: 70,
|
|
696
|
+
closeDelay: 0,
|
|
697
|
+
children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
698
|
+
asChild: true,
|
|
699
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
700
|
+
type: "button",
|
|
701
|
+
onClick: () => handleDenseTileClick(tile.id),
|
|
702
|
+
className: "absolute rounded-[10px] border border-white/80 shadow-sm transition hover:scale-[1.03]",
|
|
703
|
+
style: {
|
|
704
|
+
left: tile.x,
|
|
705
|
+
top: tile.y,
|
|
706
|
+
width: tile.size,
|
|
707
|
+
height: tile.size,
|
|
708
|
+
backgroundColor: tile.color
|
|
709
|
+
},
|
|
710
|
+
"aria-label": `Open ${tile.label}`
|
|
711
|
+
})
|
|
712
|
+
}), /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(TooltipContent, {
|
|
713
|
+
className: "max-w-80",
|
|
714
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
715
|
+
className: "space-y-1",
|
|
716
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
717
|
+
className: "font-semibold",
|
|
718
|
+
children: [
|
|
719
|
+
"#",
|
|
720
|
+
tile.legendIndex,
|
|
721
|
+
" ",
|
|
722
|
+
tile.label
|
|
723
|
+
]
|
|
724
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
725
|
+
className: "text-xs opacity-80",
|
|
726
|
+
children: tile.caption
|
|
727
|
+
})]
|
|
728
|
+
})
|
|
729
|
+
}) })]
|
|
730
|
+
}, tile.id);
|
|
731
|
+
})
|
|
732
|
+
})
|
|
733
|
+
})]
|
|
734
|
+
})]
|
|
735
|
+
})
|
|
736
|
+
]
|
|
737
|
+
}),
|
|
738
|
+
/* @__PURE__ */ jsxs("div", {
|
|
739
|
+
className: "grid gap-4 lg:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]",
|
|
740
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
741
|
+
className: "rounded-[24px] border border-slate-200/70 bg-white/90 p-6 shadow-[0_16px_50px_-32px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70",
|
|
742
|
+
children: [
|
|
743
|
+
/* @__PURE__ */ jsx("p", {
|
|
744
|
+
className: "text-xs font-semibold uppercase tracking-[0.22em] text-sky-700 dark:text-sky-300",
|
|
745
|
+
children: "Current selection"
|
|
746
|
+
}),
|
|
747
|
+
/* @__PURE__ */ jsx("h2", {
|
|
748
|
+
className: "mt-2 text-lg font-semibold text-slate-900 dark:text-slate-100",
|
|
749
|
+
children: getNodeTitle(resolvedActiveNode)
|
|
750
|
+
}),
|
|
751
|
+
/* @__PURE__ */ jsx("p", {
|
|
752
|
+
className: "mt-1 text-sm text-slate-500 dark:text-slate-400",
|
|
753
|
+
children: getNodeSubtitle(resolvedActiveNode)
|
|
754
|
+
}),
|
|
755
|
+
/* @__PURE__ */ jsxs("div", {
|
|
756
|
+
className: "mt-4 grid gap-4 md:grid-cols-3",
|
|
757
|
+
children: [
|
|
758
|
+
/* @__PURE__ */ jsxs("div", {
|
|
759
|
+
className: "rounded-lg bg-slate-50/80 p-4 dark:bg-slate-900/50",
|
|
760
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
761
|
+
className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400",
|
|
762
|
+
children: "Modules"
|
|
763
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
764
|
+
className: "mt-2 text-2xl font-semibold text-slate-900 dark:text-slate-100",
|
|
765
|
+
children: resolvedActiveNode.moduleCount
|
|
766
|
+
})]
|
|
767
|
+
}),
|
|
768
|
+
/* @__PURE__ */ jsxs("div", {
|
|
769
|
+
className: "rounded-lg bg-slate-50/80 p-4 dark:bg-slate-900/50",
|
|
770
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
771
|
+
className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400",
|
|
772
|
+
children: "Outgoing imports"
|
|
773
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
774
|
+
className: "mt-2 text-2xl font-semibold text-slate-900 dark:text-slate-100",
|
|
775
|
+
children: resolvedActiveNode.totalOutgoing
|
|
776
|
+
})]
|
|
777
|
+
}),
|
|
778
|
+
/* @__PURE__ */ jsxs("div", {
|
|
779
|
+
className: "rounded-lg bg-slate-50/80 p-4 dark:bg-slate-900/50",
|
|
780
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
781
|
+
className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400",
|
|
782
|
+
children: "Incoming imports"
|
|
783
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
784
|
+
className: "mt-2 text-2xl font-semibold text-slate-900 dark:text-slate-100",
|
|
785
|
+
children: resolvedActiveNode.totalIncoming
|
|
786
|
+
})]
|
|
787
|
+
})
|
|
788
|
+
]
|
|
789
|
+
}),
|
|
790
|
+
resolvedActiveNode.kind === "module" && resolvedActiveNode.sourceNode && /* @__PURE__ */ jsxs("div", {
|
|
791
|
+
className: "mt-4 rounded-lg bg-slate-50/80 p-4 text-sm text-slate-600 dark:bg-slate-900/50 dark:text-slate-300",
|
|
792
|
+
children: [/* @__PURE__ */ jsxs("p", { children: ["Named imports in source: ", resolvedActiveNode.totalNamedImports] }), /* @__PURE__ */ jsxs("p", {
|
|
793
|
+
className: "mt-1",
|
|
794
|
+
children: [
|
|
795
|
+
"Imported by",
|
|
796
|
+
" ",
|
|
797
|
+
formatCount(resolvedActiveNode.sourceNode.importedBy.length, "module"),
|
|
798
|
+
"and imports",
|
|
799
|
+
" ",
|
|
800
|
+
formatCount(resolvedActiveNode.sourceNode.importees.length, "module"),
|
|
801
|
+
"."
|
|
802
|
+
]
|
|
803
|
+
})]
|
|
804
|
+
})
|
|
805
|
+
]
|
|
806
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
807
|
+
className: "rounded-[24px] border border-slate-200/70 bg-white/90 p-6 shadow-[0_16px_50px_-32px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70",
|
|
808
|
+
children: [
|
|
809
|
+
/* @__PURE__ */ jsx("p", {
|
|
810
|
+
className: "text-xs font-semibold uppercase tracking-[0.22em] text-slate-500 dark:text-slate-400",
|
|
811
|
+
children: "Nodes in current view"
|
|
812
|
+
}),
|
|
813
|
+
/* @__PURE__ */ jsx("p", {
|
|
814
|
+
className: "mt-1 text-sm text-slate-500 dark:text-slate-400",
|
|
815
|
+
children: paginationEnabled ? "Tiny tiles are numbered to match this page of the list." : "Tiny tiles are numbered to match the full list below."
|
|
816
|
+
}),
|
|
817
|
+
/* @__PURE__ */ jsxs("div", {
|
|
818
|
+
className: "mt-4 max-h-[26rem] space-y-3 overflow-auto pr-1",
|
|
819
|
+
children: [
|
|
820
|
+
rectangles.map((rect) => /* @__PURE__ */ jsx("button", {
|
|
821
|
+
type: "button",
|
|
822
|
+
onClick: () => handleRectClick(rect.node),
|
|
823
|
+
onMouseEnter: () => setHoveredNodeId(rect.node.id),
|
|
824
|
+
onMouseLeave: () => setHoveredNodeId((current) => current === rect.node.id ? null : current),
|
|
825
|
+
className: "block w-full rounded-2xl border border-slate-200/80 bg-slate-50/80 px-4 py-3 text-left transition hover:border-slate-300 hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900/60 dark:hover:border-slate-700 dark:hover:bg-slate-900",
|
|
826
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
827
|
+
className: "flex items-start gap-3",
|
|
828
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
829
|
+
className: "mt-0.5 flex items-center gap-2",
|
|
830
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
831
|
+
className: "inline-flex min-w-8 items-center justify-center rounded-full bg-slate-900 px-2 py-1 text-[11px] font-semibold text-white dark:bg-slate-100 dark:text-slate-900",
|
|
832
|
+
children: ["#", rect.legendIndex]
|
|
833
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
834
|
+
className: "mt-1 size-3 rounded-full",
|
|
835
|
+
style: { backgroundColor: rect.node.color }
|
|
836
|
+
})]
|
|
837
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
838
|
+
className: "min-w-0 flex-1",
|
|
839
|
+
children: [
|
|
840
|
+
/* @__PURE__ */ jsx("p", {
|
|
841
|
+
className: "truncate text-sm font-semibold text-slate-900 dark:text-slate-100",
|
|
842
|
+
children: getNodeTitle(rect.node)
|
|
843
|
+
}),
|
|
844
|
+
/* @__PURE__ */ jsx("p", {
|
|
845
|
+
className: "mt-1 text-xs text-slate-500 dark:text-slate-400",
|
|
846
|
+
children: getNodeSubtitle(rect.node)
|
|
847
|
+
}),
|
|
848
|
+
/* @__PURE__ */ jsx("p", {
|
|
849
|
+
className: "mt-1 text-xs text-slate-500 dark:text-slate-400",
|
|
850
|
+
children: getRectCaption(rect.node)
|
|
851
|
+
})
|
|
852
|
+
]
|
|
853
|
+
})]
|
|
854
|
+
})
|
|
855
|
+
}, rect.id)),
|
|
856
|
+
rectangles.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
857
|
+
className: "text-sm text-slate-500 dark:text-slate-400",
|
|
858
|
+
children: "No nodes are visible at this level."
|
|
859
|
+
}),
|
|
860
|
+
topModules.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
861
|
+
className: "pt-2",
|
|
862
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
863
|
+
className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400",
|
|
864
|
+
children: "Heaviest modules in selection"
|
|
865
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
866
|
+
className: "mt-3 space-y-3",
|
|
867
|
+
children: topModules.map((node) => /* @__PURE__ */ jsxs("button", {
|
|
868
|
+
type: "button",
|
|
869
|
+
onClick: () => updateTreemapSearch({ selected: node.id }),
|
|
870
|
+
className: "block w-full rounded-2xl border border-slate-200/80 bg-slate-50/80 px-4 py-3 text-left transition hover:border-slate-300 hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900/60 dark:hover:border-slate-700 dark:hover:bg-slate-900",
|
|
871
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
872
|
+
className: "truncate text-sm font-semibold text-slate-900 dark:text-slate-100",
|
|
873
|
+
children: node.sourcePath ?? node.label
|
|
874
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
875
|
+
className: "mt-1 text-xs text-slate-500 dark:text-slate-400",
|
|
876
|
+
children: [
|
|
877
|
+
node.totalOutgoing,
|
|
878
|
+
" outgoing • ",
|
|
879
|
+
node.totalIncoming,
|
|
880
|
+
" incoming"
|
|
881
|
+
]
|
|
882
|
+
})]
|
|
883
|
+
}, node.id))
|
|
884
|
+
})]
|
|
885
|
+
})
|
|
886
|
+
]
|
|
887
|
+
})
|
|
888
|
+
]
|
|
889
|
+
})]
|
|
890
|
+
}),
|
|
891
|
+
/* @__PURE__ */ jsx("div", {
|
|
892
|
+
className: "rounded-[24px] border border-blue-200/70 bg-blue-50/80 p-4 dark:border-blue-900/30 dark:bg-blue-950/30",
|
|
893
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
894
|
+
className: "flex gap-3",
|
|
895
|
+
children: [/* @__PURE__ */ jsx(Info, { className: "mt-0.5 size-5 shrink-0 text-blue-900 dark:text-blue-100" }), /* @__PURE__ */ jsxs("div", {
|
|
896
|
+
className: "text-sm text-blue-800 dark:text-blue-200",
|
|
897
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
898
|
+
className: "font-semibold",
|
|
899
|
+
children: "About this view"
|
|
900
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
901
|
+
className: "mt-1",
|
|
902
|
+
children: "The treemap now groups workspace code by folders and third-party code by package, so the first click reveals structure instead of dropping you into a broken leaf. Area reflects direct dependency pressure using incoming plus outgoing edges."
|
|
903
|
+
})]
|
|
904
|
+
})]
|
|
905
|
+
})
|
|
906
|
+
})
|
|
907
|
+
]
|
|
908
|
+
})
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
//#endregion
|
|
912
|
+
export { TreemapRoute as component };
|