@buoy-gg/console 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +58 -0
- package/README.md +55 -0
- package/lib/commonjs/components/ConsoleIcon.js +47 -0
- package/lib/commonjs/components/ConsoleIcon.js.map +1 -0
- package/lib/commonjs/components/ConsoleLogList.js +145 -0
- package/lib/commonjs/components/ConsoleLogList.js.map +1 -0
- package/lib/commonjs/components/ConsoleModal.js +48 -0
- package/lib/commonjs/components/ConsoleModal.js.map +1 -0
- package/lib/commonjs/components/ConsoleRoot.js +28 -0
- package/lib/commonjs/components/ConsoleRoot.js.map +1 -0
- package/lib/commonjs/devtools/ConsoleBody.js +235 -0
- package/lib/commonjs/devtools/ConsoleBody.js.map +1 -0
- package/lib/commonjs/devtools/ConsoleHeader.js +146 -0
- package/lib/commonjs/devtools/ConsoleHeader.js.map +1 -0
- package/lib/commonjs/devtools/ConsoleMessageRow.js +338 -0
- package/lib/commonjs/devtools/ConsoleMessageRow.js.map +1 -0
- package/lib/commonjs/devtools/ConsoleValue.js +254 -0
- package/lib/commonjs/devtools/ConsoleValue.js.map +1 -0
- package/lib/commonjs/devtools/ConsoleView.js +51 -0
- package/lib/commonjs/devtools/ConsoleView.js.map +1 -0
- package/lib/commonjs/devtools/consoleFilter.js +125 -0
- package/lib/commonjs/devtools/consoleFilter.js.map +1 -0
- package/lib/commonjs/devtools/consoleFormat.js +313 -0
- package/lib/commonjs/devtools/consoleFormat.js.map +1 -0
- package/lib/commonjs/devtools/origin.js +191 -0
- package/lib/commonjs/devtools/origin.js.map +1 -0
- package/lib/commonjs/devtools/theme.js +143 -0
- package/lib/commonjs/devtools/theme.js.map +1 -0
- package/lib/commonjs/devtools/useConsoleController.js +82 -0
- package/lib/commonjs/devtools/useConsoleController.js.map +1 -0
- package/lib/commonjs/devtools/useConsoleMessages.js +241 -0
- package/lib/commonjs/devtools/useConsoleMessages.js.map +1 -0
- package/lib/commonjs/hooks/useConsoleLogs.js +23 -0
- package/lib/commonjs/hooks/useConsoleLogs.js.map +1 -0
- package/lib/commonjs/index.js +175 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +45 -0
- package/lib/commonjs/preset.js.map +1 -0
- package/lib/commonjs/store/consoleLogStore.js +298 -0
- package/lib/commonjs/store/consoleLogStore.js.map +1 -0
- package/lib/commonjs/store/sanitize.js +64 -0
- package/lib/commonjs/store/sanitize.js.map +1 -0
- package/lib/commonjs/sync/consoleSyncAdapter.js +47 -0
- package/lib/commonjs/sync/consoleSyncAdapter.js.map +1 -0
- package/lib/module/components/ConsoleIcon.js +42 -0
- package/lib/module/components/ConsoleIcon.js.map +1 -0
- package/lib/module/components/ConsoleLogList.js +140 -0
- package/lib/module/components/ConsoleLogList.js.map +1 -0
- package/lib/module/components/ConsoleModal.js +44 -0
- package/lib/module/components/ConsoleModal.js.map +1 -0
- package/lib/module/components/ConsoleRoot.js +25 -0
- package/lib/module/components/ConsoleRoot.js.map +1 -0
- package/lib/module/devtools/ConsoleBody.js +230 -0
- package/lib/module/devtools/ConsoleBody.js.map +1 -0
- package/lib/module/devtools/ConsoleHeader.js +141 -0
- package/lib/module/devtools/ConsoleHeader.js.map +1 -0
- package/lib/module/devtools/ConsoleMessageRow.js +333 -0
- package/lib/module/devtools/ConsoleMessageRow.js.map +1 -0
- package/lib/module/devtools/ConsoleValue.js +249 -0
- package/lib/module/devtools/ConsoleValue.js.map +1 -0
- package/lib/module/devtools/ConsoleView.js +46 -0
- package/lib/module/devtools/ConsoleView.js.map +1 -0
- package/lib/module/devtools/consoleFilter.js +119 -0
- package/lib/module/devtools/consoleFilter.js.map +1 -0
- package/lib/module/devtools/consoleFormat.js +309 -0
- package/lib/module/devtools/consoleFormat.js.map +1 -0
- package/lib/module/devtools/origin.js +183 -0
- package/lib/module/devtools/origin.js.map +1 -0
- package/lib/module/devtools/theme.js +140 -0
- package/lib/module/devtools/theme.js.map +1 -0
- package/lib/module/devtools/useConsoleController.js +79 -0
- package/lib/module/devtools/useConsoleController.js.map +1 -0
- package/lib/module/devtools/useConsoleMessages.js +238 -0
- package/lib/module/devtools/useConsoleMessages.js.map +1 -0
- package/lib/module/hooks/useConsoleLogs.js +19 -0
- package/lib/module/hooks/useConsoleLogs.js.map +1 -0
- package/lib/module/index.js +52 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/preset.js +40 -0
- package/lib/module/preset.js.map +1 -0
- package/lib/module/store/consoleLogStore.js +284 -0
- package/lib/module/store/consoleLogStore.js.map +1 -0
- package/lib/module/store/sanitize.js +60 -0
- package/lib/module/store/sanitize.js.map +1 -0
- package/lib/module/sync/consoleSyncAdapter.js +43 -0
- package/lib/module/sync/consoleSyncAdapter.js.map +1 -0
- package/lib/typescript/components/ConsoleIcon.d.ts +14 -0
- package/lib/typescript/components/ConsoleIcon.d.ts.map +1 -0
- package/lib/typescript/components/ConsoleLogList.d.ts +8 -0
- package/lib/typescript/components/ConsoleLogList.d.ts.map +1 -0
- package/lib/typescript/components/ConsoleModal.d.ts +18 -0
- package/lib/typescript/components/ConsoleModal.d.ts.map +1 -0
- package/lib/typescript/components/ConsoleRoot.d.ts +14 -0
- package/lib/typescript/components/ConsoleRoot.d.ts.map +1 -0
- package/lib/typescript/devtools/ConsoleBody.d.ts +11 -0
- package/lib/typescript/devtools/ConsoleBody.d.ts.map +1 -0
- package/lib/typescript/devtools/ConsoleHeader.d.ts +17 -0
- package/lib/typescript/devtools/ConsoleHeader.d.ts.map +1 -0
- package/lib/typescript/devtools/ConsoleMessageRow.d.ts +20 -0
- package/lib/typescript/devtools/ConsoleMessageRow.d.ts.map +1 -0
- package/lib/typescript/devtools/ConsoleValue.d.ts +21 -0
- package/lib/typescript/devtools/ConsoleValue.d.ts.map +1 -0
- package/lib/typescript/devtools/ConsoleView.d.ts +10 -0
- package/lib/typescript/devtools/ConsoleView.d.ts.map +1 -0
- package/lib/typescript/devtools/consoleFilter.d.ts +31 -0
- package/lib/typescript/devtools/consoleFilter.d.ts.map +1 -0
- package/lib/typescript/devtools/consoleFormat.d.ts +29 -0
- package/lib/typescript/devtools/consoleFormat.d.ts.map +1 -0
- package/lib/typescript/devtools/origin.d.ts +39 -0
- package/lib/typescript/devtools/origin.d.ts.map +1 -0
- package/lib/typescript/devtools/theme.d.ts +57 -0
- package/lib/typescript/devtools/theme.d.ts.map +1 -0
- package/lib/typescript/devtools/useConsoleController.d.ts +35 -0
- package/lib/typescript/devtools/useConsoleController.d.ts.map +1 -0
- package/lib/typescript/devtools/useConsoleMessages.d.ts +74 -0
- package/lib/typescript/devtools/useConsoleMessages.d.ts.map +1 -0
- package/lib/typescript/hooks/useConsoleLogs.d.ts +11 -0
- package/lib/typescript/hooks/useConsoleLogs.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +26 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +38 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/lib/typescript/store/consoleLogStore.d.ts +71 -0
- package/lib/typescript/store/consoleLogStore.d.ts.map +1 -0
- package/lib/typescript/store/sanitize.d.ts +11 -0
- package/lib/typescript/store/sanitize.d.ts.map +1 -0
- package/lib/typescript/sync/consoleSyncAdapter.d.ts +30 -0
- package/lib/typescript/sync/consoleSyncAdapter.d.ts.map +1 -0
- package/package.json +56 -0
- package/src/components/ConsoleIcon.tsx +49 -0
- package/src/components/ConsoleLogList.tsx +133 -0
- package/src/components/ConsoleModal.tsx +48 -0
- package/src/components/ConsoleRoot.tsx +25 -0
- package/src/devtools/ConsoleBody.tsx +225 -0
- package/src/devtools/ConsoleHeader.tsx +149 -0
- package/src/devtools/ConsoleMessageRow.tsx +345 -0
- package/src/devtools/ConsoleValue.tsx +265 -0
- package/src/devtools/ConsoleView.tsx +41 -0
- package/src/devtools/consoleFilter.ts +123 -0
- package/src/devtools/consoleFormat.ts +314 -0
- package/src/devtools/origin.ts +190 -0
- package/src/devtools/theme.ts +137 -0
- package/src/devtools/useConsoleController.ts +104 -0
- package/src/devtools/useConsoleMessages.ts +337 -0
- package/src/hooks/useConsoleLogs.ts +26 -0
- package/src/index.tsx +69 -0
- package/src/preset.tsx +35 -0
- package/src/store/consoleLogStore.ts +346 -0
- package/src/store/sanitize.ts +58 -0
- package/src/sync/consoleSyncAdapter.ts +49 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConsoleMessageRow — RN port of a single DevTools `.console-message-wrapper`.
|
|
3
|
+
*
|
|
4
|
+
* Anatomy (mirrors ConsoleViewMessage):
|
|
5
|
+
* [nesting markers] [group triangle] [level icon] [timestamp] [content] [repeat badge]
|
|
6
|
+
* Error/warning rows get the surface background + text colors; non-error/warning
|
|
7
|
+
* rows get a hairline top divider. Group headers show a bold title with an
|
|
8
|
+
* expand triangle; error/warn/trace rows can expand a captured stack trace.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useState } from "react";
|
|
12
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
13
|
+
import { consoleTheme, levelStyles, monoFont } from "./theme";
|
|
14
|
+
import { formatConsoleMessage } from "./consoleFormat";
|
|
15
|
+
import { ConsoleValue } from "./ConsoleValue";
|
|
16
|
+
import type { DisplayRow } from "./useConsoleMessages";
|
|
17
|
+
|
|
18
|
+
function formatTimestamp(ts: number): string {
|
|
19
|
+
const d = new Date(ts);
|
|
20
|
+
const pad = (n: number, len = 2) => String(n).padStart(len, "0");
|
|
21
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(
|
|
22
|
+
d.getMilliseconds(),
|
|
23
|
+
3
|
|
24
|
+
)}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Small filled level glyph (warning triangle / error cross), DevTools-style. */
|
|
28
|
+
function LevelIcon({ icon, color }: { icon: "warning" | "error"; color: string }) {
|
|
29
|
+
return <Text style={[styles.levelIcon, { color }]}>{icon === "warning" ? "⚠" : "⊘"}</Text>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The repeat-count bubble (dt-small-bubble). */
|
|
33
|
+
function RepeatBadge({ count, color }: { count: number; color: string }) {
|
|
34
|
+
return (
|
|
35
|
+
<View style={[styles.repeatBadge, { borderColor: color }]}>
|
|
36
|
+
<Text style={[styles.repeatBadgeText, { color }]}>{count}</Text>
|
|
37
|
+
</View>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ConsoleMessageRowProps {
|
|
42
|
+
row: DisplayRow;
|
|
43
|
+
showTimestamps: boolean;
|
|
44
|
+
onToggleGroup: (groupId: number, currentlyCollapsed: boolean) => void;
|
|
45
|
+
onToggleCluster: (key: string) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ConsoleMessageRow({
|
|
49
|
+
row,
|
|
50
|
+
showTimestamps,
|
|
51
|
+
onToggleGroup,
|
|
52
|
+
onToggleCluster,
|
|
53
|
+
}: ConsoleMessageRowProps) {
|
|
54
|
+
const { entry } = row;
|
|
55
|
+
const level = levelStyles[entry.level];
|
|
56
|
+
const isErrorOrWarn = entry.level === "error" || entry.level === "warning";
|
|
57
|
+
const textColor = level.text;
|
|
58
|
+
const [stackOpen, setStackOpen] = useState(entry.method === "trace");
|
|
59
|
+
|
|
60
|
+
// Collapsed/large cluster header (Grouped mode): tap to expand/collapse.
|
|
61
|
+
if (row.clusterHeader) {
|
|
62
|
+
const ch = row.clusterHeader;
|
|
63
|
+
return (
|
|
64
|
+
<TouchableOpacity
|
|
65
|
+
style={[styles.wrapper, styles.dividerTop]}
|
|
66
|
+
activeOpacity={0.7}
|
|
67
|
+
onPress={() => onToggleCluster(ch.key)}
|
|
68
|
+
>
|
|
69
|
+
<View style={styles.rowWrapper}>
|
|
70
|
+
<Text style={[styles.originGutter, { color: row.originColor }]}>
|
|
71
|
+
{ch.expanded ? "▼" : "▶"}
|
|
72
|
+
</Text>
|
|
73
|
+
<View style={styles.messageColumn}>
|
|
74
|
+
<View style={styles.messageLine}>
|
|
75
|
+
<Text style={[styles.text, styles.clusterTitle, { color: row.originColor }]}>
|
|
76
|
+
{row.originLabel}
|
|
77
|
+
</Text>
|
|
78
|
+
<Text style={styles.clusterCount}> · {ch.count} logs</Text>
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
</View>
|
|
82
|
+
</TouchableOpacity>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const tokens = formatConsoleMessage(entry.args);
|
|
87
|
+
const hasStack = Boolean(entry.stack) && (isErrorOrWarn || entry.method === "trace");
|
|
88
|
+
|
|
89
|
+
const content = (
|
|
90
|
+
<View style={styles.contentWrap}>
|
|
91
|
+
{tokens.map((token, i) =>
|
|
92
|
+
token.type === "string" ? (
|
|
93
|
+
<Text
|
|
94
|
+
key={i}
|
|
95
|
+
style={[
|
|
96
|
+
styles.text,
|
|
97
|
+
{ color: textColor },
|
|
98
|
+
row.isGroupHeader && styles.groupTitle,
|
|
99
|
+
token.style,
|
|
100
|
+
]}
|
|
101
|
+
>
|
|
102
|
+
{token.value}
|
|
103
|
+
</Text>
|
|
104
|
+
) : (
|
|
105
|
+
<ConsoleValue key={i} value={token.value} generic={token.mode === "generic"} />
|
|
106
|
+
)
|
|
107
|
+
)}
|
|
108
|
+
</View>
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const inner = (
|
|
112
|
+
<View style={styles.messageColumn}>
|
|
113
|
+
<View style={styles.messageLine}>
|
|
114
|
+
{/* Group expand triangle */}
|
|
115
|
+
{row.isGroupHeader ? (
|
|
116
|
+
<Text style={[styles.groupTriangle, { color: textColor }]}>
|
|
117
|
+
{row.groupCollapsed ? "▶" : "▼"}
|
|
118
|
+
</Text>
|
|
119
|
+
) : null}
|
|
120
|
+
|
|
121
|
+
{/* Level icon */}
|
|
122
|
+
{level.icon ? <LevelIcon icon={level.icon} color={textColor} /> : null}
|
|
123
|
+
|
|
124
|
+
{/* Timestamp */}
|
|
125
|
+
{showTimestamps ? (
|
|
126
|
+
<Text style={styles.timestamp}>{formatTimestamp(entry.timestamp)}</Text>
|
|
127
|
+
) : null}
|
|
128
|
+
|
|
129
|
+
{/* Stack expand toggle */}
|
|
130
|
+
{hasStack ? (
|
|
131
|
+
<Text style={[styles.stackTriangle, { color: textColor }]}>
|
|
132
|
+
{stackOpen ? "▼" : "▶"}
|
|
133
|
+
</Text>
|
|
134
|
+
) : null}
|
|
135
|
+
|
|
136
|
+
{/* Message content */}
|
|
137
|
+
{content}
|
|
138
|
+
|
|
139
|
+
{/* Repeat badge */}
|
|
140
|
+
{row.repeatCount > 1 ? <RepeatBadge count={row.repeatCount} color={textColor} /> : null}
|
|
141
|
+
|
|
142
|
+
{/* Origin label (component / function / call site) */}
|
|
143
|
+
{row.originLabel ? (
|
|
144
|
+
<Text style={[styles.originLabel, { color: row.originColor }]} numberOfLines={1}>
|
|
145
|
+
{row.originLabel}
|
|
146
|
+
</Text>
|
|
147
|
+
) : null}
|
|
148
|
+
</View>
|
|
149
|
+
|
|
150
|
+
{/* Stack trace */}
|
|
151
|
+
{hasStack && stackOpen ? (
|
|
152
|
+
<View style={styles.stackTrace}>
|
|
153
|
+
{(entry.stack ?? "")
|
|
154
|
+
.split("\n")
|
|
155
|
+
.filter((l) => l.trim())
|
|
156
|
+
.map((line, i) => (
|
|
157
|
+
<Text key={i} style={styles.stackFrame}>
|
|
158
|
+
{line.trim()}
|
|
159
|
+
</Text>
|
|
160
|
+
))}
|
|
161
|
+
</View>
|
|
162
|
+
) : null}
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const rowStyle = [
|
|
167
|
+
styles.wrapper,
|
|
168
|
+
level.background ? { backgroundColor: level.background } : styles.dividerTop,
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
// Nesting indentation markers.
|
|
172
|
+
const markers = Array.from({ length: row.depth }, (_, i) => (
|
|
173
|
+
<View key={i} style={styles.nestingMarker} />
|
|
174
|
+
));
|
|
175
|
+
|
|
176
|
+
// Origin gutter: swimlane columns (multi-track) or a single bracket.
|
|
177
|
+
const gutter = row.lanes ? (
|
|
178
|
+
<View style={styles.laneGutter}>
|
|
179
|
+
{row.lanes.map((cell, i) => (
|
|
180
|
+
<Text key={i} style={[styles.laneCell, cell.color ? { color: cell.color } : null]}>
|
|
181
|
+
{cell.glyph}
|
|
182
|
+
</Text>
|
|
183
|
+
))}
|
|
184
|
+
</View>
|
|
185
|
+
) : row.bracketGlyph ? (
|
|
186
|
+
<Text style={[styles.originGutter, { color: row.originColor }]}>{row.bracketGlyph}</Text>
|
|
187
|
+
) : null;
|
|
188
|
+
|
|
189
|
+
const body = (
|
|
190
|
+
<View style={styles.rowWrapper}>
|
|
191
|
+
{gutter}
|
|
192
|
+
{markers}
|
|
193
|
+
{inner}
|
|
194
|
+
</View>
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Group headers and stack-bearing rows are pressable.
|
|
198
|
+
if (row.isGroupHeader && row.groupId !== undefined) {
|
|
199
|
+
const gid = row.groupId;
|
|
200
|
+
return (
|
|
201
|
+
<TouchableOpacity
|
|
202
|
+
style={rowStyle}
|
|
203
|
+
activeOpacity={0.7}
|
|
204
|
+
onPress={() => onToggleGroup(gid, Boolean(row.groupCollapsed))}
|
|
205
|
+
>
|
|
206
|
+
{body}
|
|
207
|
+
</TouchableOpacity>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (hasStack) {
|
|
211
|
+
return (
|
|
212
|
+
<TouchableOpacity style={rowStyle} activeOpacity={0.7} onPress={() => setStackOpen((o) => !o)}>
|
|
213
|
+
{body}
|
|
214
|
+
</TouchableOpacity>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return <View style={rowStyle}>{body}</View>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const styles = StyleSheet.create({
|
|
221
|
+
wrapper: {
|
|
222
|
+
paddingVertical: 2,
|
|
223
|
+
paddingRight: 8,
|
|
224
|
+
minHeight: 18,
|
|
225
|
+
},
|
|
226
|
+
dividerTop: {
|
|
227
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
228
|
+
borderTopColor: consoleTheme.divider,
|
|
229
|
+
},
|
|
230
|
+
rowWrapper: {
|
|
231
|
+
flexDirection: "row",
|
|
232
|
+
alignItems: "flex-start",
|
|
233
|
+
},
|
|
234
|
+
originGutter: {
|
|
235
|
+
width: 11,
|
|
236
|
+
fontFamily: monoFont,
|
|
237
|
+
fontSize: 13,
|
|
238
|
+
lineHeight: 18,
|
|
239
|
+
textAlign: "center",
|
|
240
|
+
},
|
|
241
|
+
laneGutter: {
|
|
242
|
+
flexDirection: "row",
|
|
243
|
+
},
|
|
244
|
+
laneCell: {
|
|
245
|
+
width: 9,
|
|
246
|
+
fontFamily: monoFont,
|
|
247
|
+
fontSize: 13,
|
|
248
|
+
lineHeight: 18,
|
|
249
|
+
textAlign: "center",
|
|
250
|
+
},
|
|
251
|
+
originLabel: {
|
|
252
|
+
fontFamily: monoFont,
|
|
253
|
+
fontSize: 10,
|
|
254
|
+
lineHeight: 16,
|
|
255
|
+
marginLeft: 8,
|
|
256
|
+
opacity: 0.9,
|
|
257
|
+
},
|
|
258
|
+
nestingMarker: {
|
|
259
|
+
width: 14,
|
|
260
|
+
alignSelf: "stretch",
|
|
261
|
+
borderRightWidth: StyleSheet.hairlineWidth,
|
|
262
|
+
borderRightColor: consoleTheme.divider,
|
|
263
|
+
marginLeft: 2,
|
|
264
|
+
},
|
|
265
|
+
messageColumn: {
|
|
266
|
+
flex: 1,
|
|
267
|
+
flexDirection: "column",
|
|
268
|
+
paddingLeft: 6,
|
|
269
|
+
},
|
|
270
|
+
messageLine: {
|
|
271
|
+
flexDirection: "row",
|
|
272
|
+
flexWrap: "wrap",
|
|
273
|
+
alignItems: "flex-start",
|
|
274
|
+
},
|
|
275
|
+
contentWrap: {
|
|
276
|
+
flexDirection: "row",
|
|
277
|
+
flexWrap: "wrap",
|
|
278
|
+
alignItems: "flex-start",
|
|
279
|
+
flexShrink: 1,
|
|
280
|
+
},
|
|
281
|
+
text: {
|
|
282
|
+
fontFamily: monoFont,
|
|
283
|
+
fontSize: 12,
|
|
284
|
+
lineHeight: 16,
|
|
285
|
+
},
|
|
286
|
+
groupTitle: {
|
|
287
|
+
fontWeight: "bold",
|
|
288
|
+
},
|
|
289
|
+
clusterTitle: {
|
|
290
|
+
fontWeight: "600",
|
|
291
|
+
},
|
|
292
|
+
clusterCount: {
|
|
293
|
+
color: consoleTheme.tokenSubtle,
|
|
294
|
+
fontFamily: monoFont,
|
|
295
|
+
fontSize: 12,
|
|
296
|
+
lineHeight: 16,
|
|
297
|
+
},
|
|
298
|
+
groupTriangle: {
|
|
299
|
+
fontSize: 9,
|
|
300
|
+
lineHeight: 16,
|
|
301
|
+
marginRight: 4,
|
|
302
|
+
},
|
|
303
|
+
stackTriangle: {
|
|
304
|
+
fontSize: 8,
|
|
305
|
+
lineHeight: 16,
|
|
306
|
+
marginRight: 4,
|
|
307
|
+
},
|
|
308
|
+
levelIcon: {
|
|
309
|
+
fontSize: 12,
|
|
310
|
+
lineHeight: 16,
|
|
311
|
+
marginRight: 5,
|
|
312
|
+
},
|
|
313
|
+
timestamp: {
|
|
314
|
+
color: consoleTheme.tokenSubtle,
|
|
315
|
+
fontFamily: monoFont,
|
|
316
|
+
fontSize: 11,
|
|
317
|
+
lineHeight: 16,
|
|
318
|
+
marginRight: 6,
|
|
319
|
+
},
|
|
320
|
+
repeatBadge: {
|
|
321
|
+
borderWidth: 1,
|
|
322
|
+
borderRadius: 9,
|
|
323
|
+
paddingHorizontal: 5,
|
|
324
|
+
minWidth: 16,
|
|
325
|
+
alignItems: "center",
|
|
326
|
+
justifyContent: "center",
|
|
327
|
+
marginLeft: 6,
|
|
328
|
+
marginTop: 1,
|
|
329
|
+
},
|
|
330
|
+
repeatBadgeText: {
|
|
331
|
+
fontSize: 10,
|
|
332
|
+
fontWeight: "700",
|
|
333
|
+
lineHeight: 14,
|
|
334
|
+
},
|
|
335
|
+
stackTrace: {
|
|
336
|
+
marginTop: 2,
|
|
337
|
+
marginLeft: 12,
|
|
338
|
+
},
|
|
339
|
+
stackFrame: {
|
|
340
|
+
color: consoleTheme.tokenSubtle,
|
|
341
|
+
fontFamily: monoFont,
|
|
342
|
+
fontSize: 11,
|
|
343
|
+
lineHeight: 15,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConsoleValue — RN approximation of DevTools' object/value rendering
|
|
3
|
+
* (ObjectPropertiesSection + RemoteObjectPreviewFormatter).
|
|
4
|
+
*
|
|
5
|
+
* Primitives render with the DevTools dark-theme `object-value-*` token colors.
|
|
6
|
+
* Objects/arrays render an italic one-line preview with an expand triangle;
|
|
7
|
+
* expanding reveals a property tree (lazy, recursive). Matches the console's
|
|
8
|
+
* collapsed-preview / click-to-expand behavior.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useState } from "react";
|
|
12
|
+
import { Text, View, TouchableOpacity, StyleSheet } from "react-native";
|
|
13
|
+
import type { TextStyle } from "react-native";
|
|
14
|
+
import { consoleTheme, monoFont } from "./theme";
|
|
15
|
+
|
|
16
|
+
const MAX_PREVIEW_PROPERTIES = 5;
|
|
17
|
+
const MAX_PREVIEW_ARRAY = 6;
|
|
18
|
+
|
|
19
|
+
type ValueKind =
|
|
20
|
+
| "number"
|
|
21
|
+
| "bigint"
|
|
22
|
+
| "boolean"
|
|
23
|
+
| "string"
|
|
24
|
+
| "null"
|
|
25
|
+
| "undefined"
|
|
26
|
+
| "function"
|
|
27
|
+
| "symbol"
|
|
28
|
+
| "array"
|
|
29
|
+
| "object"
|
|
30
|
+
| "error";
|
|
31
|
+
|
|
32
|
+
function kindOf(value: unknown): ValueKind {
|
|
33
|
+
if (value === null) return "null";
|
|
34
|
+
if (value === undefined) return "undefined";
|
|
35
|
+
if (Array.isArray(value)) return "array";
|
|
36
|
+
if (value instanceof Error) return "error";
|
|
37
|
+
const t = typeof value;
|
|
38
|
+
if (t === "number") return "number";
|
|
39
|
+
if (t === "bigint") return "bigint";
|
|
40
|
+
if (t === "boolean") return "boolean";
|
|
41
|
+
if (t === "string") return "string";
|
|
42
|
+
if (t === "function") return "function";
|
|
43
|
+
if (t === "symbol") return "symbol";
|
|
44
|
+
return "object";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const KIND_COLOR: Partial<Record<ValueKind, string>> = {
|
|
48
|
+
number: consoleTheme.valueNumber,
|
|
49
|
+
bigint: consoleTheme.valueBigint,
|
|
50
|
+
boolean: consoleTheme.valueNumber,
|
|
51
|
+
string: consoleTheme.valueString,
|
|
52
|
+
null: consoleTheme.valueNullish,
|
|
53
|
+
undefined: consoleTheme.valueNullish,
|
|
54
|
+
symbol: consoleTheme.valueString,
|
|
55
|
+
function: consoleTheme.valueNode,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** The constructor/type label DevTools shows before an object's preview. */
|
|
59
|
+
function constructorName(value: object): string {
|
|
60
|
+
if (Array.isArray(value)) return "";
|
|
61
|
+
const proto = Object.getPrototypeOf(value);
|
|
62
|
+
if (!proto) return "Object";
|
|
63
|
+
const name = proto.constructor?.name;
|
|
64
|
+
return !name || name === "Object" ? "" : name;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One-line scalar text for a value (used inside previews). */
|
|
68
|
+
function scalarText(value: unknown): string {
|
|
69
|
+
const kind = kindOf(value);
|
|
70
|
+
switch (kind) {
|
|
71
|
+
case "string":
|
|
72
|
+
return `'${value as string}'`;
|
|
73
|
+
case "function": {
|
|
74
|
+
const name = (value as { name?: string }).name;
|
|
75
|
+
return `ƒ ${name || ""}`.trimEnd();
|
|
76
|
+
}
|
|
77
|
+
case "symbol":
|
|
78
|
+
return (value as symbol).toString();
|
|
79
|
+
case "bigint":
|
|
80
|
+
return `${String(value)}n`;
|
|
81
|
+
case "array":
|
|
82
|
+
return `Array(${(value as unknown[]).length})`;
|
|
83
|
+
case "error":
|
|
84
|
+
return (value as Error).name;
|
|
85
|
+
case "object": {
|
|
86
|
+
const name = constructorName(value as object);
|
|
87
|
+
return name ? `${name} {…}` : "{…}";
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Build the collapsed italic preview string for an object/array. */
|
|
95
|
+
function buildPreview(value: object): string {
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
const head = value
|
|
98
|
+
.slice(0, MAX_PREVIEW_ARRAY)
|
|
99
|
+
.map((v) => scalarText(v))
|
|
100
|
+
.join(", ");
|
|
101
|
+
const more = value.length > MAX_PREVIEW_ARRAY ? ", …" : "";
|
|
102
|
+
return `(${value.length}) [${head}${more}]`;
|
|
103
|
+
}
|
|
104
|
+
const entries = Object.entries(value);
|
|
105
|
+
const head = entries
|
|
106
|
+
.slice(0, MAX_PREVIEW_PROPERTIES)
|
|
107
|
+
.map(([k, v]) => `${k}: ${scalarText(v)}`)
|
|
108
|
+
.join(", ");
|
|
109
|
+
const more = entries.length > MAX_PREVIEW_PROPERTIES ? ", …" : "";
|
|
110
|
+
const ctor = constructorName(value);
|
|
111
|
+
const prefix = ctor ? `${ctor} ` : "";
|
|
112
|
+
return `${prefix}{${head}${more}}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isExpandable(value: unknown): value is object {
|
|
116
|
+
const kind = kindOf(value);
|
|
117
|
+
if (kind === "array") return (value as unknown[]).length > 0;
|
|
118
|
+
if (kind === "object") return Object.keys(value as object).length > 0;
|
|
119
|
+
if (kind === "error") return true;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const Triangle = ({ open }: { open: boolean }) => (
|
|
124
|
+
<Text style={[styles.triangle, open && styles.triangleOpen]}>▶</Text>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
interface ConsoleValueProps {
|
|
128
|
+
value: unknown;
|
|
129
|
+
/** Force the generic (non-preview) object tree, like %O. */
|
|
130
|
+
generic?: boolean;
|
|
131
|
+
/** Nesting depth guard. */
|
|
132
|
+
depth?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Render a single console argument / property value. */
|
|
136
|
+
export function ConsoleValue({ value, depth = 0 }: ConsoleValueProps) {
|
|
137
|
+
const [expanded, setExpanded] = useState(false);
|
|
138
|
+
const kind = kindOf(value);
|
|
139
|
+
|
|
140
|
+
// Errors: collapsed to a single "Name: message" line (like Chrome / the web
|
|
141
|
+
// dashboard), expandable to the full stack. Avoids dumping a 100-line stack
|
|
142
|
+
// inline on native, where the arg is a real Error (not a sanitized object).
|
|
143
|
+
if (kind === "error") {
|
|
144
|
+
const err = value as Error;
|
|
145
|
+
const head = `${err.name}: ${err.message}`.split("\n")[0];
|
|
146
|
+
const stack = err.stack && err.stack.trim() !== head.trim() ? err.stack : undefined;
|
|
147
|
+
if (!stack) {
|
|
148
|
+
return (
|
|
149
|
+
<Text style={[styles.mono, { color: consoleTheme.onErrorContainer }]} numberOfLines={1}>
|
|
150
|
+
{head}
|
|
151
|
+
</Text>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return (
|
|
155
|
+
<View style={styles.objectContainer}>
|
|
156
|
+
<TouchableOpacity
|
|
157
|
+
style={styles.objectHeader}
|
|
158
|
+
activeOpacity={0.6}
|
|
159
|
+
onPress={() => setExpanded((e) => !e)}
|
|
160
|
+
>
|
|
161
|
+
<Triangle open={expanded} />
|
|
162
|
+
<Text
|
|
163
|
+
style={[styles.mono, styles.errorHead]}
|
|
164
|
+
numberOfLines={expanded ? undefined : 1}
|
|
165
|
+
>
|
|
166
|
+
{head}
|
|
167
|
+
</Text>
|
|
168
|
+
</TouchableOpacity>
|
|
169
|
+
{expanded ? <Text style={[styles.mono, styles.errorStack]}>{stack}</Text> : null}
|
|
170
|
+
</View>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!isExpandable(value)) {
|
|
175
|
+
const color = KIND_COLOR[kind] ?? consoleTheme.onSurface;
|
|
176
|
+
return <Text style={[styles.mono, { color }]}>{scalarText(value)}</Text>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const obj = value as object;
|
|
180
|
+
const entries = Array.isArray(obj)
|
|
181
|
+
? obj.map((v, i) => [String(i), v] as [string, unknown])
|
|
182
|
+
: Object.entries(obj);
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<View style={styles.objectContainer}>
|
|
186
|
+
<TouchableOpacity
|
|
187
|
+
onPress={() => setExpanded((e) => !e)}
|
|
188
|
+
activeOpacity={0.6}
|
|
189
|
+
style={styles.objectHeader}
|
|
190
|
+
>
|
|
191
|
+
<Triangle open={expanded} />
|
|
192
|
+
<Text style={[styles.mono, styles.preview]} numberOfLines={expanded ? undefined : 1}>
|
|
193
|
+
{buildPreview(obj)}
|
|
194
|
+
</Text>
|
|
195
|
+
</TouchableOpacity>
|
|
196
|
+
{expanded && depth < 6 ? (
|
|
197
|
+
<View style={styles.children}>
|
|
198
|
+
{entries.map(([key, v]) => (
|
|
199
|
+
<View key={key} style={styles.propertyRow}>
|
|
200
|
+
<Text style={[styles.mono, styles.propertyKey]}>{key}:</Text>
|
|
201
|
+
<View style={styles.propertyValue}>
|
|
202
|
+
<ConsoleValue value={v} depth={depth + 1} />
|
|
203
|
+
</View>
|
|
204
|
+
</View>
|
|
205
|
+
))}
|
|
206
|
+
</View>
|
|
207
|
+
) : null}
|
|
208
|
+
</View>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const monoStyle: TextStyle = {
|
|
213
|
+
fontFamily: monoFont,
|
|
214
|
+
fontSize: 12,
|
|
215
|
+
lineHeight: 16,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const styles = StyleSheet.create({
|
|
219
|
+
mono: monoStyle,
|
|
220
|
+
objectContainer: {
|
|
221
|
+
flexDirection: "column",
|
|
222
|
+
},
|
|
223
|
+
objectHeader: {
|
|
224
|
+
flexDirection: "row",
|
|
225
|
+
alignItems: "flex-start",
|
|
226
|
+
},
|
|
227
|
+
triangle: {
|
|
228
|
+
color: consoleTheme.tokenSubtle,
|
|
229
|
+
fontSize: 8,
|
|
230
|
+
lineHeight: 16,
|
|
231
|
+
width: 12,
|
|
232
|
+
},
|
|
233
|
+
triangleOpen: {
|
|
234
|
+
transform: [{ rotate: "90deg" }],
|
|
235
|
+
},
|
|
236
|
+
preview: {
|
|
237
|
+
color: consoleTheme.valuePreview,
|
|
238
|
+
fontStyle: "italic",
|
|
239
|
+
flexShrink: 1,
|
|
240
|
+
},
|
|
241
|
+
errorHead: {
|
|
242
|
+
color: consoleTheme.onErrorContainer,
|
|
243
|
+
flexShrink: 1,
|
|
244
|
+
},
|
|
245
|
+
errorStack: {
|
|
246
|
+
color: consoleTheme.tokenSubtle,
|
|
247
|
+
marginLeft: 14,
|
|
248
|
+
marginTop: 1,
|
|
249
|
+
},
|
|
250
|
+
children: {
|
|
251
|
+
marginLeft: 14,
|
|
252
|
+
flexDirection: "column",
|
|
253
|
+
},
|
|
254
|
+
propertyRow: {
|
|
255
|
+
flexDirection: "row",
|
|
256
|
+
alignItems: "flex-start",
|
|
257
|
+
},
|
|
258
|
+
propertyKey: {
|
|
259
|
+
color: consoleTheme.valueNode,
|
|
260
|
+
marginRight: 6,
|
|
261
|
+
},
|
|
262
|
+
propertyValue: {
|
|
263
|
+
flexShrink: 1,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConsoleView — self-contained Console panel (header bar + body) for embedding
|
|
3
|
+
* outside a JsModal (the desktop dashboard inline tool, or any standalone use).
|
|
4
|
+
*
|
|
5
|
+
* Inside the mobile floating menu, ConsoleModal composes the same pieces but
|
|
6
|
+
* routes the header into JsModal's `customContent` instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { View, StyleSheet } from "react-native";
|
|
11
|
+
import { macOSColors } from "@buoy-gg/shared-ui";
|
|
12
|
+
import { consoleTheme } from "./theme";
|
|
13
|
+
import { ConsoleHeader } from "./ConsoleHeader";
|
|
14
|
+
import { ConsoleBody } from "./ConsoleBody";
|
|
15
|
+
import { useConsoleController } from "./useConsoleController";
|
|
16
|
+
|
|
17
|
+
export function ConsoleView() {
|
|
18
|
+
const c = useConsoleController();
|
|
19
|
+
return (
|
|
20
|
+
<View style={styles.container}>
|
|
21
|
+
<View style={styles.headerBar}>
|
|
22
|
+
<ConsoleHeader c={c} />
|
|
23
|
+
</View>
|
|
24
|
+
<ConsoleBody c={c} />
|
|
25
|
+
</View>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const styles = StyleSheet.create({
|
|
30
|
+
container: {
|
|
31
|
+
flex: 1,
|
|
32
|
+
backgroundColor: consoleTheme.baseContainer,
|
|
33
|
+
},
|
|
34
|
+
headerBar: {
|
|
35
|
+
paddingHorizontal: 12,
|
|
36
|
+
paddingVertical: 8,
|
|
37
|
+
backgroundColor: consoleTheme.baseContainer,
|
|
38
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
39
|
+
borderBottomColor: macOSColors.border.default,
|
|
40
|
+
},
|
|
41
|
+
});
|