react-native-vconsole 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/lib/module/VConsole.js +283 -46
- package/lib/module/VConsole.js.map +1 -1
- package/lib/module/core/xhrProxy.js +145 -7
- package/lib/module/core/xhrProxy.js.map +1 -1
- package/lib/typescript/src/VConsole.d.ts +6 -1
- package/lib/typescript/src/VConsole.d.ts.map +1 -1
- package/lib/typescript/src/core/xhrProxy.d.ts +1 -0
- package/lib/typescript/src/core/xhrProxy.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +2 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/VConsole.tsx +356 -53
- package/src/core/xhrProxy.ts +177 -8
- package/src/types.ts +2 -0
package/src/VConsole.tsx
CHANGED
|
@@ -4,10 +4,12 @@ import {
|
|
|
4
4
|
Clipboard,
|
|
5
5
|
Dimensions,
|
|
6
6
|
FlatList,
|
|
7
|
+
Keyboard,
|
|
7
8
|
NativeModules,
|
|
8
9
|
PanResponder,
|
|
9
10
|
Platform,
|
|
10
11
|
Pressable,
|
|
12
|
+
TextInput,
|
|
11
13
|
StatusBar,
|
|
12
14
|
StyleSheet,
|
|
13
15
|
Text,
|
|
@@ -42,7 +44,7 @@ import type {
|
|
|
42
44
|
const BUTTON_WIDTH = 88;
|
|
43
45
|
const BUTTON_HEIGHT = 36;
|
|
44
46
|
const PANEL_HEIGHT_RATIO = 7 / 9;
|
|
45
|
-
const
|
|
47
|
+
const EMPTY_EXCLUDE: VConsoleExclude = {};
|
|
46
48
|
const LOG_SUB_TABS: LogFilterTab[] = ['All', 'log', 'info', 'warn', 'error'];
|
|
47
49
|
const ROOT_TABS: VConsoleTab[] = ['Log', 'Network', 'System', 'App'];
|
|
48
50
|
|
|
@@ -62,7 +64,12 @@ type NativeModuleShape = {
|
|
|
62
64
|
|
|
63
65
|
export type VConsoleProps = {
|
|
64
66
|
enable?: boolean;
|
|
65
|
-
exclude?:
|
|
67
|
+
exclude?: VConsoleExclude;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type VConsoleExclude = {
|
|
71
|
+
domains?: string[];
|
|
72
|
+
ip?: boolean;
|
|
66
73
|
};
|
|
67
74
|
|
|
68
75
|
function clamp(value: number, min: number, max: number): number {
|
|
@@ -102,6 +109,18 @@ function formatMemorySize(bytes: unknown): string {
|
|
|
102
109
|
return `${mb.toFixed(2)} MB`;
|
|
103
110
|
}
|
|
104
111
|
|
|
112
|
+
function formatLogTime(timestamp: number): string {
|
|
113
|
+
const date = new Date(timestamp);
|
|
114
|
+
if (Number.isNaN(date.getTime())) {
|
|
115
|
+
return '--:--:--.---';
|
|
116
|
+
}
|
|
117
|
+
const hh = String(date.getHours()).padStart(2, '0');
|
|
118
|
+
const mm = String(date.getMinutes()).padStart(2, '0');
|
|
119
|
+
const ss = String(date.getSeconds()).padStart(2, '0');
|
|
120
|
+
const ms = String(date.getMilliseconds()).padStart(3, '0');
|
|
121
|
+
return `${hh}:${mm}:${ss}.${ms}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
105
124
|
function prettyText(value: unknown): string {
|
|
106
125
|
if (value === undefined) {
|
|
107
126
|
return '';
|
|
@@ -116,19 +135,103 @@ function prettyText(value: unknown): string {
|
|
|
116
135
|
}
|
|
117
136
|
}
|
|
118
137
|
|
|
138
|
+
function isNetworkErrorEntry(item: NetworkEntry): boolean {
|
|
139
|
+
return item.isError === true;
|
|
140
|
+
}
|
|
141
|
+
|
|
119
142
|
function buildNetworkCopyText(item: NetworkEntry): string {
|
|
120
143
|
const status = item.status ?? '-';
|
|
121
144
|
const duration =
|
|
122
145
|
typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-';
|
|
146
|
+
const isError = isNetworkErrorEntry(item);
|
|
123
147
|
|
|
124
|
-
|
|
148
|
+
const segments = [
|
|
125
149
|
`${item.method} ${item.url}`,
|
|
126
150
|
`status ${status} duration ${duration}`,
|
|
127
151
|
`request headers\n${prettyText(item.requestHeaders)}`,
|
|
128
152
|
`request body\n${prettyText(item.requestBody)}`,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
if (isError) {
|
|
156
|
+
segments.push(
|
|
157
|
+
`error reason\n${item.errorReason ?? 'Network request failed'}`
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
segments.push(`response headers\n${prettyText(item.responseHeaders)}`);
|
|
161
|
+
segments.push(`response data\n${prettyText(item.responseData)}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return segments.join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const FORBIDDEN_RETRY_HEADERS = new Set([
|
|
168
|
+
'host',
|
|
169
|
+
'content-length',
|
|
170
|
+
'accept-encoding',
|
|
171
|
+
'connection',
|
|
172
|
+
'origin',
|
|
173
|
+
'referer',
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
function normalizeRetryUrl(rawUrl: string): string {
|
|
177
|
+
if (!rawUrl) {
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
if (/^\/\//.test(rawUrl)) {
|
|
181
|
+
return `https:${rawUrl}`;
|
|
182
|
+
}
|
|
183
|
+
return rawUrl;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildRetryHeaders(
|
|
187
|
+
headers: Record<string, string> | undefined
|
|
188
|
+
): Record<string, string> {
|
|
189
|
+
const nextHeaders: Record<string, string> = {};
|
|
190
|
+
if (!headers) {
|
|
191
|
+
return nextHeaders;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
195
|
+
if (!FORBIDDEN_RETRY_HEADERS.has(key.toLowerCase())) {
|
|
196
|
+
nextHeaders[key] = value;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return nextHeaders;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildRetryBody(payload: unknown, method: string): unknown | undefined {
|
|
203
|
+
if (method === 'GET' || method === 'HEAD' || payload == null) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
if (typeof payload === 'string') {
|
|
207
|
+
return payload;
|
|
208
|
+
}
|
|
209
|
+
if (typeof payload === 'number' || typeof payload === 'boolean') {
|
|
210
|
+
return String(payload);
|
|
211
|
+
}
|
|
212
|
+
if (typeof FormData !== 'undefined' && payload instanceof FormData) {
|
|
213
|
+
return payload;
|
|
214
|
+
}
|
|
215
|
+
if (
|
|
216
|
+
typeof URLSearchParams !== 'undefined' &&
|
|
217
|
+
payload instanceof URLSearchParams
|
|
218
|
+
) {
|
|
219
|
+
return payload;
|
|
220
|
+
}
|
|
221
|
+
if (typeof Blob !== 'undefined' && payload instanceof Blob) {
|
|
222
|
+
return payload;
|
|
223
|
+
}
|
|
224
|
+
if (typeof ArrayBuffer !== 'undefined' && payload instanceof ArrayBuffer) {
|
|
225
|
+
return payload;
|
|
226
|
+
}
|
|
227
|
+
if (ArrayBuffer.isView(payload)) {
|
|
228
|
+
return payload;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return JSON.stringify(payload);
|
|
232
|
+
} catch {
|
|
233
|
+
return String(payload);
|
|
234
|
+
}
|
|
132
235
|
}
|
|
133
236
|
|
|
134
237
|
type ObjectTreeProps = {
|
|
@@ -225,7 +328,7 @@ function useFlatListRefs() {
|
|
|
225
328
|
|
|
226
329
|
export function VConsole({
|
|
227
330
|
enable = true,
|
|
228
|
-
exclude =
|
|
331
|
+
exclude = EMPTY_EXCLUDE,
|
|
229
332
|
}: VConsoleProps) {
|
|
230
333
|
const nativeModule = NativeModules.Vconsole as NativeModuleShape | undefined;
|
|
231
334
|
const { width, height } = Dimensions.get('window');
|
|
@@ -258,6 +361,11 @@ export function VConsole({
|
|
|
258
361
|
const [logSubTab, setLogSubTab] = useState<LogFilterTab>('All');
|
|
259
362
|
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
|
260
363
|
const [networkEntries, setNetworkEntries] = useState<NetworkEntry[]>([]);
|
|
364
|
+
const [logFilterInput, setLogFilterInput] = useState('');
|
|
365
|
+
const [networkFilterInput, setNetworkFilterInput] = useState('');
|
|
366
|
+
const [debouncedLogFilter, setDebouncedLogFilter] = useState('');
|
|
367
|
+
const [debouncedNetworkFilter, setDebouncedNetworkFilter] = useState('');
|
|
368
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
261
369
|
const [expandedMap, setExpandedMap] = useState<ExpandedMap>({});
|
|
262
370
|
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
|
|
263
371
|
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
|
@@ -266,10 +374,14 @@ export function VConsole({
|
|
|
266
374
|
const panelTranslateY = useRef(new Animated.Value(panelHeight)).current;
|
|
267
375
|
const logListRefs = useFlatListRefs();
|
|
268
376
|
const networkListRef = useRef<FlatList<NetworkEntry>>(null);
|
|
269
|
-
const
|
|
270
|
-
() =>
|
|
271
|
-
|
|
377
|
+
const normalizedExcludeDomains = useMemo(
|
|
378
|
+
() =>
|
|
379
|
+
(exclude.domains ?? [])
|
|
380
|
+
.map((item) => item.trim().toLowerCase())
|
|
381
|
+
.filter(Boolean),
|
|
382
|
+
[exclude.domains]
|
|
272
383
|
);
|
|
384
|
+
const shouldExcludeIp = exclude.ip === true;
|
|
273
385
|
|
|
274
386
|
useEffect(() => {
|
|
275
387
|
if (!enable) {
|
|
@@ -278,7 +390,10 @@ export function VConsole({
|
|
|
278
390
|
}
|
|
279
391
|
|
|
280
392
|
installConsoleProxy();
|
|
281
|
-
installXhrProxy({
|
|
393
|
+
installXhrProxy({
|
|
394
|
+
excludeHosts: normalizedExcludeDomains,
|
|
395
|
+
excludeIp: shouldExcludeIp,
|
|
396
|
+
});
|
|
282
397
|
|
|
283
398
|
const unsubscribeLog = subscribeLogEntries(setLogEntries);
|
|
284
399
|
const unsubscribeNetwork = subscribeNetworkEntries(setNetworkEntries);
|
|
@@ -291,7 +406,7 @@ export function VConsole({
|
|
|
291
406
|
uninstallConsoleProxy();
|
|
292
407
|
uninstallXhrProxy();
|
|
293
408
|
};
|
|
294
|
-
}, [enable,
|
|
409
|
+
}, [enable, normalizedExcludeDomains, shouldExcludeIp]);
|
|
295
410
|
|
|
296
411
|
useEffect(() => {
|
|
297
412
|
dragPosition.stopAnimation((value) => {
|
|
@@ -317,6 +432,39 @@ export function VConsole({
|
|
|
317
432
|
}
|
|
318
433
|
}, [activeTab, appInfo, nativeModule, panelVisible, systemInfo]);
|
|
319
434
|
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
const showEvent =
|
|
437
|
+
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
438
|
+
const hideEvent =
|
|
439
|
+
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
440
|
+
|
|
441
|
+
const showSubscription = Keyboard.addListener(showEvent, (event) => {
|
|
442
|
+
setKeyboardHeight(event.endCoordinates?.height ?? 0);
|
|
443
|
+
});
|
|
444
|
+
const hideSubscription = Keyboard.addListener(hideEvent, () => {
|
|
445
|
+
setKeyboardHeight(0);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
return () => {
|
|
449
|
+
showSubscription.remove();
|
|
450
|
+
hideSubscription.remove();
|
|
451
|
+
};
|
|
452
|
+
}, []);
|
|
453
|
+
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
const timer = setTimeout(() => {
|
|
456
|
+
setDebouncedLogFilter(logFilterInput);
|
|
457
|
+
}, 1000);
|
|
458
|
+
return () => clearTimeout(timer);
|
|
459
|
+
}, [logFilterInput]);
|
|
460
|
+
|
|
461
|
+
useEffect(() => {
|
|
462
|
+
const timer = setTimeout(() => {
|
|
463
|
+
setDebouncedNetworkFilter(networkFilterInput);
|
|
464
|
+
}, 1000);
|
|
465
|
+
return () => clearTimeout(timer);
|
|
466
|
+
}, [networkFilterInput]);
|
|
467
|
+
|
|
320
468
|
const panResponder = useMemo(
|
|
321
469
|
() =>
|
|
322
470
|
PanResponder.create({
|
|
@@ -370,15 +518,36 @@ export function VConsole({
|
|
|
370
518
|
});
|
|
371
519
|
};
|
|
372
520
|
|
|
521
|
+
const normalizedLogFilter = debouncedLogFilter.trim().toLowerCase();
|
|
522
|
+
const normalizedNetworkFilter = debouncedNetworkFilter.trim().toLowerCase();
|
|
523
|
+
|
|
524
|
+
const filteredLogEntries = useMemo(() => {
|
|
525
|
+
if (!normalizedLogFilter) {
|
|
526
|
+
return logEntries;
|
|
527
|
+
}
|
|
528
|
+
return logEntries.filter((item) =>
|
|
529
|
+
item.text.toLowerCase().includes(normalizedLogFilter)
|
|
530
|
+
);
|
|
531
|
+
}, [logEntries, normalizedLogFilter]);
|
|
532
|
+
|
|
533
|
+
const filteredNetworkEntries = useMemo(() => {
|
|
534
|
+
if (!normalizedNetworkFilter) {
|
|
535
|
+
return networkEntries;
|
|
536
|
+
}
|
|
537
|
+
return networkEntries.filter((item) =>
|
|
538
|
+
item.url.toLowerCase().includes(normalizedNetworkFilter)
|
|
539
|
+
);
|
|
540
|
+
}, [networkEntries, normalizedNetworkFilter]);
|
|
541
|
+
|
|
373
542
|
const logDataByTab = useMemo(
|
|
374
543
|
() => ({
|
|
375
|
-
All:
|
|
376
|
-
log:
|
|
377
|
-
info:
|
|
378
|
-
warn:
|
|
379
|
-
error:
|
|
544
|
+
All: filteredLogEntries,
|
|
545
|
+
log: filteredLogEntries.filter((item) => item.level === 'log'),
|
|
546
|
+
info: filteredLogEntries.filter((item) => item.level === 'info'),
|
|
547
|
+
warn: filteredLogEntries.filter((item) => item.level === 'warn'),
|
|
548
|
+
error: filteredLogEntries.filter((item) => item.level === 'error'),
|
|
380
549
|
}),
|
|
381
|
-
[
|
|
550
|
+
[filteredLogEntries]
|
|
382
551
|
);
|
|
383
552
|
|
|
384
553
|
const onToggleNode = (key: string) => {
|
|
@@ -407,6 +576,39 @@ export function VConsole({
|
|
|
407
576
|
networkListRef.current?.scrollToEnd({ animated: true });
|
|
408
577
|
};
|
|
409
578
|
|
|
579
|
+
const retryNetworkRequest = (item: NetworkEntry) => {
|
|
580
|
+
const method = (item.method || 'GET').toUpperCase();
|
|
581
|
+
const url = normalizeRetryUrl(item.url);
|
|
582
|
+
if (!url) {
|
|
583
|
+
console.error('[vConsole] Retry failed: empty request URL');
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const headers = buildRetryHeaders(item.requestHeaders);
|
|
588
|
+
const body = buildRetryBody(item.requestBody, method);
|
|
589
|
+
const hasContentType = Object.keys(headers).some(
|
|
590
|
+
(key) => key.toLowerCase() === 'content-type'
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
if (
|
|
594
|
+
body &&
|
|
595
|
+
typeof body === 'string' &&
|
|
596
|
+
typeof item.requestBody === 'object' &&
|
|
597
|
+
item.requestBody !== null &&
|
|
598
|
+
!hasContentType
|
|
599
|
+
) {
|
|
600
|
+
headers['Content-Type'] = 'application/json';
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
fetch(url, {
|
|
604
|
+
method,
|
|
605
|
+
headers,
|
|
606
|
+
body: body as never,
|
|
607
|
+
}).catch((error: unknown) => {
|
|
608
|
+
console.error('[vConsole] Retry request failed', error);
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
|
|
410
612
|
const renderRootTab = (tab: VConsoleTab) => (
|
|
411
613
|
<Pressable
|
|
412
614
|
key={tab}
|
|
@@ -445,6 +647,10 @@ export function VConsole({
|
|
|
445
647
|
<View style={styles.listItemMain}>
|
|
446
648
|
<Text style={[styles.logLevelText, { color: levelTheme.color }]}>
|
|
447
649
|
[{item.level.toUpperCase()}]
|
|
650
|
+
<Text style={styles.logTimeText}>
|
|
651
|
+
{' '}
|
|
652
|
+
{formatLogTime(item.timestamp)}
|
|
653
|
+
</Text>
|
|
448
654
|
</Text>
|
|
449
655
|
<View style={styles.logPayload}>
|
|
450
656
|
{item.args.map((arg, index) => (
|
|
@@ -471,18 +677,31 @@ export function VConsole({
|
|
|
471
677
|
const renderNetworkItem: FlatListProps<NetworkEntry>['renderItem'] = ({
|
|
472
678
|
item,
|
|
473
679
|
}) => {
|
|
680
|
+
const isError = isNetworkErrorEntry(item);
|
|
681
|
+
const startedTime = formatLogTime(item.startedAt);
|
|
682
|
+
const finishedTime =
|
|
683
|
+
typeof item.finishedAt === 'number'
|
|
684
|
+
? formatLogTime(item.finishedAt)
|
|
685
|
+
: '-';
|
|
474
686
|
return (
|
|
475
|
-
<View
|
|
687
|
+
<View
|
|
688
|
+
style={[
|
|
689
|
+
styles.listItem,
|
|
690
|
+
isError ? { backgroundColor: LOG_THEME.error.backgroundColor } : null,
|
|
691
|
+
]}
|
|
692
|
+
>
|
|
476
693
|
<View style={styles.listItemMain}>
|
|
477
694
|
<Text style={styles.networkTitle}>
|
|
478
695
|
{item.method} {item.url}
|
|
479
696
|
</Text>
|
|
480
697
|
<Text style={styles.networkLabel}>
|
|
481
|
-
|
|
698
|
+
Time: {startedTime}
|
|
699
|
+
{finishedTime !== '-' ? ` ~ ${finishedTime}` : ''}
|
|
482
700
|
{' '}
|
|
483
701
|
Duration:{' '}
|
|
484
702
|
{typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-'}
|
|
485
703
|
</Text>
|
|
704
|
+
<Text style={styles.networkLabel}>Status: {item.status ?? '-'}</Text>
|
|
486
705
|
<View style={styles.networkBlock}>
|
|
487
706
|
<Text style={styles.networkLabel}>Request Headers</Text>
|
|
488
707
|
<ObjectTree
|
|
@@ -501,26 +720,39 @@ export function VConsole({
|
|
|
501
720
|
onToggle={onToggleNode}
|
|
502
721
|
/>
|
|
503
722
|
</View>
|
|
504
|
-
|
|
505
|
-
<
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
723
|
+
{isError ? (
|
|
724
|
+
<View style={styles.networkBlock}>
|
|
725
|
+
<Text style={[styles.networkLabel, styles.networkErrorLabel]}>
|
|
726
|
+
Error Reason
|
|
727
|
+
</Text>
|
|
728
|
+
<Text style={styles.networkErrorText}>
|
|
729
|
+
{item.errorReason ?? 'Network request failed'}
|
|
730
|
+
</Text>
|
|
731
|
+
</View>
|
|
732
|
+
) : (
|
|
733
|
+
<>
|
|
734
|
+
<View style={styles.networkBlock}>
|
|
735
|
+
<Text style={styles.networkLabel}>Response Headers</Text>
|
|
736
|
+
<ObjectTree
|
|
737
|
+
value={item.responseHeaders}
|
|
738
|
+
nodeKey={`${item.id}.responseHeaders`}
|
|
739
|
+
expandedMap={expandedMap}
|
|
740
|
+
onToggle={onToggleNode}
|
|
741
|
+
/>
|
|
742
|
+
</View>
|
|
743
|
+
<View style={styles.networkBlock}>
|
|
744
|
+
<Text style={styles.networkLabel}>Response Data</Text>
|
|
745
|
+
<ScrollView horizontal={true}>
|
|
746
|
+
<ObjectTree
|
|
747
|
+
value={item.responseData ?? ''}
|
|
748
|
+
nodeKey={`${item.id}.responseData`}
|
|
749
|
+
expandedMap={expandedMap}
|
|
750
|
+
onToggle={onToggleNode}
|
|
751
|
+
/>
|
|
752
|
+
</ScrollView>
|
|
753
|
+
</View>
|
|
754
|
+
</>
|
|
755
|
+
)}
|
|
524
756
|
</View>
|
|
525
757
|
<Pressable
|
|
526
758
|
style={styles.copyButton}
|
|
@@ -530,12 +762,18 @@ export function VConsole({
|
|
|
530
762
|
>
|
|
531
763
|
<Text style={styles.copyButtonText}>Copy</Text>
|
|
532
764
|
</Pressable>
|
|
765
|
+
<Pressable
|
|
766
|
+
style={styles.retryButton}
|
|
767
|
+
onPress={() => retryNetworkRequest(item)}
|
|
768
|
+
>
|
|
769
|
+
<Text style={styles.retryButtonText}>Retry</Text>
|
|
770
|
+
</Pressable>
|
|
533
771
|
</View>
|
|
534
772
|
);
|
|
535
773
|
};
|
|
536
774
|
|
|
537
|
-
const renderLogPanel = () => (
|
|
538
|
-
<View style={styles.contentArea}>
|
|
775
|
+
const renderLogPanel = (visible: boolean) => (
|
|
776
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
539
777
|
<View style={styles.subTabRow}>
|
|
540
778
|
{LOG_SUB_TABS.map((tab) => (
|
|
541
779
|
<Pressable
|
|
@@ -573,6 +811,16 @@ export function VConsole({
|
|
|
573
811
|
</View>
|
|
574
812
|
))}
|
|
575
813
|
</View>
|
|
814
|
+
<View style={styles.filterInputWrap}>
|
|
815
|
+
<TextInput
|
|
816
|
+
style={styles.filterInput}
|
|
817
|
+
textAlignVertical="center"
|
|
818
|
+
value={logFilterInput}
|
|
819
|
+
onChangeText={setLogFilterInput}
|
|
820
|
+
placeholder="filter..."
|
|
821
|
+
placeholderTextColor="#999999"
|
|
822
|
+
/>
|
|
823
|
+
</View>
|
|
576
824
|
<View style={styles.actionsRow}>
|
|
577
825
|
{renderActionButton('Clear', () => {
|
|
578
826
|
clearLogEntries();
|
|
@@ -585,15 +833,24 @@ export function VConsole({
|
|
|
585
833
|
</View>
|
|
586
834
|
);
|
|
587
835
|
|
|
588
|
-
const renderNetworkPanel = () => (
|
|
589
|
-
<View style={styles.contentArea}>
|
|
836
|
+
const renderNetworkPanel = (visible: boolean) => (
|
|
837
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
590
838
|
<FlatList
|
|
591
839
|
ref={networkListRef}
|
|
592
|
-
data={
|
|
840
|
+
data={filteredNetworkEntries}
|
|
593
841
|
keyExtractor={(item) => `network-${item.id}`}
|
|
594
842
|
renderItem={renderNetworkItem}
|
|
595
843
|
ItemSeparatorComponent={ListSeparator}
|
|
596
844
|
/>
|
|
845
|
+
<View style={styles.filterInputWrap}>
|
|
846
|
+
<TextInput
|
|
847
|
+
style={styles.filterInput}
|
|
848
|
+
value={networkFilterInput}
|
|
849
|
+
onChangeText={setNetworkFilterInput}
|
|
850
|
+
placeholder="filter"
|
|
851
|
+
placeholderTextColor="#999999"
|
|
852
|
+
/>
|
|
853
|
+
</View>
|
|
597
854
|
<View style={styles.actionsRow}>
|
|
598
855
|
{renderActionButton('Clear', () => {
|
|
599
856
|
clearNetworkEntries();
|
|
@@ -606,8 +863,8 @@ export function VConsole({
|
|
|
606
863
|
</View>
|
|
607
864
|
);
|
|
608
865
|
|
|
609
|
-
const renderSystemPanel = () => (
|
|
610
|
-
<View style={styles.contentArea}>
|
|
866
|
+
const renderSystemPanel = (visible: boolean) => (
|
|
867
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
611
868
|
<View style={styles.infoCard}>
|
|
612
869
|
<Text style={styles.infoText}>
|
|
613
870
|
Brand: {systemInfo?.manufacturer ?? '-'}
|
|
@@ -642,8 +899,8 @@ export function VConsole({
|
|
|
642
899
|
</View>
|
|
643
900
|
);
|
|
644
901
|
|
|
645
|
-
const renderAppPanel = () => (
|
|
646
|
-
<View style={styles.contentArea}>
|
|
902
|
+
const renderAppPanel = (visible: boolean) => (
|
|
903
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
647
904
|
<View style={styles.infoCard}>
|
|
648
905
|
<Text style={styles.infoText}>
|
|
649
906
|
App Version: {appInfo?.appVersion ?? '-'}
|
|
@@ -686,15 +943,16 @@ export function VConsole({
|
|
|
686
943
|
styles.panel,
|
|
687
944
|
{
|
|
688
945
|
height: panelHeight,
|
|
946
|
+
marginBottom: keyboardHeight,
|
|
689
947
|
transform: [{ translateY: panelTranslateY }],
|
|
690
948
|
},
|
|
691
949
|
]}
|
|
692
950
|
>
|
|
693
951
|
<View style={styles.topTabRow}>{ROOT_TABS.map(renderRootTab)}</View>
|
|
694
|
-
{activeTab === 'Log'
|
|
695
|
-
{activeTab === 'Network'
|
|
696
|
-
{activeTab === 'System'
|
|
697
|
-
{activeTab === 'App'
|
|
952
|
+
{renderLogPanel(activeTab === 'Log')}
|
|
953
|
+
{renderNetworkPanel(activeTab === 'Network')}
|
|
954
|
+
{renderSystemPanel(activeTab === 'System')}
|
|
955
|
+
{renderAppPanel(activeTab === 'App')}
|
|
698
956
|
</Animated.View>
|
|
699
957
|
</View>
|
|
700
958
|
) : null}
|
|
@@ -813,6 +1071,11 @@ const styles = StyleSheet.create({
|
|
|
813
1071
|
fontWeight: '700',
|
|
814
1072
|
marginBottom: 4,
|
|
815
1073
|
},
|
|
1074
|
+
logTimeText: {
|
|
1075
|
+
fontSize: 11,
|
|
1076
|
+
fontWeight: '400',
|
|
1077
|
+
color: '#888888',
|
|
1078
|
+
},
|
|
816
1079
|
logPayload: {
|
|
817
1080
|
flex: 1,
|
|
818
1081
|
},
|
|
@@ -830,6 +1093,20 @@ const styles = StyleSheet.create({
|
|
|
830
1093
|
fontSize: 11,
|
|
831
1094
|
color: '#333333',
|
|
832
1095
|
},
|
|
1096
|
+
retryButton: {
|
|
1097
|
+
position: 'absolute',
|
|
1098
|
+
right: 8,
|
|
1099
|
+
top: 40,
|
|
1100
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
1101
|
+
borderColor: '#D0D0D0',
|
|
1102
|
+
borderRadius: 6,
|
|
1103
|
+
paddingVertical: 4,
|
|
1104
|
+
paddingHorizontal: 8,
|
|
1105
|
+
},
|
|
1106
|
+
retryButtonText: {
|
|
1107
|
+
fontSize: 11,
|
|
1108
|
+
color: '#333333',
|
|
1109
|
+
},
|
|
833
1110
|
valuePrimitive: {
|
|
834
1111
|
color: '#222222',
|
|
835
1112
|
fontSize: 12,
|
|
@@ -892,6 +1169,32 @@ const styles = StyleSheet.create({
|
|
|
892
1169
|
color: '#444444',
|
|
893
1170
|
marginBottom: 2,
|
|
894
1171
|
},
|
|
1172
|
+
networkErrorLabel: {
|
|
1173
|
+
color: LOG_THEME.error.color,
|
|
1174
|
+
fontWeight: '600',
|
|
1175
|
+
},
|
|
1176
|
+
networkErrorText: {
|
|
1177
|
+
color: LOG_THEME.error.color,
|
|
1178
|
+
fontSize: 12,
|
|
1179
|
+
},
|
|
1180
|
+
filterInputWrap: {
|
|
1181
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
1182
|
+
borderTopColor: '#E1E1E1',
|
|
1183
|
+
paddingHorizontal: 8,
|
|
1184
|
+
paddingTop: 8,
|
|
1185
|
+
paddingBottom: 6,
|
|
1186
|
+
},
|
|
1187
|
+
filterInput: {
|
|
1188
|
+
height: 34,
|
|
1189
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
1190
|
+
borderColor: '#D0D0D0',
|
|
1191
|
+
borderRadius: 8,
|
|
1192
|
+
paddingHorizontal: 10,
|
|
1193
|
+
fontSize: 12,
|
|
1194
|
+
color: '#222222',
|
|
1195
|
+
backgroundColor: '#FFFFFF',
|
|
1196
|
+
paddingVertical: 0,
|
|
1197
|
+
},
|
|
895
1198
|
actionsRow: {
|
|
896
1199
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
897
1200
|
borderTopColor: '#E1E1E1',
|