@zenbu-labs/pixel 0.0.0 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +166 -0
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +75 -0
- package/dist/context-menu.d.ts +20 -0
- package/dist/context-menu.js +57 -0
- package/dist/debug.d.ts +1 -0
- package/dist/debug.js +9 -0
- package/dist/devtools-layout.d.ts +21 -0
- package/dist/devtools-layout.js +45 -0
- package/dist/devtools.d.ts +33 -0
- package/dist/devtools.js +125 -0
- package/dist/electron/index.d.ts +3 -0
- package/dist/electron/index.js +4 -0
- package/dist/host/guest.d.ts +5 -0
- package/dist/host/guest.js +178 -0
- package/dist/host/server.d.ts +91 -0
- package/dist/host/server.js +296 -0
- package/dist/host/shell.d.ts +35 -0
- package/dist/host/shell.js +113 -0
- package/dist/host/strip.d.ts +6 -0
- package/dist/host/strip.js +63 -0
- package/dist/icons.d.ts +30 -0
- package/dist/icons.js +58 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +39 -0
- package/dist/instances.d.ts +30 -0
- package/dist/instances.js +177 -0
- package/dist/layout.d.ts +5 -0
- package/dist/layout.js +34 -0
- package/dist/popup.d.ts +21 -0
- package/dist/popup.js +47 -0
- package/dist/preload/index.d.ts +15 -0
- package/dist/preload/index.js +3 -0
- package/dist/react/colors.d.ts +3 -0
- package/dist/react/colors.js +25 -0
- package/dist/react/components.d.ts +21 -0
- package/dist/react/components.js +45 -0
- package/dist/react/devtools/app.d.ts +3 -0
- package/dist/react/devtools/app.js +89 -0
- package/dist/react/devtools/console-capture.d.ts +1 -0
- package/dist/react/devtools/console-capture.js +41 -0
- package/dist/react/devtools/controller.d.ts +45 -0
- package/dist/react/devtools/controller.js +252 -0
- package/dist/react/devtools/elements.d.ts +4 -0
- package/dist/react/devtools/elements.js +152 -0
- package/dist/react/devtools/export-profile.d.ts +2 -0
- package/dist/react/devtools/export-profile.js +225 -0
- package/dist/react/devtools/fiber-hook.d.ts +1 -0
- package/dist/react/devtools/fiber-hook.js +89 -0
- package/dist/react/devtools/log-panel.d.ts +4 -0
- package/dist/react/devtools/log-panel.js +113 -0
- package/dist/react/devtools/profiler.d.ts +11 -0
- package/dist/react/devtools/profiler.js +374 -0
- package/dist/react/devtools/store.d.ts +27 -0
- package/dist/react/devtools/store.js +61 -0
- package/dist/react/devtools/stores.d.ts +92 -0
- package/dist/react/devtools/stores.js +41 -0
- package/dist/react/devtools/theme.d.ts +35 -0
- package/dist/react/devtools/theme.js +77 -0
- package/dist/react/devtools/ui.d.ts +24 -0
- package/dist/react/devtools/ui.js +59 -0
- package/dist/react/index.d.ts +110 -0
- package/dist/react/index.js +538 -0
- package/dist/react/markdown.d.ts +25 -0
- package/dist/react/markdown.js +270 -0
- package/dist/react/native.d.ts +116 -0
- package/dist/react/native.js +56 -0
- package/dist/react/reconciler-config.d.ts +241 -0
- package/dist/react/reconciler-config.js +412 -0
- package/dist/react/styles.d.ts +88 -0
- package/dist/react/styles.js +85 -0
- package/dist/react/surface.d.ts +55 -0
- package/dist/react/surface.js +74 -0
- package/dist/registry.d.ts +53 -0
- package/dist/registry.js +193 -0
- package/dist/root.d.ts +19 -0
- package/dist/root.js +342 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/sandbox.js +84 -0
- package/dist/scale.d.ts +7 -0
- package/dist/scale.js +31 -0
- package/dist/ssh/index.d.ts +58 -0
- package/dist/ssh/index.js +445 -0
- package/dist/terminal/applescript.d.ts +2 -0
- package/dist/terminal/applescript.js +20 -0
- package/dist/terminal/detect.d.ts +10 -0
- package/dist/terminal/detect.js +34 -0
- package/dist/terminal/graphics.d.ts +9 -0
- package/dist/terminal/graphics.js +142 -0
- package/dist/terminal/index.d.ts +12 -0
- package/dist/terminal/index.js +23 -0
- package/dist/terminal/run.d.ts +2 -0
- package/dist/terminal/run.js +15 -0
- package/dist/terminal/shared.d.ts +20 -0
- package/dist/terminal/shared.js +88 -0
- package/dist/terminal/terminal.d.ts +66 -0
- package/dist/terminal/terminal.js +6 -0
- package/dist/terminal/terminals/cmux.d.ts +2 -0
- package/dist/terminal/terminals/cmux.js +98 -0
- package/dist/terminal/terminals/ghostty-script.d.ts +1 -0
- package/dist/terminal/terminals/ghostty-script.js +179 -0
- package/dist/terminal/terminals/ghostty.d.ts +2 -0
- package/dist/terminal/terminals/ghostty.js +336 -0
- package/dist/terminal/terminals/herdr.d.ts +2 -0
- package/dist/terminal/terminals/herdr.js +141 -0
- package/dist/terminal/terminals/index.d.ts +5 -0
- package/dist/terminal/terminals/index.js +26 -0
- package/dist/terminal/terminals/kitty.d.ts +2 -0
- package/dist/terminal/terminals/kitty.js +209 -0
- package/dist/terminal/terminals/supacode.d.ts +2 -0
- package/dist/terminal/terminals/supacode.js +79 -0
- package/dist/terminal/terminals/tmux.d.ts +2 -0
- package/dist/terminal/terminals/tmux.js +132 -0
- package/dist/terminal/terminals/tty7.d.ts +2 -0
- package/dist/terminal/terminals/tty7.js +58 -0
- package/dist/terminal/terminals/vscode.d.ts +2 -0
- package/dist/terminal/terminals/vscode.js +13 -0
- package/dist/terminal/terminals/wezterm.d.ts +2 -0
- package/dist/terminal/terminals/wezterm.js +59 -0
- package/dist/theme.d.ts +22 -0
- package/dist/theme.js +40 -0
- package/dist/web/browser-session.d.ts +13 -0
- package/dist/web/browser-session.js +186 -0
- package/dist/web/cursor.d.ts +1 -0
- package/dist/web/cursor.js +24 -0
- package/dist/web/devtools.d.ts +39 -0
- package/dist/web/devtools.js +300 -0
- package/dist/web/favicon.d.ts +7 -0
- package/dist/web/favicon.js +73 -0
- package/dist/web/frame-rate.d.ts +1 -0
- package/dist/web/frame-rate.js +12 -0
- package/dist/web/host.d.ts +123 -0
- package/dist/web/host.js +573 -0
- package/dist/web/input.d.ts +49 -0
- package/dist/web/input.js +468 -0
- package/dist/web/offscreen.d.ts +2 -0
- package/dist/web/offscreen.js +23 -0
- package/dist/web/paint.d.ts +11 -0
- package/dist/web/paint.js +128 -0
- package/dist/web/popup.d.ts +40 -0
- package/dist/web/popup.js +144 -0
- package/dist/web/session.d.ts +20 -0
- package/dist/web/session.js +110 -0
- package/dist/web/types.d.ts +38 -0
- package/dist/web/types.js +39 -0
- package/dist/web/zoom.d.ts +5 -0
- package/dist/web/zoom.js +36 -0
- package/dist/webview.d.ts +55 -0
- package/dist/webview.js +481 -0
- package/package.json +69 -3
- package/scripts/apparmor.sh +71 -0
- package/scripts/postinstall.mjs +150 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ElementsPanel = ElementsPanel;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const components_1 = require("../components");
|
|
7
|
+
const reconciler_config_1 = require("../reconciler-config");
|
|
8
|
+
const controller_1 = require("./controller");
|
|
9
|
+
const stores_1 = require("./stores");
|
|
10
|
+
const store_1 = require("./store");
|
|
11
|
+
const theme_1 = require("./theme");
|
|
12
|
+
const ui_1 = require("./ui");
|
|
13
|
+
function flattenTree(expanded) {
|
|
14
|
+
const container = (0, reconciler_config_1.devtoolsBridge)().containers[reconciler_config_1.APP_VIEW];
|
|
15
|
+
if (!container)
|
|
16
|
+
return [];
|
|
17
|
+
const rows = [];
|
|
18
|
+
const walk = (instance, depth) => {
|
|
19
|
+
const expandable = instance.children.length > 0;
|
|
20
|
+
rows.push({ instance, depth, expandable });
|
|
21
|
+
if (expandable && expanded.has(instance.id)) {
|
|
22
|
+
for (const child of instance.children)
|
|
23
|
+
walk(child, depth + 1);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
for (const child of container.children)
|
|
27
|
+
walk(child, 0);
|
|
28
|
+
return rows;
|
|
29
|
+
}
|
|
30
|
+
function label(instance) {
|
|
31
|
+
const tag = instance.type;
|
|
32
|
+
if (instance.type === "text") {
|
|
33
|
+
const kids = instance.props.children;
|
|
34
|
+
const text = Array.isArray(kids) ? kids.join("") : String(kids ?? "");
|
|
35
|
+
return { tag, text: text.length > 40 ? `${text.slice(0, 40)}…` : text };
|
|
36
|
+
}
|
|
37
|
+
return { tag, text: null };
|
|
38
|
+
}
|
|
39
|
+
function ElementRow(props) {
|
|
40
|
+
const { row, selected, expanded, rem } = props;
|
|
41
|
+
const { instance } = row;
|
|
42
|
+
const { tag, text } = label(instance);
|
|
43
|
+
return ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: {
|
|
44
|
+
flexDirection: "row",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
gap: rem * 0.3,
|
|
47
|
+
padding: {
|
|
48
|
+
left: rem * 0.5 + row.depth * rem * 0.9,
|
|
49
|
+
right: rem * 0.4,
|
|
50
|
+
top: rem * 0.08,
|
|
51
|
+
bottom: rem * 0.08,
|
|
52
|
+
},
|
|
53
|
+
background: selected ? theme_1.theme.selectionBg : undefined,
|
|
54
|
+
hoverBackground: selected ? theme_1.theme.selectionBg : theme_1.theme.hover,
|
|
55
|
+
}, onClick: () => {
|
|
56
|
+
if (row.expandable)
|
|
57
|
+
(0, controller_1.toggleExpanded)(instance.id);
|
|
58
|
+
(0, controller_1.selectNode)(instance.id);
|
|
59
|
+
}, onMouseEnter: () => (0, controller_1.setHighlight)(instance.id), onMouseLeave: () => (0, controller_1.setHighlight)(null), children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: {
|
|
60
|
+
color: row.expandable ? theme_1.theme.dim : theme_1.theme.faint,
|
|
61
|
+
fontSize: rem * 0.7,
|
|
62
|
+
font: theme_1.MONO,
|
|
63
|
+
wrap: false,
|
|
64
|
+
flexShrink: 0,
|
|
65
|
+
}, children: row.expandable ? (expanded ? "-" : "+") : " " }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.tag, fontSize: rem * 0.74, font: theme_1.MONO, wrap: false }, children: `<${tag}>` }), instance.props.id && ((0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.attrName, fontSize: rem * 0.7, font: theme_1.MONO, wrap: false }, children: `#${instance.props.id}` })), text && ((0, jsx_runtime_1.jsx)(components_1.Box, { style: { flexShrink: 1, overflow: "hidden" }, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.dim, fontSize: rem * 0.7, font: theme_1.MONO, wrap: false }, children: `"${text}"` }) }))] }));
|
|
66
|
+
}
|
|
67
|
+
const ZERO_EDGES = { l: 0, t: 0, r: 0, b: 0 };
|
|
68
|
+
const BOX_COLORS = {
|
|
69
|
+
margin: { bg: "#f6b26bb8", hover: "#f6b26b" },
|
|
70
|
+
border: { bg: "#ffe599b8", hover: "#ffe599" },
|
|
71
|
+
padding: { bg: "#93c47db8", hover: "#93c47d" },
|
|
72
|
+
content: { bg: "#6fa8dcb8", hover: "#6fa8dc" },
|
|
73
|
+
};
|
|
74
|
+
const BOX_TEXT = "#1c1e21";
|
|
75
|
+
function formatPx(value) {
|
|
76
|
+
return `${Math.round(value * 10) / 10}`;
|
|
77
|
+
}
|
|
78
|
+
function BoxValue(props) {
|
|
79
|
+
return ((0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: BOX_TEXT, fontSize: props.rem * 0.62, font: theme_1.MONO, wrap: false }, children: props.value }));
|
|
80
|
+
}
|
|
81
|
+
function BoxRing(props) {
|
|
82
|
+
const { id, area, edges, rem, children } = props;
|
|
83
|
+
const colors = BOX_COLORS[area];
|
|
84
|
+
const edge = (v) => (v === 0 ? "-" : formatPx(v));
|
|
85
|
+
return ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: {
|
|
86
|
+
flexDirection: "column",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
background: colors.bg,
|
|
89
|
+
hoverBackground: colors.hover,
|
|
90
|
+
padding: { left: rem * 0.45, right: rem * 0.45, top: rem * 0.1, bottom: rem * 0.1 },
|
|
91
|
+
}, onMouseEnter: () => (0, controller_1.setHighlight)(id, area), onMouseLeave: () => (0, controller_1.setHighlight)(null), children: [(0, jsx_runtime_1.jsx)(components_1.Box, { style: { position: "absolute", inset: { left: rem * 0.25, top: rem * 0.12 } }, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: BOX_TEXT, fontSize: rem * 0.56, font: theme_1.MONO, wrap: false }, children: area }) }), (0, jsx_runtime_1.jsx)(BoxValue, { rem: rem, value: edge(edges.t) }), (0, jsx_runtime_1.jsxs)(components_1.Box, { style: { flexDirection: "row", alignItems: "center", gap: rem * 0.45 }, children: [(0, jsx_runtime_1.jsx)(BoxValue, { rem: rem, value: edge(edges.l) }), children, (0, jsx_runtime_1.jsx)(BoxValue, { rem: rem, value: edge(edges.r) })] }), (0, jsx_runtime_1.jsx)(BoxValue, { rem: rem, value: edge(edges.b) })] }));
|
|
92
|
+
}
|
|
93
|
+
function BoxModel(props) {
|
|
94
|
+
const { id, rect, rem } = props;
|
|
95
|
+
const padding = rect.padding ?? ZERO_EDGES;
|
|
96
|
+
const border = rect.border ?? ZERO_EDGES;
|
|
97
|
+
const margin = rect.margin ?? ZERO_EDGES;
|
|
98
|
+
const contentW = Math.max(0, rect.w - border.l - border.r - padding.l - padding.r);
|
|
99
|
+
const contentH = Math.max(0, rect.h - border.t - border.b - padding.t - padding.b);
|
|
100
|
+
return ((0, jsx_runtime_1.jsx)(components_1.Box, { style: { flexDirection: "column", alignItems: "center", padding: rem * 0.4 }, children: (0, jsx_runtime_1.jsx)(BoxRing, { id: id, area: "margin", edges: margin, rem: rem, children: (0, jsx_runtime_1.jsx)(BoxRing, { id: id, area: "border", edges: border, rem: rem, children: (0, jsx_runtime_1.jsx)(BoxRing, { id: id, area: "padding", edges: padding, rem: rem, children: (0, jsx_runtime_1.jsx)(components_1.Box, { style: {
|
|
101
|
+
background: BOX_COLORS.content.bg,
|
|
102
|
+
hoverBackground: BOX_COLORS.content.hover,
|
|
103
|
+
padding: { left: rem * 0.7, right: rem * 0.7, top: rem * 0.18, bottom: rem * 0.18 },
|
|
104
|
+
}, onMouseEnter: () => (0, controller_1.setHighlight)(id, "content"), onMouseLeave: () => (0, controller_1.setHighlight)(null), children: (0, jsx_runtime_1.jsx)(BoxValue, { rem: rem, value: `${formatPx(contentW)} × ${formatPx(contentH)}` }) }) }) }) }) }));
|
|
105
|
+
}
|
|
106
|
+
function formatValue(value) {
|
|
107
|
+
return (JSON.stringify(value, (_key, v) => typeof v === "number" ? Math.round(v * 100) / 100 : v) ?? "");
|
|
108
|
+
}
|
|
109
|
+
function DetailRow(props) {
|
|
110
|
+
const { name, value, rem } = props;
|
|
111
|
+
return ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: { flexDirection: "row", gap: rem * 0.5, padding: { left: rem * 0.5, top: 1, bottom: 1 } }, children: [(0, jsx_runtime_1.jsx)(components_1.Box, { style: { width: rem * 6.5, flexShrink: 0 }, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.attrName, fontSize: rem * 0.7, font: theme_1.MONO, wrap: false }, children: name }) }), (0, jsx_runtime_1.jsx)(components_1.Box, { style: { flexGrow: 1, flexBasis: 0, overflow: "hidden" }, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: props.color ?? theme_1.theme.attrValue, fontSize: rem * 0.7, font: theme_1.MONO }, children: value }) })] }));
|
|
112
|
+
}
|
|
113
|
+
function Details(props) {
|
|
114
|
+
const { instance, rect, rem } = props;
|
|
115
|
+
if (!instance) {
|
|
116
|
+
return (0, jsx_runtime_1.jsx)(ui_1.Empty, { rem: rem, text: "Select an element to inspect it." });
|
|
117
|
+
}
|
|
118
|
+
const style = instance.props.style ?? {};
|
|
119
|
+
const handlers = ["onClick", "onScroll", "onChange", "onSubmit"].filter((name) => typeof instance.props[name] === "function");
|
|
120
|
+
return ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: { flexDirection: "column", flexGrow: 1, flexBasis: 0, overflow: "scroll" }, children: [(0, jsx_runtime_1.jsxs)(components_1.Box, { style: {
|
|
121
|
+
flexDirection: "row",
|
|
122
|
+
gap: rem * 0.4,
|
|
123
|
+
padding: rem * 0.4,
|
|
124
|
+
alignItems: "center",
|
|
125
|
+
}, children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.tag, fontSize: rem * 0.8, font: theme_1.MONO, wrap: false }, children: `<${instance.type}>` }), instance.props.id && ((0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.attrName, fontSize: rem * 0.74, font: theme_1.MONO, wrap: false }, children: `#${instance.props.id}` })), (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.faint, fontSize: rem * 0.68, font: theme_1.MONO, wrap: false }, children: `node ${instance.id}` })] }), (0, jsx_runtime_1.jsx)(ui_1.Divider, {}), rect && (0, jsx_runtime_1.jsx)(BoxModel, { id: instance.id, rect: rect, rem: rem }), rect ? ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: { flexDirection: "column", padding: { top: rem * 0.25, bottom: rem * 0.25 } }, children: [(0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: "position", value: `${rect.x.toFixed(0)}, ${rect.y.toFixed(0)}` }), (0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: "size", value: `${rect.w.toFixed(0)} × ${rect.h.toFixed(0)}` }), rect.vw !== rect.w || rect.vh !== rect.h ? ((0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: "visible", value: `${rect.vw.toFixed(0)} × ${rect.vh.toFixed(0)}` })) : null, rect.scrollMax != null && ((0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: "scroll", value: `${(rect.scroll ?? 0).toFixed(0)} / ${rect.scrollMax.toFixed(0)}` }))] })) : ((0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: "layout", value: "(no rect yet)", color: theme_1.theme.faint })), (0, jsx_runtime_1.jsx)(ui_1.Divider, {}), (0, jsx_runtime_1.jsxs)(components_1.Box, { style: { flexDirection: "column", padding: { top: rem * 0.25, bottom: rem * 0.25 } }, children: [Object.entries(style).map(([name, value]) => ((0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: name, value: formatValue(value) }, name))), Object.keys(style).length === 0 && ((0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: "style", value: "(default)", color: theme_1.theme.faint })), handlers.map((name) => ((0, jsx_runtime_1.jsx)(DetailRow, { rem: rem, name: name, value: "f()", color: theme_1.theme.accent }, name)))] })] }));
|
|
126
|
+
}
|
|
127
|
+
function ElementsPanel(props) {
|
|
128
|
+
const { rem } = props;
|
|
129
|
+
const inspector = (0, store_1.useStore)(stores_1.inspectorStore);
|
|
130
|
+
const layout = (0, store_1.useStore)(stores_1.layoutStore);
|
|
131
|
+
const rows = (0, react_1.useMemo)(() => flattenTree(inspector.expanded), [inspector.expanded, inspector.treeVersion]);
|
|
132
|
+
(0, react_1.useEffect)(() => () => (0, controller_1.setHighlight)(null), []);
|
|
133
|
+
(0, react_1.useEffect)(() => {
|
|
134
|
+
if (inspector.expanded.size > 0 || rows.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
const expanded = new Set();
|
|
137
|
+
for (const row of rows) {
|
|
138
|
+
if (row.depth < 3 && row.expandable)
|
|
139
|
+
expanded.add(row.instance.id);
|
|
140
|
+
}
|
|
141
|
+
stores_1.inspectorStore.update((s) => ({ ...s, expanded }));
|
|
142
|
+
}, [rows, inspector.expanded.size]);
|
|
143
|
+
const selected = inspector.selectedId != null ? (0, controller_1.findInstance)(inspector.selectedId) : null;
|
|
144
|
+
const rect = selected != null ? layout.rects.get(selected.id) ?? null : null;
|
|
145
|
+
return ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: { flexDirection: "column", flexGrow: 1, flexBasis: 0, overflow: "hidden" }, children: [(0, jsx_runtime_1.jsxs)(ui_1.Toolbar, { rem: rem, children: [(0, jsx_runtime_1.jsx)(ui_1.Button, { rem: rem, label: inspector.picking ? "Picking… (esc)" : "Pick", active: inspector.picking, onClick: () => (0, controller_1.setPicking)(!inspector.picking) }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.faint, fontSize: rem * 0.68, wrap: false }, children: `${layout.rects.size} nodes` })] }), (0, jsx_runtime_1.jsx)(ui_1.Divider, {}), rows.length === 0 ? ((0, jsx_runtime_1.jsx)(ui_1.Empty, { rem: rem, text: "The app has not rendered anything yet." })) : ((0, jsx_runtime_1.jsx)(components_1.Box, { style: { flexDirection: "column", flexGrow: 3, flexBasis: 0, overflow: "scroll" }, children: rows.map((row) => ((0, jsx_runtime_1.jsx)(ElementRow, { row: row, rem: rem, selected: inspector.selectedId === row.instance.id, expanded: inspector.expanded.has(row.instance.id) }, row.instance.id))) })), (0, jsx_runtime_1.jsx)(ui_1.Divider, {}), (0, jsx_runtime_1.jsx)(components_1.Box, { style: {
|
|
146
|
+
flexDirection: "column",
|
|
147
|
+
flexGrow: 2,
|
|
148
|
+
flexBasis: 0,
|
|
149
|
+
overflow: "hidden",
|
|
150
|
+
background: theme_1.theme.panel,
|
|
151
|
+
}, children: (0, jsx_runtime_1.jsx)(Details, { instance: selected, rect: rect, rem: rem }) })] }));
|
|
152
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exportProfile = exportProfile;
|
|
4
|
+
exports.setProfileDirectory = setProfileDirectory;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const stores_1 = require("./stores");
|
|
8
|
+
const stores_2 = require("./stores");
|
|
9
|
+
/**
|
|
10
|
+
* this is model generated, so im worried
|
|
11
|
+
* that we are introducing slop prose potentially into
|
|
12
|
+
* models, below is to solve for this:
|
|
13
|
+
*
|
|
14
|
+
* THIS IS MODEL GENERATED PROSE, DO NOT WRITE IN THIS MANNER IN THE FUTURE
|
|
15
|
+
* ASK YOUR HUMAN HOW TO WRITE THINGS
|
|
16
|
+
*/
|
|
17
|
+
const GLOSSARY = {
|
|
18
|
+
file: "One profiling session of a pixel-react terminal app, exported from the devtools " +
|
|
19
|
+
"profiler. All times are milliseconds. Every span.start is relative to " +
|
|
20
|
+
"meta.startEpochMs (unix epoch ms), so span.start of 0 is the first recorded work. " +
|
|
21
|
+
"The pipeline per user-visible change is: a React render commits (lane 'react') -> " +
|
|
22
|
+
"the bridge serializes mutations and hands them to the engine ('ops flush', lane " +
|
|
23
|
+
"'bridge') -> the engine applies them ('ops.apply') -> re-layout (tree.*) -> repaint " +
|
|
24
|
+
"(tree.paint) -> the frame is composed and written to the terminal ('draw').",
|
|
25
|
+
span_fields: {
|
|
26
|
+
name: "string - what ran. Known engine/bridge names are documented in meta.names; " +
|
|
27
|
+
"anything else is a React component or host element (<box>/<text>/<input>) render.",
|
|
28
|
+
lane: "'react' (React renders and commits, JS side) | 'bridge' (JS->engine mutation " +
|
|
29
|
+
"batches) | 'engine' (native engine work for the app view) | 'images' (async " +
|
|
30
|
+
"image decode lifecycle; see image.wait/image.decode) | 'devtools-engine' " +
|
|
31
|
+
"(native engine work for the devtools pane itself; usually ignorable).",
|
|
32
|
+
start: "number - ms since session start.",
|
|
33
|
+
dur: "number - total duration in ms, including children.",
|
|
34
|
+
depth: "number - nesting level. A span is a child of the closest earlier span in the " +
|
|
35
|
+
"same lane with depth one less whose [start, start+dur] contains it.",
|
|
36
|
+
self: "number - ms excluding children. Only present on React component spans.",
|
|
37
|
+
arg: "number, optional extra datum. For 'ops flush' and 'ops.apply' it is the batch " +
|
|
38
|
+
"sequence number - matching values link a JS flush to the engine applying it. " +
|
|
39
|
+
"For paint.* aggregates it is an item count (rects painted, glyphs drawn). For " +
|
|
40
|
+
"image.* spans it identifies one image load - matching values pair a wait with " +
|
|
41
|
+
"its decode.",
|
|
42
|
+
label: "string, optional. For image.* spans: the file name plus outcome " +
|
|
43
|
+
"(dimensions, or 'failed', or 'still decoding' when the recording stopped first).",
|
|
44
|
+
},
|
|
45
|
+
names: {
|
|
46
|
+
frame: "one engine frame: repaint dirty views, compose, write to the terminal",
|
|
47
|
+
"ops.apply": "engine parses and applies one mutation batch from React (arg = batch seq)",
|
|
48
|
+
"ops flush": "JS serializes and hands a mutation batch to the engine (arg = batch seq)",
|
|
49
|
+
"event <type>": "one engine event from emit to its JS handler finishing. A long span means the " +
|
|
50
|
+
"event sat queued behind a busy node event loop, not that the handler was slow.",
|
|
51
|
+
"js event-loop stall": "the node event loop missed a 25ms heartbeat by this much - synchronous JS/native " +
|
|
52
|
+
"work blocked the main thread; whatever ran is between this span's start and end.",
|
|
53
|
+
"react mount / react update": "a React commit; self = React's own render duration",
|
|
54
|
+
"canvas.clear": "clearing a view's canvas before repaint",
|
|
55
|
+
"tree.sync": "pushing batched child-list changes into the layout engine",
|
|
56
|
+
"tree.resolve": "inheriting text styles (color/size/font) down the tree",
|
|
57
|
+
"tree.reconcile": "keyed tree reconcile (Rust-side apps only)",
|
|
58
|
+
"tree.layout": "flexbox layout (taffy), including text measurement and wrapping",
|
|
59
|
+
"tree.place": "computing absolute + clipped rects and scroll clamping",
|
|
60
|
+
"tree.paint": "rasterizing a view's tree into its RGBA canvas; see paint.* children",
|
|
61
|
+
"paint.rects": "aggregate of background/border fills across all nodes (arg = count)",
|
|
62
|
+
"paint.surface": "drawing a browser page's pixels into the frame. A plain copy when the page pixels are already the size they are drawn at, and a full resample of every pixel when they are not; the surface.resampled counter says which happened.",
|
|
63
|
+
"paint.images": "drawing <Image/> content, which is resized once and cached per size, so this is normally a plain copy",
|
|
64
|
+
"paint.wrap": "aggregate of text line-wrapping during paint",
|
|
65
|
+
"paint.glyphs": "aggregate of glyph blending (arg = glyphs drawn)",
|
|
66
|
+
"paint.selection": "aggregate of input selection highlight fills",
|
|
67
|
+
"paint.scrollbars": "aggregate of overlay scrollbar painting",
|
|
68
|
+
"image.wait": "one image load from request to visible: enqueued by layout, decoded on the " +
|
|
69
|
+
"worker thread, drained into the cache at the start of a pump. Paints only block " +
|
|
70
|
+
"on this if nothing else is dirty; the engine thread never decodes.",
|
|
71
|
+
"image.decode": "the worker thread's part of that load: file decode (with brief retries while a " +
|
|
72
|
+
"just-written file appears) or, for a bitmap paste, the clipboard read + pixel " +
|
|
73
|
+
"conversion. Never runs on the engine thread.",
|
|
74
|
+
"image.sniff": "sync header-only size read on the engine thread so layout is stable before the decode lands",
|
|
75
|
+
"image.scale": "one-time resample of a decoded image to its on-screen size (corner radius baked " +
|
|
76
|
+
"in), cached per size; after this, per-frame drawing is a plain row blit",
|
|
77
|
+
"image.encode": "worker thread writing a pasted bitmap to its temp PNG, after its pixels are " +
|
|
78
|
+
"already on screen from the cache; only later re-reads of the path need the file",
|
|
79
|
+
compose: "blitting view canvases, divider and overlays into the output frame",
|
|
80
|
+
draw: "encoding the frame and writing it to the terminal",
|
|
81
|
+
"kitty.shm": "writing frame pixels into shared memory for the terminal to read",
|
|
82
|
+
"term.write": "writing the escape-sequence bytes to the tty",
|
|
83
|
+
},
|
|
84
|
+
aggregates_note: "paint.* spans are per-frame aggregates laid out back-to-back under tree.paint: " +
|
|
85
|
+
"durations and counts are real, start times are not the true instants.",
|
|
86
|
+
counters: "Point samples: 'bytes' = escape-sequence bytes written per frame; 'paint.nodes' / " +
|
|
87
|
+
"'paint.glyphs' = nodes visited / glyphs drawn per paint.",
|
|
88
|
+
cpuThrottle_note: "meta.cpuThrottle > 1 means the engine and JS threads were duty-cycle suspended to " +
|
|
89
|
+
"simulate a CPU that many times slower; durations are inflated accordingly. Throttle " +
|
|
90
|
+
"changes mid-recording appear as 'cpu throttle Nx' interactions.",
|
|
91
|
+
interactions: "User input during the recording, recorded by the engine: clicks (labelled with the " +
|
|
92
|
+
"#key of the clickable node hit, or coordinates), right-clicks, keys, paste, scroll " +
|
|
93
|
+
"bursts (coalesced; 'scroll x12' = 12 wheel ticks, dur spans the burst), divider " +
|
|
94
|
+
"drags and window resizes. Labels prefixed '[devtools] ' happened in the devtools " +
|
|
95
|
+
"pane (e.g. starting/stopping the recording), not the app. Correlate an interaction " +
|
|
96
|
+
"with the spans that follow it to see what work it triggered.",
|
|
97
|
+
logs: "What the engine logged, on the same clock as the spans, so 'at' can be compared " +
|
|
98
|
+
"directly with a span's start. A log explaining why some work was slow sits next to " +
|
|
99
|
+
"the span that did the work. 'at' is negative for lines logged before the recording " +
|
|
100
|
+
"started, which is where anything explaining a condition that held the whole time " +
|
|
101
|
+
"will be.",
|
|
102
|
+
example_queries: [
|
|
103
|
+
"jq '.summary.byName' profile.json # where did time go",
|
|
104
|
+
"jq '[.spans[] | select(.name==\"frame\") | .dur] | max' profile.json # worst frame",
|
|
105
|
+
"jq '.spans[] | select(.lane==\"react\" and .self > 1)' profile.json # slow components",
|
|
106
|
+
"jq '.spans[] | select(.arg==42)' profile.json # follow batch 42 across JS and engine",
|
|
107
|
+
"jq '.logs[] | select(.level!=\"debug\")' profile.json # what the engine complained about",
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
function round(value) {
|
|
111
|
+
return Math.round(value * 1000) / 1000;
|
|
112
|
+
}
|
|
113
|
+
function summarize(session) {
|
|
114
|
+
const byName = new Map();
|
|
115
|
+
for (const span of session.spans) {
|
|
116
|
+
const entry = byName.get(span.name) ?? {
|
|
117
|
+
lane: span.lane,
|
|
118
|
+
count: 0,
|
|
119
|
+
totalMs: 0,
|
|
120
|
+
maxMs: 0,
|
|
121
|
+
};
|
|
122
|
+
entry.count += 1;
|
|
123
|
+
entry.totalMs += span.dur;
|
|
124
|
+
entry.maxMs = Math.max(entry.maxMs, span.dur);
|
|
125
|
+
byName.set(span.name, entry);
|
|
126
|
+
}
|
|
127
|
+
const names = [...byName.entries()]
|
|
128
|
+
.sort((a, b) => b[1].totalMs - a[1].totalMs)
|
|
129
|
+
.map(([name, entry]) => [
|
|
130
|
+
name,
|
|
131
|
+
{
|
|
132
|
+
lane: entry.lane,
|
|
133
|
+
count: entry.count,
|
|
134
|
+
totalMs: round(entry.totalMs),
|
|
135
|
+
meanMs: round(entry.totalMs / entry.count),
|
|
136
|
+
maxMs: round(entry.maxMs),
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
const frames = session.frames;
|
|
140
|
+
const frameTotal = frames.reduce((sum, f) => sum + f.dur, 0);
|
|
141
|
+
return {
|
|
142
|
+
durationMs: round(session.end - session.start),
|
|
143
|
+
frames: {
|
|
144
|
+
count: frames.length,
|
|
145
|
+
avgMs: round(frames.length ? frameTotal / frames.length : 0),
|
|
146
|
+
worstMs: round(frames.reduce((max, f) => Math.max(max, f.dur), 0)),
|
|
147
|
+
},
|
|
148
|
+
byName: Object.fromEntries(names),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function exportSpan(span, sessionStart) {
|
|
152
|
+
const out = {
|
|
153
|
+
name: span.name,
|
|
154
|
+
lane: span.lane,
|
|
155
|
+
start: round(span.start - sessionStart),
|
|
156
|
+
dur: round(span.dur),
|
|
157
|
+
depth: span.depth,
|
|
158
|
+
};
|
|
159
|
+
if (span.self != null)
|
|
160
|
+
out.self = round(span.self);
|
|
161
|
+
if (span.arg != null)
|
|
162
|
+
out.arg = span.arg;
|
|
163
|
+
if (span.label != null)
|
|
164
|
+
out.label = span.label;
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
function exportProfile() {
|
|
168
|
+
const session = stores_2.profilerStore.get().session;
|
|
169
|
+
if (!session)
|
|
170
|
+
return null;
|
|
171
|
+
const document = {
|
|
172
|
+
meta: {
|
|
173
|
+
generator: "pixel-react devtools profiler",
|
|
174
|
+
exportedAt: new Date().toISOString(),
|
|
175
|
+
startEpochMs: round(session.start),
|
|
176
|
+
cpuThrottle: stores_1.devtoolsStore.get().cpuRate,
|
|
177
|
+
// A daemon can outlive many rebuilds, so every export says which process
|
|
178
|
+
// produced it and when that process started.
|
|
179
|
+
pid: process.pid,
|
|
180
|
+
processStartedAt: new Date(Date.now() - process.uptime() * 1000).toISOString(),
|
|
181
|
+
versions: {
|
|
182
|
+
node: process.versions.node,
|
|
183
|
+
electron: process.versions.electron ?? null,
|
|
184
|
+
chrome: process.versions.chrome ?? null,
|
|
185
|
+
},
|
|
186
|
+
glossary: GLOSSARY,
|
|
187
|
+
},
|
|
188
|
+
summary: summarize(session),
|
|
189
|
+
interactions: session.marks.map((mark) => ({
|
|
190
|
+
label: mark.name,
|
|
191
|
+
start: round(mark.start - session.start),
|
|
192
|
+
dur: round(mark.dur),
|
|
193
|
+
})),
|
|
194
|
+
spans: session.spans.map((span) => exportSpan(span, session.start)),
|
|
195
|
+
counters: session.counters.map((counter) => ({
|
|
196
|
+
name: counter.name,
|
|
197
|
+
at: round(counter.at - session.start),
|
|
198
|
+
value: counter.value,
|
|
199
|
+
})),
|
|
200
|
+
logs: stores_1.engineLogs.store.get().rows.map((row) => ({
|
|
201
|
+
at: round(row.epochMs - session.start),
|
|
202
|
+
level: row.level,
|
|
203
|
+
target: row.target,
|
|
204
|
+
text: row.text,
|
|
205
|
+
count: row.count,
|
|
206
|
+
})),
|
|
207
|
+
};
|
|
208
|
+
const stamp = new Date()
|
|
209
|
+
.toISOString()
|
|
210
|
+
.replace(/[:.]/g, "-")
|
|
211
|
+
.replace("T", "-")
|
|
212
|
+
.slice(0, 19);
|
|
213
|
+
const dir = (0, node_path_1.join)(profileBase, "profiles");
|
|
214
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
215
|
+
const path = (0, node_path_1.join)(dir, `devtools-profile-${stamp}.json`);
|
|
216
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify(document, null, 1));
|
|
217
|
+
stores_1.engineLogs.push("info", "profiler", `profile exported to ${path}`);
|
|
218
|
+
return path;
|
|
219
|
+
}
|
|
220
|
+
// A daemon's own cwd means nothing to the person who launched it, so the app
|
|
221
|
+
// names the directory exports are relative to; it defaults to the process cwd.
|
|
222
|
+
let profileBase = process.cwd();
|
|
223
|
+
function setProfileDirectory(base) {
|
|
224
|
+
profileBase = (0, node_path_1.resolve)(base);
|
|
225
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installFiberHook(): void;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installFiberHook = installFiberHook;
|
|
4
|
+
const stores_1 = require("./stores");
|
|
5
|
+
const PERFORMED_WORK = 0b1;
|
|
6
|
+
function fiberName(fiber) {
|
|
7
|
+
const type = fiber.type;
|
|
8
|
+
if (typeof type === "string")
|
|
9
|
+
return `<${type}>`;
|
|
10
|
+
if (typeof type === "function")
|
|
11
|
+
return type.displayName || type.name || "Anonymous";
|
|
12
|
+
if (type && typeof type === "object") {
|
|
13
|
+
if (type.displayName)
|
|
14
|
+
return type.displayName;
|
|
15
|
+
const render = type.render;
|
|
16
|
+
if (render)
|
|
17
|
+
return render.name || "ForwardRef";
|
|
18
|
+
if (type.type) {
|
|
19
|
+
return fiberName({ ...fiber, type: type.type });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function didRender(fiber) {
|
|
25
|
+
return ((fiber.flags & PERFORMED_WORK) !== 0 &&
|
|
26
|
+
typeof fiber.actualDuration === "number" &&
|
|
27
|
+
typeof fiber.actualStartTime === "number" &&
|
|
28
|
+
fiber.actualStartTime > 0);
|
|
29
|
+
}
|
|
30
|
+
function collect(fiber, depth, epoch) {
|
|
31
|
+
for (let node = fiber; node; node = node.sibling) {
|
|
32
|
+
let childDepth = depth;
|
|
33
|
+
if (didRender(node)) {
|
|
34
|
+
const name = fiberName(node);
|
|
35
|
+
if (name) {
|
|
36
|
+
let childTotal = 0;
|
|
37
|
+
for (let child = node.child; child; child = child.sibling) {
|
|
38
|
+
if (didRender(child))
|
|
39
|
+
childTotal += child.actualDuration ?? 0;
|
|
40
|
+
}
|
|
41
|
+
const dur = node.actualDuration ?? 0;
|
|
42
|
+
(0, stores_1.recordSpan)({
|
|
43
|
+
name,
|
|
44
|
+
start: epoch + (node.actualStartTime ?? 0),
|
|
45
|
+
dur,
|
|
46
|
+
depth,
|
|
47
|
+
lane: "react",
|
|
48
|
+
self: Math.max(0, dur - childTotal),
|
|
49
|
+
});
|
|
50
|
+
childDepth = depth + 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
collect(node.child, childDepth, epoch);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let installed = false;
|
|
57
|
+
function installFiberHook() {
|
|
58
|
+
if (installed)
|
|
59
|
+
return;
|
|
60
|
+
installed = true;
|
|
61
|
+
const globals = globalThis;
|
|
62
|
+
if (globals.__REACT_DEVTOOLS_GLOBAL_HOOK__)
|
|
63
|
+
return;
|
|
64
|
+
const hook = {
|
|
65
|
+
isDisabled: false,
|
|
66
|
+
supportsFiber: true,
|
|
67
|
+
renderers: new Map(),
|
|
68
|
+
inject(renderer) {
|
|
69
|
+
const id = this.renderers.size + 1;
|
|
70
|
+
this.renderers.set(id, renderer);
|
|
71
|
+
return id;
|
|
72
|
+
},
|
|
73
|
+
onCommitFiberRoot(_rendererId, root) {
|
|
74
|
+
if (!stores_1.profilerStore.get().recording)
|
|
75
|
+
return;
|
|
76
|
+
if (root.containerInfo?.view !== 0)
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
collect(root.current.child, 0, performance.timeOrigin);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
onCommitFiberUnmount() { },
|
|
85
|
+
onPostCommitFiberRoot() { },
|
|
86
|
+
onScheduleFiberRoot() { },
|
|
87
|
+
};
|
|
88
|
+
globals.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
|
|
89
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogPanel = LogPanel;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const components_1 = require("../components");
|
|
7
|
+
const stores_1 = require("./stores");
|
|
8
|
+
const store_1 = require("./store");
|
|
9
|
+
const theme_1 = require("./theme");
|
|
10
|
+
const ui_1 = require("./ui");
|
|
11
|
+
const ROW_TINTS = {
|
|
12
|
+
warn: "#e5c07b14",
|
|
13
|
+
error: "#e06c7514",
|
|
14
|
+
};
|
|
15
|
+
const TEXT_COLORS = {
|
|
16
|
+
debug: theme_1.theme.dim,
|
|
17
|
+
info: theme_1.theme.text,
|
|
18
|
+
warn: theme_1.theme.warn,
|
|
19
|
+
error: theme_1.theme.danger,
|
|
20
|
+
};
|
|
21
|
+
function LogLine(props) {
|
|
22
|
+
const { entry, showTarget, rem } = props;
|
|
23
|
+
const { row } = entry;
|
|
24
|
+
return ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: {
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
alignItems: "start",
|
|
27
|
+
gap: rem * 0.5,
|
|
28
|
+
padding: { left: rem * 0.6, right: rem * 0.6, top: rem * 0.14, bottom: rem * 0.14 },
|
|
29
|
+
background: ROW_TINTS[row.level],
|
|
30
|
+
hoverBackground: theme_1.theme.hover,
|
|
31
|
+
}, children: [showTarget && ((0, jsx_runtime_1.jsx)(components_1.Box, { style: { width: rem * 3.2, flexShrink: 0 }, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.faint, fontSize: rem * 0.66, font: theme_1.MONO, wrap: false }, children: row.target }) })), (0, jsx_runtime_1.jsx)(components_1.Box, { style: { flexGrow: 1, flexBasis: 0, overflow: "hidden" }, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: TEXT_COLORS[row.level] ?? theme_1.theme.text, fontSize: rem * 0.74, font: theme_1.MONO }, children: row.text }) }), row.count > 1 && ((0, jsx_runtime_1.jsx)(components_1.Box, { style: {
|
|
32
|
+
background: theme_1.theme.accentDim,
|
|
33
|
+
cornerRadius: rem * 0.5,
|
|
34
|
+
padding: { left: rem * 0.3, right: rem * 0.3 },
|
|
35
|
+
flexShrink: 0,
|
|
36
|
+
}, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { color: theme_1.theme.accent, fontSize: rem * 0.62, wrap: false }, children: `x${row.count}` }) }))] }));
|
|
37
|
+
}
|
|
38
|
+
const SOURCES = ["program", "engine", "both"];
|
|
39
|
+
const LEVEL_FILTERS = ["all", "info", "warn", "error"];
|
|
40
|
+
function targetVisible(entry, source) {
|
|
41
|
+
if (entry.source === "program")
|
|
42
|
+
return entry.row.target !== "console";
|
|
43
|
+
if (source === "both")
|
|
44
|
+
return true;
|
|
45
|
+
return entry.row.target !== "engine";
|
|
46
|
+
}
|
|
47
|
+
function LogPanel(props) {
|
|
48
|
+
const { rem } = props;
|
|
49
|
+
const programBuffer = (0, store_1.useStore)(stores_1.consoleLogs.store);
|
|
50
|
+
const engineBuffer = (0, store_1.useStore)(stores_1.engineLogs.store);
|
|
51
|
+
const [source, setSource] = (0, react_1.useState)("program");
|
|
52
|
+
const [filter, setFilter] = (0, react_1.useState)("");
|
|
53
|
+
const [level, setLevel] = (0, react_1.useState)("all");
|
|
54
|
+
const follow = (0, react_1.useRef)(true);
|
|
55
|
+
const list = (0, react_1.useRef)(null);
|
|
56
|
+
(0, react_1.useEffect)(() => {
|
|
57
|
+
if (follow.current)
|
|
58
|
+
list.current?.scrollTo(1e9, false);
|
|
59
|
+
}, [programBuffer.version, engineBuffer.version, source]);
|
|
60
|
+
let entries;
|
|
61
|
+
if (source === "program") {
|
|
62
|
+
entries = programBuffer.rows.map((row) => ({ row, source: "program" }));
|
|
63
|
+
}
|
|
64
|
+
else if (source === "engine") {
|
|
65
|
+
entries = engineBuffer.rows.map((row) => ({ row, source: "engine" }));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
entries = [
|
|
69
|
+
...programBuffer.rows.map((row) => ({ row, source: "program" })),
|
|
70
|
+
...engineBuffer.rows.map((row) => ({ row, source: "engine" })),
|
|
71
|
+
].sort((a, b) => a.row.epochMs - b.row.epochMs);
|
|
72
|
+
}
|
|
73
|
+
const query = filter.trim().toLowerCase();
|
|
74
|
+
const rows = entries.filter(({ row }) => {
|
|
75
|
+
if (level === "warn" && row.level !== "warn" && row.level !== "error")
|
|
76
|
+
return false;
|
|
77
|
+
if (level === "error" && row.level !== "error")
|
|
78
|
+
return false;
|
|
79
|
+
if (level === "info" && row.level === "debug")
|
|
80
|
+
return false;
|
|
81
|
+
if (query && !row.text.toLowerCase().includes(query))
|
|
82
|
+
return false;
|
|
83
|
+
return true;
|
|
84
|
+
});
|
|
85
|
+
const clear = () => {
|
|
86
|
+
if (source !== "engine")
|
|
87
|
+
stores_1.consoleLogs.clear();
|
|
88
|
+
if (source !== "program")
|
|
89
|
+
stores_1.engineLogs.clear();
|
|
90
|
+
};
|
|
91
|
+
return ((0, jsx_runtime_1.jsxs)(components_1.Box, { style: { flexDirection: "column", flexGrow: 1, flexBasis: 0, overflow: "hidden" }, children: [(0, jsx_runtime_1.jsxs)(ui_1.Toolbar, { rem: rem, children: [SOURCES.map((entry) => ((0, jsx_runtime_1.jsx)(ui_1.Chip, { rem: rem, label: entry, active: source === entry, onClick: () => setSource(entry) }, entry))), (0, jsx_runtime_1.jsx)(components_1.Box, { style: { flexGrow: 1 } }), LEVEL_FILTERS.map((entry) => ((0, jsx_runtime_1.jsx)(ui_1.Chip, { rem: rem, label: entry, active: level === entry, onClick: () => setLevel(entry) }, entry))), (0, jsx_runtime_1.jsx)(ui_1.Button, { rem: rem, label: "Clear", onClick: clear })] }), (0, jsx_runtime_1.jsx)(components_1.Box, { style: {
|
|
92
|
+
background: theme_1.theme.panel,
|
|
93
|
+
padding: { left: rem * 0.35, right: rem * 0.35, bottom: rem * 0.35 },
|
|
94
|
+
flexShrink: 0,
|
|
95
|
+
}, children: (0, jsx_runtime_1.jsx)(components_1.Box, { style: {
|
|
96
|
+
background: theme_1.theme.bg,
|
|
97
|
+
border: { width: 1, color: theme_1.theme.border },
|
|
98
|
+
cornerRadius: rem * 0.25,
|
|
99
|
+
padding: { left: rem * 0.4, right: rem * 0.4, top: rem * 0.1, bottom: rem * 0.1 },
|
|
100
|
+
flexGrow: 1,
|
|
101
|
+
flexBasis: 0,
|
|
102
|
+
overflow: "hidden",
|
|
103
|
+
}, children: (0, jsx_runtime_1.jsx)(components_1.Input, { style: {
|
|
104
|
+
flexGrow: 1,
|
|
105
|
+
flexBasis: 0,
|
|
106
|
+
fontSize: rem * 0.72,
|
|
107
|
+
color: theme_1.theme.text,
|
|
108
|
+
font: theme_1.MONO,
|
|
109
|
+
wrap: false,
|
|
110
|
+
}, defaultValue: "", onChange: setFilter, caretColor: theme_1.theme.accent }) }) }), (0, jsx_runtime_1.jsx)(ui_1.Divider, {}), (0, jsx_runtime_1.jsx)(components_1.Box, { ref: list, style: { flexDirection: "column", flexGrow: 1, flexBasis: 0, overflow: "scroll" }, onScroll: (e) => {
|
|
111
|
+
follow.current = e.offset >= e.max - 2;
|
|
112
|
+
}, children: rows.slice(-500).map((entry) => ((0, jsx_runtime_1.jsx)(LogLine, { entry: entry, showTarget: targetVisible(entry, source), rem: rem }, `${entry.source}-${entry.row.id}`))) })] }));
|
|
113
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface Viewport {
|
|
3
|
+
t0: number;
|
|
4
|
+
t1: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const viewportStore: import("./store").Store<Viewport | null>;
|
|
7
|
+
export declare function profilerHandleKey(key: string): boolean;
|
|
8
|
+
export declare function ProfilerPanel(props: {
|
|
9
|
+
rem: number;
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
export {};
|