react-native-inapp-inspector 1.0.15 → 1.0.17
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/commonjs/components/AnimatedEntrance.d.ts +12 -0
- package/dist/commonjs/components/AnimatedEntrance.js +72 -0
- package/dist/commonjs/components/ConsoleLogCard.js +114 -29
- package/dist/commonjs/components/EmptyState.js +56 -5
- package/dist/commonjs/components/ReduxTreeView.js +96 -27
- package/dist/commonjs/components/TouchableScale.js +18 -2
- package/dist/commonjs/index.js +100 -34
- package/dist/esm/components/AnimatedEntrance.d.ts +12 -0
- package/dist/esm/components/AnimatedEntrance.js +37 -0
- package/dist/esm/components/ConsoleLogCard.js +116 -31
- package/dist/esm/components/EmptyState.js +24 -6
- package/dist/esm/components/ReduxTreeView.js +95 -29
- package/dist/esm/components/TouchableScale.js +18 -2
- package/dist/esm/index.js +101 -35
- package/package.json +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { StyleSheet, Text,
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Animated, LayoutAnimation, Platform, Pressable, StyleSheet, Text, UIManager, View, } from 'react-native';
|
|
3
3
|
import { AppColors } from '../styles/AppColors';
|
|
4
4
|
import { AppFonts } from '../styles/AppFonts';
|
|
5
5
|
import HighlightText from './HighlightText';
|
|
@@ -70,7 +70,10 @@ const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle,
|
|
|
70
70
|
const remainingText = message.substring(fullPrefix.length);
|
|
71
71
|
const tags = fullPrefix.match(/\[[^\]]+\]/g) || [];
|
|
72
72
|
const getTagColor = (tag) => {
|
|
73
|
-
const cleanTag = tag
|
|
73
|
+
const cleanTag = tag
|
|
74
|
+
.replace(/[\[\]]/g, '')
|
|
75
|
+
.trim()
|
|
76
|
+
.toUpperCase();
|
|
74
77
|
if (cleanTag === 'API')
|
|
75
78
|
return '#0284C7';
|
|
76
79
|
if (cleanTag === 'TEST')
|
|
@@ -87,7 +90,14 @@ const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle,
|
|
|
87
90
|
for (let i = 0; i < cleanTag.length; i++) {
|
|
88
91
|
hash = cleanTag.charCodeAt(i) + ((hash << 5) - hash);
|
|
89
92
|
}
|
|
90
|
-
const colors = [
|
|
93
|
+
const colors = [
|
|
94
|
+
'#0891B2',
|
|
95
|
+
'#0D9488',
|
|
96
|
+
'#2563EB',
|
|
97
|
+
'#D97706',
|
|
98
|
+
'#E11D48',
|
|
99
|
+
'#8B5CF6',
|
|
100
|
+
];
|
|
91
101
|
return colors[Math.abs(hash) % colors.length];
|
|
92
102
|
};
|
|
93
103
|
return (<Text style={textStyle} numberOfLines={numberOfLines}>
|
|
@@ -108,6 +118,7 @@ const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle,
|
|
|
108
118
|
};
|
|
109
119
|
export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchStr = '', isWebView = false, }) {
|
|
110
120
|
const [expanded, setExpanded] = useState(false);
|
|
121
|
+
const chevronAnim = useRef(new Animated.Value(0)).current;
|
|
111
122
|
const jsonContent = getJsonContent(item.message);
|
|
112
123
|
const isAnalyticsError = item.message
|
|
113
124
|
.toLowerCase()
|
|
@@ -191,6 +202,26 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
191
202
|
}
|
|
192
203
|
};
|
|
193
204
|
const colors = getLogColors();
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (Platform.OS === 'android') {
|
|
207
|
+
UIManager.setLayoutAnimationEnabledExperimental?.(true);
|
|
208
|
+
}
|
|
209
|
+
}, []);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
Animated.timing(chevronAnim, {
|
|
212
|
+
toValue: expanded ? 1 : 0,
|
|
213
|
+
duration: 180,
|
|
214
|
+
useNativeDriver: true,
|
|
215
|
+
}).start();
|
|
216
|
+
}, [chevronAnim, expanded]);
|
|
217
|
+
const toggleExpanded = () => {
|
|
218
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
219
|
+
setExpanded(prev => !prev);
|
|
220
|
+
};
|
|
221
|
+
const chevronRotate = chevronAnim.interpolate({
|
|
222
|
+
inputRange: [0, 1],
|
|
223
|
+
outputRange: ['0deg', '180deg'],
|
|
224
|
+
});
|
|
194
225
|
// Show limited lines unless expanded
|
|
195
226
|
const numLines = expanded ? undefined : 5;
|
|
196
227
|
const hasLongMessage = jsonContent
|
|
@@ -211,10 +242,8 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
211
242
|
paddingRight: 4,
|
|
212
243
|
},
|
|
213
244
|
]}>
|
|
214
|
-
|
|
215
245
|
{/* Left Content Area */}
|
|
216
|
-
<Pressable onPress={
|
|
217
|
-
|
|
246
|
+
<Pressable onPress={toggleExpanded} style={{ flex: 1, paddingRight: 6 }}>
|
|
218
247
|
<View style={styles.cardHeader}>
|
|
219
248
|
<View style={styles.headerLeft}>
|
|
220
249
|
<CopyButton value={item.message} label="Log message"/>
|
|
@@ -232,7 +261,10 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
232
261
|
},
|
|
233
262
|
]}>
|
|
234
263
|
<Text style={[styles.badgeText, { color: '#6B4EFF' }]}>
|
|
235
|
-
console.
|
|
264
|
+
console.
|
|
265
|
+
{('sourceMethod' in item ? item.sourceMethod : undefined) ||
|
|
266
|
+
item.type ||
|
|
267
|
+
'log'}
|
|
236
268
|
</Text>
|
|
237
269
|
</View>
|
|
238
270
|
{jsonContent && (<View style={[
|
|
@@ -244,7 +276,9 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
244
276
|
},
|
|
245
277
|
]}>
|
|
246
278
|
<Text style={[styles.badgeText, { color: '#0D9488' }]}>
|
|
247
|
-
{Array.isArray(jsonContent.data)
|
|
279
|
+
{Array.isArray(jsonContent.data)
|
|
280
|
+
? `Array[${jsonContent.data.length}]`
|
|
281
|
+
: `Object{${Object.keys(jsonContent.data).length}}`}
|
|
248
282
|
</Text>
|
|
249
283
|
</View>)}
|
|
250
284
|
{isAnalyticsError && (<View style={[
|
|
@@ -263,7 +297,10 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
263
297
|
borderWidth: 1,
|
|
264
298
|
},
|
|
265
299
|
]}>
|
|
266
|
-
<Text style={[
|
|
300
|
+
<Text style={[
|
|
301
|
+
styles.badgeText,
|
|
302
|
+
{ color: AppColors.grayTextStrong },
|
|
303
|
+
]}>
|
|
267
304
|
user-log
|
|
268
305
|
</Text>
|
|
269
306
|
</View>)}
|
|
@@ -279,43 +316,92 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
279
316
|
webview
|
|
280
317
|
</Text>
|
|
281
318
|
</View>)}
|
|
282
|
-
<Text style={[styles.serialNumber, { color: AppColors.grayTextWeak }]}
|
|
283
|
-
|
|
319
|
+
<Text style={[styles.serialNumber, { color: AppColors.grayTextWeak }]}>
|
|
320
|
+
#{item.id + 1}
|
|
321
|
+
</Text>
|
|
322
|
+
<Text style={[styles.timestamp, { color: AppColors.grayTextWeak }]}>
|
|
323
|
+
{formatTime(item.timestamp)}
|
|
324
|
+
</Text>
|
|
284
325
|
</View>
|
|
285
|
-
|
|
286
|
-
{caller && caller !== 'Unknown' && (<Text style={[
|
|
326
|
+
|
|
327
|
+
{caller && caller !== 'Unknown' && (<Text style={[
|
|
328
|
+
styles.callerText,
|
|
329
|
+
{ color: AppColors.grayTextWeak, marginRight: 4 },
|
|
330
|
+
]} numberOfLines={1} ellipsizeMode="middle">
|
|
287
331
|
{caller.split('/').pop() || caller}
|
|
288
332
|
</Text>)}
|
|
289
333
|
</View>
|
|
290
334
|
|
|
291
|
-
<View style={[
|
|
335
|
+
<View style={[
|
|
336
|
+
styles.cardBody,
|
|
337
|
+
{
|
|
338
|
+
backgroundColor: AppColors.primaryLight,
|
|
339
|
+
borderColor: AppColors.dividerColor,
|
|
340
|
+
},
|
|
341
|
+
]}>
|
|
292
342
|
{jsonContent ? (<>
|
|
293
|
-
{jsonContent.header ? (<Pressable onPress={
|
|
343
|
+
{jsonContent.header ? (<Pressable onPress={toggleExpanded}>
|
|
294
344
|
{getLogMessageWithBadges(jsonContent.header, searchStr, [styles.messageText, { color: AppColors.primaryBlack }], styles.highlight, numLines)}
|
|
295
345
|
</Pressable>) : null}
|
|
296
|
-
{expanded ? (<View style={[
|
|
346
|
+
{expanded ? (<View style={[
|
|
347
|
+
styles.jsonContainer,
|
|
348
|
+
{
|
|
349
|
+
backgroundColor: AppColors.grayBackground,
|
|
350
|
+
borderColor: AppColors.dividerColor,
|
|
351
|
+
},
|
|
352
|
+
]}>
|
|
297
353
|
<JsonViewer data={jsonContent.data} search={searchStr} forceOpen={expanded}/>
|
|
298
|
-
</View>) : (<Pressable onPress={
|
|
299
|
-
|
|
354
|
+
</View>) : (<Pressable onPress={toggleExpanded} style={[
|
|
355
|
+
styles.jsonPreviewContainer,
|
|
356
|
+
{
|
|
357
|
+
backgroundColor: AppColors.grayBackground,
|
|
358
|
+
borderColor: AppColors.dividerColor,
|
|
359
|
+
},
|
|
360
|
+
]}>
|
|
361
|
+
<HighlightText text={getJsonPreviewText(jsonContent.data).text} search={searchStr} style={[
|
|
362
|
+
styles.jsonPreviewText,
|
|
363
|
+
{ color: AppColors.primaryBlack },
|
|
364
|
+
]} highlightStyle={styles.highlight} detectLinks={true}/>
|
|
300
365
|
</Pressable>)}
|
|
301
|
-
</>) : (<Pressable onPress={
|
|
366
|
+
</>) : (<Pressable onPress={toggleExpanded}>
|
|
302
367
|
{getLogMessageWithBadges(item.message, searchStr, [styles.messageText, { color: AppColors.primaryBlack }], styles.highlight, numLines)}
|
|
303
368
|
</Pressable>)}
|
|
304
|
-
{hasLongMessage && (<Pressable onPress={
|
|
369
|
+
{hasLongMessage && (<Pressable onPress={toggleExpanded} style={styles.seeMoreBtn} hitSlop={8}>
|
|
305
370
|
<Text style={styles.seeMoreText}>
|
|
306
371
|
{expanded ? 'See Less' : 'See More'}
|
|
307
372
|
</Text>
|
|
308
373
|
</Pressable>)}
|
|
309
374
|
</View>
|
|
310
375
|
|
|
311
|
-
{expanded && (<View style={[
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
376
|
+
{expanded && (<View style={[
|
|
377
|
+
styles.cardFooter,
|
|
378
|
+
{ borderTopColor: AppColors.dividerColor, gap: 6 },
|
|
379
|
+
]}>
|
|
380
|
+
<View style={{
|
|
381
|
+
flexDirection: 'row',
|
|
382
|
+
justifyContent: 'space-between',
|
|
383
|
+
alignItems: 'center',
|
|
384
|
+
}}>
|
|
385
|
+
<Text style={{
|
|
386
|
+
fontFamily: AppFonts.interRegular,
|
|
387
|
+
fontSize: 10.5,
|
|
388
|
+
color: AppColors.grayTextWeak,
|
|
389
|
+
}}>
|
|
390
|
+
Length: {item.message.length} chars • Size:{' '}
|
|
391
|
+
{encodeURIComponent(item.message).replace(/%[0-9A-F]{2}/g, 'a').length}{' '}
|
|
392
|
+
bytes
|
|
315
393
|
</Text>
|
|
316
394
|
</View>
|
|
317
|
-
{caller && caller !== 'Unknown' && (<View style={{
|
|
318
|
-
|
|
395
|
+
{caller && caller !== 'Unknown' && (<View style={{
|
|
396
|
+
flexDirection: 'row',
|
|
397
|
+
alignItems: 'center',
|
|
398
|
+
justifyContent: 'space-between',
|
|
399
|
+
marginTop: 4,
|
|
400
|
+
}}>
|
|
401
|
+
<Text style={[
|
|
402
|
+
styles.fullCallerText,
|
|
403
|
+
{ color: AppColors.grayText, flex: 1, marginRight: 8 },
|
|
404
|
+
]} numberOfLines={2}>
|
|
319
405
|
Caller: {caller}
|
|
320
406
|
</Text>
|
|
321
407
|
<CopyButton value={caller} label="Caller stack frame"/>
|
|
@@ -324,7 +410,7 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
324
410
|
</Pressable>
|
|
325
411
|
|
|
326
412
|
{/* Right Isolated Chevron Area */}
|
|
327
|
-
<Pressable onPress={
|
|
413
|
+
<Pressable onPress={toggleExpanded} style={{
|
|
328
414
|
width: 28,
|
|
329
415
|
alignItems: 'center',
|
|
330
416
|
justifyContent: 'center',
|
|
@@ -332,11 +418,10 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
332
418
|
marginTop: expanded ? 8 : 0,
|
|
333
419
|
height: expanded ? 32 : undefined,
|
|
334
420
|
}} hitSlop={12}>
|
|
335
|
-
<View style={{ transform: [{ rotate:
|
|
421
|
+
<Animated.View style={{ transform: [{ rotate: chevronRotate }] }}>
|
|
336
422
|
<ChevronIcon size={16} color={AppColors.grayTextWeak}/>
|
|
337
|
-
</View>
|
|
423
|
+
</Animated.View>
|
|
338
424
|
</Pressable>
|
|
339
|
-
|
|
340
425
|
</View>
|
|
341
426
|
</View>);
|
|
342
427
|
});
|
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { DevSettings, Alert,
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { Animated, DevSettings, Alert, Text } from 'react-native';
|
|
3
3
|
// Components
|
|
4
4
|
import TouchableScale from './TouchableScale';
|
|
5
|
+
import AnimatedEntrance from './AnimatedEntrance';
|
|
5
6
|
// Assets
|
|
6
7
|
import { EmptyRadarIcon } from './NetworkIcons';
|
|
7
8
|
// Stylesheet
|
|
8
9
|
import { AppColors } from '../styles/AppColors';
|
|
9
10
|
import styles from '../styles';
|
|
10
11
|
const EmptyState = ({ isSearch }) => {
|
|
12
|
+
const iconPulse = useRef(new Animated.Value(1)).current;
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const loop = Animated.loop(Animated.sequence([
|
|
15
|
+
Animated.timing(iconPulse, {
|
|
16
|
+
toValue: 1.06,
|
|
17
|
+
duration: 900,
|
|
18
|
+
useNativeDriver: true,
|
|
19
|
+
}),
|
|
20
|
+
Animated.timing(iconPulse, {
|
|
21
|
+
toValue: 1,
|
|
22
|
+
duration: 900,
|
|
23
|
+
useNativeDriver: true,
|
|
24
|
+
}),
|
|
25
|
+
]));
|
|
26
|
+
loop.start();
|
|
27
|
+
return () => loop.stop();
|
|
28
|
+
}, [iconPulse]);
|
|
11
29
|
const handleReload = () => {
|
|
12
30
|
if (__DEV__ && DevSettings && DevSettings.reload) {
|
|
13
31
|
DevSettings.reload();
|
|
@@ -15,10 +33,10 @@ const EmptyState = ({ isSearch }) => {
|
|
|
15
33
|
}
|
|
16
34
|
Alert.alert('Reload', 'App reload is typically only available in development mode.');
|
|
17
35
|
};
|
|
18
|
-
return (<
|
|
19
|
-
<View style={styles.emptyIconWrap}>
|
|
36
|
+
return (<AnimatedEntrance style={styles.emptyContainer} distance={14}>
|
|
37
|
+
<Animated.View style={[styles.emptyIconWrap, { transform: [{ scale: iconPulse }] }]}>
|
|
20
38
|
<EmptyRadarIcon color={AppColors.purple} size={32}/>
|
|
21
|
-
</View>
|
|
39
|
+
</Animated.View>
|
|
22
40
|
<Text style={styles.emptyTitle}>
|
|
23
41
|
{isSearch ? 'No matching APIs' : 'No network activity'}
|
|
24
42
|
</Text>
|
|
@@ -30,6 +48,6 @@ const EmptyState = ({ isSearch }) => {
|
|
|
30
48
|
{!isSearch && (<TouchableScale style={styles.reloadBtn} onPress={handleReload}>
|
|
31
49
|
<Text style={styles.reloadBtnText}>Reload App</Text>
|
|
32
50
|
</TouchableScale>)}
|
|
33
|
-
</
|
|
51
|
+
</AnimatedEntrance>);
|
|
34
52
|
};
|
|
35
53
|
export default EmptyState;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Animated, LayoutAnimation, Platform, Pressable, StyleSheet, Text, UIManager, View, } from 'react-native';
|
|
3
3
|
import { AppColors } from '../styles/AppColors';
|
|
4
4
|
import { AppFonts } from '../styles/AppFonts';
|
|
5
5
|
import { ChevronIcon } from './NetworkIcons';
|
|
6
6
|
import Svg, { Path } from 'react-native-svg';
|
|
7
|
+
import AnimatedEntrance from './AnimatedEntrance';
|
|
7
8
|
// Custom icons
|
|
8
9
|
const DatabaseIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
9
10
|
<Path d="M12 2C6.5 2 2 4.2 2 7v10c0 2.8 4.5 5 10 5s10-2.2 10-5V7c0-2.8-4.5-5-10-5z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
@@ -16,6 +17,26 @@ const BoltIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<Svg width=
|
|
|
16
17
|
const FolderIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
17
18
|
<Path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
18
19
|
</Svg>);
|
|
20
|
+
const animateTreeLayout = () => {
|
|
21
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
22
|
+
};
|
|
23
|
+
const AnimatedChevron = ({ color, expanded, size, style, }) => {
|
|
24
|
+
const progress = useRef(new Animated.Value(expanded ? 1 : 0)).current;
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
Animated.timing(progress, {
|
|
27
|
+
toValue: expanded ? 1 : 0,
|
|
28
|
+
duration: 180,
|
|
29
|
+
useNativeDriver: true,
|
|
30
|
+
}).start();
|
|
31
|
+
}, [expanded, progress]);
|
|
32
|
+
const rotate = progress.interpolate({
|
|
33
|
+
inputRange: [0, 1],
|
|
34
|
+
outputRange: ['0deg', '90deg'],
|
|
35
|
+
});
|
|
36
|
+
return (<Animated.View style={[style, { transform: [{ rotate }] }]}>
|
|
37
|
+
<ChevronIcon color={color} size={size}/>
|
|
38
|
+
</Animated.View>);
|
|
39
|
+
};
|
|
19
40
|
const ReduxValueNode = ({ name, value, level, search }) => {
|
|
20
41
|
const [expanded, setExpanded] = useState(level < 1);
|
|
21
42
|
const isObject = typeof value === 'object' && value !== null;
|
|
@@ -39,7 +60,11 @@ const ReduxValueNode = ({ name, value, level, search }) => {
|
|
|
39
60
|
return null;
|
|
40
61
|
}
|
|
41
62
|
if (!isObject) {
|
|
42
|
-
const valStr = value === null
|
|
63
|
+
const valStr = value === null
|
|
64
|
+
? 'null'
|
|
65
|
+
: value === undefined
|
|
66
|
+
? 'undefined'
|
|
67
|
+
: String(value);
|
|
43
68
|
// Pick different colors for primitives
|
|
44
69
|
let valColor = '#0D9488'; // String
|
|
45
70
|
if (value === null || value === undefined) {
|
|
@@ -63,13 +88,16 @@ const ReduxValueNode = ({ name, value, level, search }) => {
|
|
|
63
88
|
</View>);
|
|
64
89
|
}
|
|
65
90
|
const keys = Object.keys(value);
|
|
66
|
-
const summaryText = isArray
|
|
91
|
+
const summaryText = isArray
|
|
92
|
+
? `Array [${keys.length}]`
|
|
93
|
+
: `Object {${keys.length}}`;
|
|
67
94
|
return (<View style={reduxValueStyles.treeNodeBlock}>
|
|
68
|
-
<Pressable onPress={() =>
|
|
95
|
+
<Pressable onPress={() => {
|
|
96
|
+
animateTreeLayout();
|
|
97
|
+
setExpanded(!expanded);
|
|
98
|
+
}} style={reduxValueStyles.treeRow}>
|
|
69
99
|
<View style={reduxValueStyles.treeLeafConnector}/>
|
|
70
|
-
<
|
|
71
|
-
<ChevronIcon color={AppColors.grayTextWeak} size={10}/>
|
|
72
|
-
</View>
|
|
100
|
+
<AnimatedChevron color={AppColors.grayTextWeak} expanded={expanded} size={10} style={reduxValueStyles.chevronWrap}/>
|
|
73
101
|
<Text style={reduxValueStyles.keyText} selectable={true}>
|
|
74
102
|
{nameStr}
|
|
75
103
|
<Text style={reduxValueStyles.colonText}>: </Text>
|
|
@@ -84,13 +112,24 @@ const ReduxValueNode = ({ name, value, level, search }) => {
|
|
|
84
112
|
export const ReduxTreeView = ({ state, lastActionMap, search, }) => {
|
|
85
113
|
const [storeExpanded, setStoreExpanded] = useState(true);
|
|
86
114
|
const [reducerExpanded, setReducerExpanded] = useState({});
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (Platform.OS === 'android') {
|
|
117
|
+
UIManager.setLayoutAnimationEnabledExperimental?.(true);
|
|
118
|
+
}
|
|
119
|
+
}, []);
|
|
87
120
|
if (!state || typeof state !== 'object') {
|
|
88
|
-
return (<Text style={{
|
|
121
|
+
return (<Text style={{
|
|
122
|
+
fontFamily: AppFonts.interRegular,
|
|
123
|
+
fontSize: 12,
|
|
124
|
+
color: AppColors.grayTextWeak,
|
|
125
|
+
padding: 12,
|
|
126
|
+
}}>
|
|
89
127
|
No state object to display.
|
|
90
128
|
</Text>);
|
|
91
129
|
}
|
|
92
130
|
const reducers = Object.keys(state);
|
|
93
131
|
const toggleReducer = (key) => {
|
|
132
|
+
animateTreeLayout();
|
|
94
133
|
setReducerExpanded(prev => ({
|
|
95
134
|
...prev,
|
|
96
135
|
[key]: !prev[key],
|
|
@@ -98,10 +137,11 @@ export const ReduxTreeView = ({ state, lastActionMap, search, }) => {
|
|
|
98
137
|
};
|
|
99
138
|
return (<View style={styles.container}>
|
|
100
139
|
{/* Root Node: Store */}
|
|
101
|
-
<Pressable onPress={() =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
140
|
+
<Pressable onPress={() => {
|
|
141
|
+
animateTreeLayout();
|
|
142
|
+
setStoreExpanded(!storeExpanded);
|
|
143
|
+
}} style={styles.storeHeader}>
|
|
144
|
+
<AnimatedChevron color="#FFFFFF" expanded={storeExpanded} size={12} style={styles.chevronWrap}/>
|
|
105
145
|
<Text style={styles.storeTitle}>🏪 Redux Store</Text>
|
|
106
146
|
<View style={styles.badge}>
|
|
107
147
|
<Text style={styles.badgeText}>{reducers.length} Reducers</Text>
|
|
@@ -116,14 +156,15 @@ export const ReduxTreeView = ({ state, lastActionMap, search, }) => {
|
|
|
116
156
|
const lastAction = lastActionMap[reducerKey];
|
|
117
157
|
return (<View key={reducerKey} style={styles.reducerContainer}>
|
|
118
158
|
{/* Visual Branch Line for Reducer */}
|
|
119
|
-
<View style={[
|
|
159
|
+
<View style={[
|
|
160
|
+
styles.reducerVerticalLine,
|
|
161
|
+
isLastReducer && { bottom: '50%' },
|
|
162
|
+
]}/>
|
|
120
163
|
|
|
121
164
|
{/* Reducer Header */}
|
|
122
165
|
<Pressable onPress={() => toggleReducer(reducerKey)} style={styles.reducerHeader}>
|
|
123
166
|
<View style={styles.reducerHorizontalLine}/>
|
|
124
|
-
<
|
|
125
|
-
<ChevronIcon color={AppColors.purple} size={10}/>
|
|
126
|
-
</View>
|
|
167
|
+
<AnimatedChevron color={AppColors.purple} expanded={isExpanded} size={10} style={styles.chevronWrap}/>
|
|
127
168
|
<View style={styles.iconWrap}>
|
|
128
169
|
<FolderIcon color={AppColors.purple} size={11}/>
|
|
129
170
|
</View>
|
|
@@ -141,13 +182,24 @@ export const ReduxTreeView = ({ state, lastActionMap, search, }) => {
|
|
|
141
182
|
<BoltIcon color="#DB2777" size={11}/>
|
|
142
183
|
</View>
|
|
143
184
|
<View style={{ flex: 1 }}>
|
|
144
|
-
<View style={{
|
|
185
|
+
<View style={{
|
|
186
|
+
flexDirection: 'row',
|
|
187
|
+
alignItems: 'center',
|
|
188
|
+
flexWrap: 'wrap',
|
|
189
|
+
gap: 6,
|
|
190
|
+
}}>
|
|
145
191
|
<Text style={styles.childLabel}>Last Action:</Text>
|
|
146
192
|
{lastAction ? (<View style={styles.actionTypeBadge}>
|
|
147
|
-
<Text style={styles.actionTypeText}>
|
|
148
|
-
|
|
193
|
+
<Text style={styles.actionTypeText}>
|
|
194
|
+
{lastAction.type}
|
|
195
|
+
</Text>
|
|
196
|
+
</View>) : (<Text style={styles.noActionText}>
|
|
197
|
+
None dispatched
|
|
198
|
+
</Text>)}
|
|
149
199
|
</View>
|
|
150
|
-
{lastAction && (<Text style={styles.timestampText}>
|
|
200
|
+
{lastAction && (<Text style={styles.timestampText}>
|
|
201
|
+
Dispatched: {lastAction.timestamp}
|
|
202
|
+
</Text>)}
|
|
151
203
|
{lastAction && lastAction.payload !== null && (<View style={{ marginTop: 6 }}>
|
|
152
204
|
<ReduxValueNode name="payload" value={lastAction.payload} level={0} search={search}/>
|
|
153
205
|
</View>)}
|
|
@@ -176,6 +228,7 @@ export const ReduxTreeView = ({ state, lastActionMap, search, }) => {
|
|
|
176
228
|
export const ReduxActionTimeline = ({ history, onClear, search, }) => {
|
|
177
229
|
const [expandedActionId, setExpandedActionId] = useState(null);
|
|
178
230
|
const toggleExpand = (id) => {
|
|
231
|
+
animateTreeLayout();
|
|
179
232
|
setExpandedActionId(prev => (prev === id ? null : id));
|
|
180
233
|
};
|
|
181
234
|
const filteredHistory = history.filter(action => {
|
|
@@ -193,7 +246,9 @@ export const ReduxActionTimeline = ({ history, onClear, search, }) => {
|
|
|
193
246
|
});
|
|
194
247
|
return (<View style={timelineStyles.container}>
|
|
195
248
|
<View style={timelineStyles.headerRow}>
|
|
196
|
-
<Text style={timelineStyles.headerTitle}
|
|
249
|
+
<Text style={timelineStyles.headerTitle}>
|
|
250
|
+
⚡ Dispatched Actions ({filteredHistory.length})
|
|
251
|
+
</Text>
|
|
197
252
|
{history.length > 0 && (<Pressable onPress={onClear} style={timelineStyles.clearBtn}>
|
|
198
253
|
<Text style={timelineStyles.clearBtnText}>Clear Log</Text>
|
|
199
254
|
</Pressable>)}
|
|
@@ -209,9 +264,12 @@ export const ReduxActionTimeline = ({ history, onClear, search, }) => {
|
|
|
209
264
|
{filteredHistory.map((item, index) => {
|
|
210
265
|
const isLast = index === filteredHistory.length - 1;
|
|
211
266
|
const isExpanded = expandedActionId === item.id;
|
|
212
|
-
return (<
|
|
267
|
+
return (<AnimatedEntrance key={item.id} index={index} distance={8} style={timelineStyles.timelineItem}>
|
|
213
268
|
{/* Visual Line */}
|
|
214
|
-
<View style={[
|
|
269
|
+
<View style={[
|
|
270
|
+
timelineStyles.verticalLine,
|
|
271
|
+
isLast && { bottom: '50%' },
|
|
272
|
+
]}/>
|
|
215
273
|
<View style={timelineStyles.circleIndicator}>
|
|
216
274
|
<View style={timelineStyles.circleInner}/>
|
|
217
275
|
</View>
|
|
@@ -219,13 +277,18 @@ export const ReduxActionTimeline = ({ history, onClear, search, }) => {
|
|
|
219
277
|
{/* Card */}
|
|
220
278
|
<Pressable onPress={() => toggleExpand(item.id)} style={[
|
|
221
279
|
timelineStyles.card,
|
|
222
|
-
isExpanded && {
|
|
280
|
+
isExpanded && {
|
|
281
|
+
borderColor: AppColors.purple,
|
|
282
|
+
backgroundColor: AppColors.purpleShade50,
|
|
283
|
+
},
|
|
223
284
|
]}>
|
|
224
285
|
<View style={timelineStyles.cardHeader}>
|
|
225
286
|
<View style={timelineStyles.typeBadge}>
|
|
226
287
|
<Text style={timelineStyles.typeText}>{item.type}</Text>
|
|
227
288
|
</View>
|
|
228
|
-
<Text style={timelineStyles.timestamp}>
|
|
289
|
+
<Text style={timelineStyles.timestamp}>
|
|
290
|
+
{item.timestamp}
|
|
291
|
+
</Text>
|
|
229
292
|
</View>
|
|
230
293
|
|
|
231
294
|
{item.affectedSlices.length > 0 && (<View style={timelineStyles.slicesRow}>
|
|
@@ -237,12 +300,15 @@ export const ReduxActionTimeline = ({ history, onClear, search, }) => {
|
|
|
237
300
|
|
|
238
301
|
{isExpanded && (<View style={timelineStyles.payloadContainer}>
|
|
239
302
|
<Text style={timelineStyles.payloadTitle}>Payload</Text>
|
|
240
|
-
{item.payload !== null &&
|
|
241
|
-
|
|
303
|
+
{item.payload !== null &&
|
|
304
|
+
typeof item.payload === 'object' ? (<ReduxValueNode name="action.payload" value={item.payload} level={0} search={search}/>) : (<Text style={timelineStyles.primitivePayload}>
|
|
305
|
+
{item.payload === null
|
|
306
|
+
? 'null'
|
|
307
|
+
: String(item.payload)}
|
|
242
308
|
</Text>)}
|
|
243
309
|
</View>)}
|
|
244
310
|
</Pressable>
|
|
245
|
-
</
|
|
311
|
+
</AnimatedEntrance>);
|
|
246
312
|
})}
|
|
247
313
|
</View>)}
|
|
248
314
|
</View>);
|
|
@@ -2,6 +2,22 @@ import React, { useRef } from 'react';
|
|
|
2
2
|
import { Animated, Pressable, StyleSheet } from 'react-native';
|
|
3
3
|
const TouchableScale = ({ onPress, style, children, hitSlop, disabled, }) => {
|
|
4
4
|
const scale = useRef(new Animated.Value(1)).current;
|
|
5
|
+
const opacity = useRef(new Animated.Value(1)).current;
|
|
6
|
+
const animatePress = (pressed) => {
|
|
7
|
+
Animated.parallel([
|
|
8
|
+
Animated.spring(scale, {
|
|
9
|
+
toValue: pressed ? 0.94 : 1,
|
|
10
|
+
friction: 6,
|
|
11
|
+
tension: 120,
|
|
12
|
+
useNativeDriver: true,
|
|
13
|
+
}),
|
|
14
|
+
Animated.timing(opacity, {
|
|
15
|
+
toValue: pressed ? 0.86 : 1,
|
|
16
|
+
duration: pressed ? 90 : 140,
|
|
17
|
+
useNativeDriver: true,
|
|
18
|
+
}),
|
|
19
|
+
]).start();
|
|
20
|
+
};
|
|
5
21
|
const flattenedStyle = StyleSheet.flatten(style) || {};
|
|
6
22
|
const layoutStyle = {
|
|
7
23
|
flex: flattenedStyle.flex,
|
|
@@ -14,8 +30,8 @@ const TouchableScale = ({ onPress, style, children, hitSlop, disabled, }) => {
|
|
|
14
30
|
flexShrink: flattenedStyle.flexShrink,
|
|
15
31
|
gap: flattenedStyle.gap,
|
|
16
32
|
};
|
|
17
|
-
return (<Pressable disabled={disabled} style={style} onPressIn={() =>
|
|
18
|
-
<Animated.View style={[{ transform: [{ scale }] }, layoutStyle]}>
|
|
33
|
+
return (<Pressable disabled={disabled} style={style} onPressIn={() => animatePress(true)} onPressOut={() => animatePress(false)} onPress={onPress} hitSlop={hitSlop}>
|
|
34
|
+
<Animated.View style={[{ opacity, transform: [{ scale }] }, layoutStyle]}>
|
|
19
35
|
{children}
|
|
20
36
|
</Animated.View>
|
|
21
37
|
</Pressable>);
|