@atolljs/devtools 0.1.7 → 0.1.8
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 +408 -71
- package/app/api.js +68 -0
- package/app/index.html +161 -2
- package/app/main.js +877 -127
- package/app/panels/actions.js +324 -0
- package/app/panels/audit-rules.js +728 -0
- package/app/panels/audits.js +438 -0
- package/app/panels/inspect-island.js +351 -0
- package/app/panels/memory-values.js +287 -0
- package/app/panels/performance.js +304 -0
- package/app/panels/reactivity.js +271 -0
- package/app/panels/recorder.js +344 -0
- package/app/panels/recording-format.js +166 -0
- package/app/panels/route.d.ts +44 -0
- package/app/panels/route.js +264 -0
- package/app/panels/shell.js +789 -0
- package/app/panels/toast.js +38 -0
- package/dist/bin.js +704 -5
- package/dist/chunk-57NGHQUL.js +344 -0
- package/dist/{chunk-4N3NBFL3.js → chunk-AGT6VNZI.js} +179 -217
- package/dist/chunk-WIL2P7HG.js +661 -0
- package/dist/client.d.ts +7 -0
- package/dist/control.d.ts +4 -0
- package/dist/index.js +2 -1
- package/dist/node.js +2 -1
- package/dist/nodeWs.d.ts +1 -0
- package/dist/otel.d.ts +35 -0
- package/dist/otel.js +87 -0
- package/dist/otelMap.d.ts +232 -0
- package/dist/otelTransport.d.ts +69 -0
- package/dist/overlay.d.ts +16 -2
- package/dist/probes.d.ts +7 -0
- package/dist/protocol.d.ts +46 -4
- package/dist/server.d.ts +19 -0
- package/dist/server.js +61 -3
- package/package.json +6 -2
- package/src/bin.ts +27 -2
- package/src/broadcast.ts +6 -0
- package/src/client.ts +26 -2
- package/src/control.ts +51 -0
- package/src/nodeWs.ts +7 -1
- package/src/otel.ts +140 -0
- package/src/otelMap.ts +661 -0
- package/src/otelTransport.ts +286 -0
- package/src/overlay.ts +153 -27
- package/src/probes.ts +155 -0
- package/src/protocol.ts +52 -4
- package/src/server.ts +83 -3
|
@@ -1,199 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
var TEXTISH = /text|json|javascript|xml|html|css|svg|csv|urlencoded|graphql/;
|
|
13
|
-
var headerList = (h) => h ? [...h.entries()].slice(0, 64) : void 0;
|
|
14
|
-
var bodyPreview = (body) => {
|
|
15
|
-
if (body === null || body === void 0) return void 0;
|
|
16
|
-
if (typeof body === "string") return body.slice(0, BODY_PREVIEW);
|
|
17
|
-
if (body instanceof URLSearchParams) return String(body).slice(0, BODY_PREVIEW);
|
|
18
|
-
if (body instanceof Blob) return `[${body.type || "blob"} ${body.size}B]`;
|
|
19
|
-
if (body instanceof ArrayBuffer) return `[ArrayBuffer ${body.byteLength}B]`;
|
|
20
|
-
if (ArrayBuffer.isView(body)) return `[${body.constructor.name} ${body.byteLength}B]`;
|
|
21
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
|
|
22
|
-
return "[stream]";
|
|
23
|
-
};
|
|
24
|
-
var readPreview = async (res, cap) => {
|
|
25
|
-
const reader = res.body?.getReader();
|
|
26
|
-
if (!reader) return void 0;
|
|
27
|
-
const chunks = [];
|
|
28
|
-
let n = 0;
|
|
1
|
+
import {
|
|
2
|
+
installFetchProbe,
|
|
3
|
+
installJankProbe,
|
|
4
|
+
installMemoryProbe,
|
|
5
|
+
runDevtoolsCommand,
|
|
6
|
+
setDevtoolsSink
|
|
7
|
+
} from "./chunk-57NGHQUL.js";
|
|
8
|
+
|
|
9
|
+
// src/control.ts
|
|
10
|
+
var jsonSafe = (v) => {
|
|
11
|
+
if (v === void 0) return void 0;
|
|
29
12
|
try {
|
|
30
|
-
|
|
31
|
-
const { done, value } = await reader.read();
|
|
32
|
-
if (done || !value) break;
|
|
33
|
-
chunks.push(value);
|
|
34
|
-
n += value.byteLength;
|
|
35
|
-
}
|
|
13
|
+
return JSON.parse(JSON.stringify(v));
|
|
36
14
|
} catch {
|
|
15
|
+
return String(v);
|
|
37
16
|
}
|
|
38
|
-
if (!n) return void 0;
|
|
39
|
-
const all = new Uint8Array(n);
|
|
40
|
-
let off = 0;
|
|
41
|
-
for (const c of chunks) {
|
|
42
|
-
all.set(c, off);
|
|
43
|
-
off += c.byteLength;
|
|
44
|
-
}
|
|
45
|
-
return new TextDecoder().decode(all) + (n >= cap ? "\u2026" : "");
|
|
46
17
|
};
|
|
47
|
-
var
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
queue: seg(best.fetchStart, best.domainLookupStart),
|
|
60
|
-
dns: seg(best.domainLookupStart, best.domainLookupEnd),
|
|
61
|
-
tcp: best.secureConnectionStart > 0 ? seg(best.connectStart, best.secureConnectionStart) : seg(best.connectStart, best.connectEnd),
|
|
62
|
-
tls: best.secureConnectionStart > 0 ? seg(best.secureConnectionStart, best.connectEnd) : 0,
|
|
63
|
-
wait: seg(best.requestStart, best.responseStart),
|
|
64
|
-
download: seg(best.responseStart, best.responseEnd),
|
|
65
|
-
transferSize: best.transferSize,
|
|
66
|
-
encodedSize: best.encodedBodySize,
|
|
67
|
-
decodedSize: best.decodedBodySize
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
18
|
+
var answerControl = async (req, reply) => {
|
|
19
|
+
try {
|
|
20
|
+
const result = await runDevtoolsCommand(req.cmd, req.args ?? {});
|
|
21
|
+
reply({ type: "control-result", sessionId: req.sessionId, id: req.id, ok: true, result: jsonSafe(result) });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
reply({
|
|
24
|
+
type: "control-result",
|
|
25
|
+
sessionId: req.sessionId,
|
|
26
|
+
id: req.id,
|
|
27
|
+
ok: false,
|
|
28
|
+
error: err instanceof Error ? err.message : String(err)
|
|
29
|
+
});
|
|
71
30
|
}
|
|
72
|
-
return void 0;
|
|
73
31
|
};
|
|
74
|
-
var
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const url = isReq ? input.url : String(input);
|
|
83
|
-
const abs = (() => {
|
|
84
|
-
try {
|
|
85
|
-
return new URL(url, self.location?.href).href;
|
|
86
|
-
} catch {
|
|
87
|
-
return url;
|
|
88
|
-
}
|
|
89
|
-
})();
|
|
90
|
-
const method = (init?.method ?? (isReq ? input.method : "GET")).toUpperCase();
|
|
91
|
-
const reqClone = isReq && !init?.body ? input.clone() : null;
|
|
92
|
-
const reqHeaders = headerList(
|
|
93
|
-
init?.headers ? new Headers(init.headers) : isReq ? input.headers : void 0
|
|
94
|
-
);
|
|
95
|
-
let reqBody = bodyPreview(init?.body);
|
|
96
|
-
try {
|
|
97
|
-
const res = await original.call(scope, input, init);
|
|
98
|
-
const len = res.headers.get("content-length");
|
|
99
|
-
emitDevtools({
|
|
100
|
-
type: "net:fetch",
|
|
101
|
-
id,
|
|
102
|
-
url: url.slice(0, 300),
|
|
103
|
-
method,
|
|
104
|
-
status: res.status,
|
|
105
|
-
ms: performance.now() - t0,
|
|
106
|
-
bytes: len === null ? void 0 : Number(len),
|
|
107
|
-
reqHeaders,
|
|
108
|
-
reqBody
|
|
109
|
-
});
|
|
110
|
-
const resClone = res.clone();
|
|
111
|
-
void (async () => {
|
|
112
|
-
reqBody ??= reqClone ? await reqClone.text().then((t2) => t2.slice(0, BODY_PREVIEW)).catch(() => void 0) : void 0;
|
|
113
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
114
|
-
const resBody = TEXTISH.test(ct) ? await readPreview(resClone, BODY_PREVIEW).catch(() => void 0) : void 0;
|
|
115
|
-
const t = await findTiming(abs, t0);
|
|
116
|
-
emitDevtools({
|
|
117
|
-
type: "net:fetch-detail",
|
|
118
|
-
id,
|
|
119
|
-
reqBody,
|
|
120
|
-
resHeaders: headerList(res.headers),
|
|
121
|
-
resBody,
|
|
122
|
-
timing: t,
|
|
123
|
-
transferSize: t?.transferSize,
|
|
124
|
-
encodedSize: t?.encodedSize,
|
|
125
|
-
decodedSize: t?.decodedSize
|
|
126
|
-
});
|
|
127
|
-
})();
|
|
128
|
-
return res;
|
|
129
|
-
} catch (err) {
|
|
130
|
-
emitDevtools({
|
|
131
|
-
type: "net:fetch",
|
|
132
|
-
id,
|
|
133
|
-
url: url.slice(0, 300),
|
|
134
|
-
method,
|
|
135
|
-
ms: performance.now() - t0,
|
|
136
|
-
error: err instanceof Error ? err.message : String(err),
|
|
137
|
-
reqHeaders,
|
|
138
|
-
reqBody
|
|
139
|
-
});
|
|
140
|
-
throw err;
|
|
141
|
-
}
|
|
32
|
+
var captureEnv = () => {
|
|
33
|
+
const g = globalThis;
|
|
34
|
+
return {
|
|
35
|
+
crossOriginIsolated: g.crossOriginIsolated,
|
|
36
|
+
sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
|
|
37
|
+
hardwareConcurrency: g.navigator?.hardwareConcurrency,
|
|
38
|
+
userAgent: g.navigator?.userAgent,
|
|
39
|
+
node: g.navigator ? void 0 : g.process?.version
|
|
142
40
|
};
|
|
143
|
-
wrapped[PROBED] = true;
|
|
144
|
-
scope.fetch = wrapped;
|
|
145
|
-
};
|
|
146
|
-
var memProbed = /* @__PURE__ */ new WeakSet();
|
|
147
|
-
var installMemoryProbe = (scope, intervalMs = 2500) => {
|
|
148
|
-
if (memProbed.has(scope)) return;
|
|
149
|
-
const perf = scope.performance;
|
|
150
|
-
if (!perf) return;
|
|
151
|
-
const mem = perf.memory;
|
|
152
|
-
const inWorker = typeof WorkerGlobalScope !== "undefined" && globalThis.self instanceof WorkerGlobalScope;
|
|
153
|
-
const measure = perf.measureUserAgentSpecificMemory?.bind(perf);
|
|
154
|
-
const procMem = globalThis.process?.memoryUsage;
|
|
155
|
-
if (!mem && (!measure || inWorker) && !procMem) return;
|
|
156
|
-
memProbed.add(scope);
|
|
157
|
-
if (measure && !inWorker) {
|
|
158
|
-
const timer2 = setInterval(() => {
|
|
159
|
-
measure().then((res) => {
|
|
160
|
-
const contexts = (res.breakdown ?? []).map((b) => ({
|
|
161
|
-
bytes: b.bytes,
|
|
162
|
-
scope: b.attribution?.[0]?.scope,
|
|
163
|
-
url: b.attribution?.[0]?.url
|
|
164
|
-
})).sort((a, b) => b.bytes - a.bytes).slice(0, 24);
|
|
165
|
-
emitDevtools({
|
|
166
|
-
type: "runtime:memory",
|
|
167
|
-
heapBytes: res.bytes,
|
|
168
|
-
heapLimitBytes: mem?.jsHeapSizeLimit,
|
|
169
|
-
contexts
|
|
170
|
-
});
|
|
171
|
-
}).catch(() => {
|
|
172
|
-
if (mem) {
|
|
173
|
-
emitDevtools({
|
|
174
|
-
type: "runtime:memory",
|
|
175
|
-
heapBytes: mem.usedJSHeapSize,
|
|
176
|
-
heapLimitBytes: mem.jsHeapSizeLimit
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
}, Math.max(intervalMs, 4e3));
|
|
181
|
-
timer2.unref?.();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const timer = setInterval(() => {
|
|
185
|
-
if (mem) {
|
|
186
|
-
emitDevtools({
|
|
187
|
-
type: "runtime:memory",
|
|
188
|
-
heapBytes: mem.usedJSHeapSize,
|
|
189
|
-
heapLimitBytes: mem.jsHeapSizeLimit
|
|
190
|
-
});
|
|
191
|
-
} else if (procMem) {
|
|
192
|
-
const u = procMem();
|
|
193
|
-
emitDevtools({ type: "runtime:memory", heapBytes: u.heapUsed, rssBytes: u.rss });
|
|
194
|
-
}
|
|
195
|
-
}, intervalMs);
|
|
196
|
-
timer.unref?.();
|
|
197
41
|
};
|
|
198
42
|
|
|
199
43
|
// src/protocol.ts
|
|
@@ -225,6 +69,8 @@ function connectBroadcast(opts, session) {
|
|
|
225
69
|
if (m.data?.type === "view") {
|
|
226
70
|
hello();
|
|
227
71
|
for (const events of tail) post({ type: "batch", session, events });
|
|
72
|
+
} else if (m.data?.type === "control" && m.data.sessionId === session.id) {
|
|
73
|
+
void answerControl(m.data, (res) => post(res));
|
|
228
74
|
}
|
|
229
75
|
};
|
|
230
76
|
const timer = setInterval(flush, opts.flushMs ?? 100);
|
|
@@ -237,6 +83,8 @@ function connectBroadcast(opts, session) {
|
|
|
237
83
|
if (typeof addEventListener === "function") addEventListener("pagehide", onHide);
|
|
238
84
|
if (opts.network !== false) installFetchProbe(globalThis);
|
|
239
85
|
if (opts.memory !== false) installMemoryProbe(globalThis);
|
|
86
|
+
const releaseJank = opts.jank !== false ? installJankProbe() : () => {
|
|
87
|
+
};
|
|
240
88
|
setDevtoolsSink((event) => {
|
|
241
89
|
if (closed) return;
|
|
242
90
|
pending.push(event);
|
|
@@ -255,6 +103,7 @@ function connectBroadcast(opts, session) {
|
|
|
255
103
|
bye();
|
|
256
104
|
setDevtoolsSink(null);
|
|
257
105
|
clearInterval(timer);
|
|
106
|
+
releaseJank();
|
|
258
107
|
if (typeof removeEventListener === "function") removeEventListener("pagehide", onHide);
|
|
259
108
|
bc.close();
|
|
260
109
|
}
|
|
@@ -269,8 +118,10 @@ var makeSession = (opts) => {
|
|
|
269
118
|
return {
|
|
270
119
|
id: `s-${Date.now().toString(36)}-${++sessionSeq}`,
|
|
271
120
|
name: opts.session?.name,
|
|
121
|
+
framework: opts.session?.framework,
|
|
272
122
|
runtime,
|
|
273
|
-
hint
|
|
123
|
+
hint,
|
|
124
|
+
env: captureEnv()
|
|
274
125
|
};
|
|
275
126
|
};
|
|
276
127
|
var autoBroadcast = () => typeof BroadcastChannel !== "undefined" && typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
@@ -298,6 +149,15 @@ function connectWebSocket(opts, session) {
|
|
|
298
149
|
send({ type: "hello", session });
|
|
299
150
|
flush();
|
|
300
151
|
};
|
|
152
|
+
const onMessage = (text) => {
|
|
153
|
+
let msg;
|
|
154
|
+
try {
|
|
155
|
+
msg = JSON.parse(text);
|
|
156
|
+
} catch {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (msg.type === "control") void answerControl(msg, (res) => send(res));
|
|
160
|
+
};
|
|
301
161
|
const isNode = typeof window === "undefined" || typeof window.document === "undefined";
|
|
302
162
|
if (typeof WebSocket !== "undefined") {
|
|
303
163
|
const ws = new WebSocket(url);
|
|
@@ -309,6 +169,7 @@ function connectWebSocket(opts, session) {
|
|
|
309
169
|
close: () => ws.close()
|
|
310
170
|
};
|
|
311
171
|
ws.onopen = onOpen;
|
|
172
|
+
ws.onmessage = (m) => onMessage(String(m.data));
|
|
312
173
|
ws.onclose = () => clearInterval(timer);
|
|
313
174
|
ws.onerror = () => ws.close();
|
|
314
175
|
} else if (isNode) {
|
|
@@ -321,6 +182,7 @@ function connectWebSocket(opts, session) {
|
|
|
321
182
|
const ws = m.connectNodeWebSocket(url);
|
|
322
183
|
sock = ws;
|
|
323
184
|
ws.onOpen = onOpen;
|
|
185
|
+
ws.onMessage = onMessage;
|
|
324
186
|
ws.onClose = () => clearInterval(timer);
|
|
325
187
|
}).catch(() => {
|
|
326
188
|
});
|
|
@@ -329,6 +191,8 @@ function connectWebSocket(opts, session) {
|
|
|
329
191
|
timer.unref?.();
|
|
330
192
|
if (opts.network !== false) installFetchProbe(globalThis);
|
|
331
193
|
if (opts.memory !== false) installMemoryProbe(globalThis);
|
|
194
|
+
const releaseJank = opts.jank !== false ? installJankProbe() : () => {
|
|
195
|
+
};
|
|
332
196
|
setDevtoolsSink((event) => {
|
|
333
197
|
if (closed) return;
|
|
334
198
|
pending.push(event);
|
|
@@ -346,6 +210,7 @@ function connectWebSocket(opts, session) {
|
|
|
346
210
|
flush();
|
|
347
211
|
sock?.close();
|
|
348
212
|
clearInterval(timer);
|
|
213
|
+
releaseJank();
|
|
349
214
|
}
|
|
350
215
|
};
|
|
351
216
|
}
|
|
@@ -361,41 +226,120 @@ var ANCHOR = {
|
|
|
361
226
|
bottomcenter: (m) => `left:50%;bottom:${m}px;transform:translateX(-50%)`,
|
|
362
227
|
bottomright: (m) => `right:${m}px;bottom:${m}px`
|
|
363
228
|
};
|
|
229
|
+
var MIN_W = 340;
|
|
230
|
+
var MIN_H = 220;
|
|
231
|
+
var seq = 0;
|
|
232
|
+
function parseHotkey(spec) {
|
|
233
|
+
const parts = spec.split("+").map((p) => p.trim().toLowerCase()).filter(Boolean);
|
|
234
|
+
const key = parts.pop() ?? "";
|
|
235
|
+
const mods = new Set(parts.map((m) => m === "option" ? "alt" : m === "cmd" || m === "meta" ? "meta" : m === "control" ? "ctrl" : m));
|
|
236
|
+
const code = /^[a-z]$/.test(key) ? `Key${key.toUpperCase()}` : /^[0-9]$/.test(key) ? `Digit${key}` : null;
|
|
237
|
+
return (e) => e.altKey === mods.has("alt") && e.shiftKey === mods.has("shift") && e.ctrlKey === mods.has("ctrl") && e.metaKey === mods.has("meta") && (code ? e.code === code : e.key.toLowerCase() === key);
|
|
238
|
+
}
|
|
364
239
|
function mountDevtoolsOverlay(opts = {}) {
|
|
365
240
|
const src = opts.src ?? "/__atoll/?mini=1";
|
|
366
|
-
const
|
|
367
|
-
const
|
|
241
|
+
const storeKey = opts.persist === false ? null : typeof opts.persist === "string" ? opts.persist : "atoll-devtools:overlay";
|
|
242
|
+
const load = () => {
|
|
243
|
+
if (!storeKey) return {};
|
|
244
|
+
try {
|
|
245
|
+
return JSON.parse(localStorage.getItem(storeKey) ?? "{}");
|
|
246
|
+
} catch {
|
|
247
|
+
return {};
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const saved = load();
|
|
251
|
+
const W = Math.max(MIN_W, saved.width ?? opts.width ?? 760);
|
|
252
|
+
const H = Math.max(MIN_H, saved.height ?? opts.height ?? 580);
|
|
368
253
|
const anchor = ANCHOR[opts.position ?? "bottomright"];
|
|
254
|
+
const hotkey = opts.hotkey === false ? null : opts.hotkey ?? "Alt+Shift+D";
|
|
255
|
+
const isHotkey = hotkey ? parseHotkey(hotkey) : null;
|
|
256
|
+
const hint = hotkey ? ` (${hotkey})` : "";
|
|
257
|
+
const id = `atoll-devtools-overlay-${++seq}`;
|
|
369
258
|
let open = false;
|
|
370
259
|
const btn = document.createElement("button");
|
|
260
|
+
btn.type = "button";
|
|
371
261
|
btn.textContent = "atoll devtools";
|
|
262
|
+
btn.title = `Open atoll devtools${hint}`;
|
|
263
|
+
btn.setAttribute("aria-controls", id);
|
|
264
|
+
btn.setAttribute("aria-expanded", "false");
|
|
372
265
|
btn.style.cssText = `${BTN_STYLE};${anchor(12)}`;
|
|
373
266
|
const panel = document.createElement("div");
|
|
267
|
+
panel.id = id;
|
|
268
|
+
panel.setAttribute("role", "dialog");
|
|
269
|
+
panel.setAttribute("aria-label", "atoll devtools");
|
|
374
270
|
panel.style.cssText = `${PANEL_STYLE};width:${W}px;height:${H}px;display:none;${anchor(16)}`;
|
|
375
271
|
const head = document.createElement("div");
|
|
376
272
|
head.style.cssText = "display:flex;align-items:center;gap:8px;padding:5px 10px;flex:none;border-bottom:1px solid #21262d;font:11px ui-monospace,monospace;color:#8b949e;background:#161b22;cursor:move;user-select:none";
|
|
377
|
-
|
|
273
|
+
const title = document.createElement("span");
|
|
274
|
+
title.textContent = "atoll devtools";
|
|
275
|
+
const base = src.split(/[?#]/)[0];
|
|
276
|
+
const frame = document.createElement("iframe");
|
|
277
|
+
let route = "";
|
|
278
|
+
const currentRoute = () => {
|
|
279
|
+
try {
|
|
280
|
+
const h = frame.contentWindow?.location.hash;
|
|
281
|
+
if (typeof h === "string" && h.startsWith("#/")) return h;
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
return route;
|
|
285
|
+
};
|
|
378
286
|
const pop = document.createElement("a");
|
|
379
|
-
pop.href =
|
|
287
|
+
pop.href = base;
|
|
380
288
|
pop.target = "_blank";
|
|
381
289
|
pop.rel = "noopener";
|
|
382
290
|
pop.textContent = "full page \u2197";
|
|
291
|
+
pop.title = "Open this view in the full-page dashboard";
|
|
383
292
|
pop.style.cssText = "color:#58a6ff;text-decoration:none;margin-left:auto";
|
|
293
|
+
const syncPop = () => {
|
|
294
|
+
pop.href = base + currentRoute();
|
|
295
|
+
};
|
|
296
|
+
pop.addEventListener("pointerenter", syncPop);
|
|
297
|
+
pop.addEventListener("focus", syncPop);
|
|
298
|
+
pop.addEventListener("click", syncPop);
|
|
384
299
|
const x = document.createElement("button");
|
|
385
|
-
x.
|
|
386
|
-
x.
|
|
387
|
-
x.
|
|
388
|
-
|
|
389
|
-
|
|
300
|
+
x.type = "button";
|
|
301
|
+
x.textContent = "\xD7";
|
|
302
|
+
x.title = `Close${hint}`;
|
|
303
|
+
x.setAttribute("aria-label", "Close atoll devtools");
|
|
304
|
+
x.style.cssText = "color:#8b949e;background:none;border:none;cursor:pointer;font:14px/1 ui-monospace,monospace;padding:0 2px";
|
|
305
|
+
head.append(title, pop, x);
|
|
390
306
|
frame.src = src;
|
|
307
|
+
frame.title = "atoll devtools dashboard";
|
|
391
308
|
frame.style.cssText = "flex:1;border:0;background:#0d1117;min-height:0";
|
|
392
309
|
const grip = document.createElement("div");
|
|
310
|
+
grip.setAttribute("aria-hidden", "true");
|
|
393
311
|
grip.style.cssText = "position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;background:linear-gradient(135deg,transparent 50%,#30363d 50%);border-radius:0 0 7px";
|
|
394
312
|
panel.append(head, frame, grip);
|
|
395
313
|
document.body.append(panel, btn);
|
|
396
|
-
const
|
|
314
|
+
const placeAt = (left, top) => {
|
|
315
|
+
panel.style.left = `${Math.min(Math.max(0, left), Math.max(0, innerWidth - 60))}px`;
|
|
316
|
+
panel.style.top = `${Math.min(Math.max(0, top), Math.max(0, innerHeight - 40))}px`;
|
|
317
|
+
panel.style.right = "auto";
|
|
318
|
+
panel.style.bottom = "auto";
|
|
319
|
+
panel.style.transform = "none";
|
|
320
|
+
};
|
|
321
|
+
if (typeof saved.left === "number" && typeof saved.top === "number") placeAt(saved.left, saved.top);
|
|
322
|
+
const mem = { ...saved };
|
|
323
|
+
const save = (what) => {
|
|
324
|
+
if (!storeKey) return;
|
|
325
|
+
if (what === "open") mem.open = open;
|
|
326
|
+
if (what === "size") {
|
|
327
|
+
mem.width = parseFloat(panel.style.width);
|
|
328
|
+
mem.height = parseFloat(panel.style.height);
|
|
329
|
+
}
|
|
330
|
+
if (what === "move") {
|
|
331
|
+
mem.left = parseFloat(panel.style.left);
|
|
332
|
+
mem.top = parseFloat(panel.style.top);
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
localStorage.setItem(storeKey, JSON.stringify(mem));
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const drag = (e, what, move) => {
|
|
397
340
|
e.preventDefault();
|
|
398
341
|
let lx = e.clientX, ly = e.clientY;
|
|
342
|
+
frame.style.pointerEvents = "none";
|
|
399
343
|
const mm = (ev) => {
|
|
400
344
|
move(ev.clientX - lx, ev.clientY - ly);
|
|
401
345
|
lx = ev.clientX;
|
|
@@ -404,44 +348,62 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
404
348
|
const up = () => {
|
|
405
349
|
removeEventListener("pointermove", mm);
|
|
406
350
|
removeEventListener("pointerup", up);
|
|
351
|
+
frame.style.pointerEvents = "";
|
|
352
|
+
save(what);
|
|
407
353
|
};
|
|
408
354
|
addEventListener("pointermove", mm);
|
|
409
355
|
addEventListener("pointerup", up);
|
|
410
356
|
};
|
|
411
357
|
head.onpointerdown = (e) => {
|
|
412
358
|
if (e.target.closest("a,button")) return;
|
|
413
|
-
drag(e, (dx, dy) => {
|
|
359
|
+
drag(e, "move", (dx, dy) => {
|
|
414
360
|
const r = panel.getBoundingClientRect();
|
|
415
|
-
|
|
416
|
-
const top = Math.min(Math.max(0, r.top + dy), innerHeight - 40);
|
|
417
|
-
panel.style.left = `${left}px`;
|
|
418
|
-
panel.style.top = `${top}px`;
|
|
419
|
-
panel.style.right = "auto";
|
|
420
|
-
panel.style.bottom = "auto";
|
|
421
|
-
panel.style.transform = "none";
|
|
361
|
+
placeAt(r.left + dx, r.top + dy);
|
|
422
362
|
});
|
|
423
363
|
};
|
|
424
364
|
grip.onpointerdown = (e) => {
|
|
425
365
|
e.stopPropagation();
|
|
426
|
-
drag(e, (dx, dy) => {
|
|
366
|
+
drag(e, "size", (dx, dy) => {
|
|
427
367
|
const r = panel.getBoundingClientRect();
|
|
428
|
-
panel.style.width = `${Math.max(
|
|
429
|
-
panel.style.height = `${Math.max(
|
|
368
|
+
panel.style.width = `${Math.max(MIN_W, r.width + dx)}px`;
|
|
369
|
+
panel.style.height = `${Math.max(MIN_H, r.height + dy)}px`;
|
|
430
370
|
});
|
|
431
371
|
};
|
|
432
|
-
const set = (v) => {
|
|
372
|
+
const set = (v, focus = false, persist = true) => {
|
|
373
|
+
const hadFocus = panel.contains(document.activeElement);
|
|
433
374
|
open = v;
|
|
434
375
|
panel.style.display = v ? "flex" : "none";
|
|
435
376
|
btn.style.display = v ? "none" : "block";
|
|
377
|
+
btn.setAttribute("aria-expanded", String(v));
|
|
378
|
+
if (v && focus) frame.focus();
|
|
379
|
+
if (!v && hadFocus) btn.focus();
|
|
380
|
+
if (persist) save("open");
|
|
436
381
|
};
|
|
437
|
-
btn.onclick = () => set(true);
|
|
382
|
+
btn.onclick = () => set(true, true);
|
|
438
383
|
x.onclick = () => set(false);
|
|
439
|
-
|
|
384
|
+
const onKey = (e) => {
|
|
385
|
+
if (!isHotkey?.(e)) return;
|
|
386
|
+
e.preventDefault();
|
|
387
|
+
set(!open, true);
|
|
388
|
+
};
|
|
389
|
+
const onMessage = (e) => {
|
|
390
|
+
if (e.source !== frame.contentWindow || !e.data || typeof e.data !== "object") return;
|
|
391
|
+
const d = e.data;
|
|
392
|
+
if (d.type === "atoll-devtools:toggle") set(!open);
|
|
393
|
+
else if (d.type === "atoll-devtools:route" && typeof d.hash === "string" && d.hash.startsWith("#/")) {
|
|
394
|
+
route = d.hash;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
if (isHotkey) addEventListener("keydown", onKey);
|
|
398
|
+
addEventListener("message", onMessage);
|
|
399
|
+
set(opts.startOpen ?? saved.open === true, false, false);
|
|
440
400
|
return {
|
|
441
401
|
open: () => set(true),
|
|
442
402
|
close: () => set(false),
|
|
443
403
|
toggle: () => set(!open),
|
|
444
404
|
unmount: () => {
|
|
405
|
+
removeEventListener("keydown", onKey);
|
|
406
|
+
removeEventListener("message", onMessage);
|
|
445
407
|
panel.remove();
|
|
446
408
|
btn.remove();
|
|
447
409
|
}
|
|
@@ -450,7 +412,7 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
450
412
|
|
|
451
413
|
// src/init.ts
|
|
452
414
|
var DEVTOOLS_PARAM = "__atoll_devtools";
|
|
453
|
-
var
|
|
415
|
+
var devtoolsEnabled = (enabled) => {
|
|
454
416
|
if (enabled !== void 0) return enabled;
|
|
455
417
|
if (typeof location !== "undefined" && typeof location.search === "string") {
|
|
456
418
|
return new URLSearchParams(location.search).has(DEVTOOLS_PARAM);
|
|
@@ -458,7 +420,7 @@ var devtoolsEnabled2 = (enabled) => {
|
|
|
458
420
|
return !!globalThis.process?.env?.ATOLL_DEVTOOLS;
|
|
459
421
|
};
|
|
460
422
|
function initDevtools(opts = {}) {
|
|
461
|
-
if (!
|
|
423
|
+
if (!devtoolsEnabled(opts.enabled)) return null;
|
|
462
424
|
const conn = connectDevtools(opts);
|
|
463
425
|
const inWindow = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
464
426
|
const overlay = inWindow && opts.overlay !== false ? mountDevtoolsOverlay(typeof opts.overlay === "object" ? opts.overlay : {}) : null;
|
|
@@ -469,6 +431,6 @@ export {
|
|
|
469
431
|
connectDevtools,
|
|
470
432
|
mountDevtoolsOverlay,
|
|
471
433
|
DEVTOOLS_PARAM,
|
|
472
|
-
|
|
434
|
+
devtoolsEnabled,
|
|
473
435
|
initDevtools
|
|
474
436
|
};
|