@proteus-vue/devtools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/component-trace.d.ts +35 -0
- package/dist/device-info.d.ts +5 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +3323 -0
- package/dist/install.d.ts +76 -0
- package/dist/ownership-info.d.ts +77 -0
- package/dist/panel.d.ts +39 -0
- package/dist/panel.js +9172 -0
- package/dist/plugins/network.d.ts +2 -0
- package/dist/plugins.d.ts +64 -0
- package/dist/session-io.d.ts +35 -0
- package/dist/snapshot-io.d.ts +37 -0
- package/dist/source.d.ts +22 -0
- package/dist/tooltip.d.ts +21 -0
- package/dist/views/components.d.ts +26 -0
- package/dist/views/device.d.ts +43 -0
- package/dist/views/errors.d.ts +5 -0
- package/dist/views/flamegraph.d.ts +20 -0
- package/dist/views/graph.d.ts +5 -0
- package/dist/views/inspector.d.ts +9 -0
- package/dist/views/ownership.d.ts +4 -0
- package/dist/views/pages.d.ts +18 -0
- package/dist/views/route.d.ts +5 -0
- package/dist/views/state.d.ts +26 -0
- package/dist/views/timeline-interaction.d.ts +12 -0
- package/dist/views/timeline.d.ts +16 -0
- package/dist/vue-devtools.d.ts +133 -0
- package/dist/ws-bridge.d.ts +21 -0
- package/package.json +43 -0
- package/panel.html +26 -0
- package/style.css +1248 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3323 @@
|
|
|
1
|
+
// src/panel.ts
|
|
2
|
+
import {
|
|
3
|
+
createTimelineCollector,
|
|
4
|
+
createFlamegraphCollector,
|
|
5
|
+
createErrorDiagnoser
|
|
6
|
+
} from "@proteus-vue/devtools-runtime";
|
|
7
|
+
|
|
8
|
+
// src/tooltip.ts
|
|
9
|
+
var TIP_DATA = /* @__PURE__ */ Symbol("pdTip");
|
|
10
|
+
function attachTip(el, data) {
|
|
11
|
+
el.dataset.tip = "";
|
|
12
|
+
el[TIP_DATA] = data;
|
|
13
|
+
}
|
|
14
|
+
function resolveTipData(target) {
|
|
15
|
+
return target[TIP_DATA] ?? null;
|
|
16
|
+
}
|
|
17
|
+
function createTooltipLayer() {
|
|
18
|
+
const tip = document.createElement("div");
|
|
19
|
+
tip.className = "pd-tooltip";
|
|
20
|
+
tip.style.display = "none";
|
|
21
|
+
tip.style.position = "fixed";
|
|
22
|
+
tip.style.zIndex = "1000";
|
|
23
|
+
tip.style.pointerEvents = "none";
|
|
24
|
+
document.body.appendChild(tip);
|
|
25
|
+
let visible = false;
|
|
26
|
+
let hideTimer = null;
|
|
27
|
+
return {
|
|
28
|
+
show(data, x, y) {
|
|
29
|
+
if (hideTimer) {
|
|
30
|
+
clearTimeout(hideTimer);
|
|
31
|
+
hideTimer = null;
|
|
32
|
+
}
|
|
33
|
+
tip.replaceChildren();
|
|
34
|
+
const title = document.createElement("div");
|
|
35
|
+
title.className = "pd-tooltip-title";
|
|
36
|
+
title.textContent = data.title;
|
|
37
|
+
tip.appendChild(title);
|
|
38
|
+
for (const line of data.lines) {
|
|
39
|
+
const row = document.createElement("div");
|
|
40
|
+
row.className = "pd-tooltip-line";
|
|
41
|
+
row.textContent = line;
|
|
42
|
+
tip.appendChild(row);
|
|
43
|
+
}
|
|
44
|
+
tip.style.display = "block";
|
|
45
|
+
const rect = tip.getBoundingClientRect();
|
|
46
|
+
let left = x + 12;
|
|
47
|
+
let top = y + 12;
|
|
48
|
+
if (left + rect.width > window.innerWidth) left = x - rect.width - 12;
|
|
49
|
+
if (top + rect.height > window.innerHeight) top = y - rect.height - 12;
|
|
50
|
+
tip.style.left = left + "px";
|
|
51
|
+
tip.style.top = top + "px";
|
|
52
|
+
visible = true;
|
|
53
|
+
},
|
|
54
|
+
hide() {
|
|
55
|
+
if (hideTimer) clearTimeout(hideTimer);
|
|
56
|
+
tip.style.display = "none";
|
|
57
|
+
visible = false;
|
|
58
|
+
},
|
|
59
|
+
dispose() {
|
|
60
|
+
if (hideTimer) clearTimeout(hideTimer);
|
|
61
|
+
tip.remove();
|
|
62
|
+
visible = false;
|
|
63
|
+
},
|
|
64
|
+
get visible() {
|
|
65
|
+
return visible;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function bindTooltip(root, layer, resolve) {
|
|
70
|
+
let hoverTimer = null;
|
|
71
|
+
let current = null;
|
|
72
|
+
function onOver(e) {
|
|
73
|
+
const target = e.target.closest("[data-tip]");
|
|
74
|
+
if (!target) {
|
|
75
|
+
layer.hide();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
current = target;
|
|
79
|
+
if (hoverTimer) clearTimeout(hoverTimer);
|
|
80
|
+
hoverTimer = setTimeout(() => {
|
|
81
|
+
if (current !== target) return;
|
|
82
|
+
const data = resolve(target);
|
|
83
|
+
if (data) layer.show(data, e.clientX, e.clientY);
|
|
84
|
+
}, 150);
|
|
85
|
+
}
|
|
86
|
+
function onMove(e) {
|
|
87
|
+
if (!current || !layer.visible) return;
|
|
88
|
+
layer.show(resolve(current) ?? { title: "", lines: [] }, e.clientX, e.clientY);
|
|
89
|
+
}
|
|
90
|
+
function onOut() {
|
|
91
|
+
current = null;
|
|
92
|
+
if (hoverTimer) clearTimeout(hoverTimer);
|
|
93
|
+
layer.hide();
|
|
94
|
+
}
|
|
95
|
+
root.addEventListener("mouseover", onOver);
|
|
96
|
+
root.addEventListener("mousemove", onMove);
|
|
97
|
+
root.addEventListener("mouseout", onOut);
|
|
98
|
+
return () => {
|
|
99
|
+
root.removeEventListener("mouseover", onOver);
|
|
100
|
+
root.removeEventListener("mousemove", onMove);
|
|
101
|
+
root.removeEventListener("mouseout", onOut);
|
|
102
|
+
layer.hide();
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/views/timeline.ts
|
|
107
|
+
var LANE_H = 26;
|
|
108
|
+
var OVERSCAN = 2;
|
|
109
|
+
function renderTimeline(container, data) {
|
|
110
|
+
container.replaceChildren();
|
|
111
|
+
const spans = data.spans;
|
|
112
|
+
if (spans.length === 0) {
|
|
113
|
+
const empty = document.createElement("div");
|
|
114
|
+
empty.className = "pd-empty";
|
|
115
|
+
empty.textContent = "\u6682\u65E0\u4E8B\u4EF6\uFF08TraceBus \u672A\u4E0A\u62A5\uFF09";
|
|
116
|
+
container.appendChild(empty);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
let winStart = data.window?.start ?? Infinity;
|
|
120
|
+
let winEnd = data.window?.end ?? -Infinity;
|
|
121
|
+
if (!data.window) {
|
|
122
|
+
for (const s of spans) {
|
|
123
|
+
if (s.start < winStart) winStart = s.start;
|
|
124
|
+
const end = s.end !== void 0 ? s.end : s.start;
|
|
125
|
+
if (end > winEnd) winEnd = end;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const total = Math.max(1, winEnd - winStart);
|
|
129
|
+
const visible = data.window ? spans.filter((s) => {
|
|
130
|
+
const end = s.end !== void 0 ? s.end : s.start;
|
|
131
|
+
return end >= winStart && s.start <= winEnd;
|
|
132
|
+
}) : spans;
|
|
133
|
+
const ruler = document.createElement("div");
|
|
134
|
+
ruler.className = "pd-ruler";
|
|
135
|
+
for (let i = 0; i <= 4; i++) {
|
|
136
|
+
const mark = document.createElement("span");
|
|
137
|
+
mark.style.width = "25%";
|
|
138
|
+
mark.textContent = Math.round(winStart + total * i / 4) + "ms";
|
|
139
|
+
ruler.appendChild(mark);
|
|
140
|
+
}
|
|
141
|
+
container.appendChild(ruler);
|
|
142
|
+
const lanes = /* @__PURE__ */ new Map();
|
|
143
|
+
for (const s of visible) {
|
|
144
|
+
const list = lanes.get(s.source);
|
|
145
|
+
if (list) list.push(s);
|
|
146
|
+
else lanes.set(s.source, [s]);
|
|
147
|
+
}
|
|
148
|
+
const laneList = Array.from(lanes);
|
|
149
|
+
if (data.virtual) {
|
|
150
|
+
const scrollTop = Math.max(0, data.virtual.scrollTop);
|
|
151
|
+
const viewHeight = Math.max(1, data.virtual.viewHeight);
|
|
152
|
+
container.style.overflowY = "auto";
|
|
153
|
+
const spacer = document.createElement("div");
|
|
154
|
+
spacer.className = "pd-timeline-spacer";
|
|
155
|
+
spacer.style.height = laneList.length * LANE_H + "px";
|
|
156
|
+
const startIdx = Math.max(0, Math.floor((scrollTop - OVERSCAN * LANE_H) / LANE_H));
|
|
157
|
+
const endIdx = Math.min(laneList.length, Math.ceil((scrollTop + viewHeight + OVERSCAN * LANE_H) / LANE_H));
|
|
158
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
159
|
+
const entry = laneList[i];
|
|
160
|
+
const lane = buildLane(entry[0], entry[1], winStart, total);
|
|
161
|
+
lane.style.position = "absolute";
|
|
162
|
+
lane.style.top = i * LANE_H + "px";
|
|
163
|
+
lane.style.left = "0";
|
|
164
|
+
lane.style.right = "0";
|
|
165
|
+
lane.style.height = LANE_H + "px";
|
|
166
|
+
lane.style.margin = "0";
|
|
167
|
+
spacer.appendChild(lane);
|
|
168
|
+
}
|
|
169
|
+
container.appendChild(spacer);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
for (const entry of laneList) {
|
|
173
|
+
container.appendChild(buildLane(entry[0], entry[1], winStart, total));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function buildLane(source, laneSpans, winStart, total) {
|
|
177
|
+
const lane = document.createElement("div");
|
|
178
|
+
lane.className = "pd-lane";
|
|
179
|
+
const label = document.createElement("div");
|
|
180
|
+
label.className = "pd-lane-label";
|
|
181
|
+
label.textContent = source;
|
|
182
|
+
lane.appendChild(label);
|
|
183
|
+
const track = document.createElement("div");
|
|
184
|
+
track.className = "pd-lane-track";
|
|
185
|
+
for (const s of laneSpans) {
|
|
186
|
+
const seg = document.createElement("div");
|
|
187
|
+
const left = (s.start - winStart) / total * 100;
|
|
188
|
+
const end = s.end !== void 0 ? s.end : s.start;
|
|
189
|
+
const width = Math.max(0.5, (end - s.start) / total * 100);
|
|
190
|
+
seg.className = "pd-span" + (s.pending ? " pd-span-pending" : "") + (s.durationMs === 0 ? " pd-span-dot" : "");
|
|
191
|
+
seg.style.left = left.toFixed(2) + "%";
|
|
192
|
+
seg.style.width = width.toFixed(2) + "%";
|
|
193
|
+
attachTip(seg, {
|
|
194
|
+
title: `${s.source}.${s.name}`,
|
|
195
|
+
lines: [s.durationMs !== void 0 ? `\u8017\u65F6 ${s.durationMs}ms` : "\u77AC\u65F6\u4E8B\u4EF6", s.pending ? "\u9636\u6BB5: pending\uFF08\u8FDB\u884C\u4E2D\uFF09" : "\u9636\u6BB5: completed"]
|
|
196
|
+
});
|
|
197
|
+
const text = document.createElement("span");
|
|
198
|
+
text.textContent = s.name + (s.durationMs !== void 0 ? " " + s.durationMs + "ms" : "");
|
|
199
|
+
seg.appendChild(text);
|
|
200
|
+
track.appendChild(seg);
|
|
201
|
+
}
|
|
202
|
+
lane.appendChild(track);
|
|
203
|
+
return lane;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/views/timeline-interaction.ts
|
|
207
|
+
var ZOOM_STEP = 1.2;
|
|
208
|
+
var MIN_SPAN = 1;
|
|
209
|
+
function fullWindow(spans) {
|
|
210
|
+
let start = Infinity;
|
|
211
|
+
let end = -Infinity;
|
|
212
|
+
for (const s of spans) {
|
|
213
|
+
if (s.start < start) start = s.start;
|
|
214
|
+
const e = s.end !== void 0 ? s.end : s.start;
|
|
215
|
+
if (e > end) end = e;
|
|
216
|
+
}
|
|
217
|
+
return { start, end };
|
|
218
|
+
}
|
|
219
|
+
function createTimelineZoom(container, getSpans, opts = {}) {
|
|
220
|
+
const { onWindowChange } = opts;
|
|
221
|
+
let window2 = null;
|
|
222
|
+
let dragging = false;
|
|
223
|
+
let dragStartX = 0;
|
|
224
|
+
let dragStartWindow = null;
|
|
225
|
+
function apply(w) {
|
|
226
|
+
window2 = w;
|
|
227
|
+
onWindowChange?.(w);
|
|
228
|
+
}
|
|
229
|
+
function clamp(w) {
|
|
230
|
+
const full = fullWindow(getSpans());
|
|
231
|
+
const maxSpan = Math.max(1, full.end - full.start);
|
|
232
|
+
const span = Math.min(maxSpan, Math.max(MIN_SPAN, w.end - w.start));
|
|
233
|
+
let start = w.start;
|
|
234
|
+
if (start < full.start) start = full.start;
|
|
235
|
+
if (start + span > full.end) start = Math.max(full.start, full.end - span);
|
|
236
|
+
return { start, end: start + span };
|
|
237
|
+
}
|
|
238
|
+
function onWheel(e) {
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
if (!window2) window2 = fullWindow(getSpans());
|
|
241
|
+
const span = window2.end - window2.start;
|
|
242
|
+
if (span <= 0) return;
|
|
243
|
+
const rect = container.getBoundingClientRect();
|
|
244
|
+
const width = Math.max(1, rect.width);
|
|
245
|
+
const cx = Number.isFinite(e.clientX) ? e.clientX : rect.left + width / 2;
|
|
246
|
+
const f = Math.min(1, Math.max(0, (cx - rect.left) / width));
|
|
247
|
+
const cursorTime = window2.start + f * span;
|
|
248
|
+
const factor = e.deltaY < 0 ? 1 / ZOOM_STEP : ZOOM_STEP;
|
|
249
|
+
const newSpan = span * factor;
|
|
250
|
+
const start = cursorTime - f * newSpan;
|
|
251
|
+
apply(clamp({ start, end: start + newSpan }));
|
|
252
|
+
}
|
|
253
|
+
function onDown(e) {
|
|
254
|
+
if (e.button !== 0) return;
|
|
255
|
+
if (!window2) window2 = fullWindow(getSpans());
|
|
256
|
+
dragging = true;
|
|
257
|
+
dragStartX = e.clientX;
|
|
258
|
+
dragStartWindow = { ...window2 };
|
|
259
|
+
e.preventDefault();
|
|
260
|
+
}
|
|
261
|
+
function onMove(e) {
|
|
262
|
+
if (!dragging || !dragStartWindow) return;
|
|
263
|
+
const span = dragStartWindow.end - dragStartWindow.start;
|
|
264
|
+
if (span <= 0) return;
|
|
265
|
+
const rect = container.getBoundingClientRect();
|
|
266
|
+
const dt = (e.clientX - dragStartX) / Math.max(1, rect.width) * span;
|
|
267
|
+
apply(clamp({ start: dragStartWindow.start - dt, end: dragStartWindow.end - dt }));
|
|
268
|
+
}
|
|
269
|
+
function onUp() {
|
|
270
|
+
dragging = false;
|
|
271
|
+
dragStartWindow = null;
|
|
272
|
+
}
|
|
273
|
+
function onDblClick() {
|
|
274
|
+
apply(fullWindow(getSpans()));
|
|
275
|
+
}
|
|
276
|
+
container.addEventListener("wheel", onWheel, { passive: false });
|
|
277
|
+
container.addEventListener("mousedown", onDown);
|
|
278
|
+
container.addEventListener("mousemove", onMove);
|
|
279
|
+
container.addEventListener("mouseup", onUp);
|
|
280
|
+
container.addEventListener("dblclick", onDblClick);
|
|
281
|
+
return {
|
|
282
|
+
getWindow: () => window2,
|
|
283
|
+
destroy() {
|
|
284
|
+
container.removeEventListener("wheel", onWheel);
|
|
285
|
+
container.removeEventListener("mousedown", onDown);
|
|
286
|
+
container.removeEventListener("mousemove", onMove);
|
|
287
|
+
container.removeEventListener("mouseup", onUp);
|
|
288
|
+
container.removeEventListener("dblclick", onDblClick);
|
|
289
|
+
dragging = false;
|
|
290
|
+
dragStartWindow = null;
|
|
291
|
+
window2 = null;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/views/flamegraph.ts
|
|
297
|
+
var ROW_HEIGHT = 22;
|
|
298
|
+
var SRC_COLORS = {
|
|
299
|
+
lifecycle: "#3e5770",
|
|
300
|
+
router: "#2e7d5b",
|
|
301
|
+
api: "#1d6fb8",
|
|
302
|
+
store: "#8a5a17",
|
|
303
|
+
compiler: "#6a3d9a",
|
|
304
|
+
capability: "#0e7c86",
|
|
305
|
+
component: "#9a3d52",
|
|
306
|
+
hmr: "#3d9a6a",
|
|
307
|
+
// ★合成「录制会话」根(视图层包装,不落数据/对比):中性灰
|
|
308
|
+
session: "#6b7280"
|
|
309
|
+
};
|
|
310
|
+
function srcColor(source) {
|
|
311
|
+
return SRC_COLORS[source] ?? "#555";
|
|
312
|
+
}
|
|
313
|
+
function pct(v) {
|
|
314
|
+
return Math.min(100, v).toFixed(2) + "%";
|
|
315
|
+
}
|
|
316
|
+
function clampPct(v) {
|
|
317
|
+
return Math.max(0.4, Math.min(100, v)).toFixed(2) + "%";
|
|
318
|
+
}
|
|
319
|
+
function compareMap(compare) {
|
|
320
|
+
const m = /* @__PURE__ */ new Map();
|
|
321
|
+
for (const c of compare) m.set(c.source + "\0" + c.name, c);
|
|
322
|
+
return m;
|
|
323
|
+
}
|
|
324
|
+
function renderFlamegraph(container, data, hooks = {}) {
|
|
325
|
+
container.replaceChildren();
|
|
326
|
+
const roots = data.focus ? [data.focus] : data.nodes;
|
|
327
|
+
if (roots.length === 0) {
|
|
328
|
+
const empty = document.createElement("div");
|
|
329
|
+
empty.className = "pd-empty";
|
|
330
|
+
empty.textContent = '\u6682\u65E0\u5F55\u5236\uFF08\u9762\u677F\u53F3\u4E0A\u89D2"\u5F00\u59CB\u5F55\u5236"\u540E\u4EA7\u751F\uFF09';
|
|
331
|
+
container.appendChild(empty);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const cmp = data.compare ? compareMap(data.compare) : null;
|
|
335
|
+
const regs = data.compare ? data.compare.filter((c) => c.verdict === "regression") : [];
|
|
336
|
+
const imps = data.compare ? data.compare.filter((c) => c.verdict === "improvement") : [];
|
|
337
|
+
if (data.compare && data.compare.length > 0) {
|
|
338
|
+
const summary = document.createElement("div");
|
|
339
|
+
summary.className = "pd-cmp";
|
|
340
|
+
const head = document.createElement("div");
|
|
341
|
+
head.className = "pd-cmp-head";
|
|
342
|
+
head.textContent = `\u5BF9\u6BD4\u4E0A\u6B21\u5F55\u5236\uFF1A${regs.length} \u5904\u56DE\u5F52 \xB7 ${imps.length} \u5904\u4F18\u5316`;
|
|
343
|
+
summary.appendChild(head);
|
|
344
|
+
const sorted = data.compare.slice().sort((a, b) => Math.abs(b.deltaPct) - Math.abs(a.deltaPct)).slice(0, 8);
|
|
345
|
+
for (const c of sorted) {
|
|
346
|
+
const row = document.createElement("div");
|
|
347
|
+
row.className = "pd-cmp-row pd-cmp-" + c.verdict;
|
|
348
|
+
const name = document.createElement("span");
|
|
349
|
+
name.className = "pd-cmp-name";
|
|
350
|
+
name.textContent = c.source + "." + c.name;
|
|
351
|
+
const delta = document.createElement("span");
|
|
352
|
+
delta.className = "pd-cmp-delta";
|
|
353
|
+
delta.textContent = (c.deltaPct > 0 ? "+" : "") + c.deltaPct + "% (" + c.aMs + "\u2192" + c.bMs + "ms)";
|
|
354
|
+
row.appendChild(name);
|
|
355
|
+
row.appendChild(delta);
|
|
356
|
+
attachTip(row, {
|
|
357
|
+
title: `${c.source}.${c.name}`,
|
|
358
|
+
lines: [`\u4E0A\u6B21 ${c.aMs}ms \u2192 \u672C\u6B21 ${c.bMs}ms`, `\u53D8\u5316 ${(c.deltaPct > 0 ? "+" : "") + c.deltaPct}%`]
|
|
359
|
+
});
|
|
360
|
+
summary.appendChild(row);
|
|
361
|
+
}
|
|
362
|
+
container.appendChild(summary);
|
|
363
|
+
}
|
|
364
|
+
if (data.breadcrumb && data.breadcrumb.length > 0) {
|
|
365
|
+
const crumb = document.createElement("div");
|
|
366
|
+
crumb.className = "pd-fg-crumb";
|
|
367
|
+
for (const c of data.breadcrumb) {
|
|
368
|
+
const item = document.createElement("button");
|
|
369
|
+
item.className = "pd-fg-crumb-item";
|
|
370
|
+
item.textContent = c.name;
|
|
371
|
+
item.addEventListener("click", () => hooks.onFocus?.(c.id));
|
|
372
|
+
crumb.appendChild(item);
|
|
373
|
+
}
|
|
374
|
+
const up = document.createElement("button");
|
|
375
|
+
up.className = "pd-btn pd-fg-up";
|
|
376
|
+
up.textContent = "\u8FD4\u56DE\u4E0A\u7EA7";
|
|
377
|
+
up.addEventListener("click", () => hooks.onFocusUp?.());
|
|
378
|
+
crumb.appendChild(up);
|
|
379
|
+
container.appendChild(crumb);
|
|
380
|
+
}
|
|
381
|
+
let winStart = Infinity;
|
|
382
|
+
let winEnd = -Infinity;
|
|
383
|
+
for (const n of roots) {
|
|
384
|
+
if (n.startMs < winStart) winStart = n.startMs;
|
|
385
|
+
const end = n.startMs + n.durationMs;
|
|
386
|
+
if (end > winEnd) winEnd = end;
|
|
387
|
+
}
|
|
388
|
+
const total = Math.max(1, winEnd - winStart);
|
|
389
|
+
let renderRoots = roots;
|
|
390
|
+
if (!data.focus && roots.length >= 2) {
|
|
391
|
+
let childTotal = 0;
|
|
392
|
+
for (const r of roots) childTotal += r.durationMs;
|
|
393
|
+
renderRoots = [
|
|
394
|
+
{
|
|
395
|
+
id: "__session__",
|
|
396
|
+
source: "session",
|
|
397
|
+
name: "\u5F55\u5236\u4F1A\u8BDD",
|
|
398
|
+
startMs: winStart,
|
|
399
|
+
durationMs: total,
|
|
400
|
+
selfMs: Math.max(0, total - childTotal),
|
|
401
|
+
children: roots,
|
|
402
|
+
depth: 0
|
|
403
|
+
}
|
|
404
|
+
];
|
|
405
|
+
}
|
|
406
|
+
const board = document.createElement("div");
|
|
407
|
+
board.className = "pd-fg-board";
|
|
408
|
+
let maxDepth = 0;
|
|
409
|
+
const depthOf = (n, d) => {
|
|
410
|
+
let m = d;
|
|
411
|
+
for (const c of n.children) m = Math.max(m, depthOf(c, d + 1));
|
|
412
|
+
return m;
|
|
413
|
+
};
|
|
414
|
+
for (const n of renderRoots) maxDepth = Math.max(maxDepth, depthOf(n, 0));
|
|
415
|
+
board.style.height = (maxDepth + 1) * ROW_HEIGHT + "px";
|
|
416
|
+
function renderNode(box, node, depth, relStart, relDur, isRoot) {
|
|
417
|
+
const leftPct = isRoot ? (node.startMs - winStart) / total * 100 : (node.startMs - relStart) / relDur * 100;
|
|
418
|
+
const widthPct = isRoot ? node.durationMs / total * 100 : node.durationMs / relDur * 100;
|
|
419
|
+
const block = document.createElement("div");
|
|
420
|
+
const isSession = node.id === "__session__";
|
|
421
|
+
block.className = "pd-fg-node" + (node.selfMs === 0 ? " pd-fg-zero" : "") + (isSession ? " pd-fg-session" : "");
|
|
422
|
+
block.style.background = srcColor(node.source);
|
|
423
|
+
const entry = cmp ? cmp.get(node.source + "\0" + node.name) : void 0;
|
|
424
|
+
if (entry && entry.verdict !== "same") block.classList.add(entry.verdict === "regression" ? "pd-fg-reg" : "pd-fg-imp");
|
|
425
|
+
block.style.left = pct(leftPct);
|
|
426
|
+
block.style.width = clampPct(widthPct);
|
|
427
|
+
block.style.top = "0px";
|
|
428
|
+
block.style.height = ROW_HEIGHT - 2 + "px";
|
|
429
|
+
const tipLines = [`inclusive ${node.durationMs}ms`, `exclusive ${node.selfMs}ms`];
|
|
430
|
+
if (entry && entry.verdict !== "same") tipLines.push(`\u5BF9\u6BD4 ${(entry.deltaPct > 0 ? "+" : "") + entry.deltaPct}%`);
|
|
431
|
+
attachTip(block, {
|
|
432
|
+
title: `${node.source}.${node.name}`,
|
|
433
|
+
lines: tipLines
|
|
434
|
+
});
|
|
435
|
+
const label = document.createElement("span");
|
|
436
|
+
label.textContent = node.name + " " + node.selfMs + "ms";
|
|
437
|
+
block.appendChild(label);
|
|
438
|
+
if (!isSession) block.addEventListener("click", () => hooks.onFocus?.(node.id));
|
|
439
|
+
box.appendChild(block);
|
|
440
|
+
let subDepth = 0;
|
|
441
|
+
if (node.children.length) {
|
|
442
|
+
const sub = document.createElement("div");
|
|
443
|
+
sub.className = "pd-fg-children";
|
|
444
|
+
sub.style.left = pct(leftPct);
|
|
445
|
+
sub.style.width = clampPct(widthPct);
|
|
446
|
+
sub.style.top = ROW_HEIGHT + "px";
|
|
447
|
+
let maxChildDepth = 0;
|
|
448
|
+
for (const c of node.children) maxChildDepth = Math.max(maxChildDepth, renderNode(sub, c, depth + 1, node.startMs, node.durationMs, false));
|
|
449
|
+
sub.style.height = maxChildDepth * ROW_HEIGHT + "px";
|
|
450
|
+
box.appendChild(sub);
|
|
451
|
+
subDepth = maxChildDepth;
|
|
452
|
+
}
|
|
453
|
+
return subDepth + 1;
|
|
454
|
+
}
|
|
455
|
+
for (const r of renderRoots) renderNode(board, r, 0, winStart, total, true);
|
|
456
|
+
container.appendChild(board);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/views/inspector.ts
|
|
460
|
+
function editInitial(value, kind) {
|
|
461
|
+
if (kind === "string") return String(value);
|
|
462
|
+
if (kind === "null") return value === void 0 ? "undefined" : "null";
|
|
463
|
+
return String(value);
|
|
464
|
+
}
|
|
465
|
+
function parseEdit(text, kind) {
|
|
466
|
+
if (kind === "number") {
|
|
467
|
+
const n = Number(text.trim());
|
|
468
|
+
if (!Number.isFinite(n)) return { ok: false };
|
|
469
|
+
return { ok: true, value: n };
|
|
470
|
+
}
|
|
471
|
+
if (kind === "boolean") {
|
|
472
|
+
if (text.trim() === "true") return { ok: true, value: true };
|
|
473
|
+
if (text.trim() === "false") return { ok: true, value: false };
|
|
474
|
+
return { ok: false };
|
|
475
|
+
}
|
|
476
|
+
if (kind === "string") return { ok: true, value: text };
|
|
477
|
+
const t = text.trim();
|
|
478
|
+
if (t === "" || t === "null") return { ok: true, value: null };
|
|
479
|
+
if (t === "undefined") return { ok: true, value: void 0 };
|
|
480
|
+
if (t === "true") return { ok: true, value: true };
|
|
481
|
+
if (t === "false") return { ok: true, value: false };
|
|
482
|
+
if (/^-?\d+(\.\d+)?$/.test(t)) return { ok: true, value: Number(t) };
|
|
483
|
+
return { ok: true, value: text };
|
|
484
|
+
}
|
|
485
|
+
function startEdit(valEl, value, kind, path, hooks) {
|
|
486
|
+
const input = document.createElement("input");
|
|
487
|
+
input.className = "pd-kv-edit";
|
|
488
|
+
input.value = editInitial(value, kind);
|
|
489
|
+
valEl.replaceWith(input);
|
|
490
|
+
input.focus();
|
|
491
|
+
let done = false;
|
|
492
|
+
const finish = (commit) => {
|
|
493
|
+
if (done) return;
|
|
494
|
+
done = true;
|
|
495
|
+
if (commit) {
|
|
496
|
+
const parsed = parseEdit(input.value, kind);
|
|
497
|
+
if (parsed.ok) {
|
|
498
|
+
const nk = kindOf(parsed.value);
|
|
499
|
+
valEl.textContent = formatPrimitive(parsed.value, nk);
|
|
500
|
+
valEl.className = "pd-kv-value pd-t-" + nk;
|
|
501
|
+
input.replaceWith(valEl);
|
|
502
|
+
hooks.onEdit?.(path, parsed.value);
|
|
503
|
+
} else {
|
|
504
|
+
input.replaceWith(valEl);
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
input.replaceWith(valEl);
|
|
509
|
+
};
|
|
510
|
+
input.addEventListener("keydown", (e) => {
|
|
511
|
+
if (e.key === "Enter") finish(true);
|
|
512
|
+
else if (e.key === "Escape") finish(false);
|
|
513
|
+
});
|
|
514
|
+
input.addEventListener("blur", () => finish(true));
|
|
515
|
+
}
|
|
516
|
+
function kindOf(value) {
|
|
517
|
+
if (value === null || value === void 0) return "null";
|
|
518
|
+
const t = typeof value;
|
|
519
|
+
if (t === "number") return "number";
|
|
520
|
+
if (t === "string") return "string";
|
|
521
|
+
if (t === "boolean") return "boolean";
|
|
522
|
+
if (Array.isArray(value)) return "array";
|
|
523
|
+
return "object";
|
|
524
|
+
}
|
|
525
|
+
function summarize(value) {
|
|
526
|
+
const k = kindOf(value);
|
|
527
|
+
if (k === "array") return "Array(" + value.length + ")";
|
|
528
|
+
if (k === "object") {
|
|
529
|
+
const keys = Object.keys(value);
|
|
530
|
+
return "Object {" + keys.slice(0, 3).join(", ") + (keys.length > 3 ? ", \u2026" : "") + "}";
|
|
531
|
+
}
|
|
532
|
+
return formatPrimitive(value, k);
|
|
533
|
+
}
|
|
534
|
+
function formatPrimitive(value, kind) {
|
|
535
|
+
if (kind === "string") return JSON.stringify(value);
|
|
536
|
+
if (kind === "null") return value === void 0 ? "undefined" : "null";
|
|
537
|
+
return String(value);
|
|
538
|
+
}
|
|
539
|
+
function renderKeyValue(container, key, value, depth, initiallyOpen = false, path = [], hooks = {}) {
|
|
540
|
+
const kind = kindOf(value);
|
|
541
|
+
const row = document.createElement("div");
|
|
542
|
+
row.className = "pd-kv";
|
|
543
|
+
row.style.paddingLeft = 10 + depth * 14 + "px";
|
|
544
|
+
const toggle = document.createElement("span");
|
|
545
|
+
toggle.className = "pd-kv-toggle";
|
|
546
|
+
const keyEl = document.createElement("span");
|
|
547
|
+
keyEl.className = "pd-kv-key";
|
|
548
|
+
keyEl.textContent = key;
|
|
549
|
+
const valEl = document.createElement("span");
|
|
550
|
+
valEl.className = "pd-kv-value pd-t-" + kind;
|
|
551
|
+
const collapsible = kind === "object" || kind === "array";
|
|
552
|
+
toggle.textContent = collapsible ? "\u25B8" : "";
|
|
553
|
+
if (collapsible) {
|
|
554
|
+
valEl.textContent = summarize(value);
|
|
555
|
+
const childBox = document.createElement("div");
|
|
556
|
+
childBox.style.display = initiallyOpen ? "block" : "none";
|
|
557
|
+
if (initiallyOpen) toggle.textContent = "\u25BE";
|
|
558
|
+
let built = false;
|
|
559
|
+
const build = () => {
|
|
560
|
+
if (built) return;
|
|
561
|
+
built = true;
|
|
562
|
+
const entries = kind === "array" ? value.map((v, i) => [String(i), v]) : Object.entries(value);
|
|
563
|
+
for (const [k, v] of entries) renderKeyValue(childBox, k, v, depth + 1, false, path.concat(k), hooks);
|
|
564
|
+
};
|
|
565
|
+
if (initiallyOpen) build();
|
|
566
|
+
const expand = () => {
|
|
567
|
+
const open = childBox.style.display !== "none";
|
|
568
|
+
childBox.style.display = open ? "none" : "block";
|
|
569
|
+
toggle.textContent = open ? "\u25B8" : "\u25BE";
|
|
570
|
+
if (!open) build();
|
|
571
|
+
};
|
|
572
|
+
row.addEventListener("click", expand);
|
|
573
|
+
row.appendChild(toggle);
|
|
574
|
+
row.appendChild(keyEl);
|
|
575
|
+
row.appendChild(valEl);
|
|
576
|
+
container.appendChild(row);
|
|
577
|
+
container.appendChild(childBox);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
valEl.textContent = formatPrimitive(value, kind);
|
|
581
|
+
if (hooks.onEdit) {
|
|
582
|
+
valEl.classList.add("pd-kv-editable");
|
|
583
|
+
valEl.addEventListener("click", (e) => {
|
|
584
|
+
e.stopPropagation();
|
|
585
|
+
startEdit(valEl, value, kind, path, hooks);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
row.appendChild(toggle);
|
|
589
|
+
row.appendChild(keyEl);
|
|
590
|
+
row.appendChild(valEl);
|
|
591
|
+
container.appendChild(row);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/views/state.ts
|
|
595
|
+
function diffLines(before, after) {
|
|
596
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
597
|
+
const lines = [];
|
|
598
|
+
for (const k of keys) {
|
|
599
|
+
const b = before[k];
|
|
600
|
+
const a = after[k];
|
|
601
|
+
if (JSON.stringify(b) === JSON.stringify(a)) continue;
|
|
602
|
+
lines.push(k + ": " + summarize(b) + " \u2192 " + summarize(a));
|
|
603
|
+
}
|
|
604
|
+
return lines;
|
|
605
|
+
}
|
|
606
|
+
function renderState(container, data, hooks = {}) {
|
|
607
|
+
container.replaceChildren();
|
|
608
|
+
const stores = data.snapshot.stores;
|
|
609
|
+
const selected = data.selectedStore !== void 0 && stores.some((s) => s.id === data.selectedStore) ? data.selectedStore : stores[0]?.id ?? "";
|
|
610
|
+
const toolbar = document.createElement("div");
|
|
611
|
+
toolbar.className = "pd-toolbar";
|
|
612
|
+
const exportBtn = document.createElement("button");
|
|
613
|
+
exportBtn.className = "pd-btn";
|
|
614
|
+
exportBtn.textContent = "\u5BFC\u51FA\u5FEB\u7167 JSON";
|
|
615
|
+
exportBtn.addEventListener("click", () => hooks.onExport?.());
|
|
616
|
+
toolbar.appendChild(exportBtn);
|
|
617
|
+
const importBtn = document.createElement("button");
|
|
618
|
+
importBtn.className = "pd-btn";
|
|
619
|
+
importBtn.textContent = "\u5BFC\u5165\u5FEB\u7167 JSON";
|
|
620
|
+
const fileInput = document.createElement("input");
|
|
621
|
+
fileInput.type = "file";
|
|
622
|
+
fileInput.accept = ".json,application/json";
|
|
623
|
+
fileInput.style.display = "none";
|
|
624
|
+
importBtn.addEventListener("click", () => fileInput.click());
|
|
625
|
+
fileInput.addEventListener("change", () => {
|
|
626
|
+
const f = fileInput.files?.[0];
|
|
627
|
+
fileInput.value = "";
|
|
628
|
+
if (!f) return;
|
|
629
|
+
const reader = new FileReader();
|
|
630
|
+
reader.onload = () => hooks.onImport?.(String(reader.result ?? ""));
|
|
631
|
+
reader.readAsText(f);
|
|
632
|
+
});
|
|
633
|
+
toolbar.appendChild(importBtn);
|
|
634
|
+
toolbar.appendChild(fileInput);
|
|
635
|
+
if (hooks.onExportSession || hooks.onImportSession) {
|
|
636
|
+
const sessionExportBtn = document.createElement("button");
|
|
637
|
+
sessionExportBtn.className = "pd-btn";
|
|
638
|
+
sessionExportBtn.textContent = "\u5BFC\u51FA\u4F1A\u8BDD JSON";
|
|
639
|
+
sessionExportBtn.title = "\u5BFC\u51FA SessionBundle\uFF08\u53EF\u91CD\u653E\u4E8B\u4EF6\u65E5\u5FD7 + \u8BBE\u5907 + store \u5FEB\u7167\uFF09";
|
|
640
|
+
sessionExportBtn.addEventListener("click", () => hooks.onExportSession?.());
|
|
641
|
+
toolbar.appendChild(sessionExportBtn);
|
|
642
|
+
const sessionImportBtn = document.createElement("button");
|
|
643
|
+
sessionImportBtn.className = "pd-btn";
|
|
644
|
+
sessionImportBtn.textContent = "\u5BFC\u5165\u4F1A\u8BDD JSON";
|
|
645
|
+
sessionImportBtn.title = "\u5BFC\u5165 SessionBundle\uFF08\u6E05\u7A7A\u805A\u5408 \u2192 \u91CD\u653E\u4E8B\u4EF6\u5168\u89C6\u56FE\u91CD\u5EFA \u2192 \u72B6\u6001\u6062\u590D\uFF09";
|
|
646
|
+
const sessionFileInput = document.createElement("input");
|
|
647
|
+
sessionFileInput.type = "file";
|
|
648
|
+
sessionFileInput.accept = ".json,application/json";
|
|
649
|
+
sessionFileInput.style.display = "none";
|
|
650
|
+
sessionImportBtn.addEventListener("click", () => sessionFileInput.click());
|
|
651
|
+
sessionFileInput.addEventListener("change", () => {
|
|
652
|
+
const f = sessionFileInput.files?.[0];
|
|
653
|
+
sessionFileInput.value = "";
|
|
654
|
+
if (!f) return;
|
|
655
|
+
const reader = new FileReader();
|
|
656
|
+
reader.onload = () => hooks.onImportSession?.(String(reader.result ?? ""));
|
|
657
|
+
reader.readAsText(f);
|
|
658
|
+
});
|
|
659
|
+
toolbar.appendChild(sessionImportBtn);
|
|
660
|
+
toolbar.appendChild(sessionFileInput);
|
|
661
|
+
}
|
|
662
|
+
const stepInfo = document.createElement("span");
|
|
663
|
+
stepInfo.textContent = "\u6B65\u9AA4 " + data.steps.length + " \xB7 stores " + stores.length;
|
|
664
|
+
toolbar.appendChild(stepInfo);
|
|
665
|
+
container.appendChild(toolbar);
|
|
666
|
+
if (stores.length) {
|
|
667
|
+
const picker = document.createElement("div");
|
|
668
|
+
picker.className = "pd-store-picker";
|
|
669
|
+
for (const s of stores) {
|
|
670
|
+
const chip = document.createElement("button");
|
|
671
|
+
chip.className = "pd-store-chip" + (s.id === selected ? " pd-store-chip-active" : "");
|
|
672
|
+
chip.textContent = s.id;
|
|
673
|
+
chip.addEventListener("click", () => hooks.onSelectStore?.(s.id));
|
|
674
|
+
picker.appendChild(chip);
|
|
675
|
+
}
|
|
676
|
+
container.appendChild(picker);
|
|
677
|
+
}
|
|
678
|
+
const sel = stores.find((s) => s.id === selected);
|
|
679
|
+
if (sel) {
|
|
680
|
+
const card2 = document.createElement("div");
|
|
681
|
+
card2.className = "pd-store";
|
|
682
|
+
const head = document.createElement("div");
|
|
683
|
+
head.className = "pd-store-head";
|
|
684
|
+
head.textContent = selected + " \xB7 state";
|
|
685
|
+
card2.appendChild(head);
|
|
686
|
+
const inspector = document.createElement("div");
|
|
687
|
+
inspector.className = "pd-inspector";
|
|
688
|
+
renderKeyValue(inspector, "(root)", sel.state, 0, true, [], hooks.onEditValue ? { onEdit: (path, value) => hooks.onEditValue?.(selected, path, value) } : void 0);
|
|
689
|
+
card2.appendChild(inspector);
|
|
690
|
+
container.appendChild(card2);
|
|
691
|
+
const mine = data.steps.filter((st) => st.storeId === selected);
|
|
692
|
+
const tl = document.createElement("div");
|
|
693
|
+
tl.className = "pd-store-timeline";
|
|
694
|
+
const tlHead = document.createElement("div");
|
|
695
|
+
tlHead.className = "pd-section-head";
|
|
696
|
+
tlHead.textContent = "actions / patches\uFF08" + mine.length + "\uFF09";
|
|
697
|
+
tl.appendChild(tlHead);
|
|
698
|
+
if (mine.length === 0) {
|
|
699
|
+
const empty = document.createElement("div");
|
|
700
|
+
empty.className = "pd-empty";
|
|
701
|
+
empty.textContent = "\u6682\u65E0\u53D8\u66F4";
|
|
702
|
+
tl.appendChild(empty);
|
|
703
|
+
}
|
|
704
|
+
for (let i = mine.length - 1; i >= 0; i--) {
|
|
705
|
+
const st = mine[i];
|
|
706
|
+
const row = document.createElement("div");
|
|
707
|
+
row.className = "pd-tl-row";
|
|
708
|
+
const badge = document.createElement("span");
|
|
709
|
+
const isAction = st.type === "action";
|
|
710
|
+
badge.className = "pd-tl-badge " + (isAction ? "pd-tl-action" : "pd-tl-patch");
|
|
711
|
+
badge.textContent = isAction ? "action" : "patch";
|
|
712
|
+
const name = document.createElement("span");
|
|
713
|
+
name.className = "pd-tl-name";
|
|
714
|
+
name.textContent = String(st.payload?.name ?? "?");
|
|
715
|
+
const meta = document.createElement("span");
|
|
716
|
+
meta.className = "pd-tl-meta";
|
|
717
|
+
meta.textContent = "#" + st.index + " \xB7 " + st.timestamp + "ms";
|
|
718
|
+
row.appendChild(badge);
|
|
719
|
+
row.appendChild(name);
|
|
720
|
+
row.appendChild(meta);
|
|
721
|
+
const diff = diffLines(st.before, st.after);
|
|
722
|
+
if (diff.length > 0) {
|
|
723
|
+
attachTip(row, { title: st.type + " #" + st.index, lines: diff });
|
|
724
|
+
}
|
|
725
|
+
row.addEventListener("click", () => hooks.onTimeTravel?.(st.index));
|
|
726
|
+
tl.appendChild(row);
|
|
727
|
+
}
|
|
728
|
+
container.appendChild(tl);
|
|
729
|
+
}
|
|
730
|
+
if (data.steps.length > 0) {
|
|
731
|
+
const sliderRow = document.createElement("div");
|
|
732
|
+
sliderRow.className = "pd-toolbar";
|
|
733
|
+
const input = document.createElement("input");
|
|
734
|
+
input.type = "range";
|
|
735
|
+
input.min = "0";
|
|
736
|
+
input.max = String(data.steps.length - 1);
|
|
737
|
+
const initial = data.travelIndex !== void 0 && data.travelIndex >= 0 && data.travelIndex <= data.steps.length - 1 ? data.travelIndex : data.steps.length - 1;
|
|
738
|
+
input.value = String(initial);
|
|
739
|
+
input.className = "pd-range";
|
|
740
|
+
const hint = document.createElement("span");
|
|
741
|
+
hint.textContent = "\u56DE\u653E " + (initial + 1) + "/" + data.steps.length;
|
|
742
|
+
input.addEventListener("input", () => {
|
|
743
|
+
const i = Number(input.value);
|
|
744
|
+
hint.textContent = "\u56DE\u653E " + (i + 1) + "/" + data.steps.length;
|
|
745
|
+
});
|
|
746
|
+
input.addEventListener("change", () => {
|
|
747
|
+
hooks.onTimeTravel?.(Number(input.value));
|
|
748
|
+
});
|
|
749
|
+
sliderRow.appendChild(input);
|
|
750
|
+
sliderRow.appendChild(hint);
|
|
751
|
+
container.appendChild(sliderRow);
|
|
752
|
+
}
|
|
753
|
+
if (stores.length === 0) {
|
|
754
|
+
const empty = document.createElement("div");
|
|
755
|
+
empty.className = "pd-empty";
|
|
756
|
+
empty.textContent = "\u6682\u65E0 store\uFF08TraceBus store \u4E8B\u4EF6\u672A\u4E0A\u62A5\uFF09";
|
|
757
|
+
container.appendChild(empty);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/views/route.ts
|
|
762
|
+
function renderRoute(container, data) {
|
|
763
|
+
container.replaceChildren();
|
|
764
|
+
const records = data.records;
|
|
765
|
+
if (records.length === 0) {
|
|
766
|
+
const empty = document.createElement("div");
|
|
767
|
+
empty.className = "pd-empty";
|
|
768
|
+
empty.textContent = "\u6682\u65E0\u5BFC\u822A\u8BB0\u5F55";
|
|
769
|
+
container.appendChild(empty);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
773
|
+
const r = records[i];
|
|
774
|
+
const row = document.createElement("div");
|
|
775
|
+
row.className = "pd-nav";
|
|
776
|
+
const path = document.createElement("div");
|
|
777
|
+
path.className = "pd-nav-path";
|
|
778
|
+
const from = document.createElement("span");
|
|
779
|
+
from.className = "pd-route";
|
|
780
|
+
from.textContent = r.from.path;
|
|
781
|
+
const arrow = document.createElement("span");
|
|
782
|
+
arrow.className = "pd-arrow";
|
|
783
|
+
arrow.textContent = " \u2192 ";
|
|
784
|
+
const to = document.createElement("span");
|
|
785
|
+
to.className = "pd-route";
|
|
786
|
+
to.textContent = r.to.path + (r.to.query ? "?" + JSON.stringify(r.to.query) : "");
|
|
787
|
+
path.appendChild(from);
|
|
788
|
+
path.appendChild(arrow);
|
|
789
|
+
path.appendChild(to);
|
|
790
|
+
const meta = document.createElement("span");
|
|
791
|
+
meta.className = "pd-nav-meta";
|
|
792
|
+
meta.textContent = r.durationMs + "ms" + (r.traceId ? " #" + r.traceId : "");
|
|
793
|
+
path.appendChild(meta);
|
|
794
|
+
row.appendChild(path);
|
|
795
|
+
const guards = document.createElement("div");
|
|
796
|
+
guards.className = "pd-guards";
|
|
797
|
+
for (const g of r.guards) {
|
|
798
|
+
const badge = document.createElement("span");
|
|
799
|
+
badge.className = "pd-guard pd-guard-" + g.result;
|
|
800
|
+
badge.textContent = g.name + " " + g.durationMs + "ms (" + g.result + ")";
|
|
801
|
+
attachTip(badge, {
|
|
802
|
+
title: "\u5B88\u536B " + g.name,
|
|
803
|
+
lines: [`\u7ED3\u679C: ${g.result}`, `\u8017\u65F6 ${g.durationMs}ms`]
|
|
804
|
+
});
|
|
805
|
+
guards.appendChild(badge);
|
|
806
|
+
}
|
|
807
|
+
row.appendChild(guards);
|
|
808
|
+
container.appendChild(row);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/views/errors.ts
|
|
813
|
+
function renderErrors(container, data) {
|
|
814
|
+
container.replaceChildren();
|
|
815
|
+
const reports = data.reports;
|
|
816
|
+
if (reports.length === 0) {
|
|
817
|
+
const empty = document.createElement("div");
|
|
818
|
+
empty.className = "pd-empty";
|
|
819
|
+
empty.textContent = "\u6682\u65E0\u5F02\u5E38\uFF08\u6839\u56E0\u9762\u677F\u7A7A\u95F2\uFF09";
|
|
820
|
+
container.appendChild(empty);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
for (const r of reports) {
|
|
824
|
+
const card2 = document.createElement("div");
|
|
825
|
+
card2.className = "pd-error-card";
|
|
826
|
+
const head = document.createElement("div");
|
|
827
|
+
head.className = "pd-error-head";
|
|
828
|
+
const title = document.createElement("div");
|
|
829
|
+
title.className = "pd-error-title";
|
|
830
|
+
title.textContent = r.rootCause.source + "." + r.rootCause.name + " @ " + r.rootCause.timestamp + "ms";
|
|
831
|
+
head.appendChild(title);
|
|
832
|
+
if (r.attribution) {
|
|
833
|
+
const attr = document.createElement("div");
|
|
834
|
+
attr.className = "pd-error-attr";
|
|
835
|
+
attr.textContent = "\u2691 " + r.attribution;
|
|
836
|
+
head.appendChild(attr);
|
|
837
|
+
}
|
|
838
|
+
card2.appendChild(head);
|
|
839
|
+
const chain = document.createElement("div");
|
|
840
|
+
chain.className = "pd-error-chain";
|
|
841
|
+
for (let i = r.chain.length - 1; i >= 0; i--) {
|
|
842
|
+
const c = r.chain[i];
|
|
843
|
+
const node = document.createElement("div");
|
|
844
|
+
const isRoot = c.source === r.rootCause.source && c.name === r.rootCause.name && c.timestamp === r.rootCause.timestamp;
|
|
845
|
+
node.className = "pd-chain-node" + (isRoot ? " pd-chain-root" : "");
|
|
846
|
+
node.textContent = c.source + "." + c.name;
|
|
847
|
+
chain.appendChild(node);
|
|
848
|
+
if (i > 0) {
|
|
849
|
+
const up = document.createElement("div");
|
|
850
|
+
up.className = "pd-chain-up";
|
|
851
|
+
up.textContent = "\u2191";
|
|
852
|
+
chain.appendChild(up);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
card2.appendChild(chain);
|
|
856
|
+
const impact = document.createElement("div");
|
|
857
|
+
impact.className = "pd-error-impact";
|
|
858
|
+
for (const s of r.impactSources) {
|
|
859
|
+
const chip = document.createElement("span");
|
|
860
|
+
chip.className = "pd-chip";
|
|
861
|
+
chip.textContent = s;
|
|
862
|
+
impact.appendChild(chip);
|
|
863
|
+
}
|
|
864
|
+
card2.appendChild(impact);
|
|
865
|
+
const repro = document.createElement("ol");
|
|
866
|
+
repro.className = "pd-repro";
|
|
867
|
+
for (const step of r.repro) {
|
|
868
|
+
const li = document.createElement("li");
|
|
869
|
+
li.textContent = step;
|
|
870
|
+
repro.appendChild(li);
|
|
871
|
+
}
|
|
872
|
+
card2.appendChild(repro);
|
|
873
|
+
container.appendChild(card2);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// src/views/components.ts
|
|
878
|
+
function buildChildren(nodes) {
|
|
879
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
880
|
+
const alive = new Set(nodes.map((n) => n.id));
|
|
881
|
+
for (const n of nodes) {
|
|
882
|
+
const key = n.parentId !== void 0 && alive.has(n.parentId) ? n.parentId : -1;
|
|
883
|
+
const list = byParent.get(key);
|
|
884
|
+
if (list) list.push(n);
|
|
885
|
+
else byParent.set(key, [n]);
|
|
886
|
+
}
|
|
887
|
+
return byParent;
|
|
888
|
+
}
|
|
889
|
+
function renderDetail(container, node, dom) {
|
|
890
|
+
const detail = document.createElement("div");
|
|
891
|
+
detail.className = "pd-cmp-detail";
|
|
892
|
+
const head = document.createElement("div");
|
|
893
|
+
head.className = "pd-section-head";
|
|
894
|
+
head.textContent = "#" + node.id + " " + node.name + " \xB7 \u8BE6\u60C5";
|
|
895
|
+
detail.appendChild(head);
|
|
896
|
+
const sections = [
|
|
897
|
+
["props", node.props],
|
|
898
|
+
["state", node.state]
|
|
899
|
+
];
|
|
900
|
+
for (const [label, value] of sections) {
|
|
901
|
+
const box = document.createElement("div");
|
|
902
|
+
box.className = "pd-cmp-detail-section";
|
|
903
|
+
const labelEl = document.createElement("div");
|
|
904
|
+
labelEl.className = "pd-cmp-detail-label";
|
|
905
|
+
labelEl.textContent = label;
|
|
906
|
+
box.appendChild(labelEl);
|
|
907
|
+
if (value !== void 0 && value !== null && (typeof value !== "object" || Object.keys(value).length > 0)) {
|
|
908
|
+
renderKeyValue(box, "(root)", value, 0, true);
|
|
909
|
+
} else {
|
|
910
|
+
const empty = document.createElement("div");
|
|
911
|
+
empty.className = "pd-empty";
|
|
912
|
+
empty.textContent = "\u65E0 " + label;
|
|
913
|
+
box.appendChild(empty);
|
|
914
|
+
}
|
|
915
|
+
detail.appendChild(box);
|
|
916
|
+
}
|
|
917
|
+
if (dom) {
|
|
918
|
+
const box = document.createElement("div");
|
|
919
|
+
box.className = "pd-cmp-detail-section";
|
|
920
|
+
const labelEl = document.createElement("div");
|
|
921
|
+
labelEl.className = "pd-cmp-detail-label";
|
|
922
|
+
labelEl.textContent = "DOM";
|
|
923
|
+
box.appendChild(labelEl);
|
|
924
|
+
renderDomTree(box, dom, 0);
|
|
925
|
+
detail.appendChild(box);
|
|
926
|
+
}
|
|
927
|
+
container.appendChild(detail);
|
|
928
|
+
}
|
|
929
|
+
function renderDomTree(container, node, depth) {
|
|
930
|
+
const row = document.createElement("div");
|
|
931
|
+
row.className = "pd-dom-node";
|
|
932
|
+
row.style.paddingLeft = 10 + depth * 14 + "px";
|
|
933
|
+
const tag = document.createElement("span");
|
|
934
|
+
tag.className = "pd-dom-tag";
|
|
935
|
+
tag.textContent = node.tag;
|
|
936
|
+
row.appendChild(tag);
|
|
937
|
+
if (node.id) {
|
|
938
|
+
const idEl = document.createElement("span");
|
|
939
|
+
idEl.className = "pd-dom-id";
|
|
940
|
+
idEl.textContent = "#" + node.id;
|
|
941
|
+
row.appendChild(idEl);
|
|
942
|
+
}
|
|
943
|
+
if (node.cls?.length) {
|
|
944
|
+
const clsEl = document.createElement("span");
|
|
945
|
+
clsEl.className = "pd-dom-cls";
|
|
946
|
+
clsEl.textContent = "." + node.cls.join(".");
|
|
947
|
+
row.appendChild(clsEl);
|
|
948
|
+
}
|
|
949
|
+
container.appendChild(row);
|
|
950
|
+
if (node.children.length) {
|
|
951
|
+
const box = document.createElement("div");
|
|
952
|
+
box.className = "pd-dom-children";
|
|
953
|
+
for (const child of node.children) renderDomTree(box, child, depth + 1);
|
|
954
|
+
container.appendChild(box);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function renderComponents(container, data, hooks = {}) {
|
|
958
|
+
container.replaceChildren();
|
|
959
|
+
const nodes = data.nodes;
|
|
960
|
+
if (nodes.length === 0) {
|
|
961
|
+
const empty = document.createElement("div");
|
|
962
|
+
empty.className = "pd-empty";
|
|
963
|
+
empty.textContent = "\u6682\u65E0\u7EC4\u4EF6\uFF08installComponentTrace \u63A5\u5165\u540E\u51FA\u73B0\uFF09";
|
|
964
|
+
container.appendChild(empty);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const byParent = buildChildren(nodes);
|
|
968
|
+
const selected = data.selectedId !== void 0 && nodes.some((n) => n.id === data.selectedId) ? data.selectedId : void 0;
|
|
969
|
+
function buildRow(n, depth, hasChildren) {
|
|
970
|
+
const row = document.createElement("div");
|
|
971
|
+
row.className = "pd-cmp-row" + (n.id === selected ? " pd-cmp-active" : "");
|
|
972
|
+
row.style.paddingLeft = depth * 16 + 6 + "px";
|
|
973
|
+
const toggle = document.createElement("span");
|
|
974
|
+
toggle.className = "pd-kv-toggle";
|
|
975
|
+
toggle.textContent = hasChildren ? "\u25BE" : "\xB7";
|
|
976
|
+
const name = document.createElement("span");
|
|
977
|
+
name.className = "pd-cmp-name";
|
|
978
|
+
name.textContent = n.name;
|
|
979
|
+
const meta = document.createElement("span");
|
|
980
|
+
meta.className = "pd-cmp-meta";
|
|
981
|
+
meta.textContent = "#" + n.id + (n.count > 1 ? " \xD7" + n.count : "");
|
|
982
|
+
row.appendChild(toggle);
|
|
983
|
+
row.appendChild(name);
|
|
984
|
+
row.appendChild(meta);
|
|
985
|
+
row.addEventListener("click", (e) => {
|
|
986
|
+
if (e.target.closest(".pd-cmp-row") !== row) return;
|
|
987
|
+
hooks.onSelect?.(n.id);
|
|
988
|
+
});
|
|
989
|
+
if (hasChildren) {
|
|
990
|
+
let collapsed = false;
|
|
991
|
+
let subtree = null;
|
|
992
|
+
const sub = document.createElement("div");
|
|
993
|
+
sub.className = "pd-cmp-children";
|
|
994
|
+
subtree = sub;
|
|
995
|
+
const kids = byParent.get(n.id) ?? [];
|
|
996
|
+
for (const k of kids) sub.appendChild(buildRow(k, depth + 1, (byParent.get(k.id)?.length ?? 0) > 0));
|
|
997
|
+
row.appendChild(sub);
|
|
998
|
+
toggle.addEventListener("click", (e) => {
|
|
999
|
+
e.stopPropagation();
|
|
1000
|
+
collapsed = !collapsed;
|
|
1001
|
+
toggle.textContent = collapsed ? "\u25B8" : "\u25BE";
|
|
1002
|
+
if (subtree) subtree.style.display = collapsed ? "none" : "block";
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
return row;
|
|
1006
|
+
}
|
|
1007
|
+
const roots = byParent.get(-1) ?? [];
|
|
1008
|
+
for (const r of roots) {
|
|
1009
|
+
container.appendChild(buildRow(r, 0, (byParent.get(r.id)?.length ?? 0) > 0));
|
|
1010
|
+
}
|
|
1011
|
+
if (selected !== void 0) {
|
|
1012
|
+
const sel = nodes.find((n) => n.id === selected);
|
|
1013
|
+
if (sel) renderDetail(container, sel, data.dom);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/views/pages.ts
|
|
1018
|
+
function renderPages(container, data) {
|
|
1019
|
+
container.replaceChildren();
|
|
1020
|
+
const routes = data.routes;
|
|
1021
|
+
if (routes.length === 0) {
|
|
1022
|
+
const empty = document.createElement("div");
|
|
1023
|
+
empty.className = "pd-empty";
|
|
1024
|
+
empty.textContent = "\u6682\u65E0\u8DEF\u7531\u8868\uFF08Proteus.appInfo \u6CE8\u5165\u540E\u51FA\u73B0\uFF09";
|
|
1025
|
+
container.appendChild(empty);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const stack = data.stack ?? [];
|
|
1029
|
+
if (stack.length) {
|
|
1030
|
+
const stackBox = document.createElement("div");
|
|
1031
|
+
stackBox.className = "pd-page-stack";
|
|
1032
|
+
const head = document.createElement("div");
|
|
1033
|
+
head.className = "pd-section-head";
|
|
1034
|
+
head.textContent = "\u9875\u9762\u6808\uFF08" + stack.length + "\uFF09";
|
|
1035
|
+
stackBox.appendChild(head);
|
|
1036
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
1037
|
+
const route = stack[i]?.route ?? "?";
|
|
1038
|
+
const row = document.createElement("div");
|
|
1039
|
+
row.className = "pd-page-row" + (i === stack.length - 1 ? " pd-page-current" : "");
|
|
1040
|
+
const depth = document.createElement("span");
|
|
1041
|
+
depth.className = "pd-page-depth";
|
|
1042
|
+
depth.textContent = String(stack.length - 1 - i);
|
|
1043
|
+
const name = document.createElement("span");
|
|
1044
|
+
name.className = "pd-page-name";
|
|
1045
|
+
name.textContent = route;
|
|
1046
|
+
row.appendChild(depth);
|
|
1047
|
+
row.appendChild(name);
|
|
1048
|
+
stackBox.appendChild(row);
|
|
1049
|
+
}
|
|
1050
|
+
container.appendChild(stackBox);
|
|
1051
|
+
}
|
|
1052
|
+
const main = routes.filter((r) => !r.subPackage);
|
|
1053
|
+
const subs = [];
|
|
1054
|
+
const subNames = /* @__PURE__ */ new Set();
|
|
1055
|
+
for (const r of routes) if (r.subPackage && !subNames.has(r.subPackage)) subNames.add(r.subPackage);
|
|
1056
|
+
for (const s of subNames) subs.push({ name: s, routes: routes.filter((r) => r.subPackage === s) });
|
|
1057
|
+
function renderGroup(title, list) {
|
|
1058
|
+
const group = document.createElement("div");
|
|
1059
|
+
group.className = "pd-page-group";
|
|
1060
|
+
const head = document.createElement("div");
|
|
1061
|
+
head.className = "pd-section-head";
|
|
1062
|
+
head.textContent = title + "\uFF08" + list.length + "\uFF09";
|
|
1063
|
+
group.appendChild(head);
|
|
1064
|
+
for (const r of list) {
|
|
1065
|
+
const row = document.createElement("div");
|
|
1066
|
+
row.className = "pd-page-row";
|
|
1067
|
+
const name = document.createElement("span");
|
|
1068
|
+
name.className = "pd-page-name";
|
|
1069
|
+
name.textContent = r.name;
|
|
1070
|
+
const meta = document.createElement("span");
|
|
1071
|
+
meta.className = "pd-page-meta";
|
|
1072
|
+
meta.textContent = r.path + (r.meta?.isTab ? " \xB7 tab" : "") + (r.meta?.title ? " \xB7 " + r.meta.title : "");
|
|
1073
|
+
row.appendChild(name);
|
|
1074
|
+
row.appendChild(meta);
|
|
1075
|
+
group.appendChild(row);
|
|
1076
|
+
}
|
|
1077
|
+
return group;
|
|
1078
|
+
}
|
|
1079
|
+
container.appendChild(renderGroup("\u4E3B\u5305\u9875\u9762", main));
|
|
1080
|
+
for (const s of subs) container.appendChild(renderGroup("\u5206\u5305 " + s.name, s.routes));
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/views/graph.ts
|
|
1084
|
+
function buildTree(routes) {
|
|
1085
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1086
|
+
for (const r of routes) byName.set(r.name, r);
|
|
1087
|
+
const roots = [];
|
|
1088
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
1089
|
+
for (const r of routes) nodeMap.set(r.name, { route: r, children: [] });
|
|
1090
|
+
for (const r of routes) {
|
|
1091
|
+
const node = nodeMap.get(r.name);
|
|
1092
|
+
const parent = r.parent !== void 0 && nodeMap.has(r.parent) ? nodeMap.get(r.parent) : void 0;
|
|
1093
|
+
if (parent) parent.children.push(node);
|
|
1094
|
+
else roots.push(node);
|
|
1095
|
+
}
|
|
1096
|
+
return roots;
|
|
1097
|
+
}
|
|
1098
|
+
function buildBranch(node, prefix, isLast, out, depth) {
|
|
1099
|
+
const row = document.createElement("div");
|
|
1100
|
+
row.className = "pd-graph-node" + (depth === 0 ? " pd-graph-root" : "");
|
|
1101
|
+
const line = document.createElement("span");
|
|
1102
|
+
line.className = "pd-graph-line";
|
|
1103
|
+
line.textContent = prefix + (isLast ? "\u2514\u2500 " : "\u251C\u2500 ");
|
|
1104
|
+
const name = document.createElement("span");
|
|
1105
|
+
name.className = "pd-graph-name";
|
|
1106
|
+
name.textContent = node.route.name;
|
|
1107
|
+
const meta = document.createElement("span");
|
|
1108
|
+
meta.className = "pd-graph-meta";
|
|
1109
|
+
const parts = [node.route.path];
|
|
1110
|
+
if (node.route.meta?.isTab) parts.push("tab");
|
|
1111
|
+
if (node.route.subPackage) parts.push("\u5206\u5305 " + node.route.subPackage);
|
|
1112
|
+
meta.textContent = parts.join(" \xB7 ");
|
|
1113
|
+
row.appendChild(line);
|
|
1114
|
+
row.appendChild(name);
|
|
1115
|
+
row.appendChild(meta);
|
|
1116
|
+
out.appendChild(row);
|
|
1117
|
+
const kids = node.children;
|
|
1118
|
+
for (let i = 0; i < kids.length; i++) {
|
|
1119
|
+
buildBranch(kids[i], prefix + (isLast ? " " : "\u2502 "), i === kids.length - 1, out, depth + 1);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function renderGraph(container, data) {
|
|
1123
|
+
container.replaceChildren();
|
|
1124
|
+
const routes = data.routes;
|
|
1125
|
+
if (routes.length === 0) {
|
|
1126
|
+
const empty = document.createElement("div");
|
|
1127
|
+
empty.className = "pd-empty";
|
|
1128
|
+
empty.textContent = "\u6682\u65E0\u8DEF\u7531\u8868\uFF08Proteus.appInfo \u6CE8\u5165\u540E\u51FA\u73B0\uFF09";
|
|
1129
|
+
container.appendChild(empty);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const roots = buildTree(routes);
|
|
1133
|
+
const box = document.createElement("div");
|
|
1134
|
+
box.className = "pd-graph";
|
|
1135
|
+
const head = document.createElement("div");
|
|
1136
|
+
head.className = "pd-section-head";
|
|
1137
|
+
const subCount = new Set(routes.map((r) => r.subPackage).filter((s) => s !== void 0)).size;
|
|
1138
|
+
head.textContent = "\u8DEF\u7531\u4F9D\u8D56\u6811\uFF08" + routes.length + " \u9875" + (subCount ? " \xB7 " + subCount + " \u5206\u5305" : "") + "\uFF09";
|
|
1139
|
+
box.appendChild(head);
|
|
1140
|
+
for (let i = 0; i < roots.length; i++) {
|
|
1141
|
+
buildBranch(roots[i], "", i === roots.length - 1, box, 0);
|
|
1142
|
+
}
|
|
1143
|
+
container.appendChild(box);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/views/device.ts
|
|
1147
|
+
function fmtBytes(b) {
|
|
1148
|
+
if (!b || b <= 0) return "0 B";
|
|
1149
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
1150
|
+
let i = 0;
|
|
1151
|
+
let v = b;
|
|
1152
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
1153
|
+
v /= 1024;
|
|
1154
|
+
i += 1;
|
|
1155
|
+
}
|
|
1156
|
+
return v.toFixed(i === 0 ? 0 : 1) + " " + units[i];
|
|
1157
|
+
}
|
|
1158
|
+
function renderDevice(container, data) {
|
|
1159
|
+
container.replaceChildren();
|
|
1160
|
+
const info = data.info;
|
|
1161
|
+
if (!info) {
|
|
1162
|
+
const empty = document.createElement("div");
|
|
1163
|
+
empty.className = "pd-empty";
|
|
1164
|
+
empty.textContent = "\u6682\u65E0\u8BBE\u5907\u4FE1\u606F\uFF08Proteus.deviceInfo \u6CE8\u5165\u540E\u51FA\u73B0\uFF09";
|
|
1165
|
+
container.appendChild(empty);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const overview = document.createElement("div");
|
|
1169
|
+
overview.className = "pd-dev-overview";
|
|
1170
|
+
const cards = [
|
|
1171
|
+
{ label: "\u5E73\u53F0", value: info.platform || "\u2014" },
|
|
1172
|
+
{ label: "\u57FA\u7840\u5E93", value: info.libVersion || "\u2014" },
|
|
1173
|
+
{ label: "\u5C4F\u5E55", value: info.screen ? info.screen.width + "\xD7" + info.screen.height + " @" + info.screen.dpr + "x" : "\u2014" }
|
|
1174
|
+
];
|
|
1175
|
+
if (info.memory) {
|
|
1176
|
+
const total = info.memory.totalJSHeapSize;
|
|
1177
|
+
const usedPct = total > 0 ? info.memory.usedJSHeapSize / total * 100 : 0;
|
|
1178
|
+
cards.push({ label: "JS \u5806", value: fmtBytes(info.memory.usedJSHeapSize) + " / " + fmtBytes(info.memory.jsHeapLimit), hint: usedPct.toFixed(1) + "% \u5DF2\u7528\uFF08total\uFF09" });
|
|
1179
|
+
}
|
|
1180
|
+
for (const c of cards) {
|
|
1181
|
+
const card2 = document.createElement("div");
|
|
1182
|
+
card2.className = "pd-dev-card";
|
|
1183
|
+
const label = document.createElement("div");
|
|
1184
|
+
label.className = "pd-dev-card-label";
|
|
1185
|
+
label.textContent = c.label;
|
|
1186
|
+
const value = document.createElement("div");
|
|
1187
|
+
value.className = "pd-dev-card-value";
|
|
1188
|
+
value.textContent = c.value;
|
|
1189
|
+
card2.appendChild(label);
|
|
1190
|
+
card2.appendChild(value);
|
|
1191
|
+
if (c.hint) {
|
|
1192
|
+
const hint = document.createElement("div");
|
|
1193
|
+
hint.className = "pd-dev-card-hint";
|
|
1194
|
+
hint.textContent = c.hint;
|
|
1195
|
+
card2.appendChild(hint);
|
|
1196
|
+
}
|
|
1197
|
+
overview.appendChild(card2);
|
|
1198
|
+
}
|
|
1199
|
+
container.appendChild(overview);
|
|
1200
|
+
if (info.userAgent) {
|
|
1201
|
+
const ua = document.createElement("div");
|
|
1202
|
+
ua.className = "pd-dev-ua";
|
|
1203
|
+
ua.textContent = info.userAgent;
|
|
1204
|
+
ua.title = info.userAgent;
|
|
1205
|
+
container.appendChild(ua);
|
|
1206
|
+
}
|
|
1207
|
+
const samples = data.memory;
|
|
1208
|
+
if (samples.length) {
|
|
1209
|
+
const box = document.createElement("div");
|
|
1210
|
+
box.className = "pd-dev-memory";
|
|
1211
|
+
const head = document.createElement("div");
|
|
1212
|
+
head.className = "pd-section-head";
|
|
1213
|
+
head.textContent = "\u5185\u5B58\u66F2\u7EBF\uFF08" + samples.length + " \u91C7\u6837 \xB7 \u9762\u677F\u8FDB\u7A0B\uFF09";
|
|
1214
|
+
box.appendChild(head);
|
|
1215
|
+
const max = Math.max.apply(null, samples.map((s) => s.total)) || 1;
|
|
1216
|
+
const chart = document.createElement("div");
|
|
1217
|
+
chart.className = "pd-dev-mem-chart";
|
|
1218
|
+
for (const s of samples) {
|
|
1219
|
+
const col = document.createElement("div");
|
|
1220
|
+
col.className = "pd-dev-mem-col";
|
|
1221
|
+
const usedH = Math.round(s.used / max * 100);
|
|
1222
|
+
const used = document.createElement("div");
|
|
1223
|
+
used.className = "pd-dev-mem-used";
|
|
1224
|
+
used.style.height = Math.max(1, usedH) + "%";
|
|
1225
|
+
used.title = "used " + fmtBytes(s.used);
|
|
1226
|
+
const total = document.createElement("div");
|
|
1227
|
+
total.className = "pd-dev-mem-total";
|
|
1228
|
+
total.style.height = Math.round(s.total / max * 100) + "%";
|
|
1229
|
+
total.title = "total " + fmtBytes(s.total);
|
|
1230
|
+
col.appendChild(total);
|
|
1231
|
+
col.appendChild(used);
|
|
1232
|
+
chart.appendChild(col);
|
|
1233
|
+
}
|
|
1234
|
+
box.appendChild(chart);
|
|
1235
|
+
const latest = samples[samples.length - 1];
|
|
1236
|
+
const stat = document.createElement("div");
|
|
1237
|
+
stat.className = "pd-dev-mem-stat";
|
|
1238
|
+
stat.textContent = "used " + fmtBytes(latest.used) + " / total " + fmtBytes(latest.total) + " / limit " + fmtBytes(latest.limit);
|
|
1239
|
+
box.appendChild(stat);
|
|
1240
|
+
container.appendChild(box);
|
|
1241
|
+
}
|
|
1242
|
+
const caps = Array.isArray(info.capabilities) ? info.capabilities : [];
|
|
1243
|
+
if (caps.length) {
|
|
1244
|
+
const table = document.createElement("div");
|
|
1245
|
+
table.className = "pd-dev-caps";
|
|
1246
|
+
const head = document.createElement("div");
|
|
1247
|
+
head.className = "pd-section-head";
|
|
1248
|
+
head.textContent = "\u80FD\u529B\uFF08" + caps.length + " \xB7 " + info.platform + "\uFF09";
|
|
1249
|
+
table.appendChild(head);
|
|
1250
|
+
for (const c of caps) {
|
|
1251
|
+
const row = document.createElement("div");
|
|
1252
|
+
row.className = "pd-dev-cap" + (c.supported ? " pd-dev-cap-ok" : " pd-dev-cap-no");
|
|
1253
|
+
const mark = document.createElement("span");
|
|
1254
|
+
mark.className = "pd-dev-cap-mark";
|
|
1255
|
+
mark.textContent = c.supported ? "\u2705" : "\u274C";
|
|
1256
|
+
const name = document.createElement("span");
|
|
1257
|
+
name.className = "pd-dev-cap-name";
|
|
1258
|
+
name.textContent = c.capability;
|
|
1259
|
+
const meta = document.createElement("span");
|
|
1260
|
+
meta.className = "pd-dev-cap-meta";
|
|
1261
|
+
const parts = [];
|
|
1262
|
+
parts.push(c.platforms.join("/"));
|
|
1263
|
+
if (c.required) parts.push("required");
|
|
1264
|
+
if (c.runsInWorklet) parts.push("worklet");
|
|
1265
|
+
if (c.fallback) parts.push("\u2192 " + c.fallback);
|
|
1266
|
+
if (c.priority) parts.push("p" + c.priority);
|
|
1267
|
+
meta.textContent = parts.join(" \xB7 ");
|
|
1268
|
+
row.appendChild(mark);
|
|
1269
|
+
row.appendChild(name);
|
|
1270
|
+
row.appendChild(meta);
|
|
1271
|
+
table.appendChild(row);
|
|
1272
|
+
}
|
|
1273
|
+
container.appendChild(table);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// src/views/ownership.ts
|
|
1278
|
+
function fmtBytes2(n) {
|
|
1279
|
+
if (n >= 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + " MB";
|
|
1280
|
+
if (n >= 1024) return (n / 1024).toFixed(1) + " KB";
|
|
1281
|
+
return n + " B";
|
|
1282
|
+
}
|
|
1283
|
+
function card(parent, cls, title) {
|
|
1284
|
+
const el = document.createElement("div");
|
|
1285
|
+
el.className = cls;
|
|
1286
|
+
const t = document.createElement("div");
|
|
1287
|
+
t.className = "pd-own-card-title";
|
|
1288
|
+
t.textContent = title;
|
|
1289
|
+
el.appendChild(t);
|
|
1290
|
+
parent.appendChild(el);
|
|
1291
|
+
return el;
|
|
1292
|
+
}
|
|
1293
|
+
function renderOwnership(container, data) {
|
|
1294
|
+
container.replaceChildren();
|
|
1295
|
+
container.classList.add("pd-ownership");
|
|
1296
|
+
if (!data || !data.summary) {
|
|
1297
|
+
const empty = document.createElement("div");
|
|
1298
|
+
empty.className = "pd-empty";
|
|
1299
|
+
empty.textContent = "\u6682\u65E0\u6240\u6709\u6743\u6570\u636E\uFF08installProteusDevtools({ ownership: true }) \u542F\u7528\uFF0C\u6216\u4E1A\u52A1\u5728\u521B\u5EFA OwnershipGraph \u65F6\u6302\u63A5\uFF09";
|
|
1300
|
+
container.appendChild(empty);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
const summary = card(container, "pd-own-summary", "\u6240\u6709\u6743\u6982\u8981");
|
|
1304
|
+
const stats = document.createElement("div");
|
|
1305
|
+
stats.className = "pd-own-stats";
|
|
1306
|
+
stats.textContent = `${data.summary.alive} alive / ${data.summary.total} total \xB7 ${fmtBytes2(data.summary.bytesAlive)}`;
|
|
1307
|
+
summary.appendChild(stats);
|
|
1308
|
+
const types = Object.entries(data.summary.byType);
|
|
1309
|
+
if (types.length > 0) {
|
|
1310
|
+
const typeLine = document.createElement("div");
|
|
1311
|
+
typeLine.className = "pd-own-types";
|
|
1312
|
+
typeLine.textContent = types.map(([type, c]) => `${type}: ${c.alive}/${c.allocated}`).join(" \xB7 ");
|
|
1313
|
+
summary.appendChild(typeLine);
|
|
1314
|
+
}
|
|
1315
|
+
const d = data.diagnosis;
|
|
1316
|
+
const alerts = card(container, "pd-own-alerts", "\u68C0\u6D4B");
|
|
1317
|
+
const alertCount = d.orphans.length + d.leaks.length + d.longBorrows.length + d.crossPageRefs.length;
|
|
1318
|
+
if (alertCount === 0) {
|
|
1319
|
+
const ok = document.createElement("div");
|
|
1320
|
+
ok.className = "pd-own-ok";
|
|
1321
|
+
ok.textContent = "\u2705 \u65E0\u5F02\u5E38\uFF08\u65E0\u65E0\u4E3B\u8D44\u6E90 / \u6CC4\u6F0F\u8DEF\u5F84 / \u957F\u671F\u501F\u7528 / \u8DE8\u9875\u5F3A\u5F15\u7528\uFF09";
|
|
1322
|
+
alerts.appendChild(ok);
|
|
1323
|
+
} else {
|
|
1324
|
+
for (const o of d.orphans) {
|
|
1325
|
+
const row = document.createElement("div");
|
|
1326
|
+
row.className = "pd-own-alert pd-own-alert-orphan";
|
|
1327
|
+
row.textContent = `\u{1F534} \u65E0\u4E3B\u8D44\u6E90 ${o.id}\uFF08${o.type}\uFF0C${fmtBytes2(o.byteSize)}\uFF09${o.sourceLocation ? "\u{1F4CD} " + o.sourceLocation : ""}\u2014\u2014\u5FC5\u7136\u6CC4\u6F0F\uFF08\u6846\u67B6 bug \u6216\u672A\u767B\u8BB0\uFF09`;
|
|
1328
|
+
alerts.appendChild(row);
|
|
1329
|
+
}
|
|
1330
|
+
for (const l of d.leaks) {
|
|
1331
|
+
const row = document.createElement("div");
|
|
1332
|
+
row.className = "pd-own-alert pd-own-alert-leak";
|
|
1333
|
+
row.textContent = `\u26A0\uFE0F \u6CC4\u6F0F\u8DEF\u5F84 ${l.resourceId}\uFF08${l.type}\uFF0C${fmtBytes2(l.byteSize)}\uFF09${l.sourceLocation ? "\u{1F4CD} " + l.sourceLocation : ""}`;
|
|
1334
|
+
alerts.appendChild(row);
|
|
1335
|
+
for (const hop of l.referenceChain) {
|
|
1336
|
+
const chain = document.createElement("div");
|
|
1337
|
+
chain.className = "pd-own-chain";
|
|
1338
|
+
chain.textContent = " " + hop;
|
|
1339
|
+
alerts.appendChild(chain);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
for (const b of d.longBorrows) {
|
|
1343
|
+
const row = document.createElement("div");
|
|
1344
|
+
row.className = "pd-own-alert pd-own-alert-longborrow";
|
|
1345
|
+
row.textContent = `\u{1F7E1} \u957F\u671F\u501F\u7528 ${b.resourceId} \u2190 borrowed by ${b.borrowedBy}\uFF08owner ${b.owner ?? "\u65E0\u4E3B"}\uFF09`;
|
|
1346
|
+
alerts.appendChild(row);
|
|
1347
|
+
}
|
|
1348
|
+
for (const c of d.crossPageRefs) {
|
|
1349
|
+
const row = document.createElement("div");
|
|
1350
|
+
row.className = "pd-own-alert pd-own-alert-crosspage";
|
|
1351
|
+
row.textContent = `\u26A0\uFE0F \u8DE8\u9875\u5F3A\u5F15\u7528 ${c.resourceId}\uFF08owner ${c.owner ?? "\u65E0\u4E3B"}\uFF09\u2190 \u5F3A\u6301\u6709 by ${c.heldBy}\u2014\u2014\u5E94 transferTo/weak`;
|
|
1352
|
+
alerts.appendChild(row);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const resCard = card(container, "pd-own-resources", "\u8D44\u6E90\uFF08\u6309 owner\uFF09");
|
|
1356
|
+
if (data.resources.length === 0) {
|
|
1357
|
+
const empty = document.createElement("div");
|
|
1358
|
+
empty.className = "pd-empty";
|
|
1359
|
+
empty.textContent = "\u65E0\u5B58\u6D3B\u8D44\u6E90";
|
|
1360
|
+
resCard.appendChild(empty);
|
|
1361
|
+
}
|
|
1362
|
+
for (const group of data.resources) {
|
|
1363
|
+
const ownerRow = document.createElement("div");
|
|
1364
|
+
ownerRow.className = "pd-own-owner";
|
|
1365
|
+
const bytes = group.items.reduce((s, i) => s + i.byteSize, 0);
|
|
1366
|
+
ownerRow.textContent = `\u25BC ${group.owner}\uFF08${group.items.length} \u8D44\u6E90\uFF0C${fmtBytes2(bytes)}\uFF09`;
|
|
1367
|
+
resCard.appendChild(ownerRow);
|
|
1368
|
+
for (const item of group.items) {
|
|
1369
|
+
const row = document.createElement("div");
|
|
1370
|
+
row.className = "pd-own-resource";
|
|
1371
|
+
const stateMark = group.owner === "\uFF08\u65E0\u4E3B\uFF09" ? "\u{1F534}" : "\u{1F7E2}";
|
|
1372
|
+
const borrowInfo = item.borrowedBy.length > 0 ? ` \u2190 \u{1F7E1} ${item.borrowedBy.join(", ")}` : "";
|
|
1373
|
+
row.textContent = ` ${stateMark} ${item.type} ${fmtBytes2(item.byteSize)}${item.sourceLocation ? " \u{1F4CD} " + item.sourceLocation : ""}${borrowInfo}`;
|
|
1374
|
+
resCard.appendChild(row);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const tlCard = card(container, "pd-own-timeline", `\u65F6\u95F4\u7EBF${data.timeline.truncated ? `\uFF08\u8FD1 ${data.timeline.events.length} \u6761\uFF09` : ""}`);
|
|
1378
|
+
if (data.timeline.events.length === 0) {
|
|
1379
|
+
const empty = document.createElement("div");
|
|
1380
|
+
empty.className = "pd-empty";
|
|
1381
|
+
empty.textContent = "\u6682\u65E0 alloc/drop \u8BB0\u5F55";
|
|
1382
|
+
tlCard.appendChild(empty);
|
|
1383
|
+
}
|
|
1384
|
+
for (const e of data.timeline.events) {
|
|
1385
|
+
const row = document.createElement("div");
|
|
1386
|
+
if (e.kind === "alloc") {
|
|
1387
|
+
const unpaired = data.timeline.unpairedIds.includes(e.id ?? "");
|
|
1388
|
+
row.className = "pd-own-tl" + (unpaired ? " pd-own-tl-unpaired" : "");
|
|
1389
|
+
row.textContent = `\u2191 alloc ${e.type ?? ""} ${fmtBytes2(e.byteSize ?? 0)}${e.sourceLocation ? " \u{1F4CD} " + e.sourceLocation : ""}${unpaired ? " \u26A0\uFE0F \u672A\u914D\u5BF9\uFF08\u53EF\u7591\uFF09" : ""}`;
|
|
1390
|
+
} else if (e.kind === "drop") {
|
|
1391
|
+
row.className = "pd-own-tl";
|
|
1392
|
+
row.textContent = `\u2193 drop ${e.type ?? ""} ${fmtBytes2(e.byteSize ?? 0)}${e.matchedAllocId ? `\uFF08\u2194 ${e.matchedAllocId}\uFF09` : ""}`;
|
|
1393
|
+
} else if (e.kind === "moved") {
|
|
1394
|
+
row.className = "pd-own-tl";
|
|
1395
|
+
row.textContent = `\u2192 moved ${e.type ?? ""}\uFF08owner ${e.owner ?? "\u2014"}\uFF09`;
|
|
1396
|
+
} else {
|
|
1397
|
+
row.className = "pd-own-tl";
|
|
1398
|
+
row.textContent = `${e.kind} ${e.from ?? ""} \u2192 ${e.to ?? ""}`;
|
|
1399
|
+
}
|
|
1400
|
+
tlCard.appendChild(row);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// src/snapshot-io.ts
|
|
1405
|
+
var SENSITIVE_PATTERNS = ["password", "token", "authorization", "idcard", "phone"];
|
|
1406
|
+
function isSensitiveKey(key) {
|
|
1407
|
+
const k = key.toLowerCase();
|
|
1408
|
+
for (let i = 0; i < SENSITIVE_PATTERNS.length; i++) {
|
|
1409
|
+
if (k.indexOf(SENSITIVE_PATTERNS[i]) >= 0) return true;
|
|
1410
|
+
}
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
function findSensitiveKeys(stores) {
|
|
1414
|
+
const out = [];
|
|
1415
|
+
const walk = (value) => {
|
|
1416
|
+
if (value === null || typeof value !== "object") return [];
|
|
1417
|
+
const hits = [];
|
|
1418
|
+
const obj = value;
|
|
1419
|
+
for (const k of Object.keys(obj)) {
|
|
1420
|
+
if (isSensitiveKey(k)) hits.push(k);
|
|
1421
|
+
hits.push.apply(hits, walk(obj[k]));
|
|
1422
|
+
}
|
|
1423
|
+
return hits;
|
|
1424
|
+
};
|
|
1425
|
+
for (const s of stores) {
|
|
1426
|
+
const keys = walk(s.state);
|
|
1427
|
+
if (keys.length) out.push({ storeId: s.id, keys: Array.from(new Set(keys)) });
|
|
1428
|
+
}
|
|
1429
|
+
return out;
|
|
1430
|
+
}
|
|
1431
|
+
function serializeStoreSnapshot(input) {
|
|
1432
|
+
const data = {
|
|
1433
|
+
kind: "proteus-store-snapshot",
|
|
1434
|
+
version: 1,
|
|
1435
|
+
exportedAt: Date.now(),
|
|
1436
|
+
stores: input.stores,
|
|
1437
|
+
steps: input.steps
|
|
1438
|
+
};
|
|
1439
|
+
return JSON.stringify(data, null, 2);
|
|
1440
|
+
}
|
|
1441
|
+
function parseStoreSnapshot(json) {
|
|
1442
|
+
let data;
|
|
1443
|
+
try {
|
|
1444
|
+
data = JSON.parse(json);
|
|
1445
|
+
} catch {
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
if (data === null || typeof data !== "object") return null;
|
|
1449
|
+
const d = data;
|
|
1450
|
+
if (d.kind !== "proteus-store-snapshot" || !Array.isArray(d.stores)) return null;
|
|
1451
|
+
const stores = [];
|
|
1452
|
+
for (const s of d.stores) {
|
|
1453
|
+
if (s && typeof s.id === "string" && s.state !== null && typeof s.state === "object" && !Array.isArray(s.state)) {
|
|
1454
|
+
stores.push({ id: s.id, state: s.state });
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
const steps = [];
|
|
1458
|
+
if (Array.isArray(d.steps)) {
|
|
1459
|
+
for (const st of d.steps) {
|
|
1460
|
+
if (st && typeof st.index === "number" && typeof st.storeId === "string" && (st.type === "patch" || st.type === "action")) {
|
|
1461
|
+
steps.push({
|
|
1462
|
+
index: st.index,
|
|
1463
|
+
storeId: st.storeId,
|
|
1464
|
+
type: st.type,
|
|
1465
|
+
name: typeof st.name === "string" ? st.name : st.type,
|
|
1466
|
+
payload: st.payload,
|
|
1467
|
+
timestamp: typeof st.timestamp === "number" ? st.timestamp : 0
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return { stores, steps };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// src/session-io.ts
|
|
1476
|
+
var VALID_SOURCES = ["lifecycle", "router", "store", "api", "capability", "compiler", "component", "hmr"];
|
|
1477
|
+
var VALID_PHASES = ["start", "end", "point", "error"];
|
|
1478
|
+
function serializeSession(input) {
|
|
1479
|
+
const bundle = {
|
|
1480
|
+
kind: "proteus-session",
|
|
1481
|
+
version: 1,
|
|
1482
|
+
exportedAt: Date.now(),
|
|
1483
|
+
meta: { eventCount: input.events.length },
|
|
1484
|
+
events: input.events,
|
|
1485
|
+
device: input.device,
|
|
1486
|
+
stores: input.stores,
|
|
1487
|
+
steps: input.steps
|
|
1488
|
+
};
|
|
1489
|
+
return JSON.stringify(bundle, null, 2);
|
|
1490
|
+
}
|
|
1491
|
+
function parseSession(json) {
|
|
1492
|
+
let data;
|
|
1493
|
+
try {
|
|
1494
|
+
data = JSON.parse(json);
|
|
1495
|
+
} catch {
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
if (data === null || typeof data !== "object") return null;
|
|
1499
|
+
const d = data;
|
|
1500
|
+
if (d.kind !== "proteus-session" || d.version !== 1 || !Array.isArray(d.events)) return null;
|
|
1501
|
+
const events = [];
|
|
1502
|
+
for (const e of d.events) {
|
|
1503
|
+
if (!e || typeof e !== "object") continue;
|
|
1504
|
+
const ev = e;
|
|
1505
|
+
if (typeof ev.source !== "string" || VALID_SOURCES.indexOf(ev.source) < 0) continue;
|
|
1506
|
+
if (typeof ev.phase !== "string" || VALID_PHASES.indexOf(ev.phase) < 0) continue;
|
|
1507
|
+
if (typeof ev.name !== "string" || !ev.name) continue;
|
|
1508
|
+
events.push({
|
|
1509
|
+
source: ev.source,
|
|
1510
|
+
phase: ev.phase,
|
|
1511
|
+
name: ev.name,
|
|
1512
|
+
payload: ev.payload,
|
|
1513
|
+
timestamp: typeof ev.timestamp === "number" ? ev.timestamp : Date.now(),
|
|
1514
|
+
traceId: typeof ev.traceId === "string" ? ev.traceId : void 0
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
const stores = [];
|
|
1518
|
+
if (Array.isArray(d.stores)) {
|
|
1519
|
+
for (const s of d.stores) {
|
|
1520
|
+
if (s && typeof s.id === "string" && s.state !== null && typeof s.state === "object" && !Array.isArray(s.state)) {
|
|
1521
|
+
stores.push({ id: s.id, state: s.state });
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
const steps = [];
|
|
1526
|
+
if (Array.isArray(d.steps)) {
|
|
1527
|
+
for (const st of d.steps) {
|
|
1528
|
+
if (st && typeof st.index === "number" && typeof st.storeId === "string" && (st.type === "patch" || st.type === "action")) {
|
|
1529
|
+
steps.push({
|
|
1530
|
+
index: st.index,
|
|
1531
|
+
storeId: st.storeId,
|
|
1532
|
+
type: st.type,
|
|
1533
|
+
name: typeof st.name === "string" ? st.name : st.type,
|
|
1534
|
+
payload: st.payload,
|
|
1535
|
+
timestamp: typeof st.timestamp === "number" ? st.timestamp : 0
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return { events, device: d.device, stores, steps };
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/plugins.ts
|
|
1544
|
+
function createMemoryStorage() {
|
|
1545
|
+
const map = /* @__PURE__ */ new Map();
|
|
1546
|
+
return {
|
|
1547
|
+
get: (key) => map.get(key),
|
|
1548
|
+
set: (key, value) => {
|
|
1549
|
+
map.set(key, value);
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
function createCommandRegistry() {
|
|
1554
|
+
const commands = /* @__PURE__ */ new Map();
|
|
1555
|
+
return {
|
|
1556
|
+
register(id, run) {
|
|
1557
|
+
commands.set(id, run);
|
|
1558
|
+
},
|
|
1559
|
+
run(id) {
|
|
1560
|
+
commands.get(id)?.();
|
|
1561
|
+
},
|
|
1562
|
+
list() {
|
|
1563
|
+
return Array.from(commands.keys());
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
function resolveActivationOrder(plugins) {
|
|
1568
|
+
const byName = new Map(plugins.map((p) => [p.name, p]));
|
|
1569
|
+
const deps = /* @__PURE__ */ new Map();
|
|
1570
|
+
for (const p of plugins) deps.set(p.name, (p.peerDependencies ?? []).filter((d) => byName.has(d)));
|
|
1571
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
1572
|
+
for (const [name, ds] of deps) indegree.set(name, ds.length);
|
|
1573
|
+
const queue = [];
|
|
1574
|
+
for (const [name, d] of indegree) if (d === 0) queue.push(name);
|
|
1575
|
+
const order = [];
|
|
1576
|
+
while (queue.length) {
|
|
1577
|
+
const name = queue.shift();
|
|
1578
|
+
order.push(name);
|
|
1579
|
+
for (const other of deps.keys()) {
|
|
1580
|
+
const list = deps.get(other);
|
|
1581
|
+
if (list.includes(name)) {
|
|
1582
|
+
const nd = indegree.get(other) - 1;
|
|
1583
|
+
indegree.set(other, nd);
|
|
1584
|
+
if (nd === 0) queue.push(other);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
const remaining = plugins.map((p) => p.name).filter((n) => !order.includes(n));
|
|
1589
|
+
if (remaining.length) {
|
|
1590
|
+
const start = remaining[0];
|
|
1591
|
+
const cyclePath = [];
|
|
1592
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1593
|
+
let cur = start;
|
|
1594
|
+
while (cur && !seen.has(cur)) {
|
|
1595
|
+
seen.add(cur);
|
|
1596
|
+
cyclePath.push(cur);
|
|
1597
|
+
cur = (deps.get(cur) ?? []).find((d) => remaining.includes(d)) ?? null;
|
|
1598
|
+
}
|
|
1599
|
+
if (cur) cyclePath.push(cur);
|
|
1600
|
+
return { order, cycle: cyclePath };
|
|
1601
|
+
}
|
|
1602
|
+
return { order, cycle: null };
|
|
1603
|
+
}
|
|
1604
|
+
function createPluginRegistry(initial = []) {
|
|
1605
|
+
const plugins = /* @__PURE__ */ new Map();
|
|
1606
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
1607
|
+
const errors = /* @__PURE__ */ new Map();
|
|
1608
|
+
for (const p of initial) {
|
|
1609
|
+
plugins.set(p.name, p);
|
|
1610
|
+
statuses.set(p.name, "registered");
|
|
1611
|
+
}
|
|
1612
|
+
return {
|
|
1613
|
+
register(plugin) {
|
|
1614
|
+
plugins.set(plugin.name, plugin);
|
|
1615
|
+
statuses.set(plugin.name, "registered");
|
|
1616
|
+
},
|
|
1617
|
+
unregister(name) {
|
|
1618
|
+
plugins.delete(name);
|
|
1619
|
+
statuses.delete(name);
|
|
1620
|
+
errors.delete(name);
|
|
1621
|
+
},
|
|
1622
|
+
async activateAll(ctx) {
|
|
1623
|
+
const all = Array.from(plugins.values());
|
|
1624
|
+
const { order, cycle } = resolveActivationOrder(all);
|
|
1625
|
+
if (cycle) throw new Error("\u63D2\u4EF6\u5FAA\u73AF\u4F9D\u8D56\uFF1A" + cycle.join(" \u2192 ") + "\uFF08\u8BF7\u89E3\u9664\u4F9D\u8D56\u540E\u518D\u6FC0\u6D3B\uFF09");
|
|
1626
|
+
for (const name of order) {
|
|
1627
|
+
const plugin = plugins.get(name);
|
|
1628
|
+
try {
|
|
1629
|
+
await plugin.setup(ctx(plugin));
|
|
1630
|
+
statuses.set(name, "active");
|
|
1631
|
+
errors.delete(name);
|
|
1632
|
+
} catch (err) {
|
|
1633
|
+
statuses.set(name, "crashed");
|
|
1634
|
+
errors.set(name, err instanceof Error ? err.message : String(err));
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return Array.from(plugins.keys()).map((name) => ({
|
|
1638
|
+
name,
|
|
1639
|
+
version: plugins.get(name).version,
|
|
1640
|
+
status: statuses.get(name) ?? "registered",
|
|
1641
|
+
error: errors.get(name)
|
|
1642
|
+
}));
|
|
1643
|
+
},
|
|
1644
|
+
list() {
|
|
1645
|
+
return Array.from(plugins.keys()).map((name) => ({
|
|
1646
|
+
name,
|
|
1647
|
+
version: plugins.get(name).version,
|
|
1648
|
+
status: statuses.get(name) ?? "registered",
|
|
1649
|
+
error: errors.get(name)
|
|
1650
|
+
}));
|
|
1651
|
+
},
|
|
1652
|
+
get: (name) => plugins.get(name)
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// src/panel.ts
|
|
1657
|
+
var VIEWS = ["timeline", "flamegraph", "state", "route", "errors", "components", "pages", "graph", "device", "ownership"];
|
|
1658
|
+
var VIEW_ICONS = {
|
|
1659
|
+
timeline: "\u229E",
|
|
1660
|
+
flamegraph: "\u25A4",
|
|
1661
|
+
state: "\u2630",
|
|
1662
|
+
route: "\u21C4",
|
|
1663
|
+
errors: "\u2715",
|
|
1664
|
+
components: "\u25EB",
|
|
1665
|
+
pages: "\u25A6",
|
|
1666
|
+
graph: "\u232C",
|
|
1667
|
+
device: "\u2699",
|
|
1668
|
+
ownership: "\u2B21"
|
|
1669
|
+
};
|
|
1670
|
+
function createDevtoolsPanel(root, options) {
|
|
1671
|
+
const { source, onTimeTravel } = options;
|
|
1672
|
+
root.classList.add("pd-panel");
|
|
1673
|
+
root.replaceChildren();
|
|
1674
|
+
const header = document.createElement("div");
|
|
1675
|
+
header.className = "pd-header";
|
|
1676
|
+
const title = document.createElement("div");
|
|
1677
|
+
title.className = "pd-header-title";
|
|
1678
|
+
title.textContent = "Proteus DevTools";
|
|
1679
|
+
header.appendChild(title);
|
|
1680
|
+
const status = document.createElement("div");
|
|
1681
|
+
status.className = "pd-header-status";
|
|
1682
|
+
const dot = document.createElement("span");
|
|
1683
|
+
dot.className = "pd-dot";
|
|
1684
|
+
const statusText = document.createElement("span");
|
|
1685
|
+
statusText.textContent = "\u8FDE\u63A5\u4E2D";
|
|
1686
|
+
status.appendChild(dot);
|
|
1687
|
+
status.appendChild(statusText);
|
|
1688
|
+
header.appendChild(status);
|
|
1689
|
+
root.appendChild(header);
|
|
1690
|
+
const bodyRow = document.createElement("div");
|
|
1691
|
+
bodyRow.className = "pd-body-row";
|
|
1692
|
+
const sidebar = document.createElement("div");
|
|
1693
|
+
sidebar.className = "pd-sidebar";
|
|
1694
|
+
const content = document.createElement("div");
|
|
1695
|
+
content.className = "pd-content";
|
|
1696
|
+
bodyRow.appendChild(sidebar);
|
|
1697
|
+
bodyRow.appendChild(content);
|
|
1698
|
+
root.appendChild(bodyRow);
|
|
1699
|
+
const tooltip = createTooltipLayer();
|
|
1700
|
+
const unbindTip = bindTooltip(root, tooltip, (target) => resolveTipData(target));
|
|
1701
|
+
const views = /* @__PURE__ */ new Map();
|
|
1702
|
+
const containers = /* @__PURE__ */ new Map();
|
|
1703
|
+
const pluginViews = /* @__PURE__ */ new Map();
|
|
1704
|
+
const pluginSubs = [];
|
|
1705
|
+
const crashedPlugins = /* @__PURE__ */ new Map();
|
|
1706
|
+
for (const v of VIEWS) {
|
|
1707
|
+
const item = document.createElement("div");
|
|
1708
|
+
item.className = "pd-nav-item";
|
|
1709
|
+
item.dataset.view = v;
|
|
1710
|
+
const icon = document.createElement("span");
|
|
1711
|
+
icon.className = "pd-nav-icon";
|
|
1712
|
+
icon.textContent = VIEW_ICONS[v];
|
|
1713
|
+
const label = document.createElement("span");
|
|
1714
|
+
label.textContent = v;
|
|
1715
|
+
item.appendChild(icon);
|
|
1716
|
+
item.appendChild(label);
|
|
1717
|
+
sidebar.appendChild(item);
|
|
1718
|
+
const container = document.createElement("div");
|
|
1719
|
+
container.className = "pd-view";
|
|
1720
|
+
container.dataset.view = v;
|
|
1721
|
+
views.set(v, container);
|
|
1722
|
+
containers.set(v, container);
|
|
1723
|
+
content.appendChild(container);
|
|
1724
|
+
}
|
|
1725
|
+
function registerView(id, label, icon, render, pluginName) {
|
|
1726
|
+
if (views.has(id)) return;
|
|
1727
|
+
const item = document.createElement("div");
|
|
1728
|
+
item.className = "pd-nav-item";
|
|
1729
|
+
item.dataset.view = id;
|
|
1730
|
+
const ic = document.createElement("span");
|
|
1731
|
+
ic.className = "pd-nav-icon";
|
|
1732
|
+
ic.textContent = icon ?? "\u25B8";
|
|
1733
|
+
const lb = document.createElement("span");
|
|
1734
|
+
lb.textContent = label;
|
|
1735
|
+
item.appendChild(ic);
|
|
1736
|
+
item.appendChild(lb);
|
|
1737
|
+
sidebar.appendChild(item);
|
|
1738
|
+
const container = document.createElement("div");
|
|
1739
|
+
container.className = "pd-view";
|
|
1740
|
+
container.dataset.view = id;
|
|
1741
|
+
views.set(id, container);
|
|
1742
|
+
containers.set(id, container);
|
|
1743
|
+
pluginViews.set(id, { name: pluginName, render });
|
|
1744
|
+
content.appendChild(container);
|
|
1745
|
+
}
|
|
1746
|
+
const storage = options.storage ?? createMemoryStorage();
|
|
1747
|
+
const commands = createCommandRegistry();
|
|
1748
|
+
const registry = createPluginRegistry(options.plugins ?? []);
|
|
1749
|
+
function broadcastToPlugins(e) {
|
|
1750
|
+
for (const s of pluginSubs.slice()) s.cb(e);
|
|
1751
|
+
}
|
|
1752
|
+
function crashPlugin(name, err) {
|
|
1753
|
+
crashedPlugins.set(name, err instanceof Error ? err.message : String(err));
|
|
1754
|
+
for (let i = pluginSubs.length - 1; i >= 0; i--) {
|
|
1755
|
+
if (pluginSubs[i].name === name) pluginSubs.splice(i, 1);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
const zoom = createTimelineZoom(containers.get("timeline"), () => timeline.spans(), {
|
|
1759
|
+
onWindowChange: () => scheduleRender()
|
|
1760
|
+
});
|
|
1761
|
+
const timelineView = containers.get("timeline");
|
|
1762
|
+
let tlScrollTop = 0;
|
|
1763
|
+
let tlViewHeight = 300;
|
|
1764
|
+
const onTimelineScroll = () => {
|
|
1765
|
+
tlScrollTop = timelineView.scrollTop;
|
|
1766
|
+
scheduleRender();
|
|
1767
|
+
};
|
|
1768
|
+
timelineView.addEventListener("scroll", onTimelineScroll);
|
|
1769
|
+
function show(view) {
|
|
1770
|
+
for (const [k, el] of views) el.classList.toggle("pd-view-active", k === view);
|
|
1771
|
+
for (const el of Array.from(sidebar.children)) el.classList.toggle("pd-nav-active", el.dataset.view === view);
|
|
1772
|
+
if (view === "device") startMemorySampling();
|
|
1773
|
+
else stopMemorySampling();
|
|
1774
|
+
}
|
|
1775
|
+
sidebar.addEventListener("click", (e) => {
|
|
1776
|
+
const t = e.target.closest(".pd-nav-item");
|
|
1777
|
+
if (t?.dataset.view) show(t.dataset.view);
|
|
1778
|
+
});
|
|
1779
|
+
const timeline = createTimelineCollector();
|
|
1780
|
+
const flame = createFlamegraphCollector();
|
|
1781
|
+
const errors = createErrorDiagnoser();
|
|
1782
|
+
const navs = [];
|
|
1783
|
+
const inflightNav = /* @__PURE__ */ new Map();
|
|
1784
|
+
const storeSnapshots = /* @__PURE__ */ new Map();
|
|
1785
|
+
const storePatchHistory = /* @__PURE__ */ new Map();
|
|
1786
|
+
const storeSteps = [];
|
|
1787
|
+
let stepSeq = 0;
|
|
1788
|
+
let selectedStore = "";
|
|
1789
|
+
const componentNodes = /* @__PURE__ */ new Map();
|
|
1790
|
+
const componentDom = /* @__PURE__ */ new Map();
|
|
1791
|
+
let selectedComponent = 0;
|
|
1792
|
+
let travelIndex = null;
|
|
1793
|
+
const sessionEvents = [];
|
|
1794
|
+
const SESSION_EVENT_CAP = 2e4;
|
|
1795
|
+
function pushSessionEvent(e) {
|
|
1796
|
+
sessionEvents.push(e);
|
|
1797
|
+
if (sessionEvents.length > SESSION_EVENT_CAP) sessionEvents.shift();
|
|
1798
|
+
}
|
|
1799
|
+
const memorySamples = [];
|
|
1800
|
+
let memoryTimer = null;
|
|
1801
|
+
const MEMORY_SAMPLE_MS = 1e3;
|
|
1802
|
+
function sampleMemory() {
|
|
1803
|
+
const perf = performance;
|
|
1804
|
+
const mem = perf.memory;
|
|
1805
|
+
if (!mem) return;
|
|
1806
|
+
memorySamples.push({ t: Date.now(), used: mem.usedJSHeapSize, total: mem.totalJSHeapSize, limit: mem.jsHeapLimit });
|
|
1807
|
+
if (memorySamples.length > 60) memorySamples.shift();
|
|
1808
|
+
scheduleRender();
|
|
1809
|
+
}
|
|
1810
|
+
function startMemorySampling() {
|
|
1811
|
+
if (memoryTimer) return;
|
|
1812
|
+
memoryTimer = setInterval(sampleMemory, MEMORY_SAMPLE_MS);
|
|
1813
|
+
sampleMemory();
|
|
1814
|
+
}
|
|
1815
|
+
function stopMemorySampling() {
|
|
1816
|
+
if (memoryTimer) {
|
|
1817
|
+
clearInterval(memoryTimer);
|
|
1818
|
+
memoryTimer = null;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function handleEvent(e) {
|
|
1822
|
+
if (e.source === "store" && e.payload && typeof e.payload === "object") {
|
|
1823
|
+
const p = e.payload;
|
|
1824
|
+
if (typeof p.id === "string" && !/action/i.test(e.name)) {
|
|
1825
|
+
const state = {};
|
|
1826
|
+
for (const k of Object.keys(p)) if (k !== "id") state[k] = p[k];
|
|
1827
|
+
const hist = storePatchHistory.get(p.id) ?? [];
|
|
1828
|
+
if (hist.some((h) => JSON.stringify(h.state) === JSON.stringify(state))) {
|
|
1829
|
+
storeSnapshots.set(p.id, state);
|
|
1830
|
+
if (!selectedStore) selectedStore = p.id;
|
|
1831
|
+
return true;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
timeline.ingest(e);
|
|
1836
|
+
flame.ingest(e);
|
|
1837
|
+
errors.ingest(e);
|
|
1838
|
+
if (e.source === "router") {
|
|
1839
|
+
if (e.phase === "start" && /nav/i.test(e.name)) {
|
|
1840
|
+
const p = e.payload ?? {};
|
|
1841
|
+
const id = e.traceId ?? e.name + "-" + e.timestamp;
|
|
1842
|
+
inflightNav.set(id, {
|
|
1843
|
+
record: {
|
|
1844
|
+
id,
|
|
1845
|
+
from: { path: p.from?.path ?? "?" },
|
|
1846
|
+
to: { path: p.to?.path ?? e.name, query: p.to?.query },
|
|
1847
|
+
guards: [],
|
|
1848
|
+
durationMs: 0,
|
|
1849
|
+
traceId: e.traceId,
|
|
1850
|
+
timestamp: e.timestamp
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
} else if (e.phase === "end" && /nav/i.test(e.name)) {
|
|
1854
|
+
const id = e.traceId ?? e.name + "-" + e.timestamp;
|
|
1855
|
+
const nav = inflightNav.get(id);
|
|
1856
|
+
if (nav) {
|
|
1857
|
+
nav.record.durationMs = Math.max(0, e.timestamp - nav.record.timestamp);
|
|
1858
|
+
navs.push(nav.record);
|
|
1859
|
+
inflightNav.delete(id);
|
|
1860
|
+
if (navs.length > 500) navs.shift();
|
|
1861
|
+
}
|
|
1862
|
+
} else if (/guard/i.test(e.name)) {
|
|
1863
|
+
let target = null;
|
|
1864
|
+
let targetTs = -Infinity;
|
|
1865
|
+
for (const nav of inflightNav.values()) {
|
|
1866
|
+
if (nav.record.timestamp > targetTs) {
|
|
1867
|
+
target = nav;
|
|
1868
|
+
targetTs = nav.record.timestamp;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
if (target) {
|
|
1872
|
+
let result = "next";
|
|
1873
|
+
if (/redirect/i.test(e.name)) result = "redirect";
|
|
1874
|
+
else if (/cancel/i.test(e.name)) result = "cancel";
|
|
1875
|
+
else if (/error/i.test(e.name)) result = "error";
|
|
1876
|
+
target.record.guards.push({ name: e.name, durationMs: 0, result });
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (e.source === "store") {
|
|
1881
|
+
if (e.payload && typeof e.payload === "object") {
|
|
1882
|
+
const p = e.payload;
|
|
1883
|
+
if (typeof p.id === "string") {
|
|
1884
|
+
const index = stepSeq++;
|
|
1885
|
+
const isAction = /action/i.test(e.name);
|
|
1886
|
+
if (isAction) {
|
|
1887
|
+
storeSteps.push({
|
|
1888
|
+
index,
|
|
1889
|
+
storeId: p.id,
|
|
1890
|
+
type: "action",
|
|
1891
|
+
name: String(p.name ?? "?"),
|
|
1892
|
+
payload: e.payload,
|
|
1893
|
+
timestamp: e.timestamp
|
|
1894
|
+
});
|
|
1895
|
+
if (storeSteps.length > 1e3) storeSteps.shift();
|
|
1896
|
+
} else {
|
|
1897
|
+
const state = {};
|
|
1898
|
+
for (const k of Object.keys(p)) if (k !== "id") state[k] = p[k];
|
|
1899
|
+
storeSnapshots.set(p.id, state);
|
|
1900
|
+
if (!selectedStore) selectedStore = p.id;
|
|
1901
|
+
const list = storePatchHistory.get(p.id) ?? [];
|
|
1902
|
+
list.push({ stepIndex: index, state });
|
|
1903
|
+
storePatchHistory.set(p.id, list);
|
|
1904
|
+
storeSteps.push({
|
|
1905
|
+
index,
|
|
1906
|
+
storeId: p.id,
|
|
1907
|
+
type: "patch",
|
|
1908
|
+
name: "patch",
|
|
1909
|
+
payload: e.payload,
|
|
1910
|
+
timestamp: e.timestamp
|
|
1911
|
+
});
|
|
1912
|
+
if (storeSteps.length > 1e3) storeSteps.shift();
|
|
1913
|
+
}
|
|
1914
|
+
travelIndex = null;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (e.source === "component" && e.payload && typeof e.payload === "object") {
|
|
1919
|
+
const p = e.payload;
|
|
1920
|
+
if (typeof p.id === "number") {
|
|
1921
|
+
if (/inspect/i.test(e.name)) {
|
|
1922
|
+
componentDom.set(p.id, p.dom ?? { tag: "?", children: [] });
|
|
1923
|
+
} else if (/unmount/i.test(e.name)) {
|
|
1924
|
+
componentNodes.delete(p.id);
|
|
1925
|
+
componentDom.delete(p.id);
|
|
1926
|
+
} else if (/mount/i.test(e.name)) {
|
|
1927
|
+
const existing = componentNodes.get(p.id);
|
|
1928
|
+
componentNodes.set(p.id, {
|
|
1929
|
+
id: p.id,
|
|
1930
|
+
name: p.name ?? "Anonymous",
|
|
1931
|
+
parentId: p.parentId,
|
|
1932
|
+
ts: e.timestamp,
|
|
1933
|
+
count: (existing?.count ?? 0) + 1,
|
|
1934
|
+
// ★P1:mount 时刻 props/state 快照(序列化后 JSON-safe;详情面板展示)
|
|
1935
|
+
props: p.props,
|
|
1936
|
+
state: p.state
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
pushSessionEvent(e);
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1944
|
+
const fgControls = document.createElement("div");
|
|
1945
|
+
fgControls.className = "pd-fg-controls";
|
|
1946
|
+
const recBtn = document.createElement("button");
|
|
1947
|
+
recBtn.className = "pd-btn";
|
|
1948
|
+
recBtn.textContent = "\u5F00\u59CB\u5F55\u5236";
|
|
1949
|
+
let baseline = null;
|
|
1950
|
+
let compareEntries = [];
|
|
1951
|
+
let flameFocus = null;
|
|
1952
|
+
let flamePath = [];
|
|
1953
|
+
function findFlameNode(id) {
|
|
1954
|
+
const path = [];
|
|
1955
|
+
const walk = (n) => {
|
|
1956
|
+
path.push(n);
|
|
1957
|
+
if (n.id === id) return true;
|
|
1958
|
+
for (const c of n.children) if (walk(c)) return true;
|
|
1959
|
+
path.pop();
|
|
1960
|
+
return false;
|
|
1961
|
+
};
|
|
1962
|
+
for (const r of flame.roots()) if (walk(r)) return { node: path[path.length - 1], path: path.slice() };
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
recBtn.addEventListener("click", () => {
|
|
1966
|
+
flameFocus = null;
|
|
1967
|
+
flamePath = [];
|
|
1968
|
+
if (flame.recording) {
|
|
1969
|
+
flame.stop();
|
|
1970
|
+
recBtn.textContent = "\u5F00\u59CB\u5F55\u5236";
|
|
1971
|
+
if (baseline) compareEntries = flame.compare(baseline);
|
|
1972
|
+
else compareEntries = [];
|
|
1973
|
+
baseline = flame.roots();
|
|
1974
|
+
} else {
|
|
1975
|
+
compareEntries = [];
|
|
1976
|
+
flame.start();
|
|
1977
|
+
recBtn.textContent = "\u505C\u6B62\u5F55\u5236";
|
|
1978
|
+
}
|
|
1979
|
+
rerender();
|
|
1980
|
+
});
|
|
1981
|
+
fgControls.appendChild(recBtn);
|
|
1982
|
+
function restoreAt(index) {
|
|
1983
|
+
const out = [];
|
|
1984
|
+
for (const [id, history] of storePatchHistory) {
|
|
1985
|
+
let state = null;
|
|
1986
|
+
for (const h of history) {
|
|
1987
|
+
if (h.stepIndex <= index) state = h.state;
|
|
1988
|
+
else break;
|
|
1989
|
+
}
|
|
1990
|
+
if (state) out.push({ id, state });
|
|
1991
|
+
}
|
|
1992
|
+
return out;
|
|
1993
|
+
}
|
|
1994
|
+
function setPathAt(obj, path, value) {
|
|
1995
|
+
if (path.length === 0) return false;
|
|
1996
|
+
let cur = obj;
|
|
1997
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1998
|
+
const k = path[i];
|
|
1999
|
+
const next = cur[k];
|
|
2000
|
+
if (next === null || typeof next !== "object") return false;
|
|
2001
|
+
cur = next;
|
|
2002
|
+
}
|
|
2003
|
+
const last = path[path.length - 1];
|
|
2004
|
+
if (Array.isArray(cur)) {
|
|
2005
|
+
;
|
|
2006
|
+
cur[Number(last)] = value;
|
|
2007
|
+
} else {
|
|
2008
|
+
;
|
|
2009
|
+
cur[last] = value;
|
|
2010
|
+
}
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
function editStoreValue(storeId, path, value) {
|
|
2014
|
+
const cur = storeSnapshots.get(storeId);
|
|
2015
|
+
if (!cur) return;
|
|
2016
|
+
const next = JSON.parse(JSON.stringify(cur));
|
|
2017
|
+
if (!setPathAt(next, path, value)) return;
|
|
2018
|
+
storeSnapshots.set(storeId, next);
|
|
2019
|
+
options.onApplyState?.([{ id: storeId, state: next }]);
|
|
2020
|
+
source.sendCommand?.("Proteus.restoreStores", { stores: [{ id: storeId, state: next }] });
|
|
2021
|
+
scheduleRender();
|
|
2022
|
+
}
|
|
2023
|
+
function exportSnapshot() {
|
|
2024
|
+
const stores = Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] }));
|
|
2025
|
+
const json = serializeStoreSnapshot({ stores, steps: storeSteps.map((s) => ({ index: s.index, storeId: s.storeId, type: s.type, name: s.name, payload: s.payload, timestamp: s.timestamp })) });
|
|
2026
|
+
const sensitive = findSensitiveKeys(stores);
|
|
2027
|
+
if (sensitive.length) {
|
|
2028
|
+
const summary = sensitive.map((h) => `${h.storeId}: ${h.keys.join(", ")}`).join("\uFF1B");
|
|
2029
|
+
const ok = typeof window !== "undefined" && typeof window.confirm === "function" ? window.confirm(`\u5FEB\u7167\u5305\u542B\u654F\u611F\u5B57\u6BB5\uFF08\u5C06\u4E00\u5E76\u5BFC\u51FA\uFF09\uFF1A
|
|
2030
|
+
${summary}
|
|
2031
|
+
|
|
2032
|
+
\u786E\u8BA4\u5BFC\u51FA\uFF1F`) : true;
|
|
2033
|
+
if (!ok) return "";
|
|
2034
|
+
}
|
|
2035
|
+
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return json;
|
|
2036
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
2037
|
+
const url = URL.createObjectURL(blob);
|
|
2038
|
+
const a = document.createElement("a");
|
|
2039
|
+
a.href = url;
|
|
2040
|
+
a.download = "proteus-store-snapshot.json";
|
|
2041
|
+
a.click();
|
|
2042
|
+
URL.revokeObjectURL(url);
|
|
2043
|
+
return json;
|
|
2044
|
+
}
|
|
2045
|
+
function importSnapshot(json) {
|
|
2046
|
+
const parsed = parseStoreSnapshot(json);
|
|
2047
|
+
if (!parsed) return;
|
|
2048
|
+
storeSnapshots.clear();
|
|
2049
|
+
storePatchHistory.clear();
|
|
2050
|
+
storeSteps.length = 0;
|
|
2051
|
+
stepSeq = 0;
|
|
2052
|
+
selectedStore = "";
|
|
2053
|
+
for (const s of parsed.stores) {
|
|
2054
|
+
storeSnapshots.set(s.id, s.state);
|
|
2055
|
+
if (!selectedStore) selectedStore = s.id;
|
|
2056
|
+
}
|
|
2057
|
+
for (const st of parsed.steps) {
|
|
2058
|
+
const index = stepSeq++;
|
|
2059
|
+
storeSteps.push({ index, storeId: st.storeId, type: st.type, name: st.name, payload: st.payload, timestamp: st.timestamp });
|
|
2060
|
+
if (st.type === "patch" && st.payload && typeof st.payload === "object") {
|
|
2061
|
+
const p = st.payload;
|
|
2062
|
+
const state = {};
|
|
2063
|
+
for (const k of Object.keys(p)) if (k !== "id") state[k] = p[k];
|
|
2064
|
+
const list = storePatchHistory.get(st.storeId) ?? [];
|
|
2065
|
+
list.push({ stepIndex: index, state });
|
|
2066
|
+
storePatchHistory.set(st.storeId, list);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
scheduleRender();
|
|
2070
|
+
options.onApplyState?.(Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })));
|
|
2071
|
+
}
|
|
2072
|
+
function exportSession() {
|
|
2073
|
+
const json = serializeSession({
|
|
2074
|
+
events: sessionEvents,
|
|
2075
|
+
device: options.deviceInfo ? options.deviceInfo() : source.deviceInfo?.() ?? void 0,
|
|
2076
|
+
stores: Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })),
|
|
2077
|
+
steps: storeSteps.map((s) => ({ index: s.index, storeId: s.storeId, type: s.type, name: s.name, payload: s.payload, timestamp: s.timestamp }))
|
|
2078
|
+
});
|
|
2079
|
+
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return json;
|
|
2080
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
2081
|
+
const url = URL.createObjectURL(blob);
|
|
2082
|
+
const a = document.createElement("a");
|
|
2083
|
+
a.href = url;
|
|
2084
|
+
a.download = "proteus-session.json";
|
|
2085
|
+
a.click();
|
|
2086
|
+
URL.revokeObjectURL(url);
|
|
2087
|
+
return json;
|
|
2088
|
+
}
|
|
2089
|
+
function importSession(json) {
|
|
2090
|
+
const parsed = parseSession(json);
|
|
2091
|
+
if (!parsed) return;
|
|
2092
|
+
timeline.clear();
|
|
2093
|
+
errors.clear();
|
|
2094
|
+
navs.length = 0;
|
|
2095
|
+
inflightNav.clear();
|
|
2096
|
+
storeSnapshots.clear();
|
|
2097
|
+
storePatchHistory.clear();
|
|
2098
|
+
storeSteps.length = 0;
|
|
2099
|
+
stepSeq = 0;
|
|
2100
|
+
travelIndex = null;
|
|
2101
|
+
selectedStore = "";
|
|
2102
|
+
componentNodes.clear();
|
|
2103
|
+
componentDom.clear();
|
|
2104
|
+
selectedComponent = 0;
|
|
2105
|
+
sessionEvents.length = 0;
|
|
2106
|
+
for (const e of parsed.events) handleEvent(e);
|
|
2107
|
+
options.onApplyState?.(Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })));
|
|
2108
|
+
scheduleRender();
|
|
2109
|
+
}
|
|
2110
|
+
function timeTravel(index) {
|
|
2111
|
+
travelIndex = index;
|
|
2112
|
+
options.onTimeTravel?.(index);
|
|
2113
|
+
const restore = restoreAt(index);
|
|
2114
|
+
options.onApplyState?.(restore);
|
|
2115
|
+
source.sendCommand?.("Proteus.restoreStores", { stores: restore });
|
|
2116
|
+
}
|
|
2117
|
+
function rerender() {
|
|
2118
|
+
tlViewHeight = timelineView.clientHeight || 300;
|
|
2119
|
+
renderTimeline(containers.get("timeline"), { spans: timeline.spans(), window: zoom.getWindow() ?? void 0, virtual: { scrollTop: tlScrollTop, viewHeight: tlViewHeight } });
|
|
2120
|
+
function fgData() {
|
|
2121
|
+
const data = { nodes: flame.roots(), compare: compareEntries.length ? compareEntries : void 0 };
|
|
2122
|
+
if (flameFocus && flamePath.length) {
|
|
2123
|
+
data.focus = flameFocus;
|
|
2124
|
+
data.breadcrumb = flamePath.map((n) => ({ id: n.id, name: n.source + "." + n.name }));
|
|
2125
|
+
}
|
|
2126
|
+
return data;
|
|
2127
|
+
}
|
|
2128
|
+
renderFlamegraph(containers.get("flamegraph"), fgData(), {
|
|
2129
|
+
// ★点击块 → 聚焦缩放(zoom 到该节点子树)
|
|
2130
|
+
onFocus: (id) => {
|
|
2131
|
+
const found = findFlameNode(id);
|
|
2132
|
+
if (found) {
|
|
2133
|
+
flameFocus = found.node;
|
|
2134
|
+
flamePath = found.path;
|
|
2135
|
+
scheduleRender();
|
|
2136
|
+
}
|
|
2137
|
+
},
|
|
2138
|
+
// ★返回上级(面包屑上一级;根 → 退出聚焦)
|
|
2139
|
+
onFocusUp: () => {
|
|
2140
|
+
if (flamePath.length > 1) {
|
|
2141
|
+
const parent = findFlameNode(flamePath[flamePath.length - 2].id);
|
|
2142
|
+
if (parent) {
|
|
2143
|
+
flameFocus = parent.node;
|
|
2144
|
+
flamePath = parent.path;
|
|
2145
|
+
} else {
|
|
2146
|
+
flameFocus = null;
|
|
2147
|
+
flamePath = [];
|
|
2148
|
+
}
|
|
2149
|
+
} else {
|
|
2150
|
+
flameFocus = null;
|
|
2151
|
+
flamePath = [];
|
|
2152
|
+
}
|
|
2153
|
+
scheduleRender();
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
const flameContainer = containers.get("flamegraph");
|
|
2157
|
+
flameContainer.insertBefore(fgControls, flameContainer.firstChild);
|
|
2158
|
+
renderErrors(containers.get("errors"), { reports: errors.diagnose() });
|
|
2159
|
+
renderRoute(containers.get("route"), { records: navs });
|
|
2160
|
+
renderState(
|
|
2161
|
+
containers.get("state"),
|
|
2162
|
+
{
|
|
2163
|
+
snapshot: { version: 1, takenAt: Date.now(), stores: Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })) },
|
|
2164
|
+
// ★P0:真实 before/after(patch 步骤:该 store 补丁历史中前一条/本条状态;action 步骤无状态变更)
|
|
2165
|
+
steps: storeSteps.map((s) => {
|
|
2166
|
+
let before = {};
|
|
2167
|
+
let after = {};
|
|
2168
|
+
if (s.type === "patch") {
|
|
2169
|
+
const hist = storePatchHistory.get(s.storeId) ?? [];
|
|
2170
|
+
const idx = hist.findIndex((h) => h.stepIndex === s.index);
|
|
2171
|
+
if (idx >= 0) {
|
|
2172
|
+
after = hist[idx].state;
|
|
2173
|
+
if (idx > 0) before = hist[idx - 1].state;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
return { index: s.index, storeId: s.storeId, type: s.type, payload: s.payload, timestamp: s.timestamp, before, after };
|
|
2177
|
+
}),
|
|
2178
|
+
selectedStore,
|
|
2179
|
+
// ★滑块回放位置(rerender 后保持;新真实变更重置为最新)
|
|
2180
|
+
travelIndex: travelIndex ?? void 0
|
|
2181
|
+
},
|
|
2182
|
+
{
|
|
2183
|
+
onTimeTravel: timeTravel,
|
|
2184
|
+
onSelectStore: (id) => {
|
|
2185
|
+
selectedStore = id;
|
|
2186
|
+
scheduleRender();
|
|
2187
|
+
},
|
|
2188
|
+
onExport: exportSnapshot,
|
|
2189
|
+
onImport: importSnapshot,
|
|
2190
|
+
// ★M11 可观测性(M8.2):会话导出/导入(完整还原另一环境)
|
|
2191
|
+
onExportSession: exportSession,
|
|
2192
|
+
onImportSession: importSession,
|
|
2193
|
+
// ★双向调试:值编辑 → 面板快照 + 本地/远程双通道写回($patch 真实状态)
|
|
2194
|
+
onEditValue: editStoreValue
|
|
2195
|
+
}
|
|
2196
|
+
);
|
|
2197
|
+
const deviceInfo = options.deviceInfo ? options.deviceInfo() : source.deviceInfo?.() ?? void 0;
|
|
2198
|
+
renderDevice(containers.get("device"), { info: deviceInfo, memory: memorySamples.slice() });
|
|
2199
|
+
const ownershipData = options.ownershipData ? options.ownershipData() : source.ownership?.() ?? void 0;
|
|
2200
|
+
renderOwnership(containers.get("ownership"), ownershipData);
|
|
2201
|
+
renderComponents(
|
|
2202
|
+
containers.get("components"),
|
|
2203
|
+
{ nodes: Array.from(componentNodes.values()), selectedId: selectedComponent || void 0, dom: selectedComponent ? componentDom.get(selectedComponent) : void 0 },
|
|
2204
|
+
{
|
|
2205
|
+
// ★P1:点击选中(同 id 再点取消选中);首次选中 → 页面元素高亮回调
|
|
2206
|
+
onSelect: (id) => {
|
|
2207
|
+
if (selectedComponent === id) {
|
|
2208
|
+
selectedComponent = 0;
|
|
2209
|
+
} else {
|
|
2210
|
+
selectedComponent = id;
|
|
2211
|
+
options.onSelectComponent?.(id);
|
|
2212
|
+
}
|
|
2213
|
+
scheduleRender();
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
);
|
|
2217
|
+
renderPages(containers.get("pages"), resolvePagesData());
|
|
2218
|
+
renderGraph(containers.get("graph"), { routes: resolvePagesData().routes });
|
|
2219
|
+
for (const [id, pv] of pluginViews) {
|
|
2220
|
+
const container = containers.get(id);
|
|
2221
|
+
const err = crashedPlugins.get(pv.name);
|
|
2222
|
+
if (err !== void 0) {
|
|
2223
|
+
renderPluginCrash(container, pv.name, err);
|
|
2224
|
+
} else {
|
|
2225
|
+
try {
|
|
2226
|
+
pv.render(container);
|
|
2227
|
+
} catch (e) {
|
|
2228
|
+
crashPlugin(pv.name, e);
|
|
2229
|
+
renderPluginCrash(container, pv.name, e instanceof Error ? e.message : String(e));
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
function renderPluginCrash(container, name, err) {
|
|
2235
|
+
container.replaceChildren();
|
|
2236
|
+
const card2 = document.createElement("div");
|
|
2237
|
+
card2.className = "pd-plugin-crash";
|
|
2238
|
+
const t = document.createElement("div");
|
|
2239
|
+
t.className = "pd-plugin-crash-title";
|
|
2240
|
+
t.textContent = "\u63D2\u4EF6\u5D29\u6E83\uFF1A" + name;
|
|
2241
|
+
const m = document.createElement("div");
|
|
2242
|
+
m.className = "pd-plugin-crash-msg";
|
|
2243
|
+
m.textContent = err;
|
|
2244
|
+
card2.appendChild(t);
|
|
2245
|
+
card2.appendChild(m);
|
|
2246
|
+
container.appendChild(card2);
|
|
2247
|
+
}
|
|
2248
|
+
let renderTimer = null;
|
|
2249
|
+
function scheduleRender() {
|
|
2250
|
+
if (renderTimer) return;
|
|
2251
|
+
renderTimer = setTimeout(() => {
|
|
2252
|
+
renderTimer = null;
|
|
2253
|
+
rerender();
|
|
2254
|
+
}, 16);
|
|
2255
|
+
}
|
|
2256
|
+
function resolvePagesData() {
|
|
2257
|
+
if (options.pages) return options.pages;
|
|
2258
|
+
const info = source.appInfo?.();
|
|
2259
|
+
return { routes: info?.routes ?? [] };
|
|
2260
|
+
}
|
|
2261
|
+
let statusConnected = false;
|
|
2262
|
+
const off = source.onEvent((e) => {
|
|
2263
|
+
if (!handleEvent(e)) {
|
|
2264
|
+
broadcastToPlugins(e);
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
broadcastToPlugins(e);
|
|
2268
|
+
scheduleRender();
|
|
2269
|
+
if (!statusConnected) {
|
|
2270
|
+
statusConnected = true;
|
|
2271
|
+
dot.classList.add("pd-dot-on");
|
|
2272
|
+
statusText.textContent = "\u5DF2\u8FDE\u63A5";
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
const offStatus = source.onStatus?.((s) => {
|
|
2276
|
+
if (s === "connected" && !statusConnected) {
|
|
2277
|
+
statusConnected = true;
|
|
2278
|
+
dot.classList.add("pd-dot-on");
|
|
2279
|
+
statusText.textContent = "\u5DF2\u8FDE\u63A5";
|
|
2280
|
+
} else if (s === "closed") {
|
|
2281
|
+
statusConnected = false;
|
|
2282
|
+
dot.classList.remove("pd-dot-on");
|
|
2283
|
+
statusText.textContent = "\u5DF2\u65AD\u5F00";
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
containers.get("flamegraph").insertBefore(fgControls, containers.get("flamegraph").firstChild);
|
|
2287
|
+
root.style.position = "relative";
|
|
2288
|
+
const paletteBtn = document.createElement("button");
|
|
2289
|
+
paletteBtn.className = "pd-btn pd-palette-btn";
|
|
2290
|
+
paletteBtn.textContent = "\u26A1";
|
|
2291
|
+
paletteBtn.title = "\u547D\u4EE4\u9762\u677F";
|
|
2292
|
+
const palette = document.createElement("div");
|
|
2293
|
+
palette.className = "pd-palette";
|
|
2294
|
+
palette.style.display = "none";
|
|
2295
|
+
function openPalette() {
|
|
2296
|
+
palette.replaceChildren();
|
|
2297
|
+
const ids = commands.list();
|
|
2298
|
+
if (ids.length === 0) {
|
|
2299
|
+
const empty = document.createElement("div");
|
|
2300
|
+
empty.className = "pd-palette-empty";
|
|
2301
|
+
empty.textContent = "\u6682\u65E0\u547D\u4EE4";
|
|
2302
|
+
palette.appendChild(empty);
|
|
2303
|
+
} else {
|
|
2304
|
+
for (const id of ids) {
|
|
2305
|
+
const item = document.createElement("div");
|
|
2306
|
+
item.className = "pd-palette-item";
|
|
2307
|
+
item.textContent = id;
|
|
2308
|
+
item.addEventListener("click", () => {
|
|
2309
|
+
commands.run(id);
|
|
2310
|
+
palette.style.display = "none";
|
|
2311
|
+
});
|
|
2312
|
+
palette.appendChild(item);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
palette.style.display = "block";
|
|
2316
|
+
}
|
|
2317
|
+
paletteBtn.addEventListener("click", () => {
|
|
2318
|
+
if (palette.style.display === "none") openPalette();
|
|
2319
|
+
else palette.style.display = "none";
|
|
2320
|
+
});
|
|
2321
|
+
header.insertBefore(paletteBtn, status);
|
|
2322
|
+
root.appendChild(palette);
|
|
2323
|
+
show("timeline");
|
|
2324
|
+
rerender();
|
|
2325
|
+
if ((options.plugins ?? []).length) {
|
|
2326
|
+
registry.activateAll((plugin) => {
|
|
2327
|
+
const pluginName = plugin.name;
|
|
2328
|
+
return {
|
|
2329
|
+
name: pluginName,
|
|
2330
|
+
bus: {
|
|
2331
|
+
on(cb) {
|
|
2332
|
+
const wrapped = (e) => {
|
|
2333
|
+
try {
|
|
2334
|
+
cb(e);
|
|
2335
|
+
} catch (err) {
|
|
2336
|
+
crashPlugin(pluginName, err);
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
pluginSubs.push({ name: pluginName, cb: wrapped });
|
|
2340
|
+
return () => {
|
|
2341
|
+
for (let i = pluginSubs.length - 1; i >= 0; i--) {
|
|
2342
|
+
if (pluginSubs[i].cb === wrapped) pluginSubs.splice(i, 1);
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
},
|
|
2347
|
+
panel: {
|
|
2348
|
+
addView(id, opts) {
|
|
2349
|
+
registerView(id, opts.label, opts.icon, opts.render, pluginName);
|
|
2350
|
+
}
|
|
2351
|
+
},
|
|
2352
|
+
commands,
|
|
2353
|
+
storage
|
|
2354
|
+
};
|
|
2355
|
+
}).then(() => scheduleRender()).catch((err) => {
|
|
2356
|
+
console.error("[proteus-devtools] \u63D2\u4EF6\u6FC0\u6D3B\u5931\u8D25", err);
|
|
2357
|
+
statusText.textContent = "\u63D2\u4EF6\u6FC0\u6D3B\u5931\u8D25";
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
return {
|
|
2361
|
+
destroy() {
|
|
2362
|
+
off();
|
|
2363
|
+
offStatus?.();
|
|
2364
|
+
unbindTip();
|
|
2365
|
+
tooltip.dispose();
|
|
2366
|
+
zoom.destroy();
|
|
2367
|
+
timelineView.removeEventListener("scroll", onTimelineScroll);
|
|
2368
|
+
stopMemorySampling();
|
|
2369
|
+
pluginSubs.length = 0;
|
|
2370
|
+
source.close();
|
|
2371
|
+
if (renderTimer) clearTimeout(renderTimer);
|
|
2372
|
+
root.replaceChildren();
|
|
2373
|
+
},
|
|
2374
|
+
show,
|
|
2375
|
+
exportSnapshot,
|
|
2376
|
+
importSnapshot,
|
|
2377
|
+
exportSession,
|
|
2378
|
+
importSession
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// src/source.ts
|
|
2383
|
+
function createTraceBusSource(bus) {
|
|
2384
|
+
return {
|
|
2385
|
+
onEvent(cb) {
|
|
2386
|
+
return bus.on(cb);
|
|
2387
|
+
},
|
|
2388
|
+
// 本地直连:进程内总线,状态恒已连接
|
|
2389
|
+
onStatus(cb) {
|
|
2390
|
+
cb("connected");
|
|
2391
|
+
return () => {
|
|
2392
|
+
};
|
|
2393
|
+
},
|
|
2394
|
+
close() {
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
function createDevtoolsWsSource(url, createSocket) {
|
|
2399
|
+
const makeSocket = createSocket ?? ((u) => new WebSocket(u));
|
|
2400
|
+
const handlers = [];
|
|
2401
|
+
const statusHandlers = [];
|
|
2402
|
+
let ws = null;
|
|
2403
|
+
let closed = false;
|
|
2404
|
+
let seq = 0;
|
|
2405
|
+
let reconnectTimer = null;
|
|
2406
|
+
let appInfoCache;
|
|
2407
|
+
let status = "connecting";
|
|
2408
|
+
let enableId = null;
|
|
2409
|
+
let appInfoId = null;
|
|
2410
|
+
let enableAcked = false;
|
|
2411
|
+
let retryTimer = null;
|
|
2412
|
+
let deviceInfoId = null;
|
|
2413
|
+
let deviceInfoCache;
|
|
2414
|
+
let ownershipId = null;
|
|
2415
|
+
let ownershipCache;
|
|
2416
|
+
function setStatus(s) {
|
|
2417
|
+
status = s;
|
|
2418
|
+
for (const h of statusHandlers) h(s);
|
|
2419
|
+
}
|
|
2420
|
+
function sendCommands(sock) {
|
|
2421
|
+
enableId = ++seq;
|
|
2422
|
+
enableAcked = false;
|
|
2423
|
+
sock.send(JSON.stringify({ id: enableId, method: "Proteus.enable" }));
|
|
2424
|
+
appInfoId = ++seq;
|
|
2425
|
+
sock.send(JSON.stringify({ id: appInfoId, method: "Proteus.appInfo" }));
|
|
2426
|
+
deviceInfoId = ++seq;
|
|
2427
|
+
sock.send(JSON.stringify({ id: deviceInfoId, method: "Proteus.deviceInfo" }));
|
|
2428
|
+
ownershipId = ++seq;
|
|
2429
|
+
sock.send(JSON.stringify({ id: ownershipId, method: "Proteus.ownership" }));
|
|
2430
|
+
}
|
|
2431
|
+
function sendCommand(method, params) {
|
|
2432
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
2433
|
+
ws.send(JSON.stringify({ id: ++seq, method, params }));
|
|
2434
|
+
}
|
|
2435
|
+
function startRetry() {
|
|
2436
|
+
if (retryTimer) return;
|
|
2437
|
+
retryTimer = setInterval(() => {
|
|
2438
|
+
if (closed || !ws || ws.readyState !== WebSocket.OPEN || enableAcked) return;
|
|
2439
|
+
sendCommands(ws);
|
|
2440
|
+
}, 2e3);
|
|
2441
|
+
}
|
|
2442
|
+
function connect() {
|
|
2443
|
+
if (closed) return;
|
|
2444
|
+
try {
|
|
2445
|
+
ws = makeSocket(url);
|
|
2446
|
+
} catch {
|
|
2447
|
+
scheduleReconnect();
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
const sock = ws;
|
|
2451
|
+
sock.onopen = () => {
|
|
2452
|
+
setStatus("connected");
|
|
2453
|
+
sendCommands(sock);
|
|
2454
|
+
startRetry();
|
|
2455
|
+
};
|
|
2456
|
+
sock.onmessage = (ev) => {
|
|
2457
|
+
let msg;
|
|
2458
|
+
try {
|
|
2459
|
+
msg = JSON.parse(String(ev.data));
|
|
2460
|
+
} catch {
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
if (!msg || typeof msg !== "object") return;
|
|
2464
|
+
if (msg.id === enableId && msg.result !== void 0) {
|
|
2465
|
+
enableAcked = true;
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
if (msg.id === appInfoId && msg.result !== void 0) {
|
|
2469
|
+
appInfoCache = msg.result;
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2472
|
+
if (msg.id === deviceInfoId && msg.result !== void 0) {
|
|
2473
|
+
deviceInfoCache = msg.result;
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (msg.id === ownershipId && msg.result !== void 0) {
|
|
2477
|
+
ownershipCache = msg.result;
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
if (msg.method !== "Proteus.event") return;
|
|
2481
|
+
const p = msg.params ?? {};
|
|
2482
|
+
const source = p.source;
|
|
2483
|
+
const name = p.name;
|
|
2484
|
+
if (!source || !name) return;
|
|
2485
|
+
const event = {
|
|
2486
|
+
source,
|
|
2487
|
+
phase: p.phase ?? "point",
|
|
2488
|
+
name,
|
|
2489
|
+
payload: p.payload,
|
|
2490
|
+
timestamp: p.timestamp ?? Date.now(),
|
|
2491
|
+
traceId: p.traceId
|
|
2492
|
+
};
|
|
2493
|
+
for (const h of handlers) h(event);
|
|
2494
|
+
};
|
|
2495
|
+
sock.onclose = () => {
|
|
2496
|
+
setStatus("closed");
|
|
2497
|
+
if (!closed) scheduleReconnect();
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
function scheduleReconnect() {
|
|
2501
|
+
if (closed || reconnectTimer) return;
|
|
2502
|
+
reconnectTimer = setTimeout(() => {
|
|
2503
|
+
reconnectTimer = null;
|
|
2504
|
+
connect();
|
|
2505
|
+
}, 1e3);
|
|
2506
|
+
}
|
|
2507
|
+
connect();
|
|
2508
|
+
return {
|
|
2509
|
+
onEvent(cb) {
|
|
2510
|
+
handlers.push(cb);
|
|
2511
|
+
return () => {
|
|
2512
|
+
const i = handlers.indexOf(cb);
|
|
2513
|
+
if (i >= 0) handlers.splice(i, 1);
|
|
2514
|
+
};
|
|
2515
|
+
},
|
|
2516
|
+
onStatus(cb) {
|
|
2517
|
+
statusHandlers.push(cb);
|
|
2518
|
+
cb(status);
|
|
2519
|
+
return () => {
|
|
2520
|
+
const i = statusHandlers.indexOf(cb);
|
|
2521
|
+
if (i >= 0) statusHandlers.splice(i, 1);
|
|
2522
|
+
};
|
|
2523
|
+
},
|
|
2524
|
+
close() {
|
|
2525
|
+
closed = true;
|
|
2526
|
+
if (reconnectTimer) {
|
|
2527
|
+
clearTimeout(reconnectTimer);
|
|
2528
|
+
reconnectTimer = null;
|
|
2529
|
+
}
|
|
2530
|
+
if (retryTimer) {
|
|
2531
|
+
clearInterval(retryTimer);
|
|
2532
|
+
retryTimer = null;
|
|
2533
|
+
}
|
|
2534
|
+
ws?.close();
|
|
2535
|
+
ws = null;
|
|
2536
|
+
},
|
|
2537
|
+
appInfo() {
|
|
2538
|
+
return appInfoCache;
|
|
2539
|
+
},
|
|
2540
|
+
/** ★M8:设备信息(环境/能力;Proteus.deviceInfo 命令响应缓存;未确认前 undefined) */
|
|
2541
|
+
deviceInfo() {
|
|
2542
|
+
return deviceInfoCache;
|
|
2543
|
+
},
|
|
2544
|
+
/** ★G-43 B4:所有权视图数据(Proteus.ownership 命令响应缓存;未确认前 undefined) */
|
|
2545
|
+
ownership() {
|
|
2546
|
+
return ownershipCache;
|
|
2547
|
+
},
|
|
2548
|
+
sendCommand
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// src/ws-bridge.ts
|
|
2553
|
+
function createTraceBusWsBridge(bus, options) {
|
|
2554
|
+
const ws = new WebSocket(options.url);
|
|
2555
|
+
function sendEvent(e) {
|
|
2556
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
2557
|
+
ws.send(
|
|
2558
|
+
JSON.stringify({
|
|
2559
|
+
method: "Proteus.event",
|
|
2560
|
+
params: { source: e.source, phase: e.phase, name: e.name, payload: e.payload, timestamp: e.timestamp, traceId: e.traceId }
|
|
2561
|
+
})
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
const off = bus.on((e) => sendEvent(e));
|
|
2565
|
+
ws.onmessage = (ev) => {
|
|
2566
|
+
let msg = null;
|
|
2567
|
+
try {
|
|
2568
|
+
msg = JSON.parse(String(ev.data));
|
|
2569
|
+
} catch {
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
if (!msg || typeof msg !== "object") return;
|
|
2573
|
+
if (msg.method === "Proteus.enable") {
|
|
2574
|
+
for (const e of bus.flush()) sendEvent(e);
|
|
2575
|
+
ws.send(JSON.stringify({ id: msg.id, result: {} }));
|
|
2576
|
+
} else if (msg.method === "Proteus.appInfo") {
|
|
2577
|
+
ws.send(JSON.stringify({ id: msg.id, result: options.appInfo ? options.appInfo() : {} }));
|
|
2578
|
+
} else if (msg.method === "Proteus.deviceInfo") {
|
|
2579
|
+
ws.send(JSON.stringify({ id: msg.id, result: options.deviceInfo ? options.deviceInfo() : {} }));
|
|
2580
|
+
} else if (msg.method === "Proteus.ownership") {
|
|
2581
|
+
ws.send(JSON.stringify({ id: msg.id, result: options.ownership ? options.ownership() : {} }));
|
|
2582
|
+
} else if (msg.method === "Proteus.restoreStores") {
|
|
2583
|
+
const params = msg.params;
|
|
2584
|
+
if (options.onRestoreStores && Array.isArray(params?.stores)) {
|
|
2585
|
+
options.onRestoreStores(params.stores);
|
|
2586
|
+
}
|
|
2587
|
+
ws.send(JSON.stringify({ id: msg.id, result: {} }));
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
return {
|
|
2591
|
+
close() {
|
|
2592
|
+
off();
|
|
2593
|
+
ws.close();
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// src/vue-devtools.ts
|
|
2599
|
+
var LAYER_ID = "proteus";
|
|
2600
|
+
function toTimelineEvent(e) {
|
|
2601
|
+
return {
|
|
2602
|
+
time: e.timestamp,
|
|
2603
|
+
title: e.source + "." + e.name,
|
|
2604
|
+
subtitle: e.phase,
|
|
2605
|
+
data: {
|
|
2606
|
+
source: e.source,
|
|
2607
|
+
name: e.name,
|
|
2608
|
+
phase: e.phase,
|
|
2609
|
+
traceId: e.traceId,
|
|
2610
|
+
payload: e.payload
|
|
2611
|
+
},
|
|
2612
|
+
groupId: e.traceId ?? e.source
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
function installProteusTimeline(api, options) {
|
|
2616
|
+
api.addTimelineLayer({ id: LAYER_ID, label: "Proteus", color: options.color ?? 11101205 });
|
|
2617
|
+
const off = options.source.onEvent((e) => {
|
|
2618
|
+
api.addTimelineEvent({ layerId: LAYER_ID, event: toTimelineEvent(e) });
|
|
2619
|
+
});
|
|
2620
|
+
return {
|
|
2621
|
+
dispose: off,
|
|
2622
|
+
get layerId() {
|
|
2623
|
+
return LAYER_ID;
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2627
|
+
function pathToPatch(path, value, groupKey) {
|
|
2628
|
+
let p = path;
|
|
2629
|
+
if (groupKey && p[0] === groupKey) p = p.slice(1);
|
|
2630
|
+
const root = {};
|
|
2631
|
+
let cur = root;
|
|
2632
|
+
for (let i = 0; i < p.length - 1; i++) {
|
|
2633
|
+
const next = {};
|
|
2634
|
+
cur[p[i]] = next;
|
|
2635
|
+
cur = next;
|
|
2636
|
+
}
|
|
2637
|
+
cur[p[p.length - 1]] = value;
|
|
2638
|
+
return root;
|
|
2639
|
+
}
|
|
2640
|
+
function configToStateRows(config) {
|
|
2641
|
+
const keys = Object.keys(config);
|
|
2642
|
+
return keys.length ? keys.map((k) => ({ key: k, value: config[k] })) : [{ key: "(empty)", value: {} }];
|
|
2643
|
+
}
|
|
2644
|
+
var PROTEUS_DEVTOOLS_PLUGIN_DESCRIPTOR = {
|
|
2645
|
+
id: "proteus",
|
|
2646
|
+
label: "Proteus",
|
|
2647
|
+
/** ★触发 fallback 的占位 logo(根相对路径:扩展/独立/dev server 下 404 或图片解码失败 → img error)
|
|
2648
|
+
* 不能传 undefined(三个 inspector 全默认图标),也不能传真实图片 URL(会直接显示图片而非字典图标) */
|
|
2649
|
+
logo: "/__proteus-inspector-icons__.svg"
|
|
2650
|
+
};
|
|
2651
|
+
var APP_CONFIG_INSPECTOR = "proteus-app-config";
|
|
2652
|
+
var STYLE_SAFETY_INSPECTOR = "proteus-style-safety";
|
|
2653
|
+
var ROUTER_INSPECTOR = "proteus-router";
|
|
2654
|
+
var TAG_STYLE = { textColor: 16777215, backgroundColor: 1735416 };
|
|
2655
|
+
function buildRouterTree(routes, currentRoute) {
|
|
2656
|
+
const byName = new Map(routes.map((r) => [r.name, r]));
|
|
2657
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
2658
|
+
const roots = [];
|
|
2659
|
+
for (const r of routes) {
|
|
2660
|
+
if (r.parent && byName.has(r.parent)) {
|
|
2661
|
+
const list = childrenOf.get(r.parent) ?? [];
|
|
2662
|
+
list.push(r);
|
|
2663
|
+
childrenOf.set(r.parent, list);
|
|
2664
|
+
} else {
|
|
2665
|
+
roots.push(r);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
const CURRENT_STYLE = { textColor: 16777215, backgroundColor: 508256 };
|
|
2669
|
+
const toNode = (r) => ({
|
|
2670
|
+
id: r.name,
|
|
2671
|
+
label: r.meta?.title ?? r.name,
|
|
2672
|
+
// ★当前路由高亮(path 匹配 → 绿色「当前」tag)
|
|
2673
|
+
tags: [
|
|
2674
|
+
...r.path === currentRoute ? [{ label: "\u5F53\u524D", ...CURRENT_STYLE }] : [],
|
|
2675
|
+
{ label: r.path, ...TAG_STYLE }
|
|
2676
|
+
],
|
|
2677
|
+
children: (childrenOf.get(r.name) ?? []).map(toNode)
|
|
2678
|
+
});
|
|
2679
|
+
return roots.map(toNode);
|
|
2680
|
+
}
|
|
2681
|
+
function installProteusInspectors(api, options = {}) {
|
|
2682
|
+
api.addInspector({ id: APP_CONFIG_INSPECTOR, label: "App Config", icon: "settings" });
|
|
2683
|
+
api.on.getInspectorTree((payload) => {
|
|
2684
|
+
if (payload.inspectorId !== APP_CONFIG_INSPECTOR) return;
|
|
2685
|
+
payload.rootNodes = [{ id: "root", label: "App Config" }];
|
|
2686
|
+
});
|
|
2687
|
+
api.on.getInspectorState((payload) => {
|
|
2688
|
+
if (payload.inspectorId !== APP_CONFIG_INSPECTOR) return;
|
|
2689
|
+
payload.state = { resolved: configToStateRows(options.getConfig ? options.getConfig() : {}) };
|
|
2690
|
+
});
|
|
2691
|
+
if (options.setConfig) {
|
|
2692
|
+
api.on.editInspectorState((payload) => {
|
|
2693
|
+
if (payload.inspectorId !== APP_CONFIG_INSPECTOR) return;
|
|
2694
|
+
options.setConfig?.(pathToPatch(payload.path, payload.state.value, "resolved"));
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
if (options.getStyleSafetyRecords) {
|
|
2698
|
+
api.addInspector({ id: STYLE_SAFETY_INSPECTOR, label: "Style Safety", icon: "gpp-good" });
|
|
2699
|
+
api.on.getInspectorTree((payload) => {
|
|
2700
|
+
if (payload.inspectorId !== STYLE_SAFETY_INSPECTOR) return;
|
|
2701
|
+
payload.rootNodes = [{ id: "root", label: "Style Safety" }];
|
|
2702
|
+
});
|
|
2703
|
+
api.on.getInspectorState((payload) => {
|
|
2704
|
+
if (payload.inspectorId !== STYLE_SAFETY_INSPECTOR) return;
|
|
2705
|
+
const records = options.getStyleSafetyRecords();
|
|
2706
|
+
payload.state = {
|
|
2707
|
+
rejected: records.map((r, i) => ({ key: r.prop || "#" + i, value: { value: r.value, reason: r.reason, ts: r.ts } }))
|
|
2708
|
+
};
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
if (options.pages) {
|
|
2712
|
+
api.addInspector({ id: ROUTER_INSPECTOR, label: "Router", icon: "route" });
|
|
2713
|
+
api.on.getInspectorTree((payload) => {
|
|
2714
|
+
if (payload.inspectorId !== ROUTER_INSPECTOR) return;
|
|
2715
|
+
const routerState = options.getRouterState?.();
|
|
2716
|
+
const nodes = buildRouterTree(options.pages?.routes ?? [], routerState?.currentRoute);
|
|
2717
|
+
if (routerState) {
|
|
2718
|
+
const recordNodes = routerState.records.slice(-20).reverse().map((r) => ({
|
|
2719
|
+
id: "rec-" + r.timestamp,
|
|
2720
|
+
// ★带参导航显示 query(如 pages/user/profile?id=1)
|
|
2721
|
+
label: `${r.from} \u2192 ${r.to}${r.query && Object.keys(r.query).length ? "?" + new URLSearchParams(r.query).toString() : ""}`,
|
|
2722
|
+
tags: [{ label: r.durationMs + "ms", ...TAG_STYLE }]
|
|
2723
|
+
}));
|
|
2724
|
+
nodes.unshift({
|
|
2725
|
+
id: "proteus-records",
|
|
2726
|
+
label: `\u5BFC\u822A\u8BB0\u5F55 (${routerState.records.length})`,
|
|
2727
|
+
tags: routerState.currentRoute ? [{ label: "\u5F53\u524D: " + routerState.currentRoute, ...TAG_STYLE }] : void 0,
|
|
2728
|
+
children: recordNodes
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
payload.rootNodes = nodes;
|
|
2732
|
+
});
|
|
2733
|
+
api.on.getInspectorState((payload) => {
|
|
2734
|
+
if (payload.inspectorId !== ROUTER_INSPECTOR) return;
|
|
2735
|
+
if (payload.nodeId === "proteus-records") {
|
|
2736
|
+
const state = options.getRouterState?.();
|
|
2737
|
+
payload.state = {
|
|
2738
|
+
\u5BFC\u822A\u8BB0\u5F55: [
|
|
2739
|
+
{ key: "currentRoute", value: state?.currentRoute ?? "\u2014" },
|
|
2740
|
+
{ key: "records", value: (state?.records ?? []).slice(-50).reverse() }
|
|
2741
|
+
]
|
|
2742
|
+
};
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
if (payload.nodeId.startsWith("rec-")) {
|
|
2746
|
+
const rec = (options.getRouterState?.()?.records ?? []).find((r) => "rec-" + r.timestamp === payload.nodeId);
|
|
2747
|
+
payload.state = rec ? {
|
|
2748
|
+
\u5BFC\u822A\u72B6\u6001: [
|
|
2749
|
+
{ key: "from", value: rec.from },
|
|
2750
|
+
{ key: "to", value: rec.to },
|
|
2751
|
+
{ key: "query", value: rec.query ?? {} },
|
|
2752
|
+
{ key: "durationMs", value: rec.durationMs },
|
|
2753
|
+
{ key: "timestamp", value: rec.timestamp },
|
|
2754
|
+
{ key: "traceId", value: rec.traceId ?? "\u2014" },
|
|
2755
|
+
{ key: "guards", value: rec.guards }
|
|
2756
|
+
]
|
|
2757
|
+
} : {};
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const route = (options.pages?.routes ?? []).find((r) => r.name === payload.nodeId);
|
|
2761
|
+
if (!route) {
|
|
2762
|
+
payload.state = {};
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
payload.state = {
|
|
2766
|
+
\u8DEF\u7531: [
|
|
2767
|
+
{ key: "path", value: route.path },
|
|
2768
|
+
{ key: "parent", value: route.parent ?? "\u2014" },
|
|
2769
|
+
{ key: "subPackage", value: route.subPackage ?? "\u2014" },
|
|
2770
|
+
{ key: "meta", value: route.meta ?? {} }
|
|
2771
|
+
]
|
|
2772
|
+
};
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
return {
|
|
2776
|
+
dispose() {
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// src/ownership-info.ts
|
|
2782
|
+
import {
|
|
2783
|
+
createOwnershipCounters,
|
|
2784
|
+
createOwnershipHistory,
|
|
2785
|
+
diagnoseOwnershipIssues,
|
|
2786
|
+
buildOwnershipTimeline,
|
|
2787
|
+
getProteusOwnershipGraph
|
|
2788
|
+
} from "@proteus-vue/render-backend";
|
|
2789
|
+
var TIMELINE_LIMIT = 200;
|
|
2790
|
+
function createOwnershipTracer(options = {}) {
|
|
2791
|
+
const graph = options.graph ?? getProteusOwnershipGraph();
|
|
2792
|
+
const counters = createOwnershipCounters(graph);
|
|
2793
|
+
const history = options.history ?? createOwnershipHistory(graph);
|
|
2794
|
+
let disposed = false;
|
|
2795
|
+
function collect() {
|
|
2796
|
+
if (disposed) {
|
|
2797
|
+
return {
|
|
2798
|
+
summary: { alive: 0, total: 0, bytesAlive: 0, byType: {} },
|
|
2799
|
+
diagnosis: { orphans: [], leaks: [], longBorrows: [], crossPageRefs: [] },
|
|
2800
|
+
resources: [],
|
|
2801
|
+
timeline: { events: [], unpairedIds: [], truncated: false }
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
const diagnosis = diagnoseOwnershipIssues(graph);
|
|
2805
|
+
const seenAllocIds = /* @__PURE__ */ new Set();
|
|
2806
|
+
for (const r of history.records) {
|
|
2807
|
+
if (r.kind === "alloc" && r.id) seenAllocIds.add(r.id);
|
|
2808
|
+
}
|
|
2809
|
+
const merged = [...history.records];
|
|
2810
|
+
for (const n of graph.nodes.values()) {
|
|
2811
|
+
if (!seenAllocIds.has(n.id)) {
|
|
2812
|
+
merged.push({ ts: n.createdAt, kind: "alloc", id: n.id, owner: n.owner, type: n.type, byteSize: n.byteSize, sourceLocation: n.sourceLocation });
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
merged.sort((a, b) => a.ts - b.ts);
|
|
2816
|
+
const tl = buildOwnershipTimeline({ records: merged }, graph);
|
|
2817
|
+
const truncated = tl.events.length > TIMELINE_LIMIT;
|
|
2818
|
+
const events = truncated ? tl.events.slice(-TIMELINE_LIMIT) : tl.events;
|
|
2819
|
+
const borrowedBy = /* @__PURE__ */ new Map();
|
|
2820
|
+
for (const e of graph.edges) {
|
|
2821
|
+
if (e.kind !== "borrows") continue;
|
|
2822
|
+
const list = borrowedBy.get(e.to) ?? [];
|
|
2823
|
+
list.push(e.from);
|
|
2824
|
+
borrowedBy.set(e.to, list);
|
|
2825
|
+
}
|
|
2826
|
+
const byOwner = /* @__PURE__ */ new Map();
|
|
2827
|
+
for (const n of graph.nodes.values()) {
|
|
2828
|
+
if (n.state !== "alive") continue;
|
|
2829
|
+
const key = n.owner ?? "\uFF08\u65E0\u4E3B\uFF09";
|
|
2830
|
+
const list = byOwner.get(key) ?? [];
|
|
2831
|
+
list.push({ id: n.id, type: n.type, byteSize: n.byteSize, owner: n.owner, state: n.state, sourceLocation: n.sourceLocation, borrowedBy: borrowedBy.get(n.id) ?? [] });
|
|
2832
|
+
byOwner.set(key, list);
|
|
2833
|
+
}
|
|
2834
|
+
return {
|
|
2835
|
+
summary: { alive: counters.alive, total: counters.total, bytesAlive: counters.bytesAlive, byType: counters.byType },
|
|
2836
|
+
diagnosis: {
|
|
2837
|
+
orphans: diagnosis.orphans.map((o) => ({ id: o.id, type: o.type, byteSize: o.byteSize, sourceLocation: o.sourceLocation })),
|
|
2838
|
+
leaks: diagnosis.leaks.map((l) => ({ resourceId: l.resourceId, type: l.type, byteSize: l.byteSize, sourceLocation: l.sourceLocation, referenceChain: l.referenceChain })),
|
|
2839
|
+
longBorrows: diagnosis.longBorrows.map((b) => ({ resourceId: b.resourceId, borrowedBy: b.borrowedBy, owner: b.owner })),
|
|
2840
|
+
crossPageRefs: diagnosis.crossPageRefs.map((c) => ({ resourceId: c.resourceId, owner: c.owner, heldBy: c.heldBy }))
|
|
2841
|
+
},
|
|
2842
|
+
resources: [...byOwner.entries()].map(([owner, items]) => ({ owner, items })),
|
|
2843
|
+
timeline: {
|
|
2844
|
+
events: events.map((e) => ({ ts: e.ts, kind: e.kind, id: e.id, owner: e.owner, type: e.type, byteSize: e.byteSize, sourceLocation: e.sourceLocation, from: e.from, to: e.to, matchedAllocId: e.matchedAlloc?.id })),
|
|
2845
|
+
unpairedIds: tl.unpairedAllocs.map((a) => a.id ?? ""),
|
|
2846
|
+
truncated
|
|
2847
|
+
}
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
return {
|
|
2851
|
+
collect,
|
|
2852
|
+
dispose() {
|
|
2853
|
+
disposed = true;
|
|
2854
|
+
history.dispose();
|
|
2855
|
+
}
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
// src/component-trace.ts
|
|
2860
|
+
import { serializeState } from "@proteus-vue/devtools-runtime";
|
|
2861
|
+
function buildDomTree(el, depth = 0) {
|
|
2862
|
+
if (depth > 4) return null;
|
|
2863
|
+
const node = { tag: el.tagName.toLowerCase(), children: [] };
|
|
2864
|
+
if (el.id) node.id = el.id;
|
|
2865
|
+
const cls = Array.from(el.classList);
|
|
2866
|
+
if (cls.length) node.cls = cls.slice(0, 10);
|
|
2867
|
+
let count = 0;
|
|
2868
|
+
for (const child of Array.from(el.children)) {
|
|
2869
|
+
if (count >= 20) break;
|
|
2870
|
+
const sub = buildDomTree(child, depth + 1);
|
|
2871
|
+
if (sub) {
|
|
2872
|
+
node.children.push(sub);
|
|
2873
|
+
count++;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
return node;
|
|
2877
|
+
}
|
|
2878
|
+
function installComponentTrace(app, bus) {
|
|
2879
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
2880
|
+
const elements = /* @__PURE__ */ new Map();
|
|
2881
|
+
let seq = 0;
|
|
2882
|
+
function getId(instance) {
|
|
2883
|
+
const id = ids.get(instance);
|
|
2884
|
+
if (id !== void 0) return id;
|
|
2885
|
+
const next = ++seq;
|
|
2886
|
+
ids.set(instance, next);
|
|
2887
|
+
return next;
|
|
2888
|
+
}
|
|
2889
|
+
function getRootEl(self) {
|
|
2890
|
+
const el = self.$el;
|
|
2891
|
+
if (!el || typeof el !== "object") return null;
|
|
2892
|
+
const node = el;
|
|
2893
|
+
if (node.nodeType === 1) return el;
|
|
2894
|
+
return node.firstElementChild ?? null;
|
|
2895
|
+
}
|
|
2896
|
+
function snapshot(self) {
|
|
2897
|
+
const out = {};
|
|
2898
|
+
if (self.$props && typeof self.$props === "object") out.props = serializeState(self.$props);
|
|
2899
|
+
const data = self.$data && typeof self.$data === "object" ? self.$data : void 0;
|
|
2900
|
+
const setupState = self.$?.setupState;
|
|
2901
|
+
if (data && Object.keys(data).length) {
|
|
2902
|
+
out.state = serializeState(data);
|
|
2903
|
+
} else if (setupState && typeof setupState === "object" && Object.keys(setupState).length) {
|
|
2904
|
+
out.state = serializeState(setupState);
|
|
2905
|
+
}
|
|
2906
|
+
return out;
|
|
2907
|
+
}
|
|
2908
|
+
app.mixin({
|
|
2909
|
+
mounted() {
|
|
2910
|
+
const self = this;
|
|
2911
|
+
const parentId = self.$parent ? getId(self.$parent) : void 0;
|
|
2912
|
+
const name = self.$options?.name ?? self.$options?.__name ?? "Anonymous";
|
|
2913
|
+
const id = getId(self);
|
|
2914
|
+
const el = getRootEl(self);
|
|
2915
|
+
elements.set(id, el);
|
|
2916
|
+
bus.emit("component", "point", "component.mount", { id, name, parentId, ...snapshot(self) }, "comp-" + id);
|
|
2917
|
+
},
|
|
2918
|
+
unmounted() {
|
|
2919
|
+
const id = getId(this);
|
|
2920
|
+
elements.delete(id);
|
|
2921
|
+
bus.emit("component", "point", "component.unmount", { id }, "comp-" + id);
|
|
2922
|
+
}
|
|
2923
|
+
});
|
|
2924
|
+
return {
|
|
2925
|
+
dispose() {
|
|
2926
|
+
},
|
|
2927
|
+
getElement(id) {
|
|
2928
|
+
return elements.get(id) ?? null;
|
|
2929
|
+
}
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
// src/install.ts
|
|
2934
|
+
import { getProteusTraceBus, createStoreTracer } from "@proteus-vue/devtools-runtime";
|
|
2935
|
+
import { setupDevtoolsPlugin } from "@vue/devtools-api";
|
|
2936
|
+
import { setCapabilityTraceBus, globalRegistry } from "@proteus-vue/capabilities";
|
|
2937
|
+
|
|
2938
|
+
// src/device-info.ts
|
|
2939
|
+
import { detectPlatform } from "@proteus-vue/capabilities";
|
|
2940
|
+
function detectRuntimePlatform() {
|
|
2941
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") return "web";
|
|
2942
|
+
return detectPlatform();
|
|
2943
|
+
}
|
|
2944
|
+
function detectBrowserVersion(ua) {
|
|
2945
|
+
if (!ua) return void 0;
|
|
2946
|
+
const m = ua.match(/(?:Chrome|Firefox|Safari|Edg)\/([\d.]+)/);
|
|
2947
|
+
if (!m) return void 0;
|
|
2948
|
+
const name = /Firefox/.test(ua) ? "Firefox" : /Edg\//.test(ua) ? "Edge" : /Chrome/.test(ua) ? "Chrome" : "Safari";
|
|
2949
|
+
return name + " " + m[1];
|
|
2950
|
+
}
|
|
2951
|
+
function detectMpLibVersion(wxGlobal) {
|
|
2952
|
+
const g = wxGlobal;
|
|
2953
|
+
try {
|
|
2954
|
+
return g?.getAppBaseInfo?.()?.SDKVersion ?? g?.getSystemInfoSync?.()?.SDKVersion;
|
|
2955
|
+
} catch {
|
|
2956
|
+
return void 0;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// src/install.ts
|
|
2961
|
+
var panelMounted = false;
|
|
2962
|
+
var panel = null;
|
|
2963
|
+
var PANEL_POS_KEY = "proteus-devtools-panel-pos";
|
|
2964
|
+
function loadPanelPos() {
|
|
2965
|
+
try {
|
|
2966
|
+
const raw = localStorage.getItem(PANEL_POS_KEY);
|
|
2967
|
+
if (!raw) return null;
|
|
2968
|
+
const p = JSON.parse(raw);
|
|
2969
|
+
if (typeof p?.left === "number" && typeof p?.top === "number") return { left: p.left, top: p.top };
|
|
2970
|
+
} catch {
|
|
2971
|
+
}
|
|
2972
|
+
return null;
|
|
2973
|
+
}
|
|
2974
|
+
function savePanelPos(left, top) {
|
|
2975
|
+
try {
|
|
2976
|
+
localStorage.setItem(PANEL_POS_KEY, JSON.stringify({ left, top }));
|
|
2977
|
+
} catch {
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
function makeFloatingDraggable(host, handle) {
|
|
2981
|
+
handle.classList.add("pd-floating-draggable");
|
|
2982
|
+
let startX = 0;
|
|
2983
|
+
let startY = 0;
|
|
2984
|
+
let origLeft = 0;
|
|
2985
|
+
let origTop = 0;
|
|
2986
|
+
handle.addEventListener("mousedown", (e) => {
|
|
2987
|
+
if (e.target.closest("button, input, a, .pd-palette-btn")) return;
|
|
2988
|
+
startX = e.clientX;
|
|
2989
|
+
startY = e.clientY;
|
|
2990
|
+
const rect = host.getBoundingClientRect();
|
|
2991
|
+
origLeft = rect.left;
|
|
2992
|
+
origTop = rect.top;
|
|
2993
|
+
host.style.left = origLeft + "px";
|
|
2994
|
+
host.style.top = origTop + "px";
|
|
2995
|
+
host.style.right = "auto";
|
|
2996
|
+
host.style.bottom = "auto";
|
|
2997
|
+
const onMove = (ev) => {
|
|
2998
|
+
const maxLeft = Math.max(0, (typeof window !== "undefined" ? window.innerWidth : 1024) - 160);
|
|
2999
|
+
const maxTop = Math.max(0, (typeof window !== "undefined" ? window.innerHeight : 768) - 80);
|
|
3000
|
+
const left = Math.max(0, Math.min(origLeft + (ev.clientX - startX), maxLeft));
|
|
3001
|
+
const top = Math.max(0, Math.min(origTop + (ev.clientY - startY), maxTop));
|
|
3002
|
+
host.style.left = left + "px";
|
|
3003
|
+
host.style.top = top + "px";
|
|
3004
|
+
};
|
|
3005
|
+
const onUp = () => {
|
|
3006
|
+
document.removeEventListener("mousemove", onMove);
|
|
3007
|
+
document.removeEventListener("mouseup", onUp);
|
|
3008
|
+
savePanelPos(parseFloat(host.style.left) || 0, parseFloat(host.style.top) || 0);
|
|
3009
|
+
};
|
|
3010
|
+
document.addEventListener("mousemove", onMove);
|
|
3011
|
+
document.addEventListener("mouseup", onUp);
|
|
3012
|
+
e.preventDefault();
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
function mountFloatingPanel(bus, pages, applyState, selectComponent, pinia, deviceInfo, ownershipData) {
|
|
3016
|
+
if (panelMounted) return;
|
|
3017
|
+
panelMounted = true;
|
|
3018
|
+
const btn = document.createElement("button");
|
|
3019
|
+
btn.className = "pd-floating-toggle";
|
|
3020
|
+
btn.textContent = "\u25C8";
|
|
3021
|
+
btn.title = "Proteus DevTools";
|
|
3022
|
+
const host = document.createElement("div");
|
|
3023
|
+
host.className = "pd-floating-host";
|
|
3024
|
+
host.style.display = "none";
|
|
3025
|
+
document.body.appendChild(btn);
|
|
3026
|
+
document.body.appendChild(host);
|
|
3027
|
+
btn.addEventListener("click", () => {
|
|
3028
|
+
if (host.style.display !== "none") {
|
|
3029
|
+
host.style.display = "none";
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
const saved = loadPanelPos();
|
|
3033
|
+
if (saved) {
|
|
3034
|
+
host.style.left = saved.left + "px";
|
|
3035
|
+
host.style.top = saved.top + "px";
|
|
3036
|
+
host.style.right = "auto";
|
|
3037
|
+
host.style.bottom = "auto";
|
|
3038
|
+
}
|
|
3039
|
+
host.style.display = "block";
|
|
3040
|
+
if (!panel) {
|
|
3041
|
+
panel = createDevtoolsPanel(host, {
|
|
3042
|
+
source: createTraceBusSource(bus),
|
|
3043
|
+
pages,
|
|
3044
|
+
// ★M8 设备面板:环境/能力信息(navigator/screen + 能力注册表快照)
|
|
3045
|
+
deviceInfo,
|
|
3046
|
+
// ★G-43 B4 所有权面板:视图数据(tracer 采集闭包)
|
|
3047
|
+
ownershipData,
|
|
3048
|
+
// ★P0:时间旅行回放 / 导入快照恢复 → 逐 store $patch 写回(结构类型零硬依赖;无 pinia → 仅面板内展示)
|
|
3049
|
+
onApplyState: applyState,
|
|
3050
|
+
// ★P1:组件视图选中 → 页面元素高亮(scrollIntoView + 描边闪烁)
|
|
3051
|
+
onSelectComponent: selectComponent
|
|
3052
|
+
});
|
|
3053
|
+
const header = host.querySelector(".pd-header");
|
|
3054
|
+
if (header) makeFloatingDraggable(host, header);
|
|
3055
|
+
if (pinia) {
|
|
3056
|
+
for (const entry of pinia._s) {
|
|
3057
|
+
const id = entry[0];
|
|
3058
|
+
const state = entry[1].$state;
|
|
3059
|
+
if (!state) continue;
|
|
3060
|
+
const snapshot = { id };
|
|
3061
|
+
for (const k of Object.keys(state)) if (k !== "$id") snapshot[k] = state[k];
|
|
3062
|
+
bus.emit("store", "point", "store.patch", snapshot, "store-init-" + id);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
3068
|
+
function collectDeviceInfo() {
|
|
3069
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
3070
|
+
const scr = typeof window !== "undefined" && window.screen ? window.screen : void 0;
|
|
3071
|
+
const win = typeof window !== "undefined" ? window : void 0;
|
|
3072
|
+
const runtimePlatform = detectRuntimePlatform();
|
|
3073
|
+
const perf = performance;
|
|
3074
|
+
const mem = typeof perf.memory === "object" && perf.memory !== null ? perf.memory : void 0;
|
|
3075
|
+
const libVersion = runtimePlatform === "web" ? detectBrowserVersion(nav?.userAgent) : detectMpLibVersion(globalThis.wx);
|
|
3076
|
+
function safeAreaInset(side) {
|
|
3077
|
+
if (typeof getComputedStyle !== "function" || typeof document === "undefined") return void 0;
|
|
3078
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue("env(safe-area-inset-" + side + ")").trim();
|
|
3079
|
+
const px = /^[\d.]+px$/.test(raw) ? parseFloat(raw) : NaN;
|
|
3080
|
+
return Number.isFinite(px) ? px : void 0;
|
|
3081
|
+
}
|
|
3082
|
+
return {
|
|
3083
|
+
platform: runtimePlatform,
|
|
3084
|
+
userAgent: nav?.userAgent,
|
|
3085
|
+
libVersion,
|
|
3086
|
+
screen: {
|
|
3087
|
+
dpr: win?.devicePixelRatio || 1,
|
|
3088
|
+
width: scr?.width ?? 0,
|
|
3089
|
+
height: scr?.height ?? 0,
|
|
3090
|
+
safeTop: safeAreaInset("top"),
|
|
3091
|
+
safeBottom: safeAreaInset("bottom")
|
|
3092
|
+
},
|
|
3093
|
+
memory: mem ? { jsHeapLimit: mem.jsHeapLimit, totalJSHeapSize: mem.totalJSHeapSize, usedJSHeapSize: mem.usedJSHeapSize } : void 0,
|
|
3094
|
+
// ★能力表按真实运行平台探测(web 有 window → 解析 web adapter)
|
|
3095
|
+
capabilities: globalRegistry.snapshot(runtimePlatform)
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
function installProteusDevtools(app, options = {}) {
|
|
3099
|
+
const bus = options.traceBus ?? getProteusTraceBus();
|
|
3100
|
+
setCapabilityTraceBus(bus);
|
|
3101
|
+
let storeTracer = null;
|
|
3102
|
+
if (options.pinia) {
|
|
3103
|
+
storeTracer = createStoreTracer(options.pinia, bus);
|
|
3104
|
+
}
|
|
3105
|
+
const componentTrace = installComponentTrace(app, bus);
|
|
3106
|
+
const ownershipDisabled = options.ownership === false;
|
|
3107
|
+
const ownershipOpts = typeof options.ownership === "object" ? options.ownership : void 0;
|
|
3108
|
+
const ownershipTracer = ownershipDisabled ? null : createOwnershipTracer({ graph: ownershipOpts?.graph, history: ownershipOpts?.history });
|
|
3109
|
+
const collectOwnershipData = ownershipTracer ? () => ownershipTracer.collect() : void 0;
|
|
3110
|
+
function highlightComponent(id) {
|
|
3111
|
+
const el = componentTrace.getElement(id);
|
|
3112
|
+
if (!el) return;
|
|
3113
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
3114
|
+
el.classList.add("pd-cmp-highlight");
|
|
3115
|
+
setTimeout(() => el.classList.remove("pd-cmp-highlight"), 1500);
|
|
3116
|
+
const dom = buildDomTree(el);
|
|
3117
|
+
bus.emit("component", "point", "component.inspect", { id, dom }, "comp-" + id);
|
|
3118
|
+
}
|
|
3119
|
+
const navRecords = [];
|
|
3120
|
+
let navCurrent = "";
|
|
3121
|
+
let navInflight = null;
|
|
3122
|
+
const offNav = bus.on((e) => {
|
|
3123
|
+
if (e.source !== "router") return;
|
|
3124
|
+
if (e.phase === "start" && /nav/i.test(e.name)) {
|
|
3125
|
+
const p = e.payload ?? {};
|
|
3126
|
+
navInflight = { from: p.from?.path ?? "?", to: p.to?.path ?? e.name, query: p.to?.query, start: e.timestamp, traceId: e.traceId, guards: [] };
|
|
3127
|
+
} else if (navInflight && e.phase === "point" && /guard/i.test(e.name)) {
|
|
3128
|
+
let result = "next";
|
|
3129
|
+
if (/redirect/i.test(e.name)) result = "redirect";
|
|
3130
|
+
else if (/cancel/i.test(e.name)) result = "cancel";
|
|
3131
|
+
else if (/error/i.test(e.name)) result = "error";
|
|
3132
|
+
navInflight.guards.push({ name: e.name, result });
|
|
3133
|
+
} else if (e.phase === "end" && /nav/i.test(e.name) && navInflight) {
|
|
3134
|
+
const rec = {
|
|
3135
|
+
from: navInflight.from,
|
|
3136
|
+
to: navInflight.to,
|
|
3137
|
+
query: navInflight.query,
|
|
3138
|
+
durationMs: Math.max(0, e.timestamp - navInflight.start),
|
|
3139
|
+
timestamp: e.timestamp,
|
|
3140
|
+
traceId: e.traceId ?? navInflight.traceId,
|
|
3141
|
+
guards: navInflight.guards
|
|
3142
|
+
};
|
|
3143
|
+
navRecords.push(rec);
|
|
3144
|
+
navCurrent = rec.to;
|
|
3145
|
+
if (navRecords.length > 50) navRecords.shift();
|
|
3146
|
+
navInflight = null;
|
|
3147
|
+
}
|
|
3148
|
+
});
|
|
3149
|
+
setupDevtoolsPlugin({ ...PROTEUS_DEVTOOLS_PLUGIN_DESCRIPTOR, app }, (devtoolsApi) => {
|
|
3150
|
+
installProteusTimeline(devtoolsApi, { source: createTraceBusSource(bus) });
|
|
3151
|
+
installProteusInspectors(devtoolsApi, {
|
|
3152
|
+
getConfig: options.getConfig,
|
|
3153
|
+
setConfig: options.setConfig,
|
|
3154
|
+
getStyleSafetyRecords: options.styleGuard ? () => options.styleGuard?.records() ?? [] : void 0,
|
|
3155
|
+
pages: options.pages,
|
|
3156
|
+
getRouterState: () => ({ currentRoute: navCurrent || void 0, records: [...navRecords] })
|
|
3157
|
+
});
|
|
3158
|
+
});
|
|
3159
|
+
if (options.mount !== false) {
|
|
3160
|
+
mountFloatingPanel(
|
|
3161
|
+
bus,
|
|
3162
|
+
options.pages,
|
|
3163
|
+
(stores) => {
|
|
3164
|
+
if (!options.pinia) return;
|
|
3165
|
+
for (const s of stores) {
|
|
3166
|
+
const store = options.pinia._s.get(s.id);
|
|
3167
|
+
store?.$patch?.(s.state);
|
|
3168
|
+
}
|
|
3169
|
+
},
|
|
3170
|
+
highlightComponent,
|
|
3171
|
+
options.pinia,
|
|
3172
|
+
collectDeviceInfo,
|
|
3173
|
+
collectOwnershipData
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
let remoteBridge = null;
|
|
3177
|
+
if (options.remote) {
|
|
3178
|
+
const remoteOpts = typeof options.remote === "object" ? options.remote : null;
|
|
3179
|
+
const path = remoteOpts !== null && remoteOpts.path ? remoteOpts.path : "/proteus-source";
|
|
3180
|
+
const protocol = typeof location !== "undefined" && location.protocol === "https:" ? "wss" : "ws";
|
|
3181
|
+
const url = `${protocol}://${typeof location !== "undefined" ? location.host : "localhost"}${path}`;
|
|
3182
|
+
remoteBridge = createTraceBusWsBridge(bus, {
|
|
3183
|
+
url,
|
|
3184
|
+
appInfo: remoteOpts !== null && remoteOpts.appInfo ? remoteOpts.appInfo : () => options.pages,
|
|
3185
|
+
// ★M8 设备面板:应用侧环境/能力上报(与本地面板同源采集闭包)
|
|
3186
|
+
deviceInfo: collectDeviceInfo,
|
|
3187
|
+
// ★G-43 B4 所有权面板:应用侧视图数据上报(与本地面板同源 tracer)
|
|
3188
|
+
ownership: collectOwnershipData,
|
|
3189
|
+
// ★远程时间旅行:面板 Proteus.restoreStores 命令 → 应用侧逐 store $patch 恢复(复用 applyState 语义)
|
|
3190
|
+
onRestoreStores: (stores) => {
|
|
3191
|
+
if (!options.pinia) return;
|
|
3192
|
+
for (const s of stores) {
|
|
3193
|
+
const store = options.pinia._s.get(s.id);
|
|
3194
|
+
store?.$patch?.(s.state);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
let offHmr = null;
|
|
3200
|
+
if (options.hmr) {
|
|
3201
|
+
const offs = [];
|
|
3202
|
+
offs.push(options.hmr.on("vite:beforeUpdate", () => bus.emit("hmr", "point", "vite:update")) ?? null);
|
|
3203
|
+
offs.push(options.hmr.on("vite:beforeFullReload", () => bus.emit("hmr", "point", "vite:full-reload")) ?? null);
|
|
3204
|
+
offs.push(
|
|
3205
|
+
options.hmr.on(
|
|
3206
|
+
"vite:error",
|
|
3207
|
+
(err) => bus.emit("hmr", "error", "vite:error", { message: err instanceof Error ? err.message : String(err) })
|
|
3208
|
+
) ?? null
|
|
3209
|
+
);
|
|
3210
|
+
offHmr = () => {
|
|
3211
|
+
for (const f of offs) f?.();
|
|
3212
|
+
};
|
|
3213
|
+
}
|
|
3214
|
+
return {
|
|
3215
|
+
traceBus: bus,
|
|
3216
|
+
destroy() {
|
|
3217
|
+
storeTracer?.dispose();
|
|
3218
|
+
componentTrace.dispose();
|
|
3219
|
+
ownershipTracer?.dispose();
|
|
3220
|
+
offNav();
|
|
3221
|
+
remoteBridge?.close();
|
|
3222
|
+
offHmr?.();
|
|
3223
|
+
}
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/plugins/network.ts
|
|
3228
|
+
function createNetworkPlugin() {
|
|
3229
|
+
return {
|
|
3230
|
+
name: "@proteus-vue/devtools-plugin-network",
|
|
3231
|
+
version: "0.1.0",
|
|
3232
|
+
setup(ctx) {
|
|
3233
|
+
const rows = [];
|
|
3234
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
3235
|
+
ctx.bus.on((e) => {
|
|
3236
|
+
if (e.source !== "api") return;
|
|
3237
|
+
if (e.phase === "start") {
|
|
3238
|
+
inflight.set(e.traceId ?? e.name + "-" + e.timestamp, e.timestamp);
|
|
3239
|
+
} else if (e.phase === "end" || e.phase === "error") {
|
|
3240
|
+
const start = inflight.get(e.traceId ?? e.name + "-" + e.timestamp);
|
|
3241
|
+
rows.unshift({
|
|
3242
|
+
name: e.name,
|
|
3243
|
+
ms: start !== void 0 ? Math.max(0, e.timestamp - start) : 0,
|
|
3244
|
+
ok: e.phase === "end",
|
|
3245
|
+
ts: e.timestamp
|
|
3246
|
+
});
|
|
3247
|
+
if (rows.length > 200) rows.pop();
|
|
3248
|
+
}
|
|
3249
|
+
});
|
|
3250
|
+
ctx.panel.addView("network", {
|
|
3251
|
+
label: "network",
|
|
3252
|
+
icon: "\u21C5",
|
|
3253
|
+
render(container) {
|
|
3254
|
+
container.replaceChildren();
|
|
3255
|
+
if (rows.length === 0) {
|
|
3256
|
+
const empty = document.createElement("div");
|
|
3257
|
+
empty.className = "pd-empty";
|
|
3258
|
+
empty.textContent = "\u6682\u65E0 API \u4E8B\u4EF6\uFF08source=api\uFF09";
|
|
3259
|
+
container.appendChild(empty);
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
const list = document.createElement("div");
|
|
3263
|
+
list.className = "pd-net";
|
|
3264
|
+
for (const r of rows.slice(0, 50)) {
|
|
3265
|
+
const row = document.createElement("div");
|
|
3266
|
+
row.className = "pd-net-row" + (r.ok ? "" : " pd-net-error");
|
|
3267
|
+
const name = document.createElement("span");
|
|
3268
|
+
name.className = "pd-net-name";
|
|
3269
|
+
name.textContent = r.name;
|
|
3270
|
+
const meta = document.createElement("span");
|
|
3271
|
+
meta.className = "pd-net-meta";
|
|
3272
|
+
meta.textContent = r.ms + "ms" + (r.ok ? "" : " \u2715");
|
|
3273
|
+
row.appendChild(name);
|
|
3274
|
+
row.appendChild(meta);
|
|
3275
|
+
list.appendChild(row);
|
|
3276
|
+
}
|
|
3277
|
+
container.appendChild(list);
|
|
3278
|
+
}
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
export {
|
|
3284
|
+
PROTEUS_DEVTOOLS_PLUGIN_DESCRIPTOR,
|
|
3285
|
+
attachTip,
|
|
3286
|
+
bindTooltip,
|
|
3287
|
+
buildDomTree,
|
|
3288
|
+
createCommandRegistry,
|
|
3289
|
+
createDevtoolsPanel,
|
|
3290
|
+
createDevtoolsWsSource,
|
|
3291
|
+
createMemoryStorage,
|
|
3292
|
+
createNetworkPlugin,
|
|
3293
|
+
createOwnershipTracer,
|
|
3294
|
+
createPluginRegistry,
|
|
3295
|
+
createTimelineZoom,
|
|
3296
|
+
createTooltipLayer,
|
|
3297
|
+
createTraceBusSource,
|
|
3298
|
+
createTraceBusWsBridge,
|
|
3299
|
+
detectBrowserVersion,
|
|
3300
|
+
detectMpLibVersion,
|
|
3301
|
+
detectRuntimePlatform,
|
|
3302
|
+
findSensitiveKeys,
|
|
3303
|
+
installComponentTrace,
|
|
3304
|
+
installProteusDevtools,
|
|
3305
|
+
installProteusInspectors,
|
|
3306
|
+
installProteusTimeline,
|
|
3307
|
+
parseSession,
|
|
3308
|
+
parseStoreSnapshot,
|
|
3309
|
+
renderComponents,
|
|
3310
|
+
renderDevice,
|
|
3311
|
+
renderErrors,
|
|
3312
|
+
renderFlamegraph,
|
|
3313
|
+
renderGraph,
|
|
3314
|
+
renderOwnership,
|
|
3315
|
+
renderPages,
|
|
3316
|
+
renderRoute,
|
|
3317
|
+
renderState,
|
|
3318
|
+
renderTimeline,
|
|
3319
|
+
resolveActivationOrder,
|
|
3320
|
+
resolveTipData,
|
|
3321
|
+
serializeSession,
|
|
3322
|
+
serializeStoreSnapshot
|
|
3323
|
+
};
|