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