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