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, { useCallback, useMemo } from 'react';
9import { StyleSheet, View } from 'react-native';
10
11import { ErrorToast } from './ErrorToast';
12import * as LogBoxData from '../Data/LogBoxData';
13import { LogBoxLog } from '../Data/LogBoxLog';
14import { useLogs } from '../Data/LogContext';
15import { useRejectionHandler } from '../useRejectionHandler';
16
17export function ErrorToastContainer() {
18  useRejectionHandler();
19  const { logs, isDisabled } = useLogs();
20  if (!logs.length || isDisabled) {
21    return null;
22  }
23  return <ErrorToastStack logs={logs} />;
24}
25
26function ErrorToastStack({ logs }: { logs: LogBoxLog[] }) {
27  const onDismissWarns = useCallback(() => {
28    LogBoxData.clearWarnings();
29  }, []);
30
31  const onDismissErrors = useCallback(() => {
32    LogBoxData.clearErrors();
33  }, []);
34
35  const setSelectedLog = useCallback((index: number): void => {
36    LogBoxData.setSelectedLog(index);
37  }, []);
38
39  function openLog(log: LogBoxLog) {
40    let index = logs.length - 1;
41
42    // Stop at zero because if we don't find any log, we'll open the first log.
43    while (index > 0 && logs[index] !== log) {
44      index -= 1;
45    }
46    setSelectedLog(index);
47  }
48
49  const warnings = useMemo(() => logs.filter((log) => log.level === 'warn'), [logs]);
50
51  const errors = useMemo(
52    () => logs.filter((log) => log.level === 'error' || log.level === 'fatal'),
53    [logs]
54  );
55
56  return (
57    <View style={styles.list}>
58      {warnings.length > 0 && (
59        <ErrorToast
60          log={warnings[warnings.length - 1]}
61          level="warn"
62          totalLogCount={warnings.length}
63          onPressOpen={() => openLog(warnings[warnings.length - 1])}
64          onPressDismiss={onDismissWarns}
65        />
66      )}
67
68      {errors.length > 0 && (
69        <ErrorToast
70          log={errors[errors.length - 1]}
71          level="error"
72          totalLogCount={errors.length}
73          onPressOpen={() => openLog(errors[errors.length - 1])}
74          onPressDismiss={onDismissErrors}
75        />
76      )}
77    </View>
78  );
79}
80
81const styles = StyleSheet.create({
82  list: {
83    bottom: 6,
84    left: 10,
85    right: 10,
86    maxWidth: 320,
87    // @ts-expect-error
88    position: 'fixed',
89  },
90});
91
92export default LogBoxData.withSubscription(ErrorToastContainer);
93