react-native-inapp-inspector 1.0.3 → 1.0.5
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 +3 -3
- package/dist/commonjs/components/AnalyticsEventCard.js +10 -10
- package/dist/commonjs/components/CodeSnippet.js +233 -10
- package/dist/commonjs/components/ConsoleLogCard.js +55 -9
- package/dist/commonjs/components/CopyButton.js +2 -1
- package/dist/commonjs/components/ErrorBoundary.d.ts +20 -0
- package/dist/commonjs/components/ErrorBoundary.js +332 -0
- package/dist/commonjs/components/NetworkIcons.d.ts +5 -0
- package/dist/commonjs/components/NetworkIcons.js +45 -1
- package/dist/commonjs/customHooks/reduxLogger.d.ts +4 -0
- package/dist/commonjs/customHooks/reduxLogger.js +30 -0
- package/dist/commonjs/customHooks/webViewLogger.d.ts +2 -0
- package/dist/commonjs/customHooks/webViewLogger.js +281 -246
- package/dist/commonjs/helpers/index.js +2 -1
- package/dist/commonjs/index.d.ts +5 -3
- package/dist/commonjs/index.js +685 -911
- package/dist/commonjs/styles/AppColors.d.ts +29 -1
- package/dist/commonjs/styles/AppColors.js +38 -2
- package/dist/commonjs/styles/index.d.ts +438 -229
- package/dist/commonjs/styles/index.js +448 -209
- package/dist/commonjs/types/index.d.ts +2 -2
- package/dist/esm/components/AnalyticsEventCard.js +10 -10
- package/dist/esm/components/CodeSnippet.js +232 -12
- package/dist/esm/components/ConsoleLogCard.js +55 -9
- package/dist/esm/components/CopyButton.js +2 -1
- package/dist/esm/components/ErrorBoundary.d.ts +20 -0
- package/dist/esm/components/ErrorBoundary.js +295 -0
- package/dist/esm/components/NetworkIcons.d.ts +5 -0
- package/dist/esm/components/NetworkIcons.js +39 -0
- package/dist/esm/customHooks/reduxLogger.d.ts +4 -0
- package/dist/esm/customHooks/reduxLogger.js +23 -0
- package/dist/esm/customHooks/webViewLogger.d.ts +2 -0
- package/dist/esm/customHooks/webViewLogger.js +281 -246
- package/dist/esm/helpers/index.js +2 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.js +683 -914
- package/dist/esm/styles/AppColors.d.ts +29 -1
- package/dist/esm/styles/AppColors.js +35 -1
- package/dist/esm/styles/index.d.ts +438 -229
- package/dist/esm/styles/index.js +412 -209
- package/dist/esm/types/index.d.ts +2 -2
- package/example/App.tsx +351 -127
- package/example/ios/Podfile.lock +26 -0
- package/example/metro.config.js +1 -1
- package/example/package-lock.json +20 -4
- package/example/package.json +4 -3
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export type ActiveTab = 'apis' | 'analytics' | 'logs' | 'webview';
|
|
2
|
+
export type ActiveTab = 'insights' | 'apis' | 'analytics' | 'logs' | 'webview' | 'redux';
|
|
3
3
|
export interface ConsoleLog {
|
|
4
4
|
id: number;
|
|
5
5
|
type: 'info' | 'warn' | 'error';
|
|
@@ -73,7 +73,7 @@ export type DiffResult = {
|
|
|
73
73
|
newVal?: any;
|
|
74
74
|
};
|
|
75
75
|
export interface CopyButtonProps {
|
|
76
|
-
value: unknown;
|
|
76
|
+
value: unknown | (() => unknown);
|
|
77
77
|
label: string;
|
|
78
78
|
iconType?: 'copy' | 'terminal' | 'fetch';
|
|
79
79
|
}
|
|
@@ -171,8 +171,8 @@ const AnalyticsEventCard = React.memo(function AnalyticsEventCard({ event, onPre
|
|
|
171
171
|
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
172
172
|
const cardStyles = StyleSheet.create({
|
|
173
173
|
container: {
|
|
174
|
-
paddingHorizontal:
|
|
175
|
-
paddingVertical:
|
|
174
|
+
paddingHorizontal: 12,
|
|
175
|
+
paddingVertical: 3,
|
|
176
176
|
},
|
|
177
177
|
gapContainer: {
|
|
178
178
|
alignItems: 'center',
|
|
@@ -189,7 +189,7 @@ const cardStyles = StyleSheet.create({
|
|
|
189
189
|
borderRadius: 8,
|
|
190
190
|
borderWidth: 1,
|
|
191
191
|
borderColor: '#EFEFEF',
|
|
192
|
-
padding:
|
|
192
|
+
padding: 8,
|
|
193
193
|
shadowColor: '#000',
|
|
194
194
|
shadowOpacity: 0.03,
|
|
195
195
|
shadowRadius: 3,
|
|
@@ -201,11 +201,11 @@ const cardStyles = StyleSheet.create({
|
|
|
201
201
|
flexDirection: 'row',
|
|
202
202
|
justifyContent: 'space-between',
|
|
203
203
|
alignItems: 'center',
|
|
204
|
-
marginBottom:
|
|
204
|
+
marginBottom: 6,
|
|
205
205
|
},
|
|
206
206
|
nameBadge: {
|
|
207
|
-
paddingHorizontal:
|
|
208
|
-
paddingVertical:
|
|
207
|
+
paddingHorizontal: 6,
|
|
208
|
+
paddingVertical: 2.5,
|
|
209
209
|
borderRadius: 6,
|
|
210
210
|
borderWidth: 1,
|
|
211
211
|
},
|
|
@@ -240,8 +240,8 @@ const cardStyles = StyleSheet.create({
|
|
|
240
240
|
flexDirection: 'row',
|
|
241
241
|
alignItems: 'center',
|
|
242
242
|
backgroundColor: AppColors.grayBackground,
|
|
243
|
-
paddingHorizontal:
|
|
244
|
-
paddingVertical:
|
|
243
|
+
paddingHorizontal: 5,
|
|
244
|
+
paddingVertical: 2,
|
|
245
245
|
borderRadius: 4,
|
|
246
246
|
borderWidth: 1,
|
|
247
247
|
borderColor: AppColors.grayBorderSecondary,
|
|
@@ -261,8 +261,8 @@ const cardStyles = StyleSheet.create({
|
|
|
261
261
|
backgroundColor: '#FFE4E6',
|
|
262
262
|
borderColor: '#FCC2D7',
|
|
263
263
|
borderWidth: 1,
|
|
264
|
-
paddingHorizontal:
|
|
265
|
-
paddingVertical:
|
|
264
|
+
paddingHorizontal: 5,
|
|
265
|
+
paddingVertical: 2,
|
|
266
266
|
borderRadius: 4,
|
|
267
267
|
},
|
|
268
268
|
duplicateText: {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { StyleSheet, Text, View, Platform, FlatList } from 'react-native';
|
|
1
|
+
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { StyleSheet, Text, View, Platform, FlatList, TextInput, Pressable } from 'react-native';
|
|
3
|
+
import Svg, { Path } from 'react-native-svg';
|
|
4
|
+
import { SearchIcon, ClearIcon } from './NetworkIcons';
|
|
5
|
+
import CopyButton from './CopyButton';
|
|
6
|
+
import TouchableScale from './TouchableScale';
|
|
7
|
+
import { AppColors } from '../styles/AppColors';
|
|
8
|
+
import { AppFonts } from '../styles/AppFonts';
|
|
3
9
|
const escapeRegExp = (string) => {
|
|
4
10
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
5
11
|
};
|
|
@@ -100,7 +106,7 @@ const formatJS = (js) => {
|
|
|
100
106
|
};
|
|
101
107
|
const tokenizeJS = (text) => {
|
|
102
108
|
const tokens = [];
|
|
103
|
-
const regex = /(\/\/.*|\/\*[\s\S]*?\*\/)|("(?:\\.|[
|
|
109
|
+
const regex = /(\/\/.*|\/\*[\s\S]*?\*\/)|(\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\'|\`(?:\\.|[^\`\\])*\`)|(\b(const|let|var|function|return|if|else|for|while|import|export|class|try|catch|new|this|async|await|break|continue|switch|case|default|throw|typeof|yield)\b)|(\b(console|window|document|fetch|global|require|module|exports|Promise|Map|Set|Array|Object|String|Number|Boolean)\b)|(\b\d+(?:\.\d+)?\b)/g;
|
|
104
110
|
let lastIndex = 0;
|
|
105
111
|
let match;
|
|
106
112
|
while ((match = regex.exec(text)) !== null) {
|
|
@@ -166,7 +172,7 @@ const tokenizeCSS = (text) => {
|
|
|
166
172
|
};
|
|
167
173
|
const tokenizeHTML = (text) => {
|
|
168
174
|
const tokens = [];
|
|
169
|
-
const regex = /(<!--[\s\S]*?-->)|(<\/?[a-zA-Z0-9:-]+>?)|("(?:\\.|[
|
|
175
|
+
const regex = /(<!--[\s\S]*?-->)|(<\/?[a-zA-Z0-9:-]+>?)|(\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\')|([a-zA-Z0-9:-]+(?=\s*=))/g;
|
|
170
176
|
let lastIndex = 0;
|
|
171
177
|
let match;
|
|
172
178
|
while ((match = regex.exec(text)) !== null) {
|
|
@@ -221,7 +227,13 @@ const getStyleForType = (type) => {
|
|
|
221
227
|
return styles.plain;
|
|
222
228
|
}
|
|
223
229
|
};
|
|
224
|
-
const
|
|
230
|
+
const ArrowUpIcon = ({ color = '#64748B', size = 16 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
231
|
+
<Path d="M18 15l-6-6-6 6" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
232
|
+
</Svg>);
|
|
233
|
+
const ArrowDownIcon = ({ color = '#64748B', size = 16 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
234
|
+
<Path d="M6 9l6 6 6-6" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
235
|
+
</Svg>);
|
|
236
|
+
const CodeSnippetLine = React.memo(({ line, lineIndex, language, search, isActiveMatch, }) => {
|
|
225
237
|
const tokens = useMemo(() => {
|
|
226
238
|
if (language === 'html')
|
|
227
239
|
return tokenizeHTML(line);
|
|
@@ -244,10 +256,10 @@ const CodeSnippetLine = React.memo(({ line, lineIndex, language, search, }) => {
|
|
|
244
256
|
})}
|
|
245
257
|
</Text>);
|
|
246
258
|
};
|
|
247
|
-
return (<View style={styles.lineRow}>
|
|
259
|
+
return (<View style={[styles.lineRow, isActiveMatch && styles.activeMatchRow]}>
|
|
248
260
|
{/* Gutter Line Number */}
|
|
249
|
-
<View style={styles.gutter}>
|
|
250
|
-
<Text style={styles.lineNumber}>{lineIndex + 1}</Text>
|
|
261
|
+
<View style={[styles.gutter, isActiveMatch && styles.activeMatchGutter]}>
|
|
262
|
+
<Text style={[styles.lineNumber, isActiveMatch && styles.activeMatchLineNumber]}>{lineIndex + 1}</Text>
|
|
251
263
|
</View>
|
|
252
264
|
|
|
253
265
|
{/* Highlighted Code Line */}
|
|
@@ -260,10 +272,27 @@ const CodeSnippetLine = React.memo(({ line, lineIndex, language, search, }) => {
|
|
|
260
272
|
</View>
|
|
261
273
|
</View>);
|
|
262
274
|
});
|
|
263
|
-
const CodeSnippet = ({ code, language,
|
|
275
|
+
const CodeSnippet = ({ code, language, }) => {
|
|
276
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
277
|
+
const [debouncedQuery, setDebouncedQuery] = useState('');
|
|
278
|
+
const [currentMatchIdx, setCurrentMatchIdx] = useState(-1);
|
|
279
|
+
const [visibleLinesCount, setVisibleLinesCount] = useState(200);
|
|
280
|
+
const flatListRef = useRef(null);
|
|
281
|
+
// Debounce search query updates
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
const timer = setTimeout(() => {
|
|
284
|
+
setDebouncedQuery(searchQuery);
|
|
285
|
+
}, 300);
|
|
286
|
+
return () => clearTimeout(timer);
|
|
287
|
+
}, [searchQuery]);
|
|
288
|
+
// If code is extremely large (e.g. > 150KB), skip the pretty printers
|
|
289
|
+
// to avoid blocking the single JS thread.
|
|
264
290
|
const formattedCode = useMemo(() => {
|
|
265
291
|
if (!code)
|
|
266
292
|
return '';
|
|
293
|
+
if (code.length > 150000) {
|
|
294
|
+
return code;
|
|
295
|
+
}
|
|
267
296
|
if (language === 'html')
|
|
268
297
|
return formatHTML(code);
|
|
269
298
|
if (language === 'css')
|
|
@@ -277,9 +306,135 @@ const CodeSnippet = ({ code, language, search = '', }) => {
|
|
|
277
306
|
return [];
|
|
278
307
|
return formattedCode.split(/\r?\n/);
|
|
279
308
|
}, [formattedCode]);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
309
|
+
// Find all line indices matching the search query
|
|
310
|
+
const matches = useMemo(() => {
|
|
311
|
+
if (!debouncedQuery.trim() || debouncedQuery.length < 2)
|
|
312
|
+
return [];
|
|
313
|
+
const query = debouncedQuery.toLowerCase();
|
|
314
|
+
const indices = [];
|
|
315
|
+
for (let i = 0; i < lines.length; i++) {
|
|
316
|
+
if (lines[i].toLowerCase().includes(query)) {
|
|
317
|
+
indices.push(i);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return indices;
|
|
321
|
+
}, [debouncedQuery, lines]);
|
|
322
|
+
// Reset scroll and states on code/language changes
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
setVisibleLinesCount(200);
|
|
325
|
+
setSearchQuery('');
|
|
326
|
+
setCurrentMatchIdx(-1);
|
|
327
|
+
}, [code, language]);
|
|
328
|
+
// Auto-scroll to the first match when search query returns matches
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
if (matches.length > 0) {
|
|
331
|
+
setCurrentMatchIdx(0);
|
|
332
|
+
scrollToMatch(0);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
setCurrentMatchIdx(-1);
|
|
336
|
+
}
|
|
337
|
+
}, [matches]);
|
|
338
|
+
const scrollToMatch = (matchIdx) => {
|
|
339
|
+
if (matches.length === 0 || matchIdx < 0 || matchIdx >= matches.length)
|
|
340
|
+
return;
|
|
341
|
+
const lineIdx = matches[matchIdx];
|
|
342
|
+
// Ensure the targeted index is loaded in FlatList
|
|
343
|
+
if (lineIdx >= visibleLinesCount) {
|
|
344
|
+
setVisibleLinesCount(lineIdx + 100);
|
|
345
|
+
}
|
|
346
|
+
// Schedule scroll after state has applied
|
|
347
|
+
setTimeout(() => {
|
|
348
|
+
try {
|
|
349
|
+
flatListRef.current?.scrollToIndex({
|
|
350
|
+
index: lineIdx,
|
|
351
|
+
animated: true,
|
|
352
|
+
viewPosition: 0.5,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
// Fallback recovery is handled by onScrollToIndexFailed
|
|
357
|
+
}
|
|
358
|
+
}, 100);
|
|
359
|
+
};
|
|
360
|
+
const onScrollToIndexFailed = (error) => {
|
|
361
|
+
flatListRef.current?.scrollToOffset({
|
|
362
|
+
offset: error.averageItemLength * error.index,
|
|
363
|
+
animated: true,
|
|
364
|
+
});
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
try {
|
|
367
|
+
flatListRef.current?.scrollToIndex({
|
|
368
|
+
index: error.index,
|
|
369
|
+
animated: true,
|
|
370
|
+
viewPosition: 0.5,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
console.warn('Scroll to line index failed after fallback retry:', err);
|
|
375
|
+
}
|
|
376
|
+
}, 120);
|
|
377
|
+
};
|
|
378
|
+
const handleNextMatch = () => {
|
|
379
|
+
if (matches.length === 0)
|
|
380
|
+
return;
|
|
381
|
+
const nextIdx = (currentMatchIdx + 1) % matches.length;
|
|
382
|
+
setCurrentMatchIdx(nextIdx);
|
|
383
|
+
scrollToMatch(nextIdx);
|
|
384
|
+
};
|
|
385
|
+
const handlePrevMatch = () => {
|
|
386
|
+
if (matches.length === 0)
|
|
387
|
+
return;
|
|
388
|
+
const prevIdx = (currentMatchIdx - 1 + matches.length) % matches.length;
|
|
389
|
+
setCurrentMatchIdx(prevIdx);
|
|
390
|
+
scrollToMatch(prevIdx);
|
|
391
|
+
};
|
|
392
|
+
const visibleLines = useMemo(() => {
|
|
393
|
+
return lines.slice(0, visibleLinesCount);
|
|
394
|
+
}, [lines, visibleLinesCount]);
|
|
395
|
+
const handleEndReached = () => {
|
|
396
|
+
if (visibleLinesCount < lines.length) {
|
|
397
|
+
setVisibleLinesCount(prev => Math.min(prev + 150, lines.length));
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
const renderLine = ({ item, index }) => {
|
|
401
|
+
const isActiveMatch = matches.length > 0 && currentMatchIdx >= 0 && index === matches[currentMatchIdx];
|
|
402
|
+
return (<CodeSnippetLine line={item} lineIndex={index} language={language} search={debouncedQuery} isActiveMatch={isActiveMatch}/>);
|
|
403
|
+
};
|
|
404
|
+
return (<View style={{ flex: 1 }}>
|
|
405
|
+
{/* Search Header Row */}
|
|
406
|
+
<View style={styles.searchRow}>
|
|
407
|
+
<View style={styles.searchBar}>
|
|
408
|
+
<SearchIcon color={AppColors.grayTextWeak} size={15}/>
|
|
409
|
+
<TextInput placeholder={`Search ${language.toUpperCase()}...`} placeholderTextColor={AppColors.grayTextWeak} value={searchQuery} onChangeText={setSearchQuery} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
410
|
+
|
|
411
|
+
{/* Matches Info */}
|
|
412
|
+
{debouncedQuery.length >= 2 && (<Text style={styles.matchCountText}>
|
|
413
|
+
{matches.length > 0 ? `${currentMatchIdx + 1}/${matches.length}` : '0/0'}
|
|
414
|
+
</Text>)}
|
|
415
|
+
|
|
416
|
+
{searchQuery.length > 0 && (<Pressable onPress={() => setSearchQuery('')} hitSlop={8} style={styles.clearBtn}>
|
|
417
|
+
<ClearIcon color={AppColors.grayTextWeak} size={12}/>
|
|
418
|
+
</Pressable>)}
|
|
419
|
+
</View>
|
|
420
|
+
|
|
421
|
+
{/* Up / Down Arrow Navigation Buttons */}
|
|
422
|
+
{matches.length > 0 && (<View style={styles.navArrowsGroup}>
|
|
423
|
+
<TouchableScale onPress={handlePrevMatch} hitSlop={8} style={styles.navArrowBtn}>
|
|
424
|
+
<ArrowUpIcon color="#475569" size={14}/>
|
|
425
|
+
</TouchableScale>
|
|
426
|
+
<TouchableScale onPress={handleNextMatch} hitSlop={8} style={styles.navArrowBtn}>
|
|
427
|
+
<ArrowDownIcon color="#475569" size={14}/>
|
|
428
|
+
</TouchableScale>
|
|
429
|
+
</View>)}
|
|
430
|
+
|
|
431
|
+
<CopyButton value={code} label={`${language.toUpperCase()} Source`}/>
|
|
432
|
+
</View>
|
|
433
|
+
|
|
434
|
+
{/* Code Snippet list container */}
|
|
435
|
+
<View style={styles.container}>
|
|
436
|
+
<FlatList ref={flatListRef} data={visibleLines} renderItem={renderLine} keyExtractor={(_, index) => String(index)} maxToRenderPerBatch={30} windowSize={5} initialNumToRender={50} removeClippedSubviews={Platform.OS === 'android'} onEndReached={handleEndReached} onEndReachedThreshold={0.5} onScrollToIndexFailed={onScrollToIndexFailed}/>
|
|
437
|
+
</View>
|
|
283
438
|
</View>);
|
|
284
439
|
};
|
|
285
440
|
const monoFont = Platform.OS === 'ios' ? 'Menlo' : 'monospace';
|
|
@@ -292,12 +447,69 @@ const styles = StyleSheet.create({
|
|
|
292
447
|
borderColor: '#E2E8F0',
|
|
293
448
|
overflow: 'hidden',
|
|
294
449
|
},
|
|
450
|
+
searchRow: {
|
|
451
|
+
flexDirection: 'row',
|
|
452
|
+
alignItems: 'center',
|
|
453
|
+
marginBottom: 6,
|
|
454
|
+
gap: 8,
|
|
455
|
+
},
|
|
456
|
+
searchBar: {
|
|
457
|
+
flex: 1,
|
|
458
|
+
flexDirection: 'row',
|
|
459
|
+
alignItems: 'center',
|
|
460
|
+
backgroundColor: '#FFFFFF',
|
|
461
|
+
borderRadius: 8,
|
|
462
|
+
borderWidth: 1,
|
|
463
|
+
borderColor: '#E2E8F0',
|
|
464
|
+
paddingHorizontal: 8,
|
|
465
|
+
height: 32,
|
|
466
|
+
},
|
|
467
|
+
searchInput: {
|
|
468
|
+
flex: 1,
|
|
469
|
+
fontFamily: AppFonts.interRegular,
|
|
470
|
+
fontSize: 12,
|
|
471
|
+
color: '#0F172A',
|
|
472
|
+
marginLeft: 6,
|
|
473
|
+
paddingVertical: 0,
|
|
474
|
+
},
|
|
475
|
+
matchCountText: {
|
|
476
|
+
fontFamily: AppFonts.interMedium,
|
|
477
|
+
fontSize: 10,
|
|
478
|
+
color: '#64748B',
|
|
479
|
+
marginHorizontal: 6,
|
|
480
|
+
backgroundColor: '#F1F5F9',
|
|
481
|
+
paddingHorizontal: 6,
|
|
482
|
+
paddingVertical: 2,
|
|
483
|
+
borderRadius: 4,
|
|
484
|
+
},
|
|
485
|
+
clearBtn: {
|
|
486
|
+
padding: 4,
|
|
487
|
+
},
|
|
488
|
+
navArrowsGroup: {
|
|
489
|
+
flexDirection: 'row',
|
|
490
|
+
alignItems: 'center',
|
|
491
|
+
backgroundColor: '#FFFFFF',
|
|
492
|
+
borderRadius: 8,
|
|
493
|
+
borderWidth: 1,
|
|
494
|
+
borderColor: '#E2E8F0',
|
|
495
|
+
height: 32,
|
|
496
|
+
paddingHorizontal: 2,
|
|
497
|
+
},
|
|
498
|
+
navArrowBtn: {
|
|
499
|
+
padding: 5,
|
|
500
|
+
borderRadius: 6,
|
|
501
|
+
alignItems: 'center',
|
|
502
|
+
justifyContent: 'center',
|
|
503
|
+
},
|
|
295
504
|
lineRow: {
|
|
296
505
|
flexDirection: 'row',
|
|
297
506
|
alignItems: 'stretch',
|
|
298
507
|
minHeight: 20,
|
|
299
508
|
paddingVertical: 1,
|
|
300
509
|
},
|
|
510
|
+
activeMatchRow: {
|
|
511
|
+
backgroundColor: 'rgba(234, 179, 8, 0.15)',
|
|
512
|
+
},
|
|
301
513
|
gutter: {
|
|
302
514
|
width: 40,
|
|
303
515
|
backgroundColor: '#F1F5F9',
|
|
@@ -308,11 +520,19 @@ const styles = StyleSheet.create({
|
|
|
308
520
|
paddingRight: 6,
|
|
309
521
|
paddingTop: 1,
|
|
310
522
|
},
|
|
523
|
+
activeMatchGutter: {
|
|
524
|
+
backgroundColor: 'rgba(234, 179, 8, 0.25)',
|
|
525
|
+
borderRightColor: 'rgba(234, 179, 8, 0.4)',
|
|
526
|
+
},
|
|
311
527
|
lineNumber: {
|
|
312
528
|
fontFamily: monoFont,
|
|
313
529
|
fontSize: 9,
|
|
314
530
|
color: '#94A3B8',
|
|
315
531
|
},
|
|
532
|
+
activeMatchLineNumber: {
|
|
533
|
+
color: '#854D0E',
|
|
534
|
+
fontWeight: 'bold',
|
|
535
|
+
},
|
|
316
536
|
codeLine: {
|
|
317
537
|
flex: 1,
|
|
318
538
|
paddingLeft: 10,
|
|
@@ -60,6 +60,52 @@ const getJsonPreviewText = (data) => {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
+
const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle, numberOfLines) => {
|
|
64
|
+
if (!message)
|
|
65
|
+
return null;
|
|
66
|
+
const prefixRegex = /^((?:\[[^\]]+\]\s*)+)/;
|
|
67
|
+
const match = message.match(prefixRegex);
|
|
68
|
+
if (match) {
|
|
69
|
+
const fullPrefix = match[1];
|
|
70
|
+
const remainingText = message.substring(fullPrefix.length);
|
|
71
|
+
const tags = fullPrefix.match(/\[[^\]]+\]/g) || [];
|
|
72
|
+
const getTagColor = (tag) => {
|
|
73
|
+
const cleanTag = tag.replace(/[\[\]]/g, '').trim().toUpperCase();
|
|
74
|
+
if (cleanTag === 'API')
|
|
75
|
+
return '#0284C7';
|
|
76
|
+
if (cleanTag === 'TEST')
|
|
77
|
+
return '#16A34A';
|
|
78
|
+
if (cleanTag === 'APP')
|
|
79
|
+
return '#4F46E5';
|
|
80
|
+
if (cleanTag === 'DETAILS')
|
|
81
|
+
return '#7C3AED';
|
|
82
|
+
if (cleanTag === 'WEBVIEW')
|
|
83
|
+
return '#EA580C';
|
|
84
|
+
if (cleanTag === 'MOCK REDUX' || cleanTag === 'REDUX')
|
|
85
|
+
return '#DB2777';
|
|
86
|
+
let hash = 0;
|
|
87
|
+
for (let i = 0; i < cleanTag.length; i++) {
|
|
88
|
+
hash = cleanTag.charCodeAt(i) + ((hash << 5) - hash);
|
|
89
|
+
}
|
|
90
|
+
const colors = ['#0891B2', '#0D9488', '#2563EB', '#D97706', '#E11D48', '#8B5CF6'];
|
|
91
|
+
return colors[Math.abs(hash) % colors.length];
|
|
92
|
+
};
|
|
93
|
+
return (<Text style={textStyle} numberOfLines={numberOfLines}>
|
|
94
|
+
{tags.map((tag, idx) => {
|
|
95
|
+
const color = getTagColor(tag);
|
|
96
|
+
return (<Text key={idx} style={{
|
|
97
|
+
fontWeight: 'bold',
|
|
98
|
+
color: color,
|
|
99
|
+
fontFamily: AppFonts.interBold,
|
|
100
|
+
}}>
|
|
101
|
+
{tag}{' '}
|
|
102
|
+
</Text>);
|
|
103
|
+
})}
|
|
104
|
+
<HighlightText text={remainingText} search={searchStr} style={textStyle} highlightStyle={highlightStyle} detectLinks={true}/>
|
|
105
|
+
</Text>);
|
|
106
|
+
}
|
|
107
|
+
return (<HighlightText text={message} search={searchStr} style={textStyle} highlightStyle={highlightStyle} numberOfLines={numberOfLines} detectLinks={true}/>);
|
|
108
|
+
};
|
|
63
109
|
export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchStr = '', isWebView = false, }) {
|
|
64
110
|
const [expanded, setExpanded] = useState(false);
|
|
65
111
|
const jsonContent = getJsonContent(item.message);
|
|
@@ -216,7 +262,7 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
216
262
|
<View style={styles.cardBody}>
|
|
217
263
|
{jsonContent ? (<>
|
|
218
264
|
{jsonContent.header ? (<Pressable onPress={() => setExpanded(prev => !prev)}>
|
|
219
|
-
|
|
265
|
+
{getLogMessageWithBadges(jsonContent.header, searchStr, styles.messageText, styles.highlight, numLines)}
|
|
220
266
|
</Pressable>) : null}
|
|
221
267
|
{expanded ? (<View style={styles.jsonContainer}>
|
|
222
268
|
<JsonViewer data={jsonContent.data} search={searchStr} forceOpen={expanded}/>
|
|
@@ -224,7 +270,7 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
224
270
|
<HighlightText text={getJsonPreviewText(jsonContent.data).text} search={searchStr} style={styles.jsonPreviewText} highlightStyle={styles.highlight} detectLinks={true}/>
|
|
225
271
|
</Pressable>)}
|
|
226
272
|
</>) : (<Pressable onPress={() => setExpanded(prev => !prev)}>
|
|
227
|
-
|
|
273
|
+
{getLogMessageWithBadges(item.message, searchStr, styles.messageText, styles.highlight, numLines)}
|
|
228
274
|
</Pressable>)}
|
|
229
275
|
{hasLongMessage && (<Pressable onPress={() => setExpanded(prev => !prev)} style={styles.seeMoreBtn} hitSlop={8}>
|
|
230
276
|
<Text style={styles.seeMoreText}>
|
|
@@ -241,8 +287,8 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
241
287
|
});
|
|
242
288
|
const styles = StyleSheet.create({
|
|
243
289
|
container: {
|
|
244
|
-
paddingHorizontal:
|
|
245
|
-
paddingVertical:
|
|
290
|
+
paddingHorizontal: 12,
|
|
291
|
+
paddingVertical: 3,
|
|
246
292
|
},
|
|
247
293
|
card: {
|
|
248
294
|
alignSelf: 'stretch',
|
|
@@ -250,7 +296,7 @@ const styles = StyleSheet.create({
|
|
|
250
296
|
borderRadius: 8,
|
|
251
297
|
borderWidth: 1,
|
|
252
298
|
borderColor: '#EFEFEF',
|
|
253
|
-
padding:
|
|
299
|
+
padding: 8,
|
|
254
300
|
shadowColor: '#000',
|
|
255
301
|
shadowOpacity: 0.03,
|
|
256
302
|
shadowRadius: 3,
|
|
@@ -263,7 +309,7 @@ const styles = StyleSheet.create({
|
|
|
263
309
|
flexDirection: 'row',
|
|
264
310
|
justifyContent: 'space-between',
|
|
265
311
|
alignItems: 'center',
|
|
266
|
-
marginBottom:
|
|
312
|
+
marginBottom: 6,
|
|
267
313
|
},
|
|
268
314
|
headerLeft: {
|
|
269
315
|
flexDirection: 'row',
|
|
@@ -297,10 +343,10 @@ const styles = StyleSheet.create({
|
|
|
297
343
|
maxWidth: '50%',
|
|
298
344
|
},
|
|
299
345
|
cardBody: {
|
|
300
|
-
marginTop:
|
|
346
|
+
marginTop: 6,
|
|
301
347
|
backgroundColor: '#FFFFFF',
|
|
302
|
-
paddingHorizontal:
|
|
303
|
-
paddingVertical:
|
|
348
|
+
paddingHorizontal: 8,
|
|
349
|
+
paddingVertical: 5,
|
|
304
350
|
borderRadius: 6,
|
|
305
351
|
borderWidth: 1,
|
|
306
352
|
borderColor: '#EBECEF',
|
|
@@ -11,7 +11,8 @@ import styles from '../styles';
|
|
|
11
11
|
const CopyButton = ({ value, label, iconType = 'copy' }) => {
|
|
12
12
|
const [copied, setCopied] = useState(false);
|
|
13
13
|
const handlePress = () => {
|
|
14
|
-
|
|
14
|
+
const resolvedValue = typeof value === 'function' ? value() : value;
|
|
15
|
+
copyToClipboard(resolvedValue, label);
|
|
15
16
|
setCopied(true);
|
|
16
17
|
setTimeout(() => setCopied(false), 1200);
|
|
17
18
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
2
|
+
interface Props {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
onClose?: () => void;
|
|
5
|
+
onReset?: () => void;
|
|
6
|
+
fallbackType?: 'modal' | 'inline';
|
|
7
|
+
}
|
|
8
|
+
interface State {
|
|
9
|
+
hasError: boolean;
|
|
10
|
+
error: Error | null;
|
|
11
|
+
}
|
|
12
|
+
export declare class ErrorBoundary extends Component<Props, State> {
|
|
13
|
+
state: State;
|
|
14
|
+
static getDerivedStateFromError(error: Error): State;
|
|
15
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
|
|
16
|
+
private handleReset;
|
|
17
|
+
private handleCopyTrace;
|
|
18
|
+
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode>> | React.JSX.Element;
|
|
19
|
+
}
|
|
20
|
+
export default ErrorBoundary;
|