Bluesky app fork with some witchin' additions 馃挮
at main 144 lines 5.1 kB view raw
1import {useCallback, useState} from 'react' 2import {LayoutAnimation, View} from 'react-native' 3import {Pressable} from 'react-native' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useFocusEffect} from '@react-navigation/native' 7 8import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 9import { 10 type CommonNavigatorParams, 11 type NativeStackScreenProps, 12} from '#/lib/routes/types' 13import {getEntries} from '#/logger/logDump' 14import {useTickEveryMinute} from '#/state/shell' 15import {useSetMinimalShellMode} from '#/state/shell' 16import {atoms as a, useTheme} from '#/alf' 17import { 18 ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottomIcon, 19 ChevronTop_Stroke2_Corner0_Rounded as ChevronTopIcon, 20} from '#/components/icons/Chevron' 21import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' 22import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 23import * as Layout from '#/components/Layout' 24import {Text} from '#/components/Typography' 25 26export function LogScreen({}: NativeStackScreenProps< 27 CommonNavigatorParams, 28 'Log' 29>) { 30 const t = useTheme() 31 const {_} = useLingui() 32 const setMinimalShellMode = useSetMinimalShellMode() 33 const [expanded, setExpanded] = useState<string[]>([]) 34 const timeAgo = useGetTimeAgo() 35 const tick = useTickEveryMinute() 36 37 useFocusEffect( 38 useCallback(() => { 39 setMinimalShellMode(false) 40 }, [setMinimalShellMode]), 41 ) 42 43 const toggler = (id: string) => () => { 44 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 45 if (expanded.includes(id)) { 46 setExpanded(expanded.filter(v => v !== id)) 47 } else { 48 setExpanded([...expanded, id]) 49 } 50 } 51 52 return ( 53 <Layout.Screen> 54 <Layout.Header.Outer> 55 <Layout.Header.BackButton /> 56 <Layout.Header.Content> 57 <Layout.Header.TitleText> 58 <Trans>System log</Trans> 59 </Layout.Header.TitleText> 60 </Layout.Header.Content> 61 <Layout.Header.Slot /> 62 </Layout.Header.Outer> 63 <Layout.Content> 64 {getEntries() 65 .slice(0) 66 .map(entry => { 67 return ( 68 <View key={`entry-${entry.id}`}> 69 <Pressable 70 style={[ 71 a.flex_row, 72 a.align_center, 73 a.py_md, 74 a.px_sm, 75 a.border_b, 76 t.atoms.border_contrast_low, 77 t.atoms.bg, 78 a.gap_sm, 79 ]} 80 onPress={toggler(entry.id)} 81 accessibilityLabel={_(msg`View debug entry`)} 82 accessibilityHint={_( 83 msg`Opens additional details for a debug entry`, 84 )}> 85 {entry.level === 'warn' || entry.level === 'error' ? ( 86 <WarningIcon size="sm" fill={t.palette.negative_500} /> 87 ) : ( 88 <CircleInfoIcon size="sm" /> 89 )} 90 <View 91 style={[ 92 a.flex_1, 93 a.flex_row, 94 a.justify_start, 95 a.align_center, 96 a.gap_sm, 97 ]}> 98 {entry.context && ( 99 <Text style={[t.atoms.text_contrast_medium]}> 100 ({String(entry.context)}) 101 </Text> 102 )} 103 <Text>{String(entry.message)}</Text> 104 </View> 105 {entry.metadata && 106 Object.keys(entry.metadata).length > 0 && 107 (expanded.includes(entry.id) ? ( 108 <ChevronTopIcon 109 size="sm" 110 style={[t.atoms.text_contrast_low]} 111 /> 112 ) : ( 113 <ChevronBottomIcon 114 size="sm" 115 style={[t.atoms.text_contrast_low]} 116 /> 117 ))} 118 <Text style={[{minWidth: 40}, t.atoms.text_contrast_medium]}> 119 {timeAgo(entry.timestamp, tick)} 120 </Text> 121 </Pressable> 122 {expanded.includes(entry.id) && ( 123 <View 124 style={[ 125 t.atoms.bg_contrast_25, 126 a.rounded_xs, 127 a.p_sm, 128 a.border_b, 129 t.atoms.border_contrast_low, 130 ]}> 131 <View style={[a.px_sm, a.py_xs]}> 132 <Text style={[a.leading_snug, {fontFamily: 'monospace'}]}> 133 {JSON.stringify(entry.metadata, null, 2)} 134 </Text> 135 </View> 136 </View> 137 )} 138 </View> 139 ) 140 })} 141 </Layout.Content> 142 </Layout.Screen> 143 ) 144}