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, useState } from 'react';
9import { Animated, Easing, GestureResponderEvent, StyleSheet, Text } from 'react-native';
10
11import { LogBoxButton } from '../UI/LogBoxButton';
12import * as LogBoxStyle from '../UI/LogBoxStyle';
13
14type Props = {
15  onPress?: ((event: GestureResponderEvent) => void) | null;
16  status: 'COMPLETE' | 'FAILED' | 'NONE' | 'PENDING';
17};
18
19export function LogBoxInspectorSourceMapStatus(props: Props) {
20  const [state, setState] = useState<{
21    animation: null | Animated.CompositeAnimation;
22    rotate: null | Animated.AnimatedInterpolation<string>;
23  }>({
24    animation: null,
25    rotate: null,
26  });
27
28  useEffect(() => {
29    if (props.status === 'PENDING') {
30      if (state.animation == null) {
31        const animated = new Animated.Value(0);
32        const animation = Animated.loop(
33          Animated.timing(animated, {
34            duration: 2000,
35            easing: Easing.linear,
36            toValue: 1,
37            useNativeDriver: true,
38          })
39        );
40        setState({
41          animation,
42          rotate: animated.interpolate({
43            inputRange: [0, 1],
44            outputRange: ['0deg', '360deg'],
45          }),
46        });
47        animation.start();
48      }
49    } else {
50      if (state.animation != null) {
51        state.animation.stop();
52        setState({
53          animation: null,
54          rotate: null,
55        });
56      }
57    }
58
59    return () => {
60      if (state.animation != null) {
61        state.animation.stop();
62      }
63    };
64  }, [props.status, state.animation]);
65
66  let image;
67  let color;
68  switch (props.status) {
69    case 'FAILED':
70      image = require('@expo/metro-runtime/assets/alert-triangle.png');
71      color = LogBoxStyle.getErrorColor(1);
72      break;
73    case 'PENDING':
74      image = require('@expo/metro-runtime/assets/loader.png');
75      color = LogBoxStyle.getWarningColor(1);
76      break;
77  }
78
79  if (props.status === 'COMPLETE' || image == null) {
80    return null;
81  }
82
83  return (
84    <LogBoxButton
85      backgroundColor={{
86        default: 'transparent',
87        pressed: LogBoxStyle.getBackgroundColor(1),
88      }}
89      hitSlop={{ bottom: 8, left: 8, right: 8, top: 8 }}
90      onPress={props.onPress}
91      style={styles.root}>
92      <Animated.Image
93        source={image}
94        tintColor={color ?? LogBoxStyle.getTextColor(0.4)}
95        style={[
96          styles.image,
97          state.rotate == null || props.status !== 'PENDING'
98            ? null
99            : { transform: [{ rotate: state.rotate }] },
100        ]}
101      />
102      <Text style={[styles.text, { color }]}>Source Map</Text>
103    </LogBoxButton>
104  );
105}
106
107const styles = StyleSheet.create({
108  root: {
109    alignItems: 'center',
110    borderRadius: 12,
111    flexDirection: 'row',
112    height: 24,
113    paddingHorizontal: 8,
114  },
115  image: {
116    height: 14,
117    width: 16,
118    marginEnd: 4,
119  },
120  text: {
121    fontSize: 12,
122    includeFontPadding: false,
123    lineHeight: 16,
124  },
125});
126