diff --git a/Libraries/LogBox/Data/LogBoxData.js b/Libraries/LogBox/Data/LogBoxData.js index 4207d8bf1ef..27af6428864 100644 --- a/Libraries/LogBox/Data/LogBoxData.js +++ b/Libraries/LogBox/Data/LogBoxData.js @@ -82,6 +82,21 @@ export function add(level: LogLevel, args: $ReadOnlyArray): void { handleUpdate(); } +export function symbolicateLogNow(log: LogBoxLog) { + log.symbolicate(() => { + handleUpdate(); + }); +} +export function retrySymbolicateLogNow(log: LogBoxLog) { + log.retrySymbolicate(() => { + handleUpdate(); + }); +} + +export function symbolicateLogLazy(log: LogBoxLog) { + log.symbolicate(); +} + export function clear(): void { if (logs.size > 0) { logs.clear(); diff --git a/Libraries/LogBox/Data/LogBoxLog.js b/Libraries/LogBox/Data/LogBoxLog.js index 5e3b59c0384..bffbf9bb8e5 100644 --- a/Libraries/LogBox/Data/LogBoxLog.js +++ b/Libraries/LogBox/Data/LogBoxLog.js @@ -63,12 +63,14 @@ class LogBoxLog { : this.stack; } - retrySymbolicate(callback: () => void): SymbolicationRequest { - LogBoxSymbolication.deleteStack(this.stack); + retrySymbolicate(callback?: () => void): SymbolicationRequest { + if (this.symbolicated.status !== 'COMPLETE') { + LogBoxSymbolication.deleteStack(this.stack); + } return this.symbolicate(callback); } - symbolicate(callback: () => void): SymbolicationRequest { + symbolicate(callback?: () => void): SymbolicationRequest { let aborted = false; if (this.symbolicated.status !== 'COMPLETE') { @@ -81,7 +83,9 @@ class LogBoxLog { this.symbolicated = {error: null, stack: null, status: 'PENDING'}; } if (!aborted) { - callback(); + if (callback != null) { + callback(); + } } }; diff --git a/Libraries/LogBox/UI/LogBoxInspector.js b/Libraries/LogBox/UI/LogBoxInspector.js index 2b98c0db839..6190a182282 100644 --- a/Libraries/LogBox/UI/LogBoxInspector.js +++ b/Libraries/LogBox/UI/LogBoxInspector.js @@ -15,6 +15,7 @@ import * as React from 'react'; import ScrollView from '../../Components/ScrollView/ScrollView'; import StyleSheet from '../../StyleSheet/StyleSheet'; import View from '../../Components/View/View'; +import * as LogBoxData from '../Data/LogBoxData'; import LogBoxInspectorFooter from './LogBoxInspectorFooter'; import LogBoxInspectorMessageHeader from './LogBoxInspectorMessageHeader'; import LogBoxInspectorReactFrames from './LogBoxInspectorReactFrames'; @@ -24,7 +25,6 @@ import LogBoxInspectorHeader from './LogBoxInspectorHeader'; import * as LogBoxStyle from './LogBoxStyle'; import type LogBoxLog from '../Data/LogBoxLog'; -import type {SymbolicationRequest} from '../Data/LogBoxLog'; type Props = $ReadOnly<{| onDismiss: () => void, @@ -34,75 +34,52 @@ type Props = $ReadOnly<{| selectedIndex: number, |}>; -class LogBoxInspector extends React.Component { - _symbolication: ?SymbolicationRequest; +function LogBoxInspector(props: Props): React.Node { + const {logs, selectedIndex} = props; - _handleDismiss = () => { - this.props.onDismiss(); - }; - - render(): React.Node { - const {logs, selectedIndex} = this.props; - - const log = logs[selectedIndex]; - if (log == null) { - return null; + const log = logs[selectedIndex]; + React.useEffect(() => { + // Symbolicate the visible log if it hasn't been already. + if (log != null && log.symbolicated.status !== 'COMPLETE') { + LogBoxData.symbolicateLogNow(log); } + }, [log]); - return ( - - - - - - ); - } - - componentDidMount(): void { - this._handleSymbolication(); - } - - componentDidUpdate(prevProps: Props): void { - if ( - prevProps.logs[prevProps.selectedIndex] !== - this.props.logs[this.props.selectedIndex] - ) { - this._handleSymbolication(); + React.useEffect(() => { + // Optimistically symbolicate the last and next logs. + if (logs.length > 1) { + const selected = selectedIndex; + const lastIndex = logs.length - 1; + const prevIndex = selected - 1 < 0 ? lastIndex : selected - 1; + const nextIndex = selected + 1 > lastIndex ? 0 : selected + 1; + LogBoxData.symbolicateLogLazy(logs[prevIndex]); + LogBoxData.symbolicateLogLazy(logs[nextIndex]); } + }, [logs, selectedIndex]); + + function _handleRetry() { + LogBoxData.retrySymbolicateLogNow(log); } - _handleRetrySymbolication = () => { - this.forceUpdate(() => { - const log = this.props.logs[this.props.selectedIndex]; - this._symbolication = log.retrySymbolicate(() => { - this.forceUpdate(); - }); - }); - }; - - _handleSymbolication(): void { - const log = this.props.logs[this.props.selectedIndex]; - if (log.symbolicated.status !== 'COMPLETE') { - this._symbolication = log.symbolicate(() => { - this.forceUpdate(); - }); - } + if (log == null) { + return null; } - _handleSelectIndex = (selectedIndex: number): void => { - this.props.onChangeSelectedIndex(selectedIndex); - }; + return ( + + + + + + ); } function LogBoxInspectorBody(props) { diff --git a/Libraries/LogBox/UI/LogBoxInspectorSourceMapStatus.js b/Libraries/LogBox/UI/LogBoxInspectorSourceMapStatus.js index db2fd9adea7..6c5fb9476cd 100644 --- a/Libraries/LogBox/UI/LogBoxInspectorSourceMapStatus.js +++ b/Libraries/LogBox/UI/LogBoxInspectorSourceMapStatus.js @@ -19,8 +19,6 @@ import LogBoxImageSource from './LogBoxImageSource'; import LogBoxButton from './LogBoxButton'; import * as LogBoxStyle from './LogBoxStyle'; -import type {CompositeAnimation} from '../../Animated/src/AnimatedImplementation'; -import type AnimatedInterpolation from '../../Animated/src/nodes/AnimatedInterpolation'; import type {PressEvent} from '../../Types/CoreEventTypes'; type Props = $ReadOnly<{| @@ -28,76 +26,15 @@ type Props = $ReadOnly<{| status: 'COMPLETE' | 'FAILED' | 'NONE' | 'PENDING', |}>; -type State = {| - animation: ?CompositeAnimation, - rotate: ?AnimatedInterpolation, -|}; - -class LogBoxInspectorSourceMapStatus extends React.Component { - state: State = { +function LogBoxInspectorSourceMapStatus(props: Props): React.Node { + const [state, setState] = React.useState({ animation: null, rotate: null, - }; + }); - render(): React.Node { - let image; - let color; - switch (this.props.status) { - case 'COMPLETE': - image = LogBoxImageSource.check; - color = LogBoxStyle.getTextColor(0.4); - break; - case 'FAILED': - image = LogBoxImageSource.alertTriangle; - color = LogBoxStyle.getErrorColor(1); - break; - case 'PENDING': - image = LogBoxImageSource.loader; - color = LogBoxStyle.getWarningColor(1); - break; - } - - return image == null ? null : ( - - - Source Map - - ); - } - - componentDidMount(): void { - this._updateAnimation(); - } - - componentDidUpdate(): void { - this._updateAnimation(); - } - - componentWillUnmount(): void { - if (this.state.animation != null) { - this.state.animation.stop(); - } - } - - _updateAnimation(): void { - if (this.props.status === 'PENDING') { - if (this.state.animation == null) { + React.useEffect(() => { + if (props.status === 'PENDING') { + if (state.animation == null) { const animated = new Animated.Value(0); const animation = Animated.loop( Animated.timing(animated, { @@ -107,29 +44,69 @@ class LogBoxInspectorSourceMapStatus extends React.Component { useNativeDriver: true, }), ); - this.setState( - { - animation, - rotate: animated.interpolate({ - inputRange: [0, 1], - outputRange: ['0deg', '360deg'], - }), - }, - () => { - animation.start(); - }, - ); + setState({ + animation, + rotate: animated.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }), + }); + animation.start(); } } else { - if (this.state.animation != null) { - this.state.animation.stop(); - this.setState({ + if (state.animation != null) { + state.animation.stop(); + setState({ animation: null, rotate: null, }); } } + + return () => { + if (state.animation != null) { + state.animation.stop(); + } + }; + }, [props.status, state.animation]); + + let image; + let color; + switch (props.status) { + case 'FAILED': + image = LogBoxImageSource.alertTriangle; + color = LogBoxStyle.getErrorColor(1); + break; + case 'PENDING': + image = LogBoxImageSource.loader; + color = LogBoxStyle.getWarningColor(1); + break; } + + if (props.status === 'COMPLETE') { + return null; + } + + return image == null ? null : ( + + + Source Map + + ); } const styles = StyleSheet.create({ diff --git a/Libraries/LogBox/UI/LogBoxInspectorStackFrames.js b/Libraries/LogBox/UI/LogBoxInspectorStackFrames.js index 16fbe3329d2..12f674f10de 100644 --- a/Libraries/LogBox/UI/LogBoxInspectorStackFrames.js +++ b/Libraries/LogBox/UI/LogBoxInspectorStackFrames.js @@ -79,7 +79,7 @@ function StackFrameHeader(props) { Stack diff --git a/Libraries/LogBox/UI/LogBoxLogNotification.js b/Libraries/LogBox/UI/LogBoxLogNotification.js index 6da4145af08..7e096e5ea1c 100644 --- a/Libraries/LogBox/UI/LogBoxLogNotification.js +++ b/Libraries/LogBox/UI/LogBoxLogNotification.js @@ -20,63 +20,42 @@ import LogBoxButton from './LogBoxButton'; import * as LogBoxStyle from './LogBoxStyle'; import LogBoxLog from '../Data/LogBoxLog'; import LogBoxMessage from './LogBoxMessage'; +import * as LogBoxData from '../Data/LogBoxData'; type Props = $ReadOnly<{| log: LogBoxLog, totalLogCount: number, level: 'warn' | 'error', - onPressOpen: (index: number) => void, + onPressOpen: () => void, onPressList: () => void, onPressDismiss: () => void, |}>; -class LogBoxLogNotification extends React.Component { - static GUTTER: number = StyleSheet.hairlineWidth; - static HEIGHT: number = 48; +function LogBoxLogNotification(props: Props): React.Node { + const {totalLogCount, level, log} = props; - shouldComponentUpdate(nextProps: Props): boolean { - const prevProps = this.props; - return ( - prevProps.onPressOpen !== nextProps.onPressOpen || - prevProps.onPressList !== nextProps.onPressList || - prevProps.onPressDismiss !== nextProps.onPressDismiss || - prevProps.log !== nextProps.log - ); - } + // Eagerly symbolicate so the stack is available when pressing to inspect. + React.useEffect(() => { + LogBoxData.symbolicateLogLazy(log); + }, [log]); - _handlePressOpen = () => { - this.props.onPressOpen(0); - }; - - _handlePressList = () => { - this.props.onPressList(); - }; - - _handlePressDismiss = () => { - this.props.onPressDismiss(); - }; - - render(): React.Node { - const {totalLogCount, level, log} = this.props; - - return ( - - - - - - - - - - ); - } + return ( + + + + + + + + + + ); } function CountBadge(props) { @@ -208,19 +187,19 @@ const dismissStyles = StyleSheet.create({ const toastStyles = StyleSheet.create({ container: { - height: LogBoxLogNotification.HEIGHT, + height: 48, position: 'relative', width: '100%', justifyContent: 'center', - marginTop: LogBoxLogNotification.GUTTER, + marginTop: 0.5, backgroundColor: LogBoxStyle.getTextColor(1), }, press: { - height: LogBoxLogNotification.HEIGHT, + height: 48, position: 'relative', width: '100%', justifyContent: 'center', - marginTop: LogBoxLogNotification.GUTTER, + marginTop: 0.5, paddingHorizontal: 12, }, content: { diff --git a/Libraries/LogBox/UI/__tests__/LogBoxInspectorSourceMapStatus-test.js b/Libraries/LogBox/UI/__tests__/LogBoxInspectorSourceMapStatus-test.js index 39962be5562..fb3ae08d064 100644 --- a/Libraries/LogBox/UI/__tests__/LogBoxInspectorSourceMapStatus-test.js +++ b/Libraries/LogBox/UI/__tests__/LogBoxInspectorSourceMapStatus-test.js @@ -17,7 +17,23 @@ const LogBoxInspectorSourceMapStatus = require('../LogBoxInspectorSourceMapStatu const render = require('../../../../jest/renderer'); describe('LogBoxInspectorSourceMapStatus', () => { - it('should render complete', () => { + it('should render for failed', () => { + const output = render.shallowRender( + {}} status="FAILED" />, + ); + + expect(output).toMatchSnapshot(); + }); + + it('should render for pending', () => { + const output = render.shallowRender( + {}} status="PENDING" />, + ); + + expect(output).toMatchSnapshot(); + }); + + it('should render null for complete', () => { const output = render.shallowRender( {}} status="COMPLETE" />, ); diff --git a/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorSourceMapStatus-test.js.snap b/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorSourceMapStatus-test.js.snap index 05daf49767d..1877e27ac7c 100644 --- a/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorSourceMapStatus-test.js.snap +++ b/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorSourceMapStatus-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`LogBoxInspectorSourceMapStatus should render complete 1`] = ` +exports[`LogBoxInspectorSourceMapStatus should render for failed 1`] = ` `; + +exports[`LogBoxInspectorSourceMapStatus should render for pending 1`] = ` + + + + Source Map + + +`; + +exports[`LogBoxInspectorSourceMapStatus should render null for complete 1`] = `null`;