react-native-inapp-inspector 1.1.12 → 1.1.13
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/AnalyticsDetail.js +64 -21
- package/dist/commonjs/components/HighlightText.js +1 -1
- package/dist/commonjs/components/JsonViewer.d.ts +3 -1
- package/dist/commonjs/components/JsonViewer.js +327 -9
- package/dist/commonjs/components/MetaAccordion.d.ts +10 -1
- package/dist/commonjs/components/MetaAccordion.js +121 -24
- package/dist/commonjs/components/NetworkIcons.d.ts +1 -0
- package/dist/commonjs/components/NetworkIcons.js +8 -1
- package/dist/commonjs/components/ReduxTreeView.d.ts +12 -3
- package/dist/commonjs/components/ReduxTreeView.js +1096 -278
- package/dist/commonjs/components/SectionHeader.d.ts +1 -1
- package/dist/commonjs/components/SectionHeader.js +7 -1
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/reduxLogger.d.ts +2 -0
- package/dist/commonjs/customHooks/reduxLogger.js +2 -0
- package/dist/commonjs/helpers/index.d.ts +1 -0
- package/dist/commonjs/helpers/index.js +15 -1
- package/dist/commonjs/helpers/settingsStore.d.ts +1 -0
- package/dist/commonjs/helpers/settingsStore.js +4 -0
- package/dist/commonjs/index.js +110 -91
- package/dist/commonjs/types/index.d.ts +3 -0
- package/dist/esm/components/AnalyticsDetail.js +64 -21
- package/dist/esm/components/HighlightText.js +3 -3
- package/dist/esm/components/JsonViewer.d.ts +3 -1
- package/dist/esm/components/JsonViewer.js +295 -10
- package/dist/esm/components/MetaAccordion.d.ts +10 -1
- package/dist/esm/components/MetaAccordion.js +90 -26
- package/dist/esm/components/NetworkIcons.d.ts +1 -0
- package/dist/esm/components/NetworkIcons.js +6 -0
- package/dist/esm/components/ReduxTreeView.d.ts +12 -3
- package/dist/esm/components/ReduxTreeView.js +1095 -279
- package/dist/esm/components/SectionHeader.d.ts +1 -1
- package/dist/esm/components/SectionHeader.js +8 -2
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/reduxLogger.d.ts +2 -0
- package/dist/esm/customHooks/reduxLogger.js +2 -0
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/index.js +14 -1
- package/dist/esm/helpers/settingsStore.d.ts +1 -0
- package/dist/esm/helpers/settingsStore.js +3 -0
- package/dist/esm/index.js +111 -92
- package/dist/esm/types/index.d.ts +3 -0
- package/package.json +1 -1
|
@@ -1,361 +1,1177 @@
|
|
|
1
|
-
import React, { useEffect,
|
|
2
|
-
import {
|
|
1
|
+
import React, { useEffect, useState, useMemo } from 'react';
|
|
2
|
+
import { LayoutAnimation, Platform, Pressable, StyleSheet, Text, TextInput, UIManager, View, ScrollView, ToastAndroid, Alert, } from 'react-native';
|
|
3
3
|
import { AppColors } from '../styles/AppColors';
|
|
4
4
|
import { AppFonts } from '../styles/AppFonts';
|
|
5
|
-
import { ChevronIcon } from './NetworkIcons';
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
import { ChevronIcon, FolderIcon, TrashIcon, HeaderPauseIcon, CopyIcon, SearchIcon, ClearIcon, ClockIcon, ExpandCollapseIcon, SignalIcon, LayersIcon, TerminalIcon, RequestIcon, DiffIcon, SortIcon, CalendarIcon, } from './NetworkIcons';
|
|
6
|
+
import JsonViewer from './JsonViewer';
|
|
7
|
+
import DiffViewer from './DiffViewer';
|
|
8
|
+
import TouchableScale from './TouchableScale';
|
|
9
|
+
import { copyToClipboard, getSize } from '../helpers';
|
|
10
|
+
import { getCustomStorage } from '../helpers/settingsStore';
|
|
11
|
+
// Enable LayoutAnimation for Android
|
|
12
|
+
if (Platform.OS === 'android') {
|
|
13
|
+
UIManager.setLayoutAnimationEnabledExperimental?.(true);
|
|
14
|
+
}
|
|
15
|
+
const animateLayout = () => {
|
|
16
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
17
|
+
};
|
|
18
|
+
// Chevron pointing left (for back buttons)
|
|
19
|
+
const BackChevronIcon = ({ color = AppColors.purple, size = 12 }) => (<View style={{ transform: [{ rotate: '180deg' }], marginRight: 4 }}>
|
|
20
|
+
<ChevronIcon color={color} size={size}/>
|
|
21
|
+
</View>);
|
|
22
|
+
// Chevron pointing right (for list items)
|
|
23
|
+
const ForwardChevronIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<View style={{ transform: [{ rotate: '0deg' }], marginLeft: 'auto' }}>
|
|
24
|
+
<ChevronIcon color={color} size={size}/>
|
|
25
|
+
</View>);
|
|
26
|
+
// Helper: check if a value recursively contains a string term
|
|
27
|
+
const valueContainsTerm = (val, term) => {
|
|
28
|
+
if (!term)
|
|
29
|
+
return true;
|
|
30
|
+
const t = term.toLowerCase();
|
|
31
|
+
if (val === null || val === undefined)
|
|
32
|
+
return false;
|
|
33
|
+
if (typeof val !== 'object') {
|
|
34
|
+
return String(val).toLowerCase().includes(t);
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(val)) {
|
|
37
|
+
return val.some(item => valueContainsTerm(item, term));
|
|
16
38
|
}
|
|
39
|
+
return Object.keys(val).some(key => key.toLowerCase().includes(t) || valueContainsTerm(val[key], term));
|
|
17
40
|
};
|
|
18
|
-
const
|
|
19
|
-
const
|
|
41
|
+
export const ReduxTreeView = ({ state, actionHistory = [], lastActionMap = {}, search = '', onSearchChange, autoRefresh = true, onToggleAutoRefresh, onClearHistory, onDetailOpenChange, selectedSliceKey = null, onSelectSliceKey, selectedActionId = null, onSelectActionId, }) => {
|
|
42
|
+
const [activeTab, setActiveTab] = useState('state');
|
|
43
|
+
const [actionSubTab, setActionSubTab] = useState('payload');
|
|
44
|
+
// local fallbacks if props are not provided
|
|
45
|
+
const [localSliceKey, setLocalSliceKey] = useState(null);
|
|
46
|
+
const [localActionId, setLocalActionId] = useState(null);
|
|
47
|
+
const activeSliceKey = onSelectSliceKey ? selectedSliceKey : localSliceKey;
|
|
48
|
+
const activeActionId = onSelectActionId ? selectedActionId : localActionId;
|
|
49
|
+
const setActiveSliceKey = (key) => {
|
|
50
|
+
if (onSelectSliceKey)
|
|
51
|
+
onSelectSliceKey(key);
|
|
52
|
+
else
|
|
53
|
+
setLocalSliceKey(key);
|
|
54
|
+
};
|
|
55
|
+
const setActiveActionId = (id) => {
|
|
56
|
+
if (onSelectActionId)
|
|
57
|
+
onSelectActionId(id);
|
|
58
|
+
else
|
|
59
|
+
setLocalActionId(id);
|
|
60
|
+
};
|
|
61
|
+
// Expand / collapse all states for detail views
|
|
62
|
+
const [sliceForceOpen, setSliceForceOpen] = useState(undefined);
|
|
63
|
+
const [actionForceOpen, setActionForceOpen] = useState(undefined);
|
|
64
|
+
const [searchModeType, setSearchModeType] = useState(true);
|
|
65
|
+
const [searchModeData, setSearchModeData] = useState(true);
|
|
66
|
+
// Sorting directions
|
|
67
|
+
const [stateSortAsc, setStateSortAsc] = useState(true);
|
|
68
|
+
const [actionSortAsc, setActionSortAsc] = useState(false);
|
|
69
|
+
// Persistence-related states for State Slice details
|
|
70
|
+
const [sliceDetailTab, setSliceDetailTab] = useState('live');
|
|
71
|
+
const [isPersisted, setIsPersisted] = useState(false);
|
|
72
|
+
const [persistedData, setPersistedData] = useState(null);
|
|
20
73
|
useEffect(() => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}, [expanded, progress]);
|
|
27
|
-
const rotate = progress.interpolate({
|
|
28
|
-
inputRange: [0, 1],
|
|
29
|
-
outputRange: ['0deg', '90deg'],
|
|
30
|
-
});
|
|
31
|
-
return (<Animated.View style={[style, { transform: [{ rotate }] }]}>
|
|
32
|
-
<ChevronIcon color={color} size={size}/>
|
|
33
|
-
</Animated.View>);
|
|
34
|
-
};
|
|
35
|
-
const ReduxValueNode = ({ name, value, level, search, lastAction }) => {
|
|
36
|
-
const [expanded, setExpanded] = useState(level < 1);
|
|
37
|
-
const isObject = typeof value === 'object' && value !== null;
|
|
38
|
-
const isArray = Array.isArray(value);
|
|
39
|
-
const nameStr = String(name);
|
|
40
|
-
// Filter check if search query matches key or value
|
|
41
|
-
const matchesSearch = (k, val) => {
|
|
42
|
-
if (!search)
|
|
43
|
-
return true;
|
|
44
|
-
const s = search.toLowerCase();
|
|
45
|
-
if (k.toLowerCase().includes(s))
|
|
46
|
-
return true;
|
|
47
|
-
if (typeof val !== 'object' && String(val).toLowerCase().includes(s))
|
|
48
|
-
return true;
|
|
49
|
-
if (typeof val === 'object' && val !== null) {
|
|
50
|
-
return Object.keys(val).some(key => matchesSearch(key, val[key]));
|
|
74
|
+
if (activeSliceKey === null) {
|
|
75
|
+
setIsPersisted(false);
|
|
76
|
+
setPersistedData(null);
|
|
77
|
+
setSliceDetailTab('live');
|
|
78
|
+
return;
|
|
51
79
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
async function checkPersistence() {
|
|
81
|
+
try {
|
|
82
|
+
const storage = getCustomStorage();
|
|
83
|
+
if (!storage) {
|
|
84
|
+
setIsPersisted(false);
|
|
85
|
+
setPersistedData(null);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Check direct key: persist:sliceKey
|
|
89
|
+
let raw = await storage.getItem(`persist:${activeSliceKey}`);
|
|
90
|
+
if (raw) {
|
|
91
|
+
try {
|
|
92
|
+
setPersistedData(JSON.parse(raw));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
setPersistedData(raw);
|
|
96
|
+
}
|
|
97
|
+
setIsPersisted(true);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Check root key: persist:root
|
|
101
|
+
let rootRaw = await storage.getItem('persist:root');
|
|
102
|
+
if (rootRaw) {
|
|
103
|
+
const parsedRoot = JSON.parse(rootRaw);
|
|
104
|
+
if (parsedRoot && parsedRoot[activeSliceKey] !== undefined) {
|
|
105
|
+
const nested = parsedRoot[activeSliceKey];
|
|
106
|
+
try {
|
|
107
|
+
setPersistedData(typeof nested === 'string' ? JSON.parse(nested) : nested);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
setPersistedData(nested);
|
|
111
|
+
}
|
|
112
|
+
setIsPersisted(true);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
console.log('Error checking Redux persistence:', e);
|
|
119
|
+
}
|
|
120
|
+
setIsPersisted(false);
|
|
121
|
+
setPersistedData(null);
|
|
66
122
|
}
|
|
67
|
-
|
|
68
|
-
|
|
123
|
+
checkPersistence();
|
|
124
|
+
}, [activeSliceKey]);
|
|
125
|
+
const handleClearPersistence = () => {
|
|
126
|
+
Alert.alert('Clear Persisted State', `Are you sure you want to delete the persisted storage item for the slice "${activeSliceKey}"? This will clear the value from device storage, but the current active in-memory Redux state will remain unchanged until the next app reload.`, [
|
|
127
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
128
|
+
{
|
|
129
|
+
text: 'Delete',
|
|
130
|
+
style: 'destructive',
|
|
131
|
+
onPress: async () => {
|
|
132
|
+
try {
|
|
133
|
+
const storage = getCustomStorage();
|
|
134
|
+
if (!storage)
|
|
135
|
+
return;
|
|
136
|
+
// Delete direct key
|
|
137
|
+
if (typeof storage.removeItem === 'function') {
|
|
138
|
+
await storage.removeItem(`persist:${activeSliceKey}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
await storage.setItem(`persist:${activeSliceKey}`, '');
|
|
142
|
+
}
|
|
143
|
+
// Delete from root key
|
|
144
|
+
let rootRaw = await storage.getItem('persist:root');
|
|
145
|
+
if (rootRaw) {
|
|
146
|
+
const parsed = JSON.parse(rootRaw);
|
|
147
|
+
if (parsed && parsed[activeSliceKey] !== undefined) {
|
|
148
|
+
delete parsed[activeSliceKey];
|
|
149
|
+
await storage.setItem('persist:root', JSON.stringify(parsed));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
setIsPersisted(false);
|
|
153
|
+
setPersistedData(null);
|
|
154
|
+
setSliceDetailTab('live');
|
|
155
|
+
if (Platform.OS === 'android') {
|
|
156
|
+
ToastAndroid.show('Persisted state cleared', ToastAndroid.SHORT);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
Alert.alert('Cleared', 'Persisted state has been cleared from device storage.');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
Alert.alert('Error', 'Failed to clear persisted state.');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]);
|
|
168
|
+
};
|
|
169
|
+
const isSearching = search.trim().length > 0;
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
onDetailOpenChange?.(activeSliceKey !== null || activeActionId !== null);
|
|
172
|
+
return () => {
|
|
173
|
+
onDetailOpenChange?.(false);
|
|
174
|
+
};
|
|
175
|
+
}, [activeSliceKey, activeActionId, onDetailOpenChange]);
|
|
176
|
+
// Reset details view when tab changes
|
|
177
|
+
const switchTab = (tab) => {
|
|
178
|
+
animateLayout();
|
|
179
|
+
setActiveTab(tab);
|
|
180
|
+
setActiveSliceKey(null);
|
|
181
|
+
setActiveActionId(null);
|
|
182
|
+
setSliceForceOpen(undefined);
|
|
183
|
+
setActionForceOpen(undefined);
|
|
184
|
+
};
|
|
185
|
+
const handleGoBackSlice = () => {
|
|
186
|
+
animateLayout();
|
|
187
|
+
setActiveSliceKey(null);
|
|
188
|
+
setSliceForceOpen(undefined);
|
|
189
|
+
};
|
|
190
|
+
const handleGoBackAction = () => {
|
|
191
|
+
animateLayout();
|
|
192
|
+
setActiveActionId(null);
|
|
193
|
+
setActionForceOpen(undefined);
|
|
194
|
+
};
|
|
195
|
+
// Filtered state keys based on search query
|
|
196
|
+
const filteredStateKeys = useMemo(() => {
|
|
197
|
+
if (!state || typeof state !== 'object')
|
|
198
|
+
return [];
|
|
199
|
+
const keys = Object.keys(state);
|
|
200
|
+
let result = keys;
|
|
201
|
+
if (isSearching) {
|
|
202
|
+
result = keys.filter(key => {
|
|
203
|
+
const typeMatch = searchModeType && key.toLowerCase().includes(search.toLowerCase());
|
|
204
|
+
const dataMatch = searchModeData && valueContainsTerm(state[key], search);
|
|
205
|
+
return typeMatch || dataMatch;
|
|
206
|
+
});
|
|
69
207
|
}
|
|
70
|
-
|
|
71
|
-
|
|
208
|
+
return [...result].sort((a, b) => {
|
|
209
|
+
const comparison = a.localeCompare(b);
|
|
210
|
+
return stateSortAsc ? comparison : -comparison;
|
|
211
|
+
});
|
|
212
|
+
}, [state, search, isSearching, searchModeType, searchModeData, stateSortAsc]);
|
|
213
|
+
// Filtered action history based on search query
|
|
214
|
+
const filteredActionHistory = useMemo(() => {
|
|
215
|
+
if (!actionHistory)
|
|
216
|
+
return [];
|
|
217
|
+
let result = actionHistory;
|
|
218
|
+
if (isSearching) {
|
|
219
|
+
result = actionHistory.filter(action => {
|
|
220
|
+
const typeMatch = searchModeType && action.type.toLowerCase().includes(search.toLowerCase());
|
|
221
|
+
const payloadMatch = searchModeData && action.payload && valueContainsTerm(action.payload, search);
|
|
222
|
+
return typeMatch || payloadMatch;
|
|
223
|
+
});
|
|
72
224
|
}
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
225
|
+
return [...result].sort((a, b) => {
|
|
226
|
+
const comparison = a.id - b.id;
|
|
227
|
+
return actionSortAsc ? comparison : -comparison;
|
|
228
|
+
});
|
|
229
|
+
}, [actionHistory, search, isSearching, searchModeType, searchModeData, actionSortAsc]);
|
|
230
|
+
// Find currently selected action object
|
|
231
|
+
const selectedAction = useMemo(() => {
|
|
232
|
+
if (activeActionId === null)
|
|
233
|
+
return null;
|
|
234
|
+
return actionHistory.find(a => a.id === activeActionId) || null;
|
|
235
|
+
}, [activeActionId, actionHistory]);
|
|
236
|
+
if (!state || typeof state !== 'object') {
|
|
237
|
+
return (<View style={styles.errorContainer}>
|
|
238
|
+
<Text style={styles.errorText}>No connected Redux store state found.</Text>
|
|
239
|
+
</View>);
|
|
240
|
+
}
|
|
241
|
+
// ── Render Detail View: State Slice ──
|
|
242
|
+
if (activeTab === 'state' && activeSliceKey !== null) {
|
|
243
|
+
const sliceData = state[activeSliceKey];
|
|
244
|
+
const fieldsCount = typeof sliceData === 'object' && sliceData !== null
|
|
245
|
+
? Object.keys(sliceData).length
|
|
246
|
+
: 1;
|
|
247
|
+
return (<View style={{ flex: 1 }}>
|
|
248
|
+
{/* Non-scrollable header */}
|
|
249
|
+
<View style={{ paddingHorizontal: 6, paddingTop: 4 }}>
|
|
250
|
+
<View style={styles.apiLikeInfoBar}>
|
|
251
|
+
<View style={styles.apiBadgeRow}>
|
|
252
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: AppColors.purple }]}>
|
|
253
|
+
<Text style={styles.apiMethodBadgeText}>REDUX</Text>
|
|
254
|
+
</View>
|
|
255
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: AppColors.purpleShade50, borderWidth: 1, borderColor: AppColors.purple + '30' }]}>
|
|
256
|
+
<Text style={[styles.apiMethodBadgeText, { color: AppColors.purple }]}>SLICE</Text>
|
|
257
|
+
</View>
|
|
258
|
+
{isPersisted && (<View style={[styles.apiMethodBadge, { backgroundColor: '#10B981' }]}>
|
|
259
|
+
<Text style={styles.apiMethodBadgeText}>PERSISTED</Text>
|
|
260
|
+
</View>)}
|
|
261
|
+
<View style={styles.apiChip}>
|
|
262
|
+
<Text style={styles.apiChipText}>{getSize(sliceData)}</Text>
|
|
263
|
+
</View>
|
|
264
|
+
<View style={styles.apiChip}>
|
|
265
|
+
<Text style={styles.apiChipText}>{fieldsCount} {fieldsCount === 1 ? 'field' : 'fields'}</Text>
|
|
266
|
+
</View>
|
|
267
|
+
</View>
|
|
268
|
+
|
|
269
|
+
<Text style={styles.apiTitleText} selectable={true}>{activeSliceKey}</Text>
|
|
270
|
+
|
|
271
|
+
<View style={styles.apiMetaRow}>
|
|
272
|
+
<ClockIcon color={AppColors.grayTextWeak} size={10}/>
|
|
273
|
+
<Text style={styles.apiMetaText}>
|
|
274
|
+
Last updated: {lastActionMap[activeSliceKey] ? lastActionMap[activeSliceKey].timestamp : 'Initial'}
|
|
86
275
|
</Text>
|
|
87
|
-
</View>
|
|
276
|
+
</View>
|
|
277
|
+
</View>
|
|
278
|
+
</View>
|
|
279
|
+
|
|
280
|
+
{/* Segment Control */}
|
|
281
|
+
<View style={{
|
|
282
|
+
flexDirection: 'row',
|
|
283
|
+
backgroundColor: AppColors.grayBackground,
|
|
284
|
+
borderRadius: 10,
|
|
285
|
+
padding: 3,
|
|
286
|
+
marginHorizontal: 6,
|
|
287
|
+
marginBottom: 10,
|
|
288
|
+
marginTop: 6,
|
|
289
|
+
borderWidth: 1,
|
|
290
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
291
|
+
}}>
|
|
292
|
+
{(isPersisted ? ['live', 'persisted'] : ['live']).map(tab => {
|
|
293
|
+
const isActive = sliceDetailTab === tab;
|
|
294
|
+
const getLabel = () => {
|
|
295
|
+
if (tab === 'live')
|
|
296
|
+
return 'Live State';
|
|
297
|
+
return `Persisted (${getSize(persistedData)})`;
|
|
298
|
+
};
|
|
299
|
+
const getIcon = () => {
|
|
300
|
+
const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
|
|
301
|
+
if (tab === 'live')
|
|
302
|
+
return <LayersIcon color={iconColor} size={11}/>;
|
|
303
|
+
return <CalendarIcon color={iconColor} size={11}/>;
|
|
304
|
+
};
|
|
305
|
+
return (<Pressable key={tab} onPress={() => {
|
|
306
|
+
animateLayout();
|
|
307
|
+
setSliceDetailTab(tab);
|
|
308
|
+
}} style={{
|
|
309
|
+
flex: 1,
|
|
310
|
+
paddingVertical: 6,
|
|
311
|
+
flexDirection: 'row',
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
justifyContent: 'center',
|
|
314
|
+
borderRadius: 8,
|
|
315
|
+
backgroundColor: isActive ? AppColors.purple : 'transparent',
|
|
316
|
+
gap: 4,
|
|
317
|
+
}}>
|
|
318
|
+
{getIcon()}
|
|
319
|
+
<Text style={{
|
|
320
|
+
fontFamily: AppFonts.interBold,
|
|
321
|
+
fontSize: 10,
|
|
322
|
+
color: isActive ? '#FFFFFF' : AppColors.grayText,
|
|
323
|
+
}}>
|
|
324
|
+
{getLabel()}
|
|
325
|
+
</Text>
|
|
326
|
+
</Pressable>);
|
|
327
|
+
})}
|
|
328
|
+
</View>
|
|
329
|
+
|
|
330
|
+
{/* Scrollable Tab Content */}
|
|
331
|
+
<View style={{ flex: 1, paddingHorizontal: 6 }}>
|
|
332
|
+
{/* Back button + controls */}
|
|
333
|
+
<View style={styles.contentDetailSubHeader}>
|
|
334
|
+
<TouchableScale onPress={handleGoBackSlice} style={styles.backBtn} hitSlop={12}>
|
|
335
|
+
<BackChevronIcon color={AppColors.purple} size={11}/>
|
|
336
|
+
<Text style={styles.backBtnText}>State Slices</Text>
|
|
337
|
+
</TouchableScale>
|
|
338
|
+
|
|
339
|
+
<View style={styles.detailHeaderActions}>
|
|
340
|
+
{isPersisted && sliceDetailTab === 'persisted' && (<TouchableScale onPress={handleClearPersistence} style={[styles.detailHeaderBtn, { marginRight: 8 }]} hitSlop={8}>
|
|
341
|
+
<TrashIcon color={AppColors.errorColor} size={14}/>
|
|
342
|
+
</TouchableScale>)}
|
|
343
|
+
|
|
344
|
+
<TouchableScale onPress={() => {
|
|
345
|
+
animateLayout();
|
|
346
|
+
setSliceForceOpen(prev => (prev === true ? false : prev === false ? undefined : true));
|
|
347
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
348
|
+
<ExpandCollapseIcon expanded={sliceForceOpen !== false} color={AppColors.purple} size={14}/>
|
|
349
|
+
</TouchableScale>
|
|
350
|
+
|
|
351
|
+
<TouchableScale onPress={() => {
|
|
352
|
+
const dataToCopy = sliceDetailTab === 'live' ? sliceData : persistedData;
|
|
353
|
+
copyToClipboard(dataToCopy, `Redux Slice '${activeSliceKey}' (${sliceDetailTab})`);
|
|
354
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
355
|
+
<CopyIcon color={AppColors.purple} size={14}/>
|
|
356
|
+
</TouchableScale>
|
|
357
|
+
</View>
|
|
358
|
+
</View>
|
|
359
|
+
|
|
360
|
+
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 24 }} showsVerticalScrollIndicator={true}>
|
|
361
|
+
<View style={{ padding: 12 }}>
|
|
362
|
+
<JsonViewer data={sliceDetailTab === 'live' ? sliceData : (persistedData ?? 'No persisted data')} search={search} forceOpen={sliceForceOpen !== undefined ? sliceForceOpen : (isSearching ? true : undefined)} fullHeight={true}/>
|
|
363
|
+
</View>
|
|
364
|
+
</ScrollView>
|
|
88
365
|
</View>
|
|
89
366
|
</View>);
|
|
90
367
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
368
|
+
// ── Render Detail View: Timeline Action ──
|
|
369
|
+
if (activeTab === 'timeline' && selectedAction !== null) {
|
|
370
|
+
const affectedSlices = selectedAction.affectedSlices || [];
|
|
371
|
+
return (<View style={{ flex: 1 }}>
|
|
372
|
+
{/* Non-scrollable header */}
|
|
373
|
+
<View style={{ paddingHorizontal: 6, paddingTop: 4 }}>
|
|
374
|
+
<View style={styles.apiLikeInfoBar}>
|
|
375
|
+
<View style={styles.apiBadgeRow}>
|
|
376
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: AppColors.purple }]}>
|
|
377
|
+
<Text style={styles.apiMethodBadgeText}>REDUX</Text>
|
|
378
|
+
</View>
|
|
379
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: '#F59E0B' }]}>
|
|
380
|
+
<Text style={styles.apiMethodBadgeText}>ACTION</Text>
|
|
381
|
+
</View>
|
|
382
|
+
<View style={styles.apiChip}>
|
|
383
|
+
<Text style={styles.apiChipText}>#{selectedAction.id}</Text>
|
|
384
|
+
</View>
|
|
385
|
+
<View style={styles.apiChip}>
|
|
386
|
+
<Text style={styles.apiChipText}>{getSize(selectedAction.payload)}</Text>
|
|
387
|
+
</View>
|
|
388
|
+
</View>
|
|
389
|
+
|
|
390
|
+
<Text style={styles.apiTitleText} selectable={true}>{selectedAction.type}</Text>
|
|
391
|
+
|
|
392
|
+
<View style={styles.apiMetaRow}>
|
|
393
|
+
<ClockIcon color={AppColors.grayTextWeak} size={10}/>
|
|
394
|
+
<Text style={styles.apiMetaText}>Dispatched: {selectedAction.timestamp}</Text>
|
|
395
|
+
</View>
|
|
396
|
+
</View>
|
|
113
397
|
</View>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
398
|
+
|
|
399
|
+
{/* Affected Slices Tags list */}
|
|
400
|
+
{affectedSlices.length > 0 && (<View style={styles.detailAffectedTags}>
|
|
401
|
+
<Text style={styles.affectedTagsLabel}>AFFECTED SLICES:</Text>
|
|
402
|
+
<View style={styles.detailTagsList}>
|
|
403
|
+
{affectedSlices.map(slice => (<View key={slice} style={styles.sliceTag}>
|
|
404
|
+
<Text style={styles.sliceTagText}>{slice}</Text>
|
|
405
|
+
</View>))}
|
|
406
|
+
</View>
|
|
407
|
+
</View>)}
|
|
408
|
+
|
|
409
|
+
{/* Segment Control */}
|
|
410
|
+
<View style={{
|
|
411
|
+
flexDirection: 'row',
|
|
412
|
+
backgroundColor: AppColors.grayBackground,
|
|
413
|
+
borderRadius: 10,
|
|
414
|
+
padding: 3,
|
|
415
|
+
marginHorizontal: 6,
|
|
416
|
+
marginBottom: 10,
|
|
417
|
+
marginTop: 6,
|
|
418
|
+
borderWidth: 1,
|
|
419
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
134
420
|
}}>
|
|
135
|
-
|
|
136
|
-
|
|
421
|
+
{['payload', 'diff', 'state'].map(tab => {
|
|
422
|
+
const isActive = actionSubTab === tab;
|
|
423
|
+
const getLabel = () => {
|
|
424
|
+
if (tab === 'payload')
|
|
425
|
+
return 'Payload';
|
|
426
|
+
if (tab === 'diff')
|
|
427
|
+
return 'Diff Changes';
|
|
428
|
+
return 'Snapshot';
|
|
429
|
+
};
|
|
430
|
+
const getIcon = () => {
|
|
431
|
+
const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
|
|
432
|
+
if (tab === 'payload')
|
|
433
|
+
return <RequestIcon color={iconColor} size={11}/>;
|
|
434
|
+
if (tab === 'diff')
|
|
435
|
+
return <DiffIcon color={iconColor} size={11}/>;
|
|
436
|
+
return <LayersIcon color={iconColor} size={11}/>;
|
|
437
|
+
};
|
|
438
|
+
return (<Pressable key={tab} onPress={() => {
|
|
439
|
+
animateLayout();
|
|
440
|
+
setActionSubTab(tab);
|
|
441
|
+
}} style={{
|
|
442
|
+
flex: 1,
|
|
443
|
+
paddingVertical: 6,
|
|
444
|
+
flexDirection: 'row',
|
|
445
|
+
alignItems: 'center',
|
|
446
|
+
justifyContent: 'center',
|
|
447
|
+
borderRadius: 8,
|
|
448
|
+
backgroundColor: isActive ? AppColors.purple : 'transparent',
|
|
449
|
+
gap: 4,
|
|
450
|
+
}}>
|
|
451
|
+
{getIcon()}
|
|
452
|
+
<Text style={{
|
|
453
|
+
fontFamily: AppFonts.interBold,
|
|
454
|
+
fontSize: 10,
|
|
455
|
+
color: isActive ? '#FFFFFF' : AppColors.grayText,
|
|
456
|
+
}}>
|
|
457
|
+
{getLabel()}
|
|
458
|
+
</Text>
|
|
459
|
+
</Pressable>);
|
|
460
|
+
})}
|
|
461
|
+
</View>
|
|
462
|
+
|
|
463
|
+
{/* Scrollable Tab Content */}
|
|
464
|
+
<View style={{ flex: 1, paddingHorizontal: 6 }}>
|
|
465
|
+
{/* Back button + controls */}
|
|
466
|
+
<View style={styles.contentDetailSubHeader}>
|
|
467
|
+
<TouchableScale onPress={handleGoBackAction} style={styles.backBtn} hitSlop={12}>
|
|
468
|
+
<BackChevronIcon color={AppColors.purple} size={11}/>
|
|
469
|
+
<Text style={styles.backBtnText}>Actions</Text>
|
|
470
|
+
</TouchableScale>
|
|
471
|
+
|
|
472
|
+
{(actionSubTab === 'payload' || actionSubTab === 'state') && (<View style={styles.detailHeaderActions}>
|
|
473
|
+
<TouchableScale onPress={() => {
|
|
474
|
+
animateLayout();
|
|
475
|
+
setActionForceOpen(prev => (prev === true ? false : prev === false ? undefined : true));
|
|
476
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
477
|
+
<ExpandCollapseIcon expanded={actionForceOpen !== false} color={AppColors.purple} size={14}/>
|
|
478
|
+
</TouchableScale>
|
|
479
|
+
|
|
480
|
+
<TouchableScale onPress={() => {
|
|
481
|
+
const dataToCopy = actionSubTab === 'payload' ? selectedAction.payload : selectedAction.nextState;
|
|
482
|
+
copyToClipboard(dataToCopy, actionSubTab === 'payload' ? 'Action payload' : 'State snapshot');
|
|
483
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
484
|
+
<CopyIcon color={AppColors.purple} size={14}/>
|
|
485
|
+
</TouchableScale>
|
|
486
|
+
</View>)}
|
|
487
|
+
</View>
|
|
488
|
+
|
|
489
|
+
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 24 }} showsVerticalScrollIndicator={true}>
|
|
490
|
+
{actionSubTab === 'payload' && (<View style={{ padding: 12 }}>
|
|
491
|
+
<JsonViewer data={selectedAction.payload ?? 'No payload data'} wrap forceOpen={actionForceOpen !== undefined ? actionForceOpen : undefined} fullHeight={true}/>
|
|
492
|
+
</View>)}
|
|
493
|
+
|
|
494
|
+
{actionSubTab === 'diff' && (<View style={{ padding: 12 }}>
|
|
495
|
+
<DiffViewer oldData={selectedAction.prevState} newData={selectedAction.nextState}/>
|
|
496
|
+
</View>)}
|
|
497
|
+
|
|
498
|
+
{actionSubTab === 'state' && (<View style={{ padding: 12 }}>
|
|
499
|
+
<JsonViewer data={selectedAction.nextState} wrap forceOpen={actionForceOpen !== undefined ? actionForceOpen : undefined} fullHeight={true}/>
|
|
500
|
+
</View>)}
|
|
501
|
+
</ScrollView>
|
|
502
|
+
</View>
|
|
503
|
+
</View>);
|
|
137
504
|
}
|
|
138
|
-
|
|
139
|
-
const toggleReducer = (key) => {
|
|
140
|
-
animateTreeLayout();
|
|
141
|
-
setReducerExpanded(prev => ({
|
|
142
|
-
...prev,
|
|
143
|
-
[key]: !prev[key],
|
|
144
|
-
}));
|
|
145
|
-
};
|
|
505
|
+
// ── Render List Views (Default) ──
|
|
146
506
|
return (<View style={styles.container}>
|
|
147
|
-
{/*
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
507
|
+
{/* ── Control Bar ── */}
|
|
508
|
+
<View style={styles.toolbar}>
|
|
509
|
+
<View style={styles.statusSection}>
|
|
510
|
+
<View style={[styles.statusBadge, autoRefresh ? styles.statusBadgeActive : styles.statusBadgePaused]}>
|
|
511
|
+
<View style={[styles.statusDot, autoRefresh ? styles.statusDotActive : styles.statusDotPaused]}/>
|
|
512
|
+
<Text style={[styles.statusBadgeText, autoRefresh ? styles.statusTextActive : styles.statusTextPaused]}>
|
|
513
|
+
{autoRefresh ? 'LIVE' : 'PAUSED'}
|
|
514
|
+
</Text>
|
|
515
|
+
</View>
|
|
156
516
|
</View>
|
|
157
|
-
|
|
158
|
-
|
|
517
|
+
|
|
518
|
+
<View style={styles.toolbarActions}>
|
|
519
|
+
{/* Sorting Toggle Button */}
|
|
520
|
+
<TouchableScale onPress={() => {
|
|
521
|
+
animateLayout();
|
|
522
|
+
if (activeTab === 'state') {
|
|
523
|
+
setStateSortAsc(prev => !prev);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
setActionSortAsc(prev => !prev);
|
|
527
|
+
}
|
|
528
|
+
}} style={styles.iconBtn} hitSlop={8}>
|
|
529
|
+
<SortIcon ascending={activeTab === 'state' ? stateSortAsc : actionSortAsc} color={AppColors.purple} size={15}/>
|
|
530
|
+
</TouchableScale>
|
|
531
|
+
|
|
532
|
+
{onToggleAutoRefresh && (<TouchableScale onPress={onToggleAutoRefresh} style={styles.iconBtn} hitSlop={8}>
|
|
533
|
+
<HeaderPauseIcon isPaused={!autoRefresh} color={AppColors.purple} size={15}/>
|
|
534
|
+
</TouchableScale>)}
|
|
535
|
+
{onClearHistory && (<TouchableScale onPress={onClearHistory} style={[styles.iconBtn, { borderColor: AppColors.errorColor + '40', backgroundColor: AppColors.errorColor + '08' }]} hitSlop={8}>
|
|
536
|
+
<TrashIcon color={AppColors.errorColor} size={15}/>
|
|
537
|
+
</TouchableScale>)}
|
|
159
538
|
</View>
|
|
160
|
-
</
|
|
539
|
+
</View>
|
|
540
|
+
|
|
541
|
+
{/* ── Search Bar ── */}
|
|
542
|
+
{onSearchChange && (<View style={styles.searchContainer}>
|
|
543
|
+
<View style={styles.searchBar}>
|
|
544
|
+
<SearchIcon color={AppColors.grayTextWeak} size={14}/>
|
|
545
|
+
<TextInput placeholder="Search keys, values, or action payloads..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={onSearchChange} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
546
|
+
{search.length > 0 && (<TouchableScale onPress={() => onSearchChange('')} hitSlop={10}>
|
|
547
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
548
|
+
</TouchableScale>)}
|
|
549
|
+
</View>
|
|
550
|
+
<View style={styles.filterChipRow}>
|
|
551
|
+
<Text style={styles.filterChipLabel}>FILTER BY:</Text>
|
|
552
|
+
<Pressable onPress={() => {
|
|
553
|
+
animateLayout();
|
|
554
|
+
if (searchModeType && !searchModeData)
|
|
555
|
+
return; // Prevent disabling both
|
|
556
|
+
setSearchModeType(!searchModeType);
|
|
557
|
+
}} style={[styles.filterChip, searchModeType && styles.filterChipActive]}>
|
|
558
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
559
|
+
<SignalIcon color={searchModeType ? AppColors.purple : AppColors.grayText} size={11}/>
|
|
560
|
+
<Text style={[styles.filterChipText, searchModeType && styles.filterChipTextActive]}>
|
|
561
|
+
Type / Key
|
|
562
|
+
</Text>
|
|
563
|
+
</View>
|
|
564
|
+
</Pressable>
|
|
565
|
+
<Pressable onPress={() => {
|
|
566
|
+
animateLayout();
|
|
567
|
+
if (!searchModeType && searchModeData)
|
|
568
|
+
return; // Prevent disabling both
|
|
569
|
+
setSearchModeData(!searchModeData);
|
|
570
|
+
}} style={[styles.filterChip, searchModeData && styles.filterChipActive]}>
|
|
571
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
572
|
+
<LayersIcon color={searchModeData ? AppColors.purple : AppColors.grayText} size={11}/>
|
|
573
|
+
<Text style={[styles.filterChipText, searchModeData && styles.filterChipTextActive]}>
|
|
574
|
+
Payload Data
|
|
575
|
+
</Text>
|
|
576
|
+
</View>
|
|
577
|
+
</Pressable>
|
|
578
|
+
</View>
|
|
579
|
+
</View>)}
|
|
161
580
|
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
581
|
+
{/* ── Tab Selector ── */}
|
|
582
|
+
<View style={styles.tabContainer}>
|
|
583
|
+
<Pressable style={[styles.tabButton, activeTab === 'state' && styles.tabButtonActive]} onPress={() => switchTab('state')}>
|
|
584
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
585
|
+
<LayersIcon color={activeTab === 'state' ? AppColors.purple : AppColors.grayText} size={12}/>
|
|
586
|
+
<Text style={[styles.tabButtonText, activeTab === 'state' && styles.tabButtonTextActive]}>
|
|
587
|
+
State Slices ({filteredStateKeys.length})
|
|
588
|
+
</Text>
|
|
589
|
+
</View>
|
|
590
|
+
</Pressable>
|
|
591
|
+
<Pressable style={[styles.tabButton, activeTab === 'timeline' && styles.tabButtonActive]} onPress={() => switchTab('timeline')}>
|
|
592
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
593
|
+
<TerminalIcon color={activeTab === 'timeline' ? AppColors.purple : AppColors.grayText} size={12}/>
|
|
594
|
+
<Text style={[styles.tabButtonText, activeTab === 'timeline' && styles.tabButtonTextActive]}>
|
|
595
|
+
Actions ({filteredActionHistory.length})
|
|
596
|
+
</Text>
|
|
597
|
+
</View>
|
|
598
|
+
</Pressable>
|
|
599
|
+
</View>
|
|
600
|
+
|
|
601
|
+
{/* ── State List View ── */}
|
|
602
|
+
{activeTab === 'state' && (<ScrollView style={styles.scrollList} contentContainerStyle={styles.scrollContent}>
|
|
603
|
+
{filteredStateKeys.length === 0 ? (<View style={styles.emptyState}>
|
|
604
|
+
<Text style={styles.emptyStateText}>
|
|
605
|
+
{isSearching ? 'No store slices match your search.' : 'Redux store contains no state.'}
|
|
606
|
+
</Text>
|
|
607
|
+
</View>) : (filteredStateKeys.map(reducerKey => {
|
|
166
608
|
const sliceData = state[reducerKey];
|
|
609
|
+
const fieldsCount = typeof sliceData === 'object' && sliceData !== null
|
|
610
|
+
? Object.keys(sliceData).length
|
|
611
|
+
: 1;
|
|
167
612
|
const lastAction = lastActionMap[reducerKey];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{/* Reducer Header */}
|
|
176
|
-
<Pressable onPress={() => toggleReducer(reducerKey)} style={styles.reducerHeader}>
|
|
177
|
-
<View style={styles.reducerHorizontalLine}/>
|
|
178
|
-
<AnimatedChevron color={AppColors.purple} expanded={isExpanded} size={10} style={styles.chevronWrap}/>
|
|
179
|
-
<View style={styles.iconWrap}>
|
|
613
|
+
const lastUpdatedTime = lastAction ? lastAction.timestamp : 'Initial';
|
|
614
|
+
return (<TouchableScale key={reducerKey} onPress={() => {
|
|
615
|
+
animateLayout();
|
|
616
|
+
setActiveSliceKey(reducerKey);
|
|
617
|
+
}} style={styles.listRow}>
|
|
618
|
+
<View style={styles.sliceIcon}>
|
|
180
619
|
<FolderIcon color={AppColors.purple} size={11}/>
|
|
181
620
|
</View>
|
|
182
|
-
<View style={
|
|
183
|
-
<Text style={styles.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
</Text>
|
|
188
|
-
</View>)}
|
|
621
|
+
<View style={styles.rowMain}>
|
|
622
|
+
<Text style={styles.rowTitle}>{reducerKey}</Text>
|
|
623
|
+
<Text style={styles.rowSub}>
|
|
624
|
+
{getSize(sliceData)} • {fieldsCount} {fieldsCount === 1 ? 'field' : 'fields'} • Updated: {lastUpdatedTime}
|
|
625
|
+
</Text>
|
|
189
626
|
</View>
|
|
190
|
-
|
|
627
|
+
<ForwardChevronIcon color={AppColors.grayTextWeak} size={10}/>
|
|
628
|
+
</TouchableScale>);
|
|
629
|
+
}))}
|
|
630
|
+
</ScrollView>)}
|
|
191
631
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
632
|
+
{/* ── Timeline List View ── */}
|
|
633
|
+
{activeTab === 'timeline' && (<ScrollView style={styles.scrollList} contentContainerStyle={styles.scrollContent}>
|
|
634
|
+
{filteredActionHistory.length === 0 ? (<View style={styles.emptyState}>
|
|
635
|
+
<Text style={styles.emptyStateText}>
|
|
636
|
+
{isSearching
|
|
637
|
+
? 'No actions match your search.'
|
|
638
|
+
: 'No actions dispatched yet. Trigger an action in the app to populate history.'}
|
|
639
|
+
</Text>
|
|
640
|
+
</View>) : (filteredActionHistory.map((action, index) => {
|
|
641
|
+
const id = action.id;
|
|
642
|
+
const affectedSlices = action.affectedSlices || [];
|
|
643
|
+
return (<TouchableScale key={id} onPress={() => {
|
|
644
|
+
animateLayout();
|
|
645
|
+
setActiveActionId(id);
|
|
646
|
+
}} style={styles.listRow}>
|
|
647
|
+
<View style={styles.seqBadge}>
|
|
648
|
+
<Text style={styles.seqText}>#{action.id}</Text>
|
|
649
|
+
</View>
|
|
650
|
+
<View style={styles.rowMain}>
|
|
651
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
652
|
+
<TerminalIcon color={AppColors.purple} size={11}/>
|
|
653
|
+
<Text style={styles.actionTitleText} numberOfLines={1}>{action.type}</Text>
|
|
654
|
+
</View>
|
|
655
|
+
<View style={styles.actionMetaRow}>
|
|
656
|
+
<ClockIcon color={AppColors.grayTextWeak} size={9}/>
|
|
657
|
+
<Text style={styles.actionTimeText}>{action.timestamp} • {getSize(action.payload)}</Text>
|
|
658
|
+
</View>
|
|
659
|
+
{affectedSlices.length > 0 && (<View style={styles.tagsContainer}>
|
|
660
|
+
{affectedSlices.map(slice => (<View key={slice} style={styles.sliceTagMini}>
|
|
661
|
+
<Text style={styles.sliceTagMiniText}>{slice}</Text>
|
|
662
|
+
</View>))}
|
|
663
|
+
</View>)}
|
|
664
|
+
</View>
|
|
665
|
+
<ForwardChevronIcon color={AppColors.grayTextWeak} size={10}/>
|
|
666
|
+
</TouchableScale>);
|
|
667
|
+
}))}
|
|
668
|
+
</ScrollView>)}
|
|
200
669
|
</View>);
|
|
201
670
|
};
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
671
|
+
const styles = StyleSheet.create({
|
|
672
|
+
container: {
|
|
673
|
+
flex: 1,
|
|
674
|
+
backgroundColor: AppColors.grayBackground,
|
|
675
|
+
},
|
|
676
|
+
scrollList: {
|
|
677
|
+
flex: 1,
|
|
678
|
+
},
|
|
679
|
+
errorContainer: {
|
|
680
|
+
padding: 24,
|
|
681
|
+
alignItems: 'center',
|
|
682
|
+
},
|
|
683
|
+
errorText: {
|
|
684
|
+
fontFamily: AppFonts.interRegular,
|
|
685
|
+
fontSize: 12,
|
|
686
|
+
color: AppColors.grayTextWeak,
|
|
687
|
+
},
|
|
688
|
+
toolbar: {
|
|
689
|
+
flexDirection: 'row',
|
|
690
|
+
alignItems: 'center',
|
|
691
|
+
justifyContent: 'space-between',
|
|
692
|
+
paddingHorizontal: 12,
|
|
693
|
+
paddingVertical: 10,
|
|
694
|
+
backgroundColor: AppColors.primaryLight,
|
|
695
|
+
borderBottomWidth: 1,
|
|
696
|
+
borderBottomColor: AppColors.dividerColor,
|
|
205
697
|
},
|
|
206
|
-
|
|
698
|
+
statusSection: {
|
|
207
699
|
flexDirection: 'row',
|
|
208
700
|
alignItems: 'center',
|
|
701
|
+
},
|
|
702
|
+
statusBadge: {
|
|
703
|
+
flexDirection: 'row',
|
|
704
|
+
alignItems: 'center',
|
|
705
|
+
paddingHorizontal: 8,
|
|
209
706
|
paddingVertical: 3,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
707
|
+
borderRadius: 12,
|
|
708
|
+
borderWidth: 1,
|
|
709
|
+
},
|
|
710
|
+
statusBadgeActive: {
|
|
711
|
+
backgroundColor: AppColors.greenStatus,
|
|
712
|
+
borderColor: AppColors.greenColor + '30',
|
|
713
|
+
},
|
|
714
|
+
statusBadgePaused: {
|
|
715
|
+
backgroundColor: AppColors.grayBackground,
|
|
716
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
717
|
+
},
|
|
718
|
+
statusDot: {
|
|
719
|
+
width: 6,
|
|
720
|
+
height: 6,
|
|
721
|
+
borderRadius: 3,
|
|
722
|
+
marginRight: 6,
|
|
723
|
+
},
|
|
724
|
+
statusDotActive: {
|
|
725
|
+
backgroundColor: AppColors.greenBaggageText,
|
|
726
|
+
},
|
|
727
|
+
statusDotPaused: {
|
|
728
|
+
backgroundColor: AppColors.grayTextWeak,
|
|
729
|
+
},
|
|
730
|
+
statusBadgeText: {
|
|
731
|
+
fontFamily: AppFonts.interBold,
|
|
732
|
+
fontSize: 9,
|
|
733
|
+
letterSpacing: 0.5,
|
|
734
|
+
},
|
|
735
|
+
statusTextActive: {
|
|
736
|
+
color: AppColors.greenBaggageText,
|
|
737
|
+
},
|
|
738
|
+
statusTextPaused: {
|
|
739
|
+
color: AppColors.grayText,
|
|
740
|
+
},
|
|
741
|
+
toolbarActions: {
|
|
742
|
+
flexDirection: 'row',
|
|
743
|
+
alignItems: 'center',
|
|
744
|
+
gap: 8,
|
|
745
|
+
},
|
|
746
|
+
iconBtn: {
|
|
747
|
+
width: 28,
|
|
748
|
+
height: 28,
|
|
749
|
+
borderRadius: 6,
|
|
750
|
+
borderWidth: 1,
|
|
751
|
+
borderColor: AppColors.purple + '40',
|
|
752
|
+
backgroundColor: AppColors.purpleShade50,
|
|
753
|
+
alignItems: 'center',
|
|
754
|
+
justifyContent: 'center',
|
|
755
|
+
},
|
|
756
|
+
searchContainer: {
|
|
757
|
+
backgroundColor: AppColors.primaryLight,
|
|
219
758
|
borderBottomWidth: 1,
|
|
220
759
|
borderBottomColor: AppColors.dividerColor,
|
|
221
|
-
|
|
760
|
+
paddingBottom: 10,
|
|
761
|
+
},
|
|
762
|
+
searchBar: {
|
|
763
|
+
flexDirection: 'row',
|
|
764
|
+
alignItems: 'center',
|
|
765
|
+
backgroundColor: AppColors.grayBackground,
|
|
766
|
+
borderRadius: 8,
|
|
767
|
+
marginHorizontal: 12,
|
|
768
|
+
marginTop: 10,
|
|
769
|
+
marginBottom: 8,
|
|
770
|
+
paddingHorizontal: 10,
|
|
771
|
+
borderWidth: 1,
|
|
772
|
+
borderColor: AppColors.dividerColor,
|
|
773
|
+
height: 36,
|
|
222
774
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
775
|
+
filterChipRow: {
|
|
776
|
+
flexDirection: 'row',
|
|
777
|
+
alignItems: 'center',
|
|
778
|
+
paddingHorizontal: 12,
|
|
779
|
+
gap: 8,
|
|
780
|
+
},
|
|
781
|
+
filterChipLabel: {
|
|
782
|
+
fontFamily: AppFonts.interBold,
|
|
783
|
+
fontSize: 9,
|
|
784
|
+
color: AppColors.grayTextWeak,
|
|
785
|
+
letterSpacing: 0.5,
|
|
786
|
+
marginRight: 2,
|
|
787
|
+
},
|
|
788
|
+
filterChip: {
|
|
789
|
+
paddingHorizontal: 10,
|
|
790
|
+
paddingVertical: 4,
|
|
791
|
+
borderRadius: 12,
|
|
792
|
+
backgroundColor: AppColors.grayBackground,
|
|
793
|
+
borderWidth: 1,
|
|
794
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
795
|
+
},
|
|
796
|
+
filterChipActive: {
|
|
797
|
+
backgroundColor: AppColors.purpleShade50,
|
|
798
|
+
borderColor: AppColors.purple,
|
|
799
|
+
},
|
|
800
|
+
filterChipText: {
|
|
801
|
+
fontFamily: AppFonts.interMedium,
|
|
802
|
+
fontSize: 10,
|
|
803
|
+
color: AppColors.grayText,
|
|
804
|
+
},
|
|
805
|
+
filterChipTextActive: {
|
|
806
|
+
color: AppColors.purple,
|
|
807
|
+
fontFamily: AppFonts.interBold,
|
|
808
|
+
},
|
|
809
|
+
searchInput: {
|
|
810
|
+
flex: 1,
|
|
811
|
+
fontFamily: AppFonts.interRegular,
|
|
812
|
+
fontSize: 12,
|
|
813
|
+
color: AppColors.grayTextStrong,
|
|
227
814
|
marginLeft: 6,
|
|
815
|
+
padding: 0,
|
|
816
|
+
},
|
|
817
|
+
tabContainer: {
|
|
818
|
+
flexDirection: 'row',
|
|
819
|
+
backgroundColor: AppColors.dividerColor,
|
|
820
|
+
borderRadius: 8,
|
|
821
|
+
padding: 3,
|
|
822
|
+
marginHorizontal: 12,
|
|
823
|
+
marginTop: 10,
|
|
824
|
+
marginBottom: 10,
|
|
228
825
|
},
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
826
|
+
tabButton: {
|
|
827
|
+
flex: 1,
|
|
828
|
+
paddingVertical: 7,
|
|
232
829
|
alignItems: 'center',
|
|
233
830
|
justifyContent: 'center',
|
|
831
|
+
borderRadius: 6,
|
|
234
832
|
},
|
|
235
|
-
|
|
833
|
+
tabButtonActive: {
|
|
834
|
+
backgroundColor: AppColors.primaryLight,
|
|
835
|
+
shadowColor: AppColors.shadowColorString,
|
|
836
|
+
shadowOffset: { width: 0, height: 1 },
|
|
837
|
+
shadowOpacity: 0.08,
|
|
838
|
+
shadowRadius: 2,
|
|
839
|
+
elevation: 2,
|
|
840
|
+
},
|
|
841
|
+
tabButtonText: {
|
|
236
842
|
fontFamily: AppFonts.interMedium,
|
|
237
|
-
fontSize:
|
|
238
|
-
color: AppColors.
|
|
843
|
+
fontSize: 12,
|
|
844
|
+
color: AppColors.grayText,
|
|
239
845
|
},
|
|
240
|
-
|
|
241
|
-
color: AppColors.
|
|
846
|
+
tabButtonTextActive: {
|
|
847
|
+
color: AppColors.purple,
|
|
848
|
+
fontFamily: AppFonts.interBold,
|
|
242
849
|
},
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
850
|
+
scrollContent: {
|
|
851
|
+
paddingBottom: 24,
|
|
852
|
+
paddingHorizontal: 12,
|
|
853
|
+
},
|
|
854
|
+
emptyState: {
|
|
855
|
+
padding: 24,
|
|
856
|
+
alignItems: 'center',
|
|
246
857
|
},
|
|
247
|
-
|
|
858
|
+
emptyStateText: {
|
|
248
859
|
fontFamily: AppFonts.interRegular,
|
|
249
|
-
fontSize:
|
|
860
|
+
fontSize: 12,
|
|
250
861
|
color: AppColors.grayTextWeak,
|
|
862
|
+
textAlign: 'center',
|
|
251
863
|
},
|
|
252
|
-
|
|
253
|
-
const styles = StyleSheet.create({
|
|
254
|
-
container: {
|
|
255
|
-
flex: 1,
|
|
256
|
-
},
|
|
257
|
-
storeHeader: {
|
|
864
|
+
listRow: {
|
|
258
865
|
flexDirection: 'row',
|
|
259
866
|
alignItems: 'center',
|
|
260
|
-
backgroundColor: AppColors.
|
|
261
|
-
borderRadius: 8,
|
|
262
|
-
paddingVertical: 10,
|
|
867
|
+
backgroundColor: AppColors.primaryLight,
|
|
263
868
|
paddingHorizontal: 12,
|
|
264
|
-
|
|
869
|
+
paddingVertical: 14,
|
|
870
|
+
borderRadius: 10,
|
|
871
|
+
borderWidth: 1,
|
|
872
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
873
|
+
marginBottom: 8,
|
|
874
|
+
shadowColor: AppColors.shadowColorString,
|
|
875
|
+
shadowOffset: { width: 0, height: 1 },
|
|
876
|
+
shadowOpacity: 0.03,
|
|
877
|
+
shadowRadius: 2,
|
|
878
|
+
elevation: 1,
|
|
265
879
|
},
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
alignItems: 'center',
|
|
270
|
-
justifyContent: 'center',
|
|
880
|
+
rowMain: {
|
|
881
|
+
flex: 1,
|
|
882
|
+
marginLeft: 10,
|
|
271
883
|
},
|
|
272
|
-
|
|
884
|
+
rowTitle: {
|
|
273
885
|
fontFamily: AppFonts.interBold,
|
|
274
|
-
fontSize:
|
|
275
|
-
color:
|
|
886
|
+
fontSize: 13.5,
|
|
887
|
+
color: AppColors.primaryBlack,
|
|
276
888
|
},
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
889
|
+
rowSub: {
|
|
890
|
+
fontFamily: AppFonts.interRegular,
|
|
891
|
+
fontSize: 10,
|
|
892
|
+
color: AppColors.grayTextWeak,
|
|
893
|
+
marginTop: 2,
|
|
894
|
+
},
|
|
895
|
+
sliceIcon: {
|
|
896
|
+
width: 22,
|
|
897
|
+
height: 22,
|
|
898
|
+
borderRadius: 6,
|
|
899
|
+
backgroundColor: AppColors.purpleShade50,
|
|
900
|
+
alignItems: 'center',
|
|
901
|
+
justifyContent: 'center',
|
|
902
|
+
},
|
|
903
|
+
seqBadge: {
|
|
904
|
+
backgroundColor: AppColors.grayBackground,
|
|
905
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
906
|
+
borderWidth: 1,
|
|
907
|
+
borderRadius: 4,
|
|
908
|
+
paddingHorizontal: 5,
|
|
280
909
|
paddingVertical: 2,
|
|
281
|
-
|
|
282
|
-
|
|
910
|
+
alignItems: 'center',
|
|
911
|
+
justifyContent: 'center',
|
|
912
|
+
minWidth: 28,
|
|
283
913
|
},
|
|
284
|
-
|
|
914
|
+
seqText: {
|
|
285
915
|
fontFamily: AppFonts.interBold,
|
|
286
|
-
fontSize:
|
|
287
|
-
color:
|
|
916
|
+
fontSize: 9.5,
|
|
917
|
+
color: AppColors.grayText,
|
|
918
|
+
},
|
|
919
|
+
actionTitleText: {
|
|
920
|
+
fontFamily: AppFonts.interBold,
|
|
921
|
+
fontSize: 13,
|
|
922
|
+
color: AppColors.purple,
|
|
923
|
+
},
|
|
924
|
+
actionMetaRow: {
|
|
925
|
+
flexDirection: 'row',
|
|
926
|
+
alignItems: 'center',
|
|
927
|
+
gap: 4,
|
|
928
|
+
marginTop: 2,
|
|
929
|
+
},
|
|
930
|
+
actionTimeText: {
|
|
931
|
+
fontFamily: AppFonts.interRegular,
|
|
932
|
+
fontSize: 9.5,
|
|
933
|
+
color: AppColors.grayTextWeak,
|
|
288
934
|
},
|
|
289
|
-
|
|
290
|
-
|
|
935
|
+
tagsContainer: {
|
|
936
|
+
flexDirection: 'row',
|
|
937
|
+
flexWrap: 'wrap',
|
|
938
|
+
gap: 4,
|
|
291
939
|
marginTop: 4,
|
|
292
940
|
},
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
941
|
+
sliceTagMini: {
|
|
942
|
+
backgroundColor: AppColors.purpleShade50,
|
|
943
|
+
borderColor: AppColors.purple + '20',
|
|
944
|
+
borderWidth: 1,
|
|
945
|
+
borderRadius: 3,
|
|
946
|
+
paddingHorizontal: 4,
|
|
947
|
+
paddingVertical: 0.5,
|
|
297
948
|
},
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
bottom: 0,
|
|
303
|
-
width: 1,
|
|
304
|
-
backgroundColor: AppColors.dividerColor,
|
|
949
|
+
sliceTagMiniText: {
|
|
950
|
+
fontFamily: AppFonts.interMedium,
|
|
951
|
+
fontSize: 8.5,
|
|
952
|
+
color: AppColors.purple,
|
|
305
953
|
},
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
backgroundColor: AppColors.
|
|
954
|
+
// ── Detail Views Styles ──
|
|
955
|
+
detailHeaderBar: {
|
|
956
|
+
flexDirection: 'row',
|
|
957
|
+
alignItems: 'center',
|
|
958
|
+
paddingHorizontal: 12,
|
|
959
|
+
paddingVertical: 12,
|
|
960
|
+
backgroundColor: AppColors.primaryLight,
|
|
961
|
+
borderBottomWidth: 1,
|
|
962
|
+
borderBottomColor: AppColors.dividerColor,
|
|
313
963
|
},
|
|
314
|
-
|
|
964
|
+
backBtn: {
|
|
315
965
|
flexDirection: 'row',
|
|
316
966
|
alignItems: 'center',
|
|
317
967
|
paddingVertical: 6,
|
|
968
|
+
paddingRight: 8,
|
|
969
|
+
},
|
|
970
|
+
backBtnText: {
|
|
971
|
+
fontFamily: AppFonts.interBold,
|
|
972
|
+
fontSize: 12.5,
|
|
973
|
+
color: AppColors.purple,
|
|
974
|
+
},
|
|
975
|
+
detailHeaderInfo: {
|
|
976
|
+
flex: 1,
|
|
977
|
+
marginLeft: 10,
|
|
978
|
+
marginRight: 10,
|
|
979
|
+
},
|
|
980
|
+
detailTitle: {
|
|
981
|
+
fontFamily: AppFonts.interBold,
|
|
982
|
+
fontSize: 13.5,
|
|
983
|
+
color: AppColors.primaryBlack,
|
|
984
|
+
},
|
|
985
|
+
detailSub: {
|
|
986
|
+
fontFamily: AppFonts.interRegular,
|
|
987
|
+
fontSize: 10,
|
|
988
|
+
color: AppColors.grayTextWeak,
|
|
989
|
+
marginTop: 2,
|
|
990
|
+
},
|
|
991
|
+
detailHeaderActions: {
|
|
992
|
+
flexDirection: 'row',
|
|
993
|
+
alignItems: 'center',
|
|
318
994
|
gap: 6,
|
|
319
995
|
},
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
996
|
+
detailHeaderBtn: {
|
|
997
|
+
padding: 6,
|
|
998
|
+
borderRadius: 6,
|
|
999
|
+
borderWidth: 1,
|
|
1000
|
+
borderColor: AppColors.purple + '20',
|
|
324
1001
|
backgroundColor: AppColors.purpleShade50,
|
|
325
1002
|
alignItems: 'center',
|
|
326
1003
|
justifyContent: 'center',
|
|
1004
|
+
width: 28,
|
|
1005
|
+
height: 28,
|
|
1006
|
+
},
|
|
1007
|
+
detailCopyBtn: {
|
|
1008
|
+
padding: 6,
|
|
1009
|
+
borderRadius: 6,
|
|
1010
|
+
borderWidth: 1,
|
|
1011
|
+
borderColor: AppColors.purple + '20',
|
|
1012
|
+
backgroundColor: AppColors.purpleShade50,
|
|
1013
|
+
},
|
|
1014
|
+
detailTimeRow: {
|
|
1015
|
+
flexDirection: 'row',
|
|
1016
|
+
alignItems: 'center',
|
|
1017
|
+
gap: 4,
|
|
1018
|
+
marginTop: 1,
|
|
1019
|
+
},
|
|
1020
|
+
detailTimeText: {
|
|
1021
|
+
fontFamily: AppFonts.interRegular,
|
|
1022
|
+
fontSize: 10,
|
|
1023
|
+
color: AppColors.grayTextWeak,
|
|
327
1024
|
},
|
|
328
|
-
|
|
1025
|
+
detailAffectedTags: {
|
|
1026
|
+
paddingHorizontal: 12,
|
|
1027
|
+
paddingVertical: 8,
|
|
1028
|
+
backgroundColor: AppColors.grayBackground,
|
|
1029
|
+
borderBottomWidth: 1,
|
|
1030
|
+
borderBottomColor: AppColors.dividerColor,
|
|
1031
|
+
},
|
|
1032
|
+
affectedTagsLabel: {
|
|
329
1033
|
fontFamily: AppFonts.interBold,
|
|
330
|
-
fontSize:
|
|
331
|
-
color: AppColors.
|
|
1034
|
+
fontSize: 9,
|
|
1035
|
+
color: AppColors.grayTextWeak,
|
|
1036
|
+
letterSpacing: 0.5,
|
|
1037
|
+
marginBottom: 4,
|
|
332
1038
|
},
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
gap: 10,
|
|
338
|
-
},
|
|
339
|
-
childrenVerticalLine: {
|
|
340
|
-
position: 'absolute',
|
|
341
|
-
left: 8,
|
|
342
|
-
top: 0,
|
|
343
|
-
bottom: 16,
|
|
344
|
-
width: 1,
|
|
345
|
-
backgroundColor: AppColors.dividerColor,
|
|
1039
|
+
detailTagsList: {
|
|
1040
|
+
flexDirection: 'row',
|
|
1041
|
+
flexWrap: 'wrap',
|
|
1042
|
+
gap: 4,
|
|
346
1043
|
},
|
|
347
|
-
|
|
348
|
-
backgroundColor:
|
|
349
|
-
borderColor: '
|
|
1044
|
+
sliceTag: {
|
|
1045
|
+
backgroundColor: AppColors.purpleShade50,
|
|
1046
|
+
borderColor: AppColors.purple + '20',
|
|
350
1047
|
borderWidth: 1,
|
|
351
|
-
borderRadius:
|
|
1048
|
+
borderRadius: 4,
|
|
352
1049
|
paddingHorizontal: 6,
|
|
353
|
-
paddingVertical:
|
|
354
|
-
|
|
1050
|
+
paddingVertical: 1.5,
|
|
1051
|
+
},
|
|
1052
|
+
sliceTagText: {
|
|
1053
|
+
fontFamily: AppFonts.interMedium,
|
|
1054
|
+
fontSize: 9.5,
|
|
1055
|
+
color: AppColors.purple,
|
|
1056
|
+
},
|
|
1057
|
+
subTabRow: {
|
|
1058
|
+
flexDirection: 'row',
|
|
1059
|
+
padding: 8,
|
|
1060
|
+
gap: 8,
|
|
1061
|
+
backgroundColor: AppColors.primaryLight,
|
|
1062
|
+
borderBottomWidth: 1,
|
|
1063
|
+
borderBottomColor: AppColors.dividerColor,
|
|
1064
|
+
},
|
|
1065
|
+
subTabPill: {
|
|
1066
|
+
paddingHorizontal: 12,
|
|
1067
|
+
paddingVertical: 5,
|
|
1068
|
+
borderRadius: 12,
|
|
1069
|
+
backgroundColor: AppColors.grayBackground,
|
|
1070
|
+
borderWidth: 1,
|
|
1071
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
1072
|
+
},
|
|
1073
|
+
subTabPillActive: {
|
|
1074
|
+
backgroundColor: AppColors.purple,
|
|
1075
|
+
borderColor: AppColors.purple,
|
|
1076
|
+
},
|
|
1077
|
+
subTabPillText: {
|
|
1078
|
+
fontFamily: AppFonts.interMedium,
|
|
1079
|
+
fontSize: 10.5,
|
|
1080
|
+
color: AppColors.grayText,
|
|
1081
|
+
},
|
|
1082
|
+
subTabPillTextActive: {
|
|
1083
|
+
color: '#FFFFFF',
|
|
1084
|
+
fontFamily: AppFonts.interBold,
|
|
355
1085
|
},
|
|
356
|
-
|
|
1086
|
+
detailScrollContent: {
|
|
1087
|
+
paddingBottom: 24,
|
|
1088
|
+
},
|
|
1089
|
+
jsonViewerContainer: {
|
|
1090
|
+
paddingHorizontal: 12,
|
|
1091
|
+
paddingVertical: 8,
|
|
1092
|
+
},
|
|
1093
|
+
detailBox: {
|
|
1094
|
+
padding: 10,
|
|
1095
|
+
},
|
|
1096
|
+
detailBoxHeader: {
|
|
1097
|
+
flexDirection: 'row',
|
|
1098
|
+
alignItems: 'center',
|
|
1099
|
+
justifyContent: 'space-between',
|
|
1100
|
+
marginBottom: 8,
|
|
1101
|
+
paddingHorizontal: 4,
|
|
1102
|
+
},
|
|
1103
|
+
detailBoxTitle: {
|
|
357
1104
|
fontFamily: AppFonts.interBold,
|
|
358
1105
|
fontSize: 10,
|
|
359
|
-
color:
|
|
1106
|
+
color: AppColors.grayTextWeak,
|
|
1107
|
+
letterSpacing: 0.5,
|
|
1108
|
+
textTransform: 'uppercase',
|
|
1109
|
+
},
|
|
1110
|
+
apiLikeInfoBar: {
|
|
1111
|
+
paddingHorizontal: 16,
|
|
1112
|
+
paddingVertical: 14,
|
|
1113
|
+
backgroundColor: AppColors.primaryLight,
|
|
1114
|
+
borderBottomWidth: 1,
|
|
1115
|
+
borderBottomColor: AppColors.dividerColor,
|
|
1116
|
+
},
|
|
1117
|
+
apiBadgeRow: {
|
|
1118
|
+
flexDirection: 'row',
|
|
1119
|
+
alignItems: 'center',
|
|
1120
|
+
gap: 6,
|
|
1121
|
+
marginBottom: 8,
|
|
1122
|
+
},
|
|
1123
|
+
apiMethodBadge: {
|
|
1124
|
+
paddingHorizontal: 8,
|
|
1125
|
+
paddingVertical: 2,
|
|
1126
|
+
borderRadius: 4,
|
|
1127
|
+
alignItems: 'center',
|
|
1128
|
+
justifyContent: 'center',
|
|
1129
|
+
},
|
|
1130
|
+
apiMethodBadgeText: {
|
|
1131
|
+
fontFamily: AppFonts.interBold,
|
|
1132
|
+
fontSize: 9.5,
|
|
1133
|
+
color: '#FFFFFF',
|
|
1134
|
+
letterSpacing: 0.5,
|
|
1135
|
+
},
|
|
1136
|
+
apiChip: {
|
|
1137
|
+
paddingHorizontal: 7,
|
|
1138
|
+
paddingVertical: 2,
|
|
1139
|
+
borderRadius: 4,
|
|
1140
|
+
backgroundColor: 'rgba(104,75,155,0.06)',
|
|
1141
|
+
borderWidth: 1,
|
|
1142
|
+
borderColor: 'rgba(104,75,155,0.15)',
|
|
1143
|
+
alignItems: 'center',
|
|
1144
|
+
justifyContent: 'center',
|
|
1145
|
+
},
|
|
1146
|
+
apiChipText: {
|
|
1147
|
+
fontFamily: AppFonts.interBold,
|
|
1148
|
+
fontSize: 9.5,
|
|
1149
|
+
color: AppColors.purple,
|
|
1150
|
+
},
|
|
1151
|
+
apiTitleText: {
|
|
1152
|
+
fontFamily: AppFonts.interBold,
|
|
1153
|
+
fontSize: 16,
|
|
1154
|
+
color: AppColors.primaryBlack,
|
|
1155
|
+
marginBottom: 8,
|
|
1156
|
+
},
|
|
1157
|
+
apiMetaRow: {
|
|
1158
|
+
flexDirection: 'row',
|
|
1159
|
+
alignItems: 'center',
|
|
1160
|
+
gap: 4,
|
|
1161
|
+
},
|
|
1162
|
+
apiMetaText: {
|
|
1163
|
+
fontFamily: AppFonts.interRegular,
|
|
1164
|
+
fontSize: 11,
|
|
1165
|
+
color: AppColors.grayTextWeak,
|
|
1166
|
+
},
|
|
1167
|
+
contentDetailSubHeader: {
|
|
1168
|
+
flexDirection: 'row',
|
|
1169
|
+
alignItems: 'center',
|
|
1170
|
+
justifyContent: 'space-between',
|
|
1171
|
+
paddingHorizontal: 12,
|
|
1172
|
+
paddingVertical: 10,
|
|
1173
|
+
borderBottomWidth: 1,
|
|
1174
|
+
borderBottomColor: AppColors.dividerColor,
|
|
1175
|
+
backgroundColor: AppColors.primaryLight,
|
|
360
1176
|
},
|
|
361
1177
|
});
|