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