react-native-inapp-inspector 1.1.28 → 1.1.31
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/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -80
- package/dist/commonjs/components/AnalyticsDetail.js +668 -108
- package/dist/commonjs/components/AppHeaderLogo.js +15 -15
- package/dist/commonjs/components/ConsoleLogCard.js +112 -66
- package/dist/commonjs/components/CopyButton.js +15 -1
- package/dist/commonjs/components/DomainHeader.js +64 -32
- package/dist/commonjs/components/HighlightText.js +7 -1
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +332 -221
- package/dist/commonjs/components/Inspector/ConsoleTab.js +162 -56
- package/dist/commonjs/components/Inspector/InspectorHeader.js +70 -119
- package/dist/commonjs/components/Inspector/LogDetail.js +53 -17
- package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
- package/dist/commonjs/components/Inspector/NetworkDetail.js +24 -24
- package/dist/commonjs/components/Inspector/NetworkTab.js +4 -1
- package/dist/commonjs/components/Inspector/ReduxDetail.js +237 -42
- package/dist/commonjs/components/Inspector/ReduxTab.js +456 -35
- package/dist/commonjs/components/JsonViewer.js +26 -4
- package/dist/commonjs/components/LogCard.js +103 -11
- package/dist/commonjs/components/NetworkIcons.js +19 -10
- package/dist/commonjs/components/Toast.d.ts +3 -0
- package/dist/commonjs/components/Toast.js +152 -0
- package/dist/commonjs/components/TouchableScale.d.ts +1 -0
- package/dist/commonjs/components/TouchableScale.js +3 -3
- package/dist/commonjs/components/TreeNode.js +12 -2
- package/dist/commonjs/constants/index.js +6 -6
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/reduxLogger.js +128 -5
- package/dist/commonjs/helpers/index.d.ts +4 -0
- package/dist/commonjs/helpers/index.js +107 -92
- package/dist/commonjs/helpers/toast.d.ts +4 -0
- package/dist/commonjs/helpers/toast.js +20 -0
- package/dist/commonjs/i18n/locales/en.json +49 -3
- package/dist/commonjs/styles/AppColors.d.ts +6 -0
- package/dist/commonjs/styles/AppColors.js +6 -0
- package/dist/commonjs/styles/index.d.ts +82 -15
- package/dist/commonjs/styles/index.js +92 -26
- package/dist/commonjs/types/interfaces.d.ts +7 -0
- package/dist/esm/components/AnalyticsDetail.js +670 -110
- package/dist/esm/components/AppHeaderLogo.js +15 -15
- package/dist/esm/components/ConsoleLogCard.js +114 -68
- package/dist/esm/components/CopyButton.js +15 -1
- package/dist/esm/components/DomainHeader.js +64 -32
- package/dist/esm/components/HighlightText.js +7 -1
- package/dist/esm/components/Inspector/AnalyticsTab.js +334 -223
- package/dist/esm/components/Inspector/ConsoleTab.js +163 -57
- package/dist/esm/components/Inspector/InspectorHeader.js +71 -120
- package/dist/esm/components/Inspector/LogDetail.js +55 -19
- package/dist/esm/components/Inspector/MainScreen.js +4 -0
- package/dist/esm/components/Inspector/NetworkDetail.js +25 -25
- package/dist/esm/components/Inspector/NetworkTab.js +4 -1
- package/dist/esm/components/Inspector/ReduxDetail.js +239 -44
- package/dist/esm/components/Inspector/ReduxTab.js +458 -37
- package/dist/esm/components/JsonViewer.js +26 -4
- package/dist/esm/components/LogCard.js +105 -13
- package/dist/esm/components/NetworkIcons.js +19 -10
- package/dist/esm/components/Toast.d.ts +3 -0
- package/dist/esm/components/Toast.js +117 -0
- package/dist/esm/components/TouchableScale.d.ts +1 -0
- package/dist/esm/components/TouchableScale.js +3 -3
- package/dist/esm/components/TreeNode.js +12 -2
- package/dist/esm/constants/index.js +6 -6
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/reduxLogger.js +128 -5
- package/dist/esm/helpers/index.d.ts +4 -0
- package/dist/esm/helpers/index.js +102 -92
- package/dist/esm/helpers/toast.d.ts +4 -0
- package/dist/esm/helpers/toast.js +15 -0
- package/dist/esm/i18n/locales/en.json +49 -3
- package/dist/esm/styles/AppColors.d.ts +6 -0
- package/dist/esm/styles/AppColors.js +6 -0
- package/dist/esm/styles/index.d.ts +82 -15
- package/dist/esm/styles/index.js +92 -26
- package/dist/esm/types/interfaces.d.ts +7 -0
- package/ios/NetworkInspectorModule.m +0 -10
- package/package.json +13 -10
|
@@ -1,55 +1,234 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
2
2
|
import { FlatList, Pressable, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
|
|
3
|
+
import { useTranslation } from '../../i18n';
|
|
3
4
|
import { useInspector } from './InspectorContext';
|
|
4
5
|
import TouchableScale from '../TouchableScale';
|
|
5
6
|
import AnimatedEntrance from '../AnimatedEntrance';
|
|
6
7
|
import EmptyState from '../EmptyState';
|
|
7
8
|
import EndOfListFooter from '../EndOfListFooter';
|
|
8
9
|
import HighlightText from '../HighlightText';
|
|
9
|
-
import { getSize } from '../../helpers';
|
|
10
|
+
import { getSize, openInVSCode } from '../../helpers';
|
|
10
11
|
import styles from '../../styles';
|
|
11
12
|
import { AppColors } from '../../styles/AppColors';
|
|
12
13
|
import { AppFonts } from '../../styles/AppFonts';
|
|
13
|
-
import {
|
|
14
|
+
import { getActionHistory, getReduxAutoRefresh, setReduxAutoRefresh, } from '../../customHooks/reduxLogger';
|
|
15
|
+
import { TerminalIcon, SearchIcon, ClearIcon, LayersIcon, ClockIcon, ForwardChevronIcon, SortArrowIcon, BoltIcon, TextAaIcon, HeaderPauseIcon, } from '../NetworkIcons';
|
|
16
|
+
const getOriginBadge = (originType) => {
|
|
17
|
+
switch (originType) {
|
|
18
|
+
case 'saga':
|
|
19
|
+
return {
|
|
20
|
+
label: 'SAGA',
|
|
21
|
+
icon: '⚡',
|
|
22
|
+
bg: AppColors.purple100,
|
|
23
|
+
text: AppColors.brandPurple,
|
|
24
|
+
border: AppColors.purple200,
|
|
25
|
+
};
|
|
26
|
+
case 'thunk':
|
|
27
|
+
return {
|
|
28
|
+
label: 'THUNK',
|
|
29
|
+
icon: '⚛️',
|
|
30
|
+
bg: AppColors.amber100,
|
|
31
|
+
text: AppColors.amber800Warm,
|
|
32
|
+
border: AppColors.amber200,
|
|
33
|
+
};
|
|
34
|
+
case 'ui':
|
|
35
|
+
return {
|
|
36
|
+
label: 'UI',
|
|
37
|
+
icon: '📱',
|
|
38
|
+
bg: AppColors.sky100,
|
|
39
|
+
text: AppColors.sky600,
|
|
40
|
+
border: AppColors.sky400,
|
|
41
|
+
};
|
|
42
|
+
case 'listener':
|
|
43
|
+
return {
|
|
44
|
+
label: 'LISTENER',
|
|
45
|
+
icon: '👂',
|
|
46
|
+
bg: AppColors.teal100,
|
|
47
|
+
text: AppColors.teal700,
|
|
48
|
+
border: AppColors.teal400,
|
|
49
|
+
};
|
|
50
|
+
default:
|
|
51
|
+
return {
|
|
52
|
+
label: 'DIRECT',
|
|
53
|
+
icon: '⚡',
|
|
54
|
+
bg: AppColors.slate100,
|
|
55
|
+
text: AppColors.slate700,
|
|
56
|
+
border: AppColors.slate200,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const getKeyChipStyle = (key) => {
|
|
61
|
+
const k = key.toLowerCase();
|
|
62
|
+
if (k.includes('load') || k.includes('pend') || k.includes('status')) {
|
|
63
|
+
return {
|
|
64
|
+
bg: AppColors.amber100,
|
|
65
|
+
border: AppColors.amber200,
|
|
66
|
+
text: AppColors.amber800Warm,
|
|
67
|
+
dot: AppColors.darkOrange,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (k.includes('err') || k.includes('fail') || k.includes('reject')) {
|
|
71
|
+
return {
|
|
72
|
+
bg: AppColors.red100,
|
|
73
|
+
border: AppColors.errorBorder,
|
|
74
|
+
text: AppColors.red600,
|
|
75
|
+
dot: AppColors.errorColor,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (k.includes('user') || k.includes('auth') || k.includes('token') || k.includes('profile')) {
|
|
79
|
+
return {
|
|
80
|
+
bg: AppColors.indigo50,
|
|
81
|
+
border: AppColors.indigo400,
|
|
82
|
+
text: AppColors.indigo600Alt,
|
|
83
|
+
dot: AppColors.indigo500,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (k.includes('item') || k.includes('data') || k.includes('list') || k.includes('entit') || k.includes('count')) {
|
|
87
|
+
return {
|
|
88
|
+
bg: AppColors.emeraldBg,
|
|
89
|
+
border: AppColors.emeraldBorder,
|
|
90
|
+
text: AppColors.emerald700,
|
|
91
|
+
dot: AppColors.emerald500,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
bg: AppColors.slate100,
|
|
96
|
+
border: AppColors.slate200,
|
|
97
|
+
text: AppColors.slate700,
|
|
98
|
+
dot: AppColors.slate400,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
14
101
|
const ReduxTab = React.memo(() => {
|
|
102
|
+
const { t } = useTranslation();
|
|
15
103
|
const { reduxState, reduxLastActionMap, reduxSearch, setReduxSearch, setSelectedReduxSlice, } = useInspector();
|
|
16
104
|
// Sort mode: 'latest' (newest updated first) vs 'alpha' (A-Z)
|
|
17
105
|
const [sortMode, setSortMode] = useState('latest');
|
|
106
|
+
// Pause / Live Auto-Refresh State
|
|
107
|
+
const [isReduxPaused, setIsReduxPaused] = useState(!getReduxAutoRefresh());
|
|
108
|
+
const handleTogglePause = useCallback(() => {
|
|
109
|
+
setIsReduxPaused(prev => {
|
|
110
|
+
const next = !prev;
|
|
111
|
+
setReduxAutoRefresh(!next);
|
|
112
|
+
return next;
|
|
113
|
+
});
|
|
114
|
+
}, []);
|
|
18
115
|
// Total state size calculation
|
|
19
116
|
const totalStateSize = useMemo(() => {
|
|
20
117
|
return getSize(reduxState);
|
|
21
118
|
}, [reduxState]);
|
|
22
|
-
//
|
|
119
|
+
// Fetch full action history for timeline counts
|
|
120
|
+
const allActions = useMemo(() => {
|
|
121
|
+
return getActionHistory();
|
|
122
|
+
}, [reduxLastActionMap]);
|
|
123
|
+
// Compute slice items with exact timestamps & rich details
|
|
23
124
|
const sliceItems = useMemo(() => {
|
|
24
125
|
if (!reduxState || typeof reduxState !== 'object')
|
|
25
126
|
return [];
|
|
26
127
|
const keys = Object.keys(reduxState);
|
|
27
128
|
return keys.map(key => {
|
|
28
129
|
const sliceVal = reduxState[key];
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
130
|
+
const isArray = Array.isArray(sliceVal);
|
|
131
|
+
const isObject = sliceVal && typeof sliceVal === 'object';
|
|
132
|
+
let typeLabel = 'Primitive';
|
|
133
|
+
let keysCount = 0;
|
|
134
|
+
let topKeys = [];
|
|
135
|
+
let totalKeysCount = 0;
|
|
136
|
+
if (isArray) {
|
|
137
|
+
keysCount = sliceVal.length;
|
|
138
|
+
typeLabel = `Array [${keysCount}]`;
|
|
139
|
+
}
|
|
140
|
+
else if (isObject) {
|
|
141
|
+
const objKeys = Object.keys(sliceVal);
|
|
142
|
+
totalKeysCount = objKeys.length;
|
|
143
|
+
keysCount = totalKeysCount;
|
|
144
|
+
topKeys = objKeys.slice(0, 4);
|
|
145
|
+
typeLabel = `Object {${keysCount}}`;
|
|
146
|
+
}
|
|
147
|
+
else if (typeof sliceVal !== 'undefined') {
|
|
148
|
+
keysCount = 1;
|
|
149
|
+
typeLabel = typeof sliceVal;
|
|
150
|
+
}
|
|
151
|
+
// Determine state status
|
|
152
|
+
let status = 'live';
|
|
153
|
+
let statusMessage;
|
|
154
|
+
if (isObject) {
|
|
155
|
+
if (sliceVal.loading === true ||
|
|
156
|
+
sliceVal.isLoading === true ||
|
|
157
|
+
sliceVal.status === 'loading' ||
|
|
158
|
+
sliceVal.status === 'pending') {
|
|
159
|
+
status = 'loading';
|
|
160
|
+
statusMessage = 'Loading';
|
|
161
|
+
}
|
|
162
|
+
else if (sliceVal.error ||
|
|
163
|
+
sliceVal.hasError ||
|
|
164
|
+
sliceVal.status === 'error' ||
|
|
165
|
+
sliceVal.status === 'failed') {
|
|
166
|
+
status = 'error';
|
|
167
|
+
statusMessage = typeof sliceVal.error === 'string' ? sliceVal.error : 'Error';
|
|
168
|
+
}
|
|
169
|
+
else if (keysCount === 0) {
|
|
170
|
+
status = 'empty';
|
|
171
|
+
statusMessage = 'Empty';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const lastActionRaw = reduxLastActionMap[key];
|
|
175
|
+
const updatedAt = lastActionRaw?.updatedAt || 0;
|
|
176
|
+
// Count actions affecting this slice in timeline
|
|
177
|
+
const timelineCount = allActions.filter(action => {
|
|
178
|
+
if (!action.affectedSlices || action.affectedSlices.length === 0)
|
|
179
|
+
return true;
|
|
180
|
+
return action.affectedSlices.includes(key);
|
|
181
|
+
}).length;
|
|
182
|
+
let lastAction = undefined;
|
|
183
|
+
if (lastActionRaw) {
|
|
184
|
+
let payloadPreview;
|
|
185
|
+
if (lastActionRaw.payload !== undefined && lastActionRaw.payload !== null) {
|
|
186
|
+
try {
|
|
187
|
+
if (typeof lastActionRaw.payload === 'object') {
|
|
188
|
+
const pKeys = Object.keys(lastActionRaw.payload);
|
|
189
|
+
payloadPreview = `{ ${pKeys.slice(0, 3).join(', ')}${pKeys.length > 3 ? '...' : ''} }`;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
payloadPreview = String(lastActionRaw.payload);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch { }
|
|
196
|
+
}
|
|
197
|
+
lastAction = {
|
|
198
|
+
type: lastActionRaw.type,
|
|
199
|
+
timestamp: lastActionRaw.timestamp,
|
|
200
|
+
updatedAt: lastActionRaw.updatedAt,
|
|
201
|
+
payloadPreview,
|
|
202
|
+
originType: lastActionRaw.originType,
|
|
203
|
+
callerFile: lastActionRaw.callerFile,
|
|
204
|
+
callerLine: lastActionRaw.callerLine,
|
|
205
|
+
callerCol: lastActionRaw.callerCol,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
36
208
|
return {
|
|
37
209
|
id: `slice-${key}`,
|
|
38
210
|
name: key,
|
|
39
211
|
keysCount,
|
|
40
212
|
sizeStr: getSize(sliceVal),
|
|
213
|
+
typeLabel,
|
|
214
|
+
topKeys,
|
|
215
|
+
totalKeysCount,
|
|
216
|
+
status,
|
|
217
|
+
statusMessage,
|
|
218
|
+
timelineCount,
|
|
41
219
|
lastAction,
|
|
42
220
|
updatedAt,
|
|
43
221
|
};
|
|
44
222
|
});
|
|
45
|
-
}, [reduxState, reduxLastActionMap]);
|
|
223
|
+
}, [reduxState, reduxLastActionMap, allActions]);
|
|
46
224
|
// Filter & Sort slices: by default, the most recently updated item is at the FIRST position
|
|
47
225
|
const filteredSlices = useMemo(() => {
|
|
48
226
|
let list = sliceItems;
|
|
49
227
|
if (reduxSearch.trim().length > 0) {
|
|
50
228
|
const q = reduxSearch.toLowerCase();
|
|
51
229
|
list = list.filter(item => item.name.toLowerCase().includes(q) ||
|
|
52
|
-
(item.lastAction && item.lastAction.type.toLowerCase().includes(q))
|
|
230
|
+
(item.lastAction && item.lastAction.type.toLowerCase().includes(q)) ||
|
|
231
|
+
item.topKeys.some(k => k.toLowerCase().includes(q)));
|
|
53
232
|
}
|
|
54
233
|
return [...list].sort((a, b) => {
|
|
55
234
|
if (sortMode === 'latest') {
|
|
@@ -82,7 +261,7 @@ const ReduxTab = React.memo(() => {
|
|
|
82
261
|
reduxTabStyles.card,
|
|
83
262
|
isRecentlyUpdated && reduxTabStyles.cardRecentlyUpdated,
|
|
84
263
|
]}>
|
|
85
|
-
{/* Top row: Badge + Slice Name + Timestamp + Chevron */}
|
|
264
|
+
{/* Top row: Badge + Slice Name + Status + Timestamp + Chevron */}
|
|
86
265
|
<View style={reduxTabStyles.cardHeader}>
|
|
87
266
|
<View style={reduxTabStyles.sliceBadge}>
|
|
88
267
|
<Text style={reduxTabStyles.sliceBadgeText}>SLICE</Text>
|
|
@@ -90,6 +269,18 @@ const ReduxTab = React.memo(() => {
|
|
|
90
269
|
|
|
91
270
|
<HighlightText text={item.name} search={reduxSearch} style={reduxTabStyles.sliceName} highlightStyle={reduxTabStyles.highlight}/>
|
|
92
271
|
|
|
272
|
+
{/* Status Badge */}
|
|
273
|
+
{item.status === 'loading' ? (<View style={reduxTabStyles.loadingPill}>
|
|
274
|
+
<Text style={reduxTabStyles.loadingPillText}>⏳ Loading</Text>
|
|
275
|
+
</View>) : item.status === 'error' ? (<View style={reduxTabStyles.errorPill}>
|
|
276
|
+
<Text style={reduxTabStyles.errorPillText}>⚠️ Error</Text>
|
|
277
|
+
</View>) : item.status === 'empty' ? (<View style={reduxTabStyles.emptyPill}>
|
|
278
|
+
<Text style={reduxTabStyles.emptyPillText}>Empty</Text>
|
|
279
|
+
</View>) : (<View style={reduxTabStyles.livePill}>
|
|
280
|
+
<View style={reduxTabStyles.liveDot}/>
|
|
281
|
+
<Text style={reduxTabStyles.livePillText}>Live</Text>
|
|
282
|
+
</View>)}
|
|
283
|
+
|
|
93
284
|
{item.lastAction?.timestamp && (<View style={reduxTabStyles.timePill}>
|
|
94
285
|
<ClockIcon color={AppColors.purple} size={10}/>
|
|
95
286
|
<Text style={reduxTabStyles.timeText}>
|
|
@@ -103,27 +294,116 @@ const ReduxTab = React.memo(() => {
|
|
|
103
294
|
{/* Middle row: Stats pills */}
|
|
104
295
|
<View style={reduxTabStyles.statsRow}>
|
|
105
296
|
<View style={reduxTabStyles.pill}>
|
|
106
|
-
<
|
|
107
|
-
<Text style={reduxTabStyles.pillValue}>{item.
|
|
297
|
+
<LayersIcon color={AppColors.grayTextWeak} size={11}/>
|
|
298
|
+
<Text style={reduxTabStyles.pillValue}>{item.typeLabel}</Text>
|
|
108
299
|
</View>
|
|
109
300
|
<View style={reduxTabStyles.pill}>
|
|
110
301
|
<Text style={reduxTabStyles.pillLabel}>Size:</Text>
|
|
111
302
|
<Text style={reduxTabStyles.pillValue}>{item.sizeStr}</Text>
|
|
112
303
|
</View>
|
|
113
|
-
<View style={reduxTabStyles.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
304
|
+
{item.timelineCount > 0 && (<View style={reduxTabStyles.pill}>
|
|
305
|
+
<Text style={reduxTabStyles.pillLabel}>Actions:</Text>
|
|
306
|
+
<Text style={reduxTabStyles.pillValue}>{item.timelineCount}</Text>
|
|
307
|
+
</View>)}
|
|
117
308
|
</View>
|
|
118
309
|
|
|
119
|
-
{/*
|
|
310
|
+
{/* Keys Preview Chips (if object has keys) */}
|
|
311
|
+
{item.topKeys.length > 0 && (<View style={reduxTabStyles.keysContainer}>
|
|
312
|
+
<Text style={reduxTabStyles.keysHeading}>Keys:</Text>
|
|
313
|
+
<View style={reduxTabStyles.keysWrap}>
|
|
314
|
+
{item.topKeys.map((k, kIdx) => {
|
|
315
|
+
const chipStyle = getKeyChipStyle(k);
|
|
316
|
+
return (<View key={kIdx} style={[
|
|
317
|
+
reduxTabStyles.keyChip,
|
|
318
|
+
{
|
|
319
|
+
backgroundColor: chipStyle.bg,
|
|
320
|
+
borderColor: chipStyle.border,
|
|
321
|
+
},
|
|
322
|
+
]}>
|
|
323
|
+
<View style={{
|
|
324
|
+
width: 5,
|
|
325
|
+
height: 5,
|
|
326
|
+
borderRadius: 2.5,
|
|
327
|
+
backgroundColor: chipStyle.dot,
|
|
328
|
+
}}/>
|
|
329
|
+
<Text style={[
|
|
330
|
+
reduxTabStyles.keyChipText,
|
|
331
|
+
{ color: chipStyle.text },
|
|
332
|
+
]} numberOfLines={1}>
|
|
333
|
+
{k}
|
|
334
|
+
</Text>
|
|
335
|
+
</View>);
|
|
336
|
+
})}
|
|
337
|
+
{item.totalKeysCount > item.topKeys.length && (<View style={reduxTabStyles.keyMoreChip}>
|
|
338
|
+
<Text style={reduxTabStyles.keyMoreChipText}>
|
|
339
|
+
+{item.totalKeysCount - item.topKeys.length}
|
|
340
|
+
</Text>
|
|
341
|
+
</View>)}
|
|
342
|
+
</View>
|
|
343
|
+
</View>)}
|
|
344
|
+
|
|
345
|
+
{/* Bottom row: Last Dispatched Action with Origin & Trigger Line */}
|
|
120
346
|
{item.lastAction && (<View style={reduxTabStyles.lastActionRow}>
|
|
121
|
-
<
|
|
122
|
-
|
|
347
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5, flex: 1, minWidth: 0, flexWrap: 'wrap' }}>
|
|
348
|
+
<BoltIcon color={AppColors.brandPurple} size={12}/>
|
|
349
|
+
<Text style={reduxTabStyles.lastActionLabel}>{t('redux.last', 'Last')}:</Text>
|
|
350
|
+
|
|
351
|
+
{/* Origin Badge */}
|
|
352
|
+
{item.lastAction.originType && (<View style={{
|
|
353
|
+
flexDirection: 'row',
|
|
354
|
+
alignItems: 'center',
|
|
355
|
+
gap: 2.5,
|
|
356
|
+
backgroundColor: getOriginBadge(item.lastAction.originType).bg,
|
|
357
|
+
paddingHorizontal: 4.5,
|
|
358
|
+
paddingVertical: 1.5,
|
|
359
|
+
borderRadius: 3.5,
|
|
360
|
+
borderWidth: 1,
|
|
361
|
+
borderColor: getOriginBadge(item.lastAction.originType).border,
|
|
362
|
+
}}>
|
|
363
|
+
<Text style={{ fontSize: 8.5 }}>
|
|
364
|
+
{getOriginBadge(item.lastAction.originType).icon}
|
|
365
|
+
</Text>
|
|
366
|
+
<Text style={{
|
|
367
|
+
fontFamily: AppFonts.interBold,
|
|
368
|
+
fontSize: 8,
|
|
369
|
+
color: getOriginBadge(item.lastAction.originType).text,
|
|
370
|
+
letterSpacing: 0.3,
|
|
371
|
+
}}>
|
|
372
|
+
{getOriginBadge(item.lastAction.originType).label}
|
|
373
|
+
</Text>
|
|
374
|
+
</View>)}
|
|
375
|
+
|
|
376
|
+
<HighlightText text={item.lastAction.type} search={reduxSearch} style={reduxTabStyles.lastActionValue} highlightStyle={reduxTabStyles.highlight} numberOfLines={1}/>
|
|
377
|
+
|
|
378
|
+
{/* Triggered from file preview */}
|
|
379
|
+
{item.lastAction.callerFile && (<Pressable onPress={() => openInVSCode(item.lastAction.callerFile, item.lastAction.callerLine, item.lastAction.callerCol)} hitSlop={6} style={{
|
|
380
|
+
flexDirection: 'row',
|
|
381
|
+
alignItems: 'center',
|
|
382
|
+
gap: 2,
|
|
383
|
+
backgroundColor: AppColors.sky100,
|
|
384
|
+
paddingHorizontal: 4.5,
|
|
385
|
+
paddingVertical: 1.5,
|
|
386
|
+
borderRadius: 3,
|
|
387
|
+
borderWidth: 0.5,
|
|
388
|
+
borderColor: AppColors.sky400,
|
|
389
|
+
}}>
|
|
390
|
+
<Text style={{
|
|
391
|
+
fontFamily: AppFonts.interMedium,
|
|
392
|
+
fontSize: 8.5,
|
|
393
|
+
color: AppColors.sky600,
|
|
394
|
+
}}>
|
|
395
|
+
📄 {item.lastAction.callerFile.split('/').pop()}:{item.lastAction.callerLine || 1}
|
|
396
|
+
</Text>
|
|
397
|
+
</Pressable>)}
|
|
398
|
+
|
|
399
|
+
{item.lastAction.payloadPreview && (<Text style={reduxTabStyles.payloadPreviewText} numberOfLines={1}>
|
|
400
|
+
{item.lastAction.payloadPreview}
|
|
401
|
+
</Text>)}
|
|
402
|
+
</View>
|
|
123
403
|
</View>)}
|
|
124
404
|
</TouchableScale>
|
|
125
405
|
</AnimatedEntrance>);
|
|
126
|
-
}, [reduxSearch, setSelectedReduxSlice]);
|
|
406
|
+
}, [reduxSearch, setSelectedReduxSlice, t]);
|
|
127
407
|
if (!reduxState) {
|
|
128
408
|
return (<View style={styles.emptyContainer}>
|
|
129
409
|
<View style={styles.emptyIconWrap}>
|
|
@@ -148,8 +428,24 @@ const ReduxTab = React.memo(() => {
|
|
|
148
428
|
<Text style={reduxTabStyles.statBigVal}>{totalStateSize}</Text>
|
|
149
429
|
</View>
|
|
150
430
|
<View style={reduxTabStyles.statDivider}/>
|
|
151
|
-
<View style={[reduxTabStyles.statCol, { flex: 1.
|
|
152
|
-
<
|
|
431
|
+
<View style={[reduxTabStyles.statCol, { flex: 1.6 }]}>
|
|
432
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
433
|
+
<Text style={reduxTabStyles.statHeading}>LAST ACTION</Text>
|
|
434
|
+
{lastGlobalAction?.originType && (<View style={{
|
|
435
|
+
backgroundColor: getOriginBadge(lastGlobalAction.originType).bg,
|
|
436
|
+
paddingHorizontal: 4,
|
|
437
|
+
paddingVertical: 1,
|
|
438
|
+
borderRadius: 3,
|
|
439
|
+
}}>
|
|
440
|
+
<Text style={{
|
|
441
|
+
fontFamily: AppFonts.interBold,
|
|
442
|
+
fontSize: 7.5,
|
|
443
|
+
color: getOriginBadge(lastGlobalAction.originType).text,
|
|
444
|
+
}}>
|
|
445
|
+
{getOriginBadge(lastGlobalAction.originType).label}
|
|
446
|
+
</Text>
|
|
447
|
+
</View>)}
|
|
448
|
+
</View>
|
|
153
449
|
<Text style={reduxTabStyles.statLastAction} numberOfLines={1}>
|
|
154
450
|
{lastGlobalAction ? lastGlobalAction.type : 'Initial'}
|
|
155
451
|
</Text>
|
|
@@ -170,6 +466,19 @@ const ReduxTab = React.memo(() => {
|
|
|
170
466
|
</Pressable>)}
|
|
171
467
|
</View>
|
|
172
468
|
|
|
469
|
+
{/* Pause / Resume Live Updates Button */}
|
|
470
|
+
<TouchableOpacity style={[
|
|
471
|
+
styles.toolbarBtn,
|
|
472
|
+
isReduxPaused && {
|
|
473
|
+
backgroundColor: `${AppColors.darkOrange}1F`,
|
|
474
|
+
borderColor: AppColors.darkOrange,
|
|
475
|
+
},
|
|
476
|
+
]} onPress={handleTogglePause} hitSlop={6}>
|
|
477
|
+
<HeaderPauseIcon isPaused={isReduxPaused} color={isReduxPaused
|
|
478
|
+
? AppColors.darkOrange
|
|
479
|
+
: AppColors.grayTextStrong} size={16}/>
|
|
480
|
+
</TouchableOpacity>
|
|
481
|
+
|
|
173
482
|
{/* Sort Button */}
|
|
174
483
|
<TouchableOpacity style={[
|
|
175
484
|
styles.toolbarBtn,
|
|
@@ -183,11 +492,16 @@ const ReduxTab = React.memo(() => {
|
|
|
183
492
|
<Text style={reduxTabStyles.resultCount}>
|
|
184
493
|
Showing {filteredSlices.length} of {sliceItems.length} state slices
|
|
185
494
|
</Text>
|
|
186
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap:
|
|
187
|
-
{
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
495
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
496
|
+
{isReduxPaused && (<View style={reduxTabStyles.pausedBannerPill}>
|
|
497
|
+
<Text style={reduxTabStyles.pausedBannerText}>PAUSED</Text>
|
|
498
|
+
</View>)}
|
|
499
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
500
|
+
{sortMode === 'latest' ? (<BoltIcon color={AppColors.brandPurple} size={11}/>) : (<TextAaIcon color={AppColors.brandPurple} size={11}/>)}
|
|
501
|
+
<Text style={reduxTabStyles.sortLabel}>
|
|
502
|
+
{sortMode === 'latest' ? 'Newest Updates First' : 'Alphabetical (A-Z)'}
|
|
503
|
+
</Text>
|
|
504
|
+
</View>
|
|
191
505
|
</View>
|
|
192
506
|
</View>
|
|
193
507
|
</View>
|
|
@@ -390,17 +704,104 @@ const reduxTabStyles = StyleSheet.create({
|
|
|
390
704
|
livePillText: {
|
|
391
705
|
fontFamily: AppFonts.interBold,
|
|
392
706
|
fontSize: 10,
|
|
393
|
-
color:
|
|
707
|
+
color: AppColors.emerald700,
|
|
394
708
|
},
|
|
395
|
-
|
|
709
|
+
loadingPill: {
|
|
710
|
+
backgroundColor: AppColors.amber100,
|
|
711
|
+
paddingHorizontal: 6,
|
|
712
|
+
paddingVertical: 3,
|
|
713
|
+
borderRadius: 5,
|
|
714
|
+
borderWidth: 1,
|
|
715
|
+
borderColor: AppColors.amber200,
|
|
716
|
+
},
|
|
717
|
+
loadingPillText: {
|
|
718
|
+
fontFamily: AppFonts.interBold,
|
|
719
|
+
fontSize: 9.5,
|
|
720
|
+
color: AppColors.amber800Warm,
|
|
721
|
+
},
|
|
722
|
+
errorPill: {
|
|
723
|
+
backgroundColor: AppColors.red100,
|
|
724
|
+
paddingHorizontal: 6,
|
|
725
|
+
paddingVertical: 3,
|
|
726
|
+
borderRadius: 5,
|
|
727
|
+
borderWidth: 1,
|
|
728
|
+
borderColor: AppColors.errorBorder,
|
|
729
|
+
},
|
|
730
|
+
errorPillText: {
|
|
731
|
+
fontFamily: AppFonts.interBold,
|
|
732
|
+
fontSize: 9.5,
|
|
733
|
+
color: AppColors.red600,
|
|
734
|
+
},
|
|
735
|
+
emptyPill: {
|
|
736
|
+
backgroundColor: AppColors.grayBackground,
|
|
737
|
+
paddingHorizontal: 6,
|
|
738
|
+
paddingVertical: 3,
|
|
739
|
+
borderRadius: 5,
|
|
740
|
+
borderWidth: 1,
|
|
741
|
+
borderColor: AppColors.dividerColor,
|
|
742
|
+
},
|
|
743
|
+
emptyPillText: {
|
|
744
|
+
fontFamily: AppFonts.interMedium,
|
|
745
|
+
fontSize: 9.5,
|
|
746
|
+
color: AppColors.grayTextWeak,
|
|
747
|
+
},
|
|
748
|
+
keysContainer: {
|
|
396
749
|
flexDirection: 'row',
|
|
397
750
|
alignItems: 'center',
|
|
398
751
|
gap: 6,
|
|
752
|
+
marginTop: 4,
|
|
753
|
+
marginBottom: 4,
|
|
754
|
+
},
|
|
755
|
+
keysHeading: {
|
|
756
|
+
fontFamily: AppFonts.interMedium,
|
|
757
|
+
fontSize: 10,
|
|
758
|
+
color: AppColors.grayTextWeak,
|
|
759
|
+
},
|
|
760
|
+
keysWrap: {
|
|
761
|
+
flexDirection: 'row',
|
|
762
|
+
flexWrap: 'wrap',
|
|
763
|
+
gap: 4,
|
|
764
|
+
flex: 1,
|
|
765
|
+
},
|
|
766
|
+
keyChip: {
|
|
767
|
+
flexDirection: 'row',
|
|
768
|
+
alignItems: 'center',
|
|
769
|
+
gap: 4,
|
|
770
|
+
backgroundColor: AppColors.slate100,
|
|
771
|
+
paddingHorizontal: 6,
|
|
772
|
+
paddingVertical: 2.5,
|
|
773
|
+
borderRadius: 5,
|
|
774
|
+
borderWidth: 1,
|
|
775
|
+
borderColor: AppColors.slate200,
|
|
776
|
+
maxWidth: 120,
|
|
777
|
+
},
|
|
778
|
+
keyChipText: {
|
|
779
|
+
fontFamily: AppFonts.interBold,
|
|
780
|
+
fontSize: 9.5,
|
|
781
|
+
color: AppColors.slate700,
|
|
782
|
+
},
|
|
783
|
+
keyMoreChip: {
|
|
784
|
+
backgroundColor: AppColors.purple100,
|
|
785
|
+
paddingHorizontal: 5,
|
|
786
|
+
paddingVertical: 2,
|
|
787
|
+
borderRadius: 4,
|
|
788
|
+
borderWidth: 1,
|
|
789
|
+
borderColor: AppColors.purple200,
|
|
790
|
+
},
|
|
791
|
+
keyMoreChipText: {
|
|
792
|
+
fontFamily: AppFonts.interBold,
|
|
793
|
+
fontSize: 9,
|
|
794
|
+
color: AppColors.brandPurple,
|
|
795
|
+
},
|
|
796
|
+
lastActionRow: {
|
|
797
|
+
flexDirection: 'row',
|
|
798
|
+
alignItems: 'center',
|
|
799
|
+
gap: 5,
|
|
399
800
|
backgroundColor: AppColors.grayBackground,
|
|
400
801
|
borderRadius: 6,
|
|
401
802
|
paddingHorizontal: 8,
|
|
402
|
-
paddingVertical:
|
|
403
|
-
marginTop:
|
|
803
|
+
paddingVertical: 5,
|
|
804
|
+
marginTop: 4,
|
|
404
805
|
borderWidth: 1,
|
|
405
806
|
borderColor: AppColors.dividerColor,
|
|
406
807
|
},
|
|
@@ -410,15 +811,35 @@ const reduxTabStyles = StyleSheet.create({
|
|
|
410
811
|
color: AppColors.grayTextWeak,
|
|
411
812
|
},
|
|
412
813
|
lastActionValue: {
|
|
413
|
-
flex: 1,
|
|
414
814
|
fontFamily: AppFonts.interBold,
|
|
415
815
|
fontSize: 11,
|
|
416
816
|
color: AppColors.primaryBlack,
|
|
417
817
|
},
|
|
818
|
+
payloadPreviewText: {
|
|
819
|
+
flex: 1,
|
|
820
|
+
fontFamily: AppFonts.interRegular,
|
|
821
|
+
fontSize: 10,
|
|
822
|
+
color: AppColors.grayTextWeak,
|
|
823
|
+
marginLeft: 4,
|
|
824
|
+
},
|
|
418
825
|
highlight: {
|
|
419
|
-
backgroundColor:
|
|
420
|
-
color:
|
|
826
|
+
backgroundColor: AppColors.yellow200,
|
|
827
|
+
color: AppColors.yellow800,
|
|
421
828
|
fontFamily: AppFonts.interBold,
|
|
422
829
|
},
|
|
830
|
+
pausedBannerPill: {
|
|
831
|
+
backgroundColor: AppColors.amber100,
|
|
832
|
+
paddingHorizontal: 6,
|
|
833
|
+
paddingVertical: 2,
|
|
834
|
+
borderRadius: 4,
|
|
835
|
+
borderWidth: 1,
|
|
836
|
+
borderColor: AppColors.amber200,
|
|
837
|
+
},
|
|
838
|
+
pausedBannerText: {
|
|
839
|
+
fontFamily: AppFonts.interBold,
|
|
840
|
+
fontSize: 9,
|
|
841
|
+
color: AppColors.amber800Warm,
|
|
842
|
+
letterSpacing: 0.5,
|
|
843
|
+
},
|
|
423
844
|
});
|
|
424
845
|
export default ReduxTab;
|
|
@@ -55,9 +55,9 @@ const JsonTableRow = React.memo(({ itemKey, val, search, }) => {
|
|
|
55
55
|
</View>);
|
|
56
56
|
});
|
|
57
57
|
// ── JsonViewer Component ─────────────────────────────────────────────────────
|
|
58
|
-
const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wrap, fullHeight = false, maxHeight, mode: externalMode, onModeChange, hideTabs = false, }) => {
|
|
58
|
+
const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth = 1, wrap, fullHeight = false, maxHeight, mode: externalMode, onModeChange, hideTabs = false, }) => {
|
|
59
59
|
const { t } = useTranslation();
|
|
60
|
-
const [internalMode, setInternalMode] = useState('
|
|
60
|
+
const [internalMode, setInternalMode] = useState('raw');
|
|
61
61
|
const mode = externalMode ?? internalMode;
|
|
62
62
|
const setMode = (newMode) => {
|
|
63
63
|
if (onModeChange) {
|
|
@@ -78,6 +78,10 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
|
|
|
78
78
|
return String(data);
|
|
79
79
|
}
|
|
80
80
|
}, [data]);
|
|
81
|
+
const [showFullRaw, setShowFullRaw] = useState(false);
|
|
82
|
+
const RAW_LIMIT = 50000;
|
|
83
|
+
const isTruncated = rawText.length > RAW_LIMIT && !showFullRaw;
|
|
84
|
+
const displayRawText = isTruncated ? rawText.slice(0, RAW_LIMIT) : rawText;
|
|
81
85
|
useEffect(() => {
|
|
82
86
|
if (externalMode) {
|
|
83
87
|
setInternalMode(externalMode);
|
|
@@ -148,7 +152,7 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
|
|
|
148
152
|
<View style={contentWrapperStyle}>
|
|
149
153
|
{mode === 'pretty' && (isEmpty ? (<View style={localStyles.emptyTable}>
|
|
150
154
|
<Text style={localStyles.emptyTableText}>
|
|
151
|
-
{t('network.
|
|
155
|
+
{t('network.jsonViewer.emptyPayload')}
|
|
152
156
|
</Text>
|
|
153
157
|
</View>) : (wrap ? (fullHeight ? (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ flexGrow: 1 }}>
|
|
154
158
|
<View style={[styles.codeBlock, { width: '100%' }]}>{tree}</View>
|
|
@@ -163,12 +167,30 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
|
|
|
163
167
|
</ScrollView>))))}
|
|
164
168
|
|
|
165
169
|
{mode === 'raw' && (<ScrollView style={scrollStyle} contentContainerStyle={localStyles.rawContainer} showsVerticalScrollIndicator={true} nestedScrollEnabled={true}>
|
|
166
|
-
<HighlightText text={
|
|
170
|
+
<HighlightText text={displayRawText} search={search} detectLinks={rawText.length < 20000} style={localStyles.rawPlainText} selectable={true} highlightStyle={{
|
|
167
171
|
backgroundColor: AppColors.yellowHighlight,
|
|
168
172
|
color: AppColors.primaryBlack,
|
|
169
173
|
borderRadius: 3,
|
|
170
174
|
paddingHorizontal: 2,
|
|
171
175
|
}}/>
|
|
176
|
+
{isTruncated && (<TouchableOpacity onPress={() => setShowFullRaw(true)} style={{
|
|
177
|
+
marginTop: 10,
|
|
178
|
+
backgroundColor: `${AppColors.brandPurple}15`,
|
|
179
|
+
paddingVertical: 8,
|
|
180
|
+
paddingHorizontal: 12,
|
|
181
|
+
borderRadius: 6,
|
|
182
|
+
alignItems: 'center',
|
|
183
|
+
borderWidth: 1,
|
|
184
|
+
borderColor: `${AppColors.brandPurple}30`,
|
|
185
|
+
}}>
|
|
186
|
+
<Text style={{
|
|
187
|
+
fontFamily: AppFonts.interBold,
|
|
188
|
+
fontSize: 11,
|
|
189
|
+
color: AppColors.brandPurple,
|
|
190
|
+
}}>
|
|
191
|
+
Showing first 50 KB ({Math.round(rawText.length / 1024)} KB total) · Tap to load full
|
|
192
|
+
</Text>
|
|
193
|
+
</TouchableOpacity>)}
|
|
172
194
|
</ScrollView>)}
|
|
173
195
|
|
|
174
196
|
{mode === 'table' && (fullHeight ? (<ScrollView style={scrollStyle}>
|