1/**
2 * Copyright (c) 650 Industries.
3 * Copyright (c) Meta Platforms, Inc. and affiliates.
4 *
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8import React, { useEffect } from 'react';
9import { Image, Pressable, StyleSheet, Text, View } from 'react-native';
10
11import { ErrorToastMessage } from './ErrorToastMessage';
12import * as LogBoxData from '../Data/LogBoxData';
13import { LogBoxLog } from '../Data/LogBoxLog';
14import * as LogBoxStyle from '../UI/LogBoxStyle';
15
16type Props = {
17  log: LogBoxLog;
18  totalLogCount: number;
19  level: 'warn' | 'error';
20  onPressOpen: () => void;
21  onPressDismiss: () => void;
22};
23
24function useSymbolicatedLog(log: LogBoxLog) {
25  // Eagerly symbolicate so the stack is available when pressing to inspect.
26  useEffect(() => {
27    LogBoxData.symbolicateLogLazy('stack', log);
28    LogBoxData.symbolicateLogLazy('component', log);
29  }, [log]);
30}
31
32export function ErrorToast(props: Props) {
33  const { totalLogCount, level, log } = props;
34
35  useSymbolicatedLog(log);
36
37  return (
38    <View style={toastStyles.container}>
39      <Pressable style={{ flex: 1 }} onPress={props.onPressOpen}>
40        {({
41          /** @ts-expect-error: react-native types are broken. */
42          hovered,
43          pressed,
44        }) => (
45          <View
46            style={[
47              toastStyles.press,
48              {
49                // @ts-expect-error: web-only type
50                transitionDuration: '150ms',
51                backgroundColor: pressed
52                  ? '#323232'
53                  : hovered
54                  ? '#111111'
55                  : LogBoxStyle.getBackgroundColor(),
56              },
57            ]}>
58            <Count count={totalLogCount} level={level} />
59            <ErrorToastMessage message={log.message} />
60            <Dismiss onPress={props.onPressDismiss} />
61          </View>
62        )}
63      </Pressable>
64    </View>
65  );
66}
67
68function Count({ count, level }: { count: number; level: Props['level'] }) {
69  return (
70    <View style={[countStyles.inside, countStyles[level]]}>
71      <Text style={countStyles.text}>{count <= 1 ? '!' : count}</Text>
72    </View>
73  );
74}
75
76function Dismiss({ onPress }: { onPress: () => void }) {
77  return (
78    <Pressable
79      style={{
80        marginLeft: 5,
81      }}
82      hitSlop={{
83        top: 12,
84        right: 10,
85        bottom: 12,
86        left: 10,
87      }}
88      onPress={onPress}>
89      {({
90        /** @ts-expect-error: react-native types are broken. */
91        hovered,
92        pressed,
93      }) => (
94        <View
95          style={[dismissStyles.press, hovered && { opacity: 0.8 }, pressed && { opacity: 0.5 }]}>
96          <Image
97            source={require('@expo/metro-runtime/assets/close.png')}
98            style={dismissStyles.image}
99          />
100        </View>
101      )}
102    </Pressable>
103  );
104}
105
106const countStyles = StyleSheet.create({
107  warn: {
108    backgroundColor: LogBoxStyle.getWarningColor(1),
109  },
110  error: {
111    backgroundColor: LogBoxStyle.getErrorColor(1),
112  },
113  log: {
114    backgroundColor: LogBoxStyle.getLogColor(1),
115  },
116  inside: {
117    marginRight: 8,
118    minWidth: 22,
119    aspectRatio: 1,
120    paddingHorizontal: 4,
121    borderRadius: 11,
122    justifyContent: 'center',
123    alignItems: 'center',
124  },
125  text: {
126    color: LogBoxStyle.getTextColor(1),
127    fontSize: 14,
128    lineHeight: 18,
129    textAlign: 'center',
130    fontWeight: '600',
131    textShadow: `0px 0px 3px ${LogBoxStyle.getBackgroundColor(0.8)}`,
132  },
133});
134
135const dismissStyles = StyleSheet.create({
136  press: {
137    backgroundColor: '#323232',
138    height: 20,
139    width: 20,
140    borderRadius: 25,
141    alignItems: 'center',
142    justifyContent: 'center',
143  },
144  image: {
145    height: 8,
146    width: 8,
147  },
148});
149
150const toastStyles = StyleSheet.create({
151  container: {
152    height: 48,
153    justifyContent: 'center',
154    marginBottom: 4,
155  },
156  press: {
157    borderWidth: 1,
158    borderRadius: 8,
159    overflow: 'hidden',
160    flexDirection: 'row',
161    alignItems: 'center',
162    borderColor: '#323232',
163    backgroundColor: LogBoxStyle.getBackgroundColor(),
164    flex: 1,
165    paddingHorizontal: 12,
166  },
167});
168