about summary refs log tree commit diff
path: root/src/screens/Log.tsx
blob: 2dd7fe84c0353515fd6e53935b808670ee0a0cda (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import {useCallback, useState} from 'react'
import {LayoutAnimation, View} from 'react-native'
import {Pressable} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'

import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
import {
  type CommonNavigatorParams,
  type NativeStackScreenProps,
} from '#/lib/routes/types'
import {getEntries} from '#/logger/logDump'
import {useTickEveryMinute} from '#/state/shell'
import {useSetMinimalShellMode} from '#/state/shell'
import {atoms as a, useTheme} from '#/alf'
import {
  ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottomIcon,
  ChevronTop_Stroke2_Corner0_Rounded as ChevronTopIcon,
} from '#/components/icons/Chevron'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
import * as Layout from '#/components/Layout'
import {Text} from '#/components/Typography'

export function LogScreen({}: NativeStackScreenProps<
  CommonNavigatorParams,
  'Log'
>) {
  const t = useTheme()
  const {_} = useLingui()
  const setMinimalShellMode = useSetMinimalShellMode()
  const [expanded, setExpanded] = useState<string[]>([])
  const timeAgo = useGetTimeAgo()
  const tick = useTickEveryMinute()

  useFocusEffect(
    useCallback(() => {
      setMinimalShellMode(false)
    }, [setMinimalShellMode]),
  )

  const toggler = (id: string) => () => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
    if (expanded.includes(id)) {
      setExpanded(expanded.filter(v => v !== id))
    } else {
      setExpanded([...expanded, id])
    }
  }

  return (
    <Layout.Screen>
      <Layout.Header.Outer>
        <Layout.Header.BackButton />
        <Layout.Header.Content>
          <Layout.Header.TitleText>
            <Trans>System log</Trans>
          </Layout.Header.TitleText>
        </Layout.Header.Content>
        <Layout.Header.Slot />
      </Layout.Header.Outer>
      <Layout.Content>
        {getEntries()
          .slice(0)
          .map(entry => {
            return (
              <View key={`entry-${entry.id}`}>
                <Pressable
                  style={[
                    a.flex_row,
                    a.align_center,
                    a.py_md,
                    a.px_sm,
                    a.border_b,
                    t.atoms.border_contrast_low,
                    t.atoms.bg,
                    a.gap_sm,
                  ]}
                  onPress={toggler(entry.id)}
                  accessibilityLabel={_(msg`View debug entry`)}
                  accessibilityHint={_(
                    msg`Opens additional details for a debug entry`,
                  )}>
                  {entry.level === 'warn' || entry.level === 'error' ? (
                    <WarningIcon size="sm" fill={t.palette.negative_500} />
                  ) : (
                    <CircleInfoIcon size="sm" />
                  )}
                  <Text style={[a.flex_1]}>{String(entry.message)}</Text>
                  {entry.metadata &&
                    Object.keys(entry.metadata).length > 0 &&
                    (expanded.includes(entry.id) ? (
                      <ChevronTopIcon
                        size="sm"
                        style={[t.atoms.text_contrast_low]}
                      />
                    ) : (
                      <ChevronBottomIcon
                        size="sm"
                        style={[t.atoms.text_contrast_low]}
                      />
                    ))}
                  <Text style={[{minWidth: 40}, t.atoms.text_contrast_medium]}>
                    {timeAgo(entry.timestamp, tick)}
                  </Text>
                </Pressable>
                {expanded.includes(entry.id) && (
                  <View
                    style={[
                      t.atoms.bg_contrast_25,
                      a.rounded_xs,
                      a.p_sm,
                      a.border_b,
                      t.atoms.border_contrast_low,
                    ]}>
                    <View style={[a.px_sm, a.py_xs]}>
                      <Text>{JSON.stringify(entry.metadata, null, 2)}</Text>
                    </View>
                  </View>
                )}
              </View>
            )
          })}
      </Layout.Content>
    </Layout.Screen>
  )
}