UNPKG

react-native-debug-toolkit

Version:

A simple yet powerful debugging toolkit for React Native with a convenient floating UI for development

279 lines (255 loc) 7.53 kB
import React, { useState, useMemo } from 'react' import { View, Text, StyleSheet, FlatList, TouchableOpacity, } from 'react-native' import ZustandLogDetails from './ZustandLogDetails' import { getZustandActionColor } from '../utils/DebugConst' const SubViewZustandLogs = ({ logs = [] }) => { const [selectedLog, setSelectedLog] = useState(null) // Memoize the sorted logs const sortedLogs = useMemo(() => { // Create a stable copy before sorting return [...logs].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); }, [logs]); // Helper to format preview of state changes const formatActionPreview = (log) => { if (!log) return ''; const { action = 'unknown', storeName } = log; return `Action: ${action}${storeName ? ` (${storeName})` : ''}`; }; // Safely stringify value for display const safeStringify = (value) => { if (value === undefined) return 'undefined'; if (value === null) return 'null'; const type = typeof value; if (type === 'string') return `"${value.length > 10 ? value.substring(0, 10) + '...' : value}"`; if (type === 'number' || type === 'boolean') return String(value); if (Array.isArray(value)) return '[...]'; if (type === 'object') return '{...}'; return String(value); }; // Helper to find key changes between states with more details const findChangedKeys = (prevState, nextState) => { if (!prevState || !nextState) return []; const changes = []; const allKeys = new Set([...Object.keys(prevState), ...Object.keys(nextState)]); allKeys.forEach(key => { const prevValue = prevState[key]; const nextValue = nextState[key]; // Check if key is added, removed, or value changed if (!(key in prevState)) { changes.push({ key, type: 'added', display: `+${key}: ${safeStringify(nextValue)}` }); } else if (!(key in nextState)) { changes.push({ key, type: 'removed', display: `-${key}: ${safeStringify(prevValue)}` }); } else if (JSON.stringify(prevValue) !== JSON.stringify(nextValue)) { // For primitive types, show the before and after values const isPrimitive = typeof prevValue !== 'object' || prevValue === null || typeof nextValue !== 'object' || nextValue === null; if (isPrimitive) { changes.push({ key, type: 'changed', display: `~${key}: ${safeStringify(prevValue)}${safeStringify(nextValue)}` }); } else { changes.push({ key, type: 'changed', display: `~${key}` }); } } }); return changes; }; const renderLogItem = ({ item }) => { const actionPreview = formatActionPreview(item); const actionColor = getZustandActionColor(item.action); const timestamp = item.timestamp ? new Date(item.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) : ''; // Get changed keys with details const changes = findChangedKeys(item.prevState, item.nextState); // Create a formatted display of changes const changesText = changes.length ? changes.map(change => change.display).join(', ') : 'No changes detected'; return ( <TouchableOpacity style={styles.logItem} onPress={() => setSelectedLog(item)}> <View style={styles.logItemContainer}> <View style={[styles.actionIndicator, { backgroundColor: actionColor }]} /> <View style={styles.logContent}> <View style={styles.logHeader}> <Text style={[styles.actionText, { color: actionColor }]}> {actionPreview} </Text> <Text style={styles.time}>{timestamp}</Text> </View> <Text style={styles.statePathsText} numberOfLines={2}> {changesText} </Text> </View> </View> </TouchableOpacity> ) } // If a log is selected, show the details view if (selectedLog) { const timestamp = selectedLog.timestamp ? new Date(selectedLog.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) : ''; const actionColor = getZustandActionColor(selectedLog.action); const actionDisplay = selectedLog.storeName ? `${selectedLog.action} (${selectedLog.storeName})` : selectedLog.action || 'Unknown Action'; return ( <View style={styles.container}> <View style={styles.detailsHeader}> <TouchableOpacity style={styles.backButton} onPress={() => setSelectedLog(null)}> <Text style={styles.backButtonText}>← Back</Text> </TouchableOpacity> <View style={styles.headerActionTime}> <Text style={[styles.headerAction, { color: actionColor }]}> {actionDisplay} </Text> <Text style={styles.headerTimestamp}>{timestamp}</Text> </View> </View> <ZustandLogDetails log={selectedLog} /> </View> ) } // Otherwise show the list view return ( <View style={styles.container}> {sortedLogs.length === 0 ? ( <Text style={styles.emptyText}>No Zustand state changes logged yet</Text> ) : ( <FlatList data={sortedLogs} renderItem={renderLogItem} keyExtractor={(item, index) => `${item.timestamp}-${index}`} style={styles.list} /> )} </View> ) } // Adapted styles from SubViewConsoleLogs const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', }, list: { flex: 1, }, emptyText: { textAlign: 'center', color: '#999', marginTop: 20, }, logItem: { borderBottomWidth: 1, borderBottomColor: '#eee', }, logItemContainer: { flexDirection: 'row', paddingVertical: 10, paddingHorizontal: 12, }, actionIndicator: { width: 4, borderRadius: 2, marginRight: 10, }, logContent: { flex: 1, }, logHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 5, }, actionText: { fontSize: 13, fontWeight: 'bold', }, time: { fontSize: 12, color: '#888', }, statePathsText: { fontSize: 14, color: '#555', fontFamily: 'monospace', }, // Details Header Styles detailsHeader: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, paddingHorizontal: 15, borderBottomWidth: 1, borderBottomColor: '#eee', backgroundColor: '#f8f9fa', }, backButton: { paddingHorizontal: 10, paddingVertical: 5, borderRadius: 4, backgroundColor: '#eee', marginRight: 15, }, backButtonText: { color: '#333', fontWeight: '500', fontSize: 14, }, headerActionTime: { flexDirection: 'row', alignItems: 'baseline', }, headerAction: { fontSize: 15, fontWeight: 'bold', marginRight: 8, }, headerTimestamp: { fontSize: 13, color: '#666', }, }) export default SubViewZustandLogs