forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}