Compare commits

...
Author SHA1 Message Date
Lorenzo Sciandra 203f3e3399 [0.62.0-rc.0] Bump version numbers 2019-12-18 15:36:07 +00:00
Spencer Ahrens e7c289b56e emit Dimensions change enent when app goes split screen
Summary:
Fixes https://github.com/facebook/react-native/issues/26830 by removing version gating around `RCTUserInterfaceStyleDidChangeNotification` sent by `RCTRootView` and observing that notif for `Dimensions` changes.

Also centralizes `RCTUserInterfaceStyleDidChangeNotification` constant definition in new `RCTConstants` file.

Changelog:
[iOS] [Fixed] - `Dimensions` module now updates on initial split screen

Reviewed By: sammy-SC

Differential Revision: D18931098

fbshipit-source-id: e9784be3f544f3b10360fbc2d6ad0324273b1a8f
2019-12-11 14:05:45 -08:00
Rick Hanlon 3cfbae6540 Fix logs stuck in pending symbolocated state
Summary:
This diff fixes an issue where symbolicated logs were getting stuck in a "pending" state if the timeout occurred before symbolication finished.

Changelog: [Internal]

Reviewed By: sahrens

Differential Revision: D18894154

fbshipit-source-id: ed225962468f67aef40e430aa798f8d426d31027
2019-12-10 19:53:38 +00:00
Rick Hanlon 3fd98d95ef RN DevX] LogBox - Handle tapping component stacks if possible
Summary:
This diff adds handling for tapping to open components in component stacks, whenever they're available.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D18882338

fbshipit-source-id: 574bff1c41c952c33e647cb142d278ace60b4631
2019-12-10 19:53:29 +00:00
Rick Hanlon c194ed2dbb Add unstable_enableLogBox
Summary:
This diff adds a new `unstable_enableLogBox` function to opt-into the new LogBox experience. If LogBox is not enabled early enough, we show an error with instructions.

With this, LogBox can be enabled with:

```
require('react-native').unstable_enableLogBox();
```

Changelog: [General] [Adds] unstable_enableLogBox

Reviewed By: zackargyle, rubennorte

Differential Revision: D18808940

fbshipit-source-id: 4b0234ddc4d1646515bf63110d5b02133780512e
2019-12-10 19:53:18 +00:00
Rick Hanlon 93bbab3878 LogBox - Update to use it's own root for the inspector
Summary:
Apologies for the large diff, it was difficult to break down.

This diff is a major refactor of LogBox that:
- Separates LogBoxNotification and LogBoxInspector
- Moves them each to their own container
- Updates AppContainer to only render the notification
- Updates the native logbox root to render the inspector
- Adds withSubscription HOC to manage subscribing to LogBoxData
- Simplifies LogBox to export an object instead of a component

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D18750011

fbshipit-source-id: 639885d29e55e125892d1c2b6bbf2826f27d78db
2019-12-10 19:53:09 +00:00
Rick Hanlon 1a305fd79b Add NativeLogBox module on Android
Summary:
This diff adds a NativeLogBox module implementation on Android to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view.

The strategy here is:
- initialize: will create a React rootview and render it.
- show: will add the rootview to a dialog and display the dialog.
- hide: will remove the rootview from it's parent, dismiss the dialog, and release the reference to the activity to prevent leaks.

Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the rootview with the `initialize` function so that it's warm by the time the dialog needs to render.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D18768517

fbshipit-source-id: 2510d6c186ccf73153ef9372c736c9e0c71bbc7d
2019-12-10 19:52:58 +00:00
Rick Hanlon 45a43241cd Add NativeLogBox module on iOS
Summary:
This diff adds a NativeLogBox module implementation on iOS to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view.

The strategy here is:
- initialize: will create a hidden window (the way redbox does) and render the LogBox to it
- show: will show the window
- hide: will hide the window

Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the window with the `initialize` function so that it's warm by the time LogBox needs to render.

Changelog: [Internal]

Reviewed By: RSNara

Differential Revision: D18750008

fbshipit-source-id: 013e55ded55c8572bb08e0219ff4cd0060ebe0da
2019-12-10 19:52:44 +00:00
Rick Hanlon 041c392ec9 Register LogBox by default in dev
Summary:
This diff stubs out registering LogBox as a renderable component. In later diffs, we'll render this component on the native side.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D18750005

fbshipit-source-id: 4db082ca2104731641d2d10de1ba83a318ab44fb
2019-12-10 19:52:23 +00:00
64 changed files with 2259 additions and 737 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ function reportException(e: ExtendedError, isFatal: boolean) {
e.jsEngine == null ? message : `${message}, js engine: ${e.jsEngine}`;
const isHandledByLogBox =
e.forceRedbox !== true && global.__reactExperimentalLogBox;
e.forceRedbox !== true && global.__unstable_isLogBoxEnabled === true;
const data = preprocessException({
message,
+4 -4
View File
@@ -1,17 +1,17 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @generated by scripts/bump-oss-version.js
* @flow
*/
exports.version = {
major: 0,
minor: 0,
minor: 62,
patch: 0,
prerelease: null,
prerelease: 'rc.0',
};
@@ -1671,6 +1671,33 @@ namespace facebook {
}
} // namespace react
} // namespace facebook
namespace facebook {
namespace react {
static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_show(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "show", @selector(show), args, count);
}
static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_hide(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "hide", @selector(hide), args, count);
}
NativeLogBoxSpecJSI::NativeLogBoxSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<CallInvoker> jsInvoker)
: ObjCTurboModule("LogBox", instance, jsInvoker) {
methodMap_["show"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_show};
methodMap_["hide"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_hide};
}
} // namespace react
@@ -1771,6 +1771,25 @@ namespace facebook {
};
} // namespace react
} // namespace facebook
@protocol NativeLogBoxSpec <RCTBridgeModule, RCTTurboModule>
- (void)show;
- (void)hide;
@end
namespace facebook {
namespace react {
/**
* ObjC++ class for module 'LogBox'
*/
class JSI_EXPORT NativeLogBoxSpecJSI : public ObjCTurboModule {
public:
NativeLogBoxSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<CallInvoker> jsInvoker);
};
} // namespace react
} // namespace facebook
@protocol NativeModalManagerSpec <RCTBridgeModule, RCTTurboModule>
- (void)addListener:(NSString *)eventName;
+98
View File
@@ -10,6 +10,7 @@
('use strict');
import * as React from 'react';
import LogBoxLog from './LogBoxLog';
import {parseLogBoxException} from './parseLogBoxLog';
import type {LogLevel} from './LogBoxLog';
@@ -190,6 +191,9 @@ function appendNewLog(newLog) {
if (addPendingLog && status !== 'PENDING') {
addPendingLog();
clearTimeout(optimisticTimeout);
} else if (status !== 'PENDING') {
// The log has already been added but we need to trigger a render.
handleUpdate();
}
});
} else {
@@ -369,3 +373,97 @@ export function observe(observer: Observer): Subscription {
},
};
}
type Props = $ReadOnly<{||}>;
type State = $ReadOnly<{|
logs: LogBoxLogs,
isDisabled: boolean,
hasError: boolean,
selectedLogIndex: number,
|}>;
type SubscribedComponent = React.AbstractComponent<
$ReadOnly<{|
logs: $ReadOnlyArray<LogBoxLog>,
isDisabled: boolean,
selectedLogIndex: number,
|}>,
>;
export function withSubscription(
WrappedComponent: SubscribedComponent,
): React.AbstractComponent<{||}> {
class LogBoxStateSubscription extends React.Component<Props, State> {
static getDerivedStateFromError() {
return {hasError: true};
}
componentDidCatch(err: Error, errorInfo: {componentStack: string, ...}) {
reportLogBoxError(err, errorInfo.componentStack);
}
_subscription: ?Subscription;
state = {
logs: new Set(),
isDisabled: false,
hasError: false,
selectedLogIndex: -1,
};
render(): React.Node {
if (this.state.hasError) {
// This happens when the component failed to render, in which case we delegate to the native redbox.
// We can't show anyback fallback UI here, because the error may be with <View> or <Text>.
return null;
}
return (
<WrappedComponent
logs={Array.from(this.state.logs)}
isDisabled={this.state.isDisabled}
selectedLogIndex={this.state.selectedLogIndex}
/>
);
}
componentDidMount(): void {
this._subscription = observe(data => {
this.setState(data);
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss = (): void => {
// Here we handle the cases when the log is dismissed and it
// was either the last log, or when the current index
// is now outside the bounds of the log array.
const {selectedLogIndex, logs: stateLogs} = this.state;
const logsArray = Array.from(stateLogs);
if (selectedLogIndex != null) {
if (logsArray.length - 1 <= 0) {
setSelectedLog(-1);
} else if (selectedLogIndex >= logsArray.length - 1) {
setSelectedLog(selectedLogIndex - 1);
}
dismiss(logsArray[selectedLogIndex]);
}
};
_handleMinimize = (): void => {
setSelectedLog(-1);
};
_handleSetSelectedLog = (index: number): void => {
setSelectedLog(index);
};
}
return LogBoxStateSubscription;
}
@@ -25,7 +25,13 @@ function getLogBoxLog() {
message: {content: '...', substitutions: []},
stack: createStack(['A', 'B', 'C']),
category: 'Message category...',
componentStack: [{component: 'LogBoxLog', location: 'LogBoxLog.js:1'}],
componentStack: [
{
content: 'LogBoxLog',
fileName: 'LogBoxLog.js',
location: {column: -1, row: 1},
},
],
codeFrame: {
fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
@@ -69,7 +75,11 @@ describe('LogBoxLog', () => {
expect(log.stack).toEqual(createStack(['A', 'B', 'C']));
expect(log.category).toEqual('Message category...');
expect(log.componentStack).toEqual([
{component: 'LogBoxLog', location: 'LogBoxLog.js:1'},
{
content: 'LogBoxLog',
fileName: 'LogBoxLog.js',
location: {column: -1, row: 1},
},
]);
expect(log.codeFrame).toEqual({
fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
@@ -117,8 +117,16 @@ describe('parseLogBoxLog', () => {
]),
).toEqual({
componentStack: [
{component: 'MyComponent', location: 'filename.js:1'},
{component: 'MyOtherComponent', location: 'filename2.js:1'},
{
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
category:
'Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?%s',
@@ -143,8 +151,16 @@ describe('parseLogBoxLog', () => {
]),
).toEqual({
componentStack: [
{component: 'MyComponent', location: 'filename.js:1'},
{component: 'MyOtherComponent', location: 'filename2.js:1'},
{
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
category: 'Some kind of message',
message: {
@@ -164,8 +180,16 @@ describe('parseLogBoxLog', () => {
]),
).toEqual({
componentStack: [
{component: 'MyComponent', location: 'filename.js:1'},
{component: 'MyOtherComponent', location: 'filename2.js:1'},
{
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
category:
'Some kind of message Some other kind of message Some third kind of message',
@@ -418,12 +442,14 @@ Please follow the instructions at: fburl.com/rn-remote-assets`,
},
componentStack: [
{
component: 'MyComponent',
location: 'filename.js:1',
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
component: 'MyOtherComponent',
location: 'filename2.js:1',
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
stack: [
+12 -10
View File
@@ -42,12 +42,7 @@ export type Message = $ReadOnly<{|
>,
|}>;
export type ComponentStack = $ReadOnlyArray<
$ReadOnly<{|
component: string,
location: string,
|}>,
>;
export type ComponentStack = $ReadOnlyArray<CodeFrame>;
const SUBSTITUTION = UTFSequence.BOM + '%s';
@@ -127,6 +122,7 @@ export function parseCategory(
},
};
}
export function parseComponentStack(message: string): ComponentStack {
return message
.split(/\n {4}in /g)
@@ -134,11 +130,17 @@ export function parseComponentStack(message: string): ComponentStack {
if (!s) {
return null;
}
let [component, location] = s.split(/ \(at /);
if (!location) {
[component, location] = s.split(/ \(/);
const match = s.match(/(.*) \(at (.*\.js):([\d]+)\)/);
if (!match) {
return null;
}
return {component, location: location && location.replace(')', '')};
let [content, fileName, row] = match.slice(1);
return {
content,
fileName,
location: {column: -1, row: parseInt(row, 10)},
};
})
.filter(Boolean);
}
+28 -98
View File
@@ -10,26 +10,14 @@
'use strict';
import * as React from 'react';
import Platform from '../Utilities/Platform';
import RCTLog from '../Utilities/RCTLog';
import LogBoxContainer from './UI/LogBoxContainer';
import * as LogBoxData from './Data/LogBoxData';
import {parseLogBoxLog} from './Data/parseLogBoxLog';
import type {LogBoxLogs, Subscription, IgnorePattern} from './Data/LogBoxData';
import type {IgnorePattern} from './Data/LogBoxData';
import LogBoxLog from './Data/LogBoxLog';
type Props = $ReadOnly<{||}>;
type State = {|
logs: LogBoxLogs,
isDisabled: boolean,
hasError: boolean,
selectedLogIndex: number,
|};
let LogBoxComponent;
let LogBox;
/**
* LogBox displays logs in the app.
@@ -48,25 +36,26 @@ if (__DEV__) {
warnImpl(...args);
};
LogBoxComponent = class LogBox extends React.Component<Props, State> {
static getDerivedStateFromError() {
return {hasError: true};
}
componentDidCatch(err, errorInfo) {
LogBoxData.reportLogBoxError(err, errorInfo.componentStack);
}
LogBox = {
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreWarnings: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
LogBox.ignoreLogs(patterns);
}
},
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
LogBoxData.addIgnorePatterns(patterns);
}
},
uninstall: (): void => {
errorImpl = error;
warnImpl = warn;
delete (console: any).disableLogBox;
},
install: (): void => {
// Trigger lazy initialization of module.
require('../NativeModules/specs/NativeLogBox');
static install(): void {
errorImpl = function(...args) {
registerError(...args);
};
@@ -92,62 +81,7 @@ if (__DEV__) {
RCTLog.setWarningHandler((...args) => {
registerWarning(...args);
});
}
static uninstall(): void {
errorImpl = error;
warnImpl = warn;
delete (console: any).disableLogBox;
}
_subscription: ?Subscription;
state = {
logs: new Set(),
isDisabled: false,
hasError: false,
selectedLogIndex: -1,
};
render(): React.Node {
if (this.state.hasError) {
// This happens when the component failed to render, in which case we delegate to the native redbox.
// We can't show anyback fallback UI here, because the error may be with <View> or <Text>.
return null;
}
return this.state.logs == null ? null : (
<LogBoxContainer
onDismiss={this._handleDismiss}
onDismissWarns={LogBoxData.clearWarnings}
onDismissErrors={LogBoxData.clearErrors}
logs={this.state.logs}
isDisabled={this.state.isDisabled}
selectedLogIndex={this.state.selectedLogIndex}
setSelectedLog={this._handleSetSelectedLog}
/>
);
}
componentDidMount(): void {
this._subscription = LogBoxData.observe(data => {
this.setState(data);
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss(log: LogBoxLog): void {
LogBoxData.dismiss(log);
}
_handleSetSelectedLog(index: number): void {
LogBoxData.setSelectedLog(index);
}
},
};
const isRCTLogAdviceWarning = (...args) => {
@@ -227,31 +161,27 @@ if (__DEV__) {
}
};
} else {
LogBoxComponent = class extends React.Component<Props, State> {
LogBox = {
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreWarnings: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
// Do nothing.
}
},
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
// Do nothing.
}
},
static install(): void {
install: (): void => {
// Do nothing.
}
},
static uninstall(): void {
uninstall: (): void => {
// Do nothing.
}
render(): React.Node {
return null;
}
},
};
}
module.exports = (LogBoxComponent: Class<React.Component<Props, State>> & {
module.exports = (LogBox: {
// TODO: deprecated, replace with ignoreLogs
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
ignoreLogs($ReadOnlyArray<IgnorePattern>): void,
@@ -0,0 +1,86 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import * as React from 'react';
import {View, StyleSheet} from 'react-native';
import * as LogBoxData from './Data/LogBoxData';
import LogBoxInspector from './UI/LogBoxInspector';
import type LogBoxLog from './Data/LogBoxLog';
import NativeLogBox from '../NativeModules/specs/NativeLogBox';
type Props = $ReadOnly<{|
logs: $ReadOnlyArray<LogBoxLog>,
selectedLogIndex: number,
isDisabled?: ?boolean,
|}>;
function NativeLogBoxVisibility(props) {
React.useLayoutEffect(() => {
if (NativeLogBox) {
if (props.visible) {
// Schedule this to try and prevent flashing the old state.
setTimeout(() => NativeLogBox.show(), 10);
} else {
NativeLogBox.hide();
}
}
}, [props.visible]);
return props.children;
}
export class _LogBoxInspectorContainer extends React.Component<Props> {
render(): React.Node {
return (
<NativeLogBoxVisibility visible={this.props.selectedLogIndex >= 0}>
<View style={StyleSheet.absoluteFill}>
<LogBoxInspector
onDismiss={this._handleDismiss}
onMinimize={this._handleMinimize}
onChangeSelectedIndex={this._handleSetSelectedLog}
logs={this.props.logs}
selectedIndex={this.props.selectedLogIndex}
/>
</View>
</NativeLogBoxVisibility>
);
}
_handleDismiss = (): void => {
// Here we handle the cases when the log is dismissed and it
// was either the last log, or when the current index
// is now outside the bounds of the log array.
const {selectedLogIndex, logs} = this.props;
const logsArray = Array.from(logs);
if (selectedLogIndex != null) {
if (logsArray.length - 1 <= 0) {
LogBoxData.setSelectedLog(-1);
} else if (selectedLogIndex >= logsArray.length - 1) {
LogBoxData.setSelectedLog(selectedLogIndex - 1);
}
LogBoxData.dismiss(logsArray[selectedLogIndex]);
}
};
_handleMinimize = (): void => {
LogBoxData.setSelectedLog(-1);
};
_handleSetSelectedLog = (index: number): void => {
LogBoxData.setSelectedLog(index);
};
}
export default (LogBoxData.withSubscription(
_LogBoxInspectorContainer,
): React.AbstractComponent<{||}>);
@@ -11,46 +11,31 @@
'use strict';
import * as React from 'react';
import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
import StyleSheet from '../../StyleSheet/StyleSheet';
import View from '../../Components/View/View';
import LogBoxInspector from './LogBoxInspector';
import LogBoxLog from '../Data/LogBoxLog';
import LogBoxLogNotification from './LogBoxLogNotification';
import type {LogBoxLogs} from '../Data/LogBoxData';
import StyleSheet from '../StyleSheet/StyleSheet';
import View from '../Components/View/View';
import * as LogBoxData from './Data/LogBoxData';
import LogBoxLog from './Data/LogBoxLog';
import LogBoxLogNotification from './UI/LogBoxNotification';
type Props = $ReadOnly<{|
onDismiss: (log: LogBoxLog) => void,
onDismissWarns: () => void,
onDismissErrors: () => void,
setSelectedLog: number => void,
logs: LogBoxLogs,
logs: $ReadOnlyArray<LogBoxLog>,
selectedLogIndex: number,
isDisabled?: ?boolean,
|}>;
function LogBoxContainer(props: Props): React.Node {
const {selectedLogIndex, setSelectedLog} = props;
export function _LogBoxNotificationContainer(props: Props): React.Node {
const {logs} = props;
const logs = Array.from(props.logs);
const onDismissWarns = () => {
LogBoxData.clearWarnings();
};
const onDismissErrors = () => {
LogBoxData.clearErrors();
};
function handleInspectorDismiss() {
// Here we handle the cases when the log is dismissed and it
// was either the last log, or when the current index
// is now outside the bounds of the log array.
if (selectedLogIndex != null) {
if (logs.length - 1 <= 0) {
setSelectedLog(-1);
} else if (selectedLogIndex >= logs.length - 1) {
setSelectedLog(selectedLogIndex - 1);
}
props.onDismiss(logs[selectedLogIndex]);
}
}
function handleInspectorMinimize() {
setSelectedLog(-1);
}
const setSelectedLog = (index: number): void => {
LogBoxData.setSelectedLog(index);
};
function openLog(log: LogBoxLog) {
let index = logs.length - 1;
@@ -62,20 +47,6 @@ function LogBoxContainer(props: Props): React.Node {
setSelectedLog(index);
}
if (selectedLogIndex > -1) {
return (
<View style={StyleSheet.absoluteFill}>
<LogBoxInspector
onDismiss={handleInspectorDismiss}
onMinimize={handleInspectorMinimize}
onChangeSelectedIndex={setSelectedLog}
logs={logs}
selectedIndex={selectedLogIndex}
/>
</View>
);
}
if (logs.length === 0 || props.isDisabled === true) {
return null;
}
@@ -93,7 +64,7 @@ function LogBoxContainer(props: Props): React.Node {
level="warn"
totalLogCount={warnings.length}
onPressOpen={() => openLog(warnings[warnings.length - 1])}
onPressDismiss={props.onDismissWarns}
onPressDismiss={onDismissWarns}
/>
</View>
)}
@@ -104,7 +75,7 @@ function LogBoxContainer(props: Props): React.Node {
level="error"
totalLogCount={errors.length}
onPressOpen={() => openLog(errors[errors.length - 1])}
onPressDismiss={props.onDismissErrors}
onPressDismiss={onDismissErrors}
/>
</View>
)}
@@ -126,4 +97,6 @@ const styles = StyleSheet.create({
},
});
export default LogBoxContainer;
export default (LogBoxData.withSubscription(
_LogBoxNotificationContainer,
): React.AbstractComponent<{||}>);
+11 -11
View File
@@ -14,7 +14,7 @@ import LogBoxInspectorCodeFrame from './LogBoxInspectorCodeFrame';
import * as React from 'react';
import ScrollView from '../../Components/ScrollView/ScrollView';
import StyleSheet from '../../StyleSheet/StyleSheet';
import Modal from '../../Modal/Modal';
import View from '../../Components/View/View';
import * as LogBoxData from '../Data/LogBoxData';
import Keyboard from '../../Components/Keyboard/Keyboard';
import LogBoxInspectorFooter from './LogBoxInspectorFooter';
@@ -23,8 +23,7 @@ import LogBoxInspectorReactFrames from './LogBoxInspectorReactFrames';
import LogBoxInspectorStackFrames from './LogBoxInspectorStackFrames';
import LogBoxInspectorHeader from './LogBoxInspectorHeader';
import * as LogBoxStyle from './LogBoxStyle';
import type LogBoxLog, {LogLevel} from '../Data/LogBoxLog';
import LogBoxLog, {type LogLevel} from '../Data/LogBoxLog';
type Props = $ReadOnly<{|
onDismiss: () => void,
@@ -40,7 +39,9 @@ function LogBoxInspector(props: Props): React.Node {
const log = logs[selectedIndex];
React.useEffect(() => {
LogBoxData.symbolicateLogNow(log);
if (log) {
LogBoxData.symbolicateLogNow(log);
}
}, [log]);
React.useEffect(() => {
@@ -68,12 +69,7 @@ function LogBoxInspector(props: Props): React.Node {
}
return (
<Modal
animationType="none"
visible
statusBarTranslucent
supportedOrientations={['portrait']}
presentationStyle="overFullScreen">
<View style={styles.root}>
<LogBoxInspectorHeader
onSelectIndex={props.onChangeSelectedIndex}
selectedIndex={selectedIndex}
@@ -86,7 +82,7 @@ function LogBoxInspector(props: Props): React.Node {
onMinimize={props.onMinimize}
level={log.level}
/>
</Modal>
</View>
);
}
@@ -143,6 +139,10 @@ function LogBoxInspectorBody(props) {
}
const styles = StyleSheet.create({
root: {
flex: 1,
backgroundColor: LogBoxStyle.getTextColor(),
},
scrollBody: {
backgroundColor: LogBoxStyle.getBackgroundColor(0.9),
flex: 1,
+2 -2
View File
@@ -126,7 +126,7 @@ const headerStyles = StyleSheet.create({
borderRadius: 3,
},
buttonImage: {
tintColor: LogBoxStyle.getBackgroundColor(1),
tintColor: LogBoxStyle.getTextColor(),
},
});
@@ -156,7 +156,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
titleText: {
color: LogBoxStyle.getBackgroundColor(1),
color: LogBoxStyle.getTextColor(),
fontSize: 16,
fontWeight: '600',
includeFontPadding: false,
@@ -18,12 +18,37 @@ import View from '../../Components/View/View';
import LogBoxButton from './LogBoxButton';
import * as LogBoxStyle from './LogBoxStyle';
import LogBoxInspectorSection from './LogBoxInspectorSection';
import openFileInEditor from '../../Core/Devtools/openFileInEditor';
import type LogBoxLog from '../Data/LogBoxLog';
type Props = $ReadOnly<{|
log: LogBoxLog,
|}>;
const BEFORE_SLASH_RE = /^(.*)[\\/]/;
// Taken from React https://github.com/facebook/react/blob/206d61f72214e8ae5b935f0bf8628491cb7f0797/packages/react-devtools-shared/src/backend/describeComponentFrame.js#L27-L41
function getPrettyFileName(path) {
let fileName = path.replace(BEFORE_SLASH_RE, '');
// In DEV, include code for a common special case:
// prefer "folder/index.js" instead of just "index.js".
if (/^index\./.test(fileName)) {
const match = path.match(BEFORE_SLASH_RE);
if (match) {
const pathBeforeSlash = match[1];
if (pathBeforeSlash) {
const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
// Note the below string contains a zero width space after the "/" character.
// This is to prevent browsers like Chrome from formatting the file name as a link.
// (Since this is a source link, it would not work to open the source file anyway.)
fileName = folderName + '/' + fileName;
}
}
}
return fileName;
}
function LogBoxInspectorReactFrames(props: Props): React.Node {
const [collapsed, setCollapsed] = React.useState(true);
if (props.log.componentStack == null || props.log.componentStack.length < 1) {
@@ -39,6 +64,10 @@ function LogBoxInspectorReactFrames(props: Props): React.Node {
}
function getCollapseMessage() {
if (props.log.componentStack.length <= 3) {
return;
}
const count = props.log.componentStack.length - 3;
if (collapsed) {
return `See ${count} more components`;
@@ -53,15 +82,34 @@ function LogBoxInspectorReactFrames(props: Props): React.Node {
<View
// Unfortunately we don't have a unique identifier for stack traces.
key={index}
style={componentStyles.frame}>
<View style={componentStyles.component}>
<Text style={componentStyles.frameName}>
<Text style={componentStyles.bracket}>{'<'}</Text>
{frame.component}
<Text style={componentStyles.bracket}>{' />'}</Text>
style={componentStyles.frameContainer}>
<LogBoxButton
backgroundColor={{
default: 'transparent',
pressed: LogBoxStyle.getBackgroundColor(1),
}}
onPress={
// Older versions of DevTools do not provide full path.
// This will not work on Windows, remove check once the
// DevTools return the full file path.
frame.fileName.startsWith('/')
? () =>
openFileInEditor(frame.fileName, frame.location?.row ?? 1)
: null
}
style={componentStyles.frame}>
<View style={componentStyles.component}>
<Text style={componentStyles.frameName}>
<Text style={componentStyles.bracket}>{'<'}</Text>
{frame.content}
<Text style={componentStyles.bracket}>{' />'}</Text>
</Text>
</View>
<Text style={componentStyles.frameLocation}>
{getPrettyFileName(frame.fileName)}
{frame.location ? `:${frame.location.row}` : ''}
</Text>
</View>
<Text style={componentStyles.frameLocation}>{frame.location}</Text>
</LogBoxButton>
</View>
))}
<View style={componentStyles.collapseContainer}>
@@ -96,9 +144,15 @@ const componentStyles = StyleSheet.create({
paddingVertical: 5,
paddingHorizontal: 10,
},
frameContainer: {
flexDirection: 'row',
paddingHorizontal: 15,
},
frame: {
paddingHorizontal: 25,
flex: 1,
paddingVertical: 4,
paddingHorizontal: 10,
borderRadius: 5,
},
component: {
flexDirection: 'row',
@@ -1,254 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @emails oncall+react_native
* @flow strict-local
*/
'use strict';
const React = require('react');
const LogBoxContainer = require('../LogBoxContainer').default;
const LogBoxLog = require('../../Data/LogBoxLog').default;
const render = require('../../../../jest/renderer');
describe('LogBoxContainer', () => {
it('should render null with no logs', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={new Set()}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render null with no selected log and disabled', () => {
const output = render.shallowRender(
<LogBoxContainer
isDisabled
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest warning notification', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest error notification', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render both an error and warning notification', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected fatal error even when disabled', () => {
const output = render.shallowRender(
<LogBoxContainer
isDisabled
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={0}
logs={
new Set([
new LogBoxLog({
level: 'fatal',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected syntax error even when disabled', () => {
const output = render.shallowRender(
<LogBoxContainer
isDisabled
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={0}
logs={
new Set([
new LogBoxLog({
level: 'syntax',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of syntax error message',
componentStack: [],
codeFrame: {
fileName:
'/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
content: ` 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |`,
},
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -40,7 +40,7 @@ describe('LogBoxInspectorReactFrames', () => {
expect(output).toMatchSnapshot();
});
it('should render componentStack frames', () => {
it('should render componentStack frames without full path pressable', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
@@ -55,8 +55,129 @@ describe('LogBoxInspectorReactFrames', () => {
category: 'Some kind of message',
componentStack: [
{
component: 'MyComponent',
location: 'MyComponentFile.js:1',
content: 'MyComponent',
fileName: 'MyComponentFile.js',
location: {
row: 1,
column: -1,
},
},
],
})
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render componentStack frames with full path pressable', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [
{
content: 'MyComponent',
fileName: '/path/to/MyComponentFile.js',
location: {
row: 1,
column: -1,
},
},
],
})
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render componentStack frames with parent folder of index.js', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [
{
content: 'MyComponent',
fileName: '/path/to/index.js',
location: {
row: 1,
column: -1,
},
},
],
})
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render componentStack frames with more than 3 stacks', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [
{
content: 'MyComponent',
fileName: '/path/to/index.js',
location: {
row: 1,
column: -1,
},
},
{
content: 'MyComponent2',
fileName: '/path/to/index2.js',
location: {
row: 1,
column: -1,
},
},
{
content: 'MyComponent3',
fileName: '/path/to/index3.js',
location: {
row: 1,
column: -1,
},
},
{
content: 'MyComponent4',
fileName: '/path/to/index4.js',
location: {
row: 1,
column: -1,
},
},
],
})
@@ -12,7 +12,7 @@
'use strict';
const React = require('react');
const LogBoxLogNotification = require('../LogBoxLogNotification').default;
const LogBoxNotification = require('../LogBoxNotification').default;
const LogBoxLog = require('../../Data/LogBoxLog').default;
const render = require('../../../../jest/renderer');
@@ -28,10 +28,10 @@ const log = new LogBoxLog({
componentStack: [],
});
describe('LogBoxLogNotification', () => {
describe('LogBoxNotification', () => {
it('should render log', () => {
const output = render.shallowRender(
<LogBoxLogNotification
<LogBoxNotification
log={log}
totalLogCount={1}
level="warn"
@@ -1,17 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxContainer should render fatal with selectedIndex 2 1`] = `
<Component
animationType="none"
hardwareAccelerated={false}
presentationStyle="overFullScreen"
statusBarTranslucent={true}
supportedOrientations={
Array [
"portrait",
]
<View
style={
Object {
"backgroundColor": "rgba(255, 255, 255, 1)",
"flex": 1,
}
}
visible={true}
>
<LogBoxInspectorHeader
level="fatal"
@@ -47,23 +43,19 @@ exports[`LogBoxContainer should render fatal with selectedIndex 2 1`] = `
onDismiss={[Function]}
onMinimize={[Function]}
/>
</Component>
</View>
`;
exports[`LogBoxContainer should render null with no logs 1`] = `null`;
exports[`LogBoxContainer should render warning with selectedIndex 0 1`] = `
<Component
animationType="none"
hardwareAccelerated={false}
presentationStyle="overFullScreen"
statusBarTranslucent={true}
supportedOrientations={
Array [
"portrait",
]
<View
style={
Object {
"backgroundColor": "rgba(255, 255, 255, 1)",
"flex": 1,
}
}
visible={true}
>
<LogBoxInspectorHeader
level="warn"
@@ -99,5 +91,5 @@ exports[`LogBoxContainer should render warning with selectedIndex 0 1`] = `
onDismiss={[Function]}
onMinimize={[Function]}
/>
</Component>
</View>
`;
@@ -39,7 +39,7 @@ exports[`LogBoxInspectorHeader should render both buttons for two total 1`] = `
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -99,7 +99,7 @@ exports[`LogBoxInspectorHeader should render no buttons for one total 1`] = `
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -153,7 +153,7 @@ exports[`LogBoxInspectorHeader should render syntax error header 1`] = `
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -207,7 +207,7 @@ exports[`LogBoxInspectorHeader should render two buttons for three or more total
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -1,81 +1,420 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxInspectorReactFrames should render componentStack frames 1`] = `
exports[`LogBoxInspectorReactFrames should render componentStack frames with full path pressable 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"paddingHorizontal": 25,
"paddingVertical": 4,
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<View
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<Text
<View
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
}
>
/&gt;
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
MyComponentFile.js
:1
</Text>
</View>
<Text
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"marginLeft": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
"borderRadius": 5,
}
}
>
MyComponentFile.js:1
</Text>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 20,
"marginTop": 0,
"paddingHorizontal": 10,
"paddingVertical": 5,
}
}
/>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render componentStack frames with more than 3 stacks 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
to/index.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent2
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
index2.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent3
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
index3.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
@@ -112,11 +451,283 @@ exports[`LogBoxInspectorReactFrames should render componentStack frames 1`] = `
}
}
>
See -2 more components
See 1 more components
</Text>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render componentStack frames with parent folder of index.js 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
to/index.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"marginLeft": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 20,
"marginTop": 0,
"paddingHorizontal": 10,
"paddingVertical": 5,
}
}
/>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render componentStack frames without full path pressable 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={null}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
MyComponentFile.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"marginLeft": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 20,
"marginTop": 0,
"paddingHorizontal": 10,
"paddingVertical": 5,
}
}
/>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render null for no componentStack frames 1`] = `null`;
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxLogNotification should render log 1`] = `
exports[`LogBoxNotification should render log 1`] = `
<View
style={
Object {
@@ -0,0 +1,212 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @emails oncall+react_native
* @flow
*/
'use strict';
const React = require('react');
const {
_LogBoxNotificationContainer: LogBoxNotificationContainer,
} = require('../LogBoxNotificationContainer');
const LogBoxLog = require('../Data/LogBoxLog').default;
const render = require('../../../jest/renderer');
describe('LogBoxNotificationContainer', () => {
it('should render null with no logs', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer selectedLogIndex={-1} logs={[]} />,
);
expect(output).toMatchSnapshot();
});
it('should render null with no selected log and disabled', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
isDisabled
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest warning notification', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest error notification', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render both an error and warning notification', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected fatal error even when disabled', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
isDisabled
selectedLogIndex={0}
logs={[
new LogBoxLog({
level: 'fatal',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected syntax error even when disabled', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
isDisabled
selectedLogIndex={0}
logs={[
new LogBoxLog({
level: 'syntax',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of syntax error message',
componentStack: [],
codeFrame: {
fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
content: ` 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |`,
},
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -0,0 +1,56 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @emails oncall+react_native
* @flow strict-local
*/
'use strict';
const React = require('react');
const {
_LogBoxInspectorContainer: LogBoxInspectorContainer,
} = require('../LogBoxInspectorContainer');
const LogBoxLog = require('../Data/LogBoxLog').default;
const render = require('../../../jest/renderer');
describe('LogBoxNotificationContainer', () => {
it('should render inspector with logs, even when disabled', () => {
const output = render.shallowRender(
<LogBoxInspectorContainer
isDisabled
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxContainer should render both an error and warning notification 1`] = `
exports[`LogBoxNotificationContainer should render both an error and warning notification 1`] = `
<View
style={
Object {
@@ -86,108 +86,15 @@ exports[`LogBoxContainer should render both an error and warning notification 1`
</View>
`;
exports[`LogBoxContainer should render null with no logs 1`] = `null`;
exports[`LogBoxNotificationContainer should render null with no logs 1`] = `null`;
exports[`LogBoxContainer should render null with no selected log and disabled 1`] = `null`;
exports[`LogBoxNotificationContainer should render null with no selected log and disabled 1`] = `null`;
exports[`LogBoxContainer should render selected fatal error even when disabled 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
logs={
Array [
LogBoxLog {
"category": "Some kind of message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "fatal",
"message": Object {
"content": "Should be selected",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
]
}
onChangeSelectedIndex={[Function]}
onDismiss={[Function]}
onMinimize={[Function]}
selectedIndex={0}
/>
</View>
`;
exports[`LogBoxNotificationContainer should render selected fatal error even when disabled 1`] = `null`;
exports[`LogBoxContainer should render selected syntax error even when disabled 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
logs={
Array [
LogBoxLog {
"category": "Some kind of syntax error message",
"codeFrame": Object {
"content": " 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |",
"fileName": "/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js",
"location": Object {
"column": 0,
"row": 199,
},
},
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "syntax",
"message": Object {
"content": "Should be selected",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
]
}
onChangeSelectedIndex={[Function]}
onDismiss={[Function]}
onMinimize={[Function]}
selectedIndex={0}
/>
</View>
`;
exports[`LogBoxNotificationContainer should render selected syntax error even when disabled 1`] = `null`;
exports[`LogBoxContainer should render the latest error notification 1`] = `
exports[`LogBoxNotificationContainer should render the latest error notification 1`] = `
<View
style={
Object {
@@ -237,7 +144,7 @@ exports[`LogBoxContainer should render the latest error notification 1`] = `
</View>
`;
exports[`LogBoxContainer should render the latest warning notification 1`] = `
exports[`LogBoxNotificationContainer should render the latest warning notification 1`] = `
<View
style={
Object {
@@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxNotificationContainer should render inspector with logs, even when disabled 1`] = `
<NativeLogBoxVisibility
visible={false}
>
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
logs={
Array [
LogBoxLog {
"category": "Some kind of message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "warn",
"message": Object {
"content": "Some kind of message",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
LogBoxLog {
"category": "Some kind of message (latest)",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "error",
"message": Object {
"content": "Some kind of message (latest)",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
]
}
onChangeSelectedIndex={[Function]}
onDismiss={[Function]}
onMinimize={[Function]}
selectedIndex={-1}
/>
</View>
</NativeLogBoxVisibility>
`;
+1 -1
View File
@@ -229,7 +229,7 @@ class Modal extends React.Component<Props> {
}
const innerChildren = __DEV__ ? (
<AppContainer rootTag={this.context.rootTag} internal_excludeLogBox>
<AppContainer rootTag={this.context.rootTag}>
{this.props.children}
</AppContainer>
) : (
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+show: () => void;
+hide: () => void;
}
export default (TurboModuleRegistry.get<Spec>('LogBox'): ?Spec);
+11 -27
View File
@@ -94,16 +94,14 @@ class AppContainer extends React.Component<Props, State> {
}
render(): React.Node {
let logBox = null;
if (__DEV__ && !this.props.internal_excludeLogBox) {
if (!global.__RCTProfileIsProfiling) {
if (global.__reactExperimentalLogBox) {
const LogBox = require('../LogBox/LogBox');
logBox = <LogBox />;
} else {
const YellowBox = require('../YellowBox/YellowBox');
logBox = <YellowBox />;
}
let yellowBox = null;
if (__DEV__) {
if (
!global.__RCTProfileIsProfiling &&
!this.props.internal_excludeLogBox
) {
const YellowBox = require('../YellowBox/YellowBox');
yellowBox = <YellowBox />;
}
}
@@ -137,7 +135,7 @@ class AppContainer extends React.Component<Props, State> {
<View style={styles.appContainer} pointerEvents="box-none">
{!this.state.hasError && innerView}
{this.state.inspector}
{logBox}
{yellowBox}
</View>
</RootTagContext.Provider>
);
@@ -152,22 +150,8 @@ const styles = StyleSheet.create({
if (__DEV__) {
if (!global.__RCTProfileIsProfiling) {
if (global.__reactExperimentalLogBox) {
const LogBox = require('../LogBox/LogBox');
LogBox.install();
// TODO: (rickhanlonii) T57484314 Temporary hack to fix LogBox experiment but we need to
// either decide to provide an error boundary by default or move this to a separate root.
AppContainer.getDerivedStateFromError = function getDerivedStateFromError(
error,
state,
) {
return {...state, hasError: true};
};
} else {
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
}
+15 -7
View File
@@ -125,6 +125,7 @@ const AppRegistry = {
appParameters.fabric,
showArchitectureIndicator,
scopedPerformanceLogger,
appKey === 'LogBox',
);
},
};
@@ -180,13 +181,15 @@ const AppRegistry = {
* See http://facebook.github.io/react-native/docs/appregistry.html#runapplication
*/
runApplication(appKey: string, appParameters: any): void {
const msg =
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
if (appKey !== 'LogBox') {
const msg =
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
}
invariant(
runnables[appKey] && runnables[appKey].run,
`"${appKey}" has not been registered. This can happen if:\n` +
@@ -292,4 +295,9 @@ const AppRegistry = {
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);
if (__DEV__) {
const LogBoxInspector = require('../LogBox/LogBoxInspectorContainer').default;
AppRegistry.registerComponent('LogBox', () => LogBoxInspector);
}
module.exports = AppRegistry;
+3 -1
View File
@@ -29,6 +29,7 @@ function renderApplication<Props: Object>(
fabric?: boolean,
showArchitectureIndicator?: boolean,
scopedPerformanceLogger?: IPerformanceLogger,
isLogBox?: boolean,
) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
@@ -39,7 +40,8 @@ function renderApplication<Props: Object>(
rootTag={rootTag}
fabric={fabric}
showArchitectureIndicator={showArchitectureIndicator}
WrapperComponent={WrapperComponent}>
WrapperComponent={WrapperComponent}
internal_excludeLogBox={isLogBox}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>
</PerformanceLoggerContext.Provider>
+44 -37
View File
@@ -12,12 +12,7 @@
const React = require('react');
import type {Category} from './Data/YellowBoxCategory';
import type {
Registry,
Subscription,
IgnorePattern,
} from './Data/YellowBoxRegistry';
import type {Registry, IgnorePattern} from './Data/YellowBoxRegistry';
import * as LogBoxData from '../LogBox/Data/LogBoxData';
type Props = $ReadOnly<{||}>;
type State = {|
@@ -47,14 +42,19 @@ let YellowBox;
if (__DEV__) {
const Platform = require('../Utilities/Platform');
const RCTLog = require('../Utilities/RCTLog');
const YellowBoxList = require('./UI/YellowBoxList');
const YellowBoxContainer = require('./YellowBoxContainer').default;
const LogBox = require('../LogBox/LogBox');
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');
const LogBoxNotificationContainer = require('../LogBox/LogBoxNotificationContainer')
.default;
// YellowBox needs to insert itself early,
// in order to access the component stacks appended by React DevTools.
const {error, warn} = console;
let errorImpl = error;
let warnImpl = warn;
let _isLogBoxEnabled = false;
let _isInstalled = false;
(console: any).error = function(...args) {
errorImpl(...args);
};
@@ -70,6 +70,11 @@ if (__DEV__) {
}
static install(): void {
_isInstalled = true;
if (_isLogBoxEnabled) {
LogBox.install();
return;
}
errorImpl = function(...args) {
error.call(console, ...args);
// Show YellowBox for the `warning` module.
@@ -102,46 +107,39 @@ if (__DEV__) {
}
static uninstall(): void {
if (_isLogBoxEnabled) {
LogBox.uninstall();
return;
}
_isInstalled = false;
errorImpl = error;
warnImpl = warn;
delete (console: any).disableYellowBox;
}
_subscription: ?Subscription;
static __unstable_enableLogBox(): void {
if (_isInstalled) {
throw new Error(
'LogBox must be enabled before AppContainer is required so that it can properly wrap the console methods.\n\nPlease enable LogBox earlier in your app.\n\n',
);
}
_isLogBoxEnabled = true;
state = {
registry: null,
};
// TODO: Temporary hack to prevent cycles with the ExceptionManager.
global.__unstable_isLogBoxEnabled = true;
}
static __unstable_isLogBoxEnabled(): boolean {
return !!_isLogBoxEnabled;
}
render(): React.Node {
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return this.state.registry == null ? null : (
<YellowBoxList
onDismiss={this._handleDismiss}
onDismissAll={this._handleDismissAll}
registry={this.state.registry}
/>
);
}
componentDidMount(): void {
this._subscription = YellowBoxRegistry.observe(registry => {
this.setState({registry});
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
if (_isLogBoxEnabled) {
return <LogBoxNotificationContainer />;
}
}
_handleDismiss = (category: Category): void => {
YellowBoxRegistry.delete(category);
};
_handleDismissAll(): void {
YellowBoxRegistry.clear();
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return <YellowBoxContainer />;
}
};
@@ -162,6 +160,13 @@ if (__DEV__) {
// Do nothing.
}
static __unstable_enableLogBox(): void {
// Do nothing.
}
static __unstable_isLogBoxEnabled(): boolean {
return false;
}
render(): React.Node {
return null;
}
@@ -172,5 +177,7 @@ module.exports = (YellowBox: Class<React.Component<Props, State>> & {
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
install(): void,
uninstall(): void,
__unstable_enableLogBox(): void,
__unstable_isLogBoxEnabled(): boolean,
...
});
+65
View File
@@ -0,0 +1,65 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const React = require('react');
import type {Category} from './Data/YellowBoxCategory';
import type {Registry, Subscription} from './Data/YellowBoxRegistry';
type Props = $ReadOnly<{||}>;
type State = $ReadOnly<{|
registry: ?Registry,
|}>;
const YellowBoxList = require('./UI/YellowBoxList');
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');
class YellowBoxContainer extends React.Component<Props, State> {
_subscription: ?Subscription;
state: State = {
registry: null,
};
render(): React.Node {
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return this.state.registry == null ? null : (
<YellowBoxList
onDismiss={this._handleDismiss}
onDismissAll={this._handleDismissAll}
registry={this.state.registry}
/>
);
}
componentDidMount(): void {
this._subscription = YellowBoxRegistry.observe(registry => {
this.setState({registry});
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss = (category: Category): void => {
YellowBoxRegistry.delete(category);
};
_handleDismissAll(): void {
YellowBoxRegistry.clear();
}
}
export default YellowBoxContainer;
@@ -11,8 +11,16 @@
'use strict';
import * as React from 'react';
const YellowBox = require('../YellowBox');
const YellowBoxRegistry = require('../Data/YellowBoxRegistry');
const LogBoxData = require('../../LogBox/Data/LogBoxData');
const render = require('../../../jest/renderer');
jest.mock('../../LogBox/LogBoxNotificationContainer', () => ({
__esModule: true,
default: 'LogBoxNotificationContainer',
}));
describe('YellowBox', () => {
const {error, warn} = console;
@@ -74,4 +82,49 @@ describe('YellowBox', () => {
(console: any).error('Warning: ...');
expect(YellowBoxRegistry.add).toBeCalled();
});
it('if LogBox is enabled, installs and uninstalls LogBox', () => {
jest.mock('../../LogBox/Data/LogBoxData');
jest.mock('../Data/YellowBoxRegistry');
YellowBox.__unstable_enableLogBox();
YellowBox.install();
(console: any).warn('Some warning');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(LogBoxData.addLog).toBeCalled();
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);
YellowBox.uninstall();
(LogBoxData.addLog: any).mockClear();
(console: any).warn('Some warning');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(LogBoxData.addLog).not.toBeCalled();
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);
});
it('throws if LogBox is enabled after YellowBox is installed', () => {
jest.mock('../Data/YellowBoxRegistry');
YellowBox.install();
expect(() => YellowBox.__unstable_enableLogBox()).toThrow(
'LogBox must be enabled before AppContainer is required so that it can properly wrap the console methods.\n\nPlease enable LogBox earlier in your app.\n\n',
);
});
it('should render YellowBoxContainer by default', () => {
const output = render.shallowRender(<YellowBox />);
expect(output).toMatchSnapshot();
});
it('should render LogBoxNotificationContainer when LogBox is enabled', () => {
YellowBox.__unstable_enableLogBox();
const output = render.shallowRender(<YellowBox />);
expect(output).toMatchSnapshot();
});
});
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`YellowBox should render LogBoxNotificationContainer when LogBox is enabled 1`] = `<LogBoxNotificationContainer />`;
exports[`YellowBox should render YellowBoxContainer by default 1`] = `<YellowBoxContainer />`;
+11
View File
@@ -0,0 +1,11 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDefines.h>
RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotification;
RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey;
+11
View File
@@ -0,0 +1,11 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTConstants.h"
NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey = @"traitCollection";
+6 -11
View File
@@ -14,6 +14,7 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTConstants.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
@@ -33,7 +34,6 @@
#endif
NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
static NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
@interface RCTUIManager (RCTRootView)
@@ -367,21 +367,16 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[self showLoadingView];
}
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if ([previousTraitCollection hasDifferentColorAppearanceComparedToTraitCollection:self.traitCollection]) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{@"traitCollection": self.traitCollection}];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{
RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey: self.traitCollection,
}];
}
#endif
- (void)dealloc
{
+5 -5
View File
@@ -21,11 +21,11 @@ NSDictionary* RCTGetReactNativeVersion(void)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^(void){
__rnVersion = @{
RCTVersionMajor: @(0),
RCTVersionMinor: @(0),
RCTVersionPatch: @(0),
RCTVersionPrerelease: [NSNull null],
};
RCTVersionMajor: @(0),
RCTVersionMinor: @(62),
RCTVersionPatch: @(0),
RCTVersionPrerelease: @"rc.0",
};
});
return __rnVersion;
}
@@ -7,14 +7,13 @@
#import "RCTSurfaceHostingView.h"
#import "RCTConstants.h"
#import "RCTDefines.h"
#import "RCTSurface.h"
#import "RCTSurfaceDelegate.h"
#import "RCTSurfaceView.h"
#import "RCTUtils.h"
static NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
@interface RCTSurfaceHostingView ()
@property (nonatomic, assign) BOOL isActivityIndicatorViewVisible;
@@ -208,21 +207,15 @@ RCT_NOT_IMPLEMENTED(- (nullable instancetype)initWithCoder:(NSCoder *)coder)
#pragma mark - UITraitCollection updates
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if ([previousTraitCollection hasDifferentColorAppearanceComparedToTraitCollection:self.traitCollection]) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{@"traitCollection": self.traitCollection}];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{
RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey: self.traitCollection,
}];
}
#endif
#pragma mark - Private stuff
+3
View File
@@ -96,6 +96,9 @@ rn_apple_library(
) + react_module_plugin_providers(
name = "RedBox",
native_class_func = "RCTRedBoxCls",
) + react_module_plugin_providers(
name = "LogBox",
native_class_func = "RCTLogBoxCls",
) + react_module_plugin_providers(
name = "TVNavigationEventEmitter",
native_class_func = "RCTTVNavigationEventEmitterCls",
+1
View File
@@ -48,6 +48,7 @@ Class RCTPerfMonitorCls(void) __attribute__((used));
Class RCTDevMenuCls(void) __attribute__((used));
Class RCTDevSettingsCls(void) __attribute__((used));
Class RCTRedBoxCls(void) __attribute__((used));
Class RCTLogBoxCls(void) __attribute__((used));
Class RCTTVNavigationEventEmitterCls(void) __attribute__((used));
Class RCTWebSocketExecutorCls(void) __attribute__((used));
Class RCTWebSocketModuleCls(void) __attribute__((used));
+1
View File
@@ -37,6 +37,7 @@ Class RCTCoreModulesClassProvider(const char *name) {
{"DevMenu", RCTDevMenuCls},
{"DevSettings", RCTDevSettingsCls},
{"RedBox", RCTRedBoxCls},
{"LogBox", RCTLogBoxCls},
{"TVNavigationEventEmitter", RCTTVNavigationEventEmitterCls},
{"WebSocketExecutor", RCTWebSocketExecutorCls},
{"WebSocketModule", RCTWebSocketModuleCls},
-2
View File
@@ -12,7 +12,5 @@
RCT_EXTERN void RCTEnableAppearancePreference(BOOL enabled);
NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
@interface RCTAppearance : RCTEventEmitter <RCTBridgeModule>
@end
+2 -1
View File
@@ -8,6 +8,7 @@
#import "RCTAppearance.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTConstants.h>
#import <React/RCTEventEmitter.h>
#import "CoreModulesPlugins.h"
@@ -86,7 +87,7 @@ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getColorScheme)
NSDictionary *userInfo = [notification userInfo];
UITraitCollection *traitCollection = nil;
if (userInfo) {
traitCollection = userInfo[@"traitCollection"];
traitCollection = userInfo[RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey];
}
NSString *newColorScheme = RCTColorSchemePreference(traitCollection);
if (![_currentColorScheme isEqualToString:newColorScheme]) {
+10 -3
View File
@@ -10,6 +10,7 @@
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTAccessibilityManager.h>
#import <React/RCTAssert.h>
#import <React/RCTConstants.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTUIUtils.h>
#import <React/RCTUtils.h>
@@ -61,9 +62,15 @@ RCT_EXPORT_MODULE()
_currentInterfaceDimensions = RCTExportedDimensions(_bridge);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(interfaceFrameDidChange)
name:UIApplicationDidBecomeActiveNotification
object:nil];
selector:@selector(interfaceFrameDidChange)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(interfaceFrameDidChange)
name:RCTUserInterfaceStyleDidChangeNotification
object:nil];
#endif
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTErrorCustomizer.h>
@class RCTJSStackFrame;
@interface RCTLogBox : NSObject <RCTBridgeModule>
- (void)show;
- (void)hide;
@end
+162
View File
@@ -0,0 +1,162 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTLogBox.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <React/RCTErrorInfo.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTJSStackFrame.h>
#import <React/RCTReloadCommand.h>
#import <React/RCTUtils.h>
#import <objc/runtime.h>
#import "CoreModulesPlugins.h"
#if RCT_DEV_MENU
@class RCTLogBoxWindow;
@protocol RCTLogBoxWindowActionDelegate <NSObject>
- (void)logBoxWindow:(RCTLogBoxWindow *)logBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame;
- (void)reloadFromlogBoxWindow:(RCTLogBoxWindow *)logBoxWindow;
- (void)loadExtraDataViewController;
@end
@interface RCTLogBoxWindow : UIWindow <UITableViewDelegate>
@property (nonatomic, weak) id<RCTLogBoxWindowActionDelegate> actionDelegate;
@end
@implementation RCTLogBoxWindow
{
UITableView *_stackTraceTableView;
NSString *_lastErrorMessage;
NSArray<RCTJSStackFrame *> *_lastStackTrace;
int _lastErrorCookie;
}
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge
{
_lastErrorCookie = -1;
if ((self = [super initWithFrame:frame])) {
#if TARGET_OS_TV
self.windowLevel = UIWindowLevelAlert + 1000;
#else
self.windowLevel = UIWindowLevelStatusBar - 1;
#endif
self.backgroundColor = [UIColor clearColor];
self.hidden = YES;
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:nil];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.rootViewController = rootViewController;
}
return self;
}
- (void)show
{
[self becomeFirstResponder];
[self makeKeyAndVisible];
}
- (void)dismiss
{
self.hidden = YES;
[self resignFirstResponder];
[RCTSharedApplication().delegate.window makeKeyWindow];
}
@end
@interface RCTLogBox () <NativeLogBoxSpec>
@end
@implementation RCTLogBox
{
RCTLogBoxWindow *_window;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
dispatch_async(dispatch_get_main_queue(), ^{
self->_window = [[RCTLogBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds bridge: self->_bridge];
});
}
RCT_EXPORT_METHOD(show)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self->_window show];
});
}
RCT_EXPORT_METHOD(hide)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self->_window dismiss];
});
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
}
@end
#else // Disabled
@interface RCTLogBox() <NativeLogBoxSpec>
@end
@implementation RCTLogBox
+ (NSString *)moduleName
{
return nil;
}
- (void)show {
// noop
}
- (void)hide {
// noop
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
}
@end
#endif
Class RCTLogBoxCls(void) {
return RCTLogBox.class;
}
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=1000.0.0-master
VERSION_NAME=0.62.0-rc.0
GROUP=com.facebook.react
POM_NAME=ReactNative
@@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*
* <p>Generated by an internal genrule from Flow types.
*
* @generated
* @nolint
*/
package com.facebook.fbreact.specs;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactModuleWithSpec;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
public abstract class NativeLogBoxSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
public NativeLogBoxSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public abstract void hide();
@ReactMethod
public abstract void show();
}
@@ -16,6 +16,7 @@ import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.devsupport.LogBoxModule;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.annotations.ReactModuleList;
import com.facebook.react.module.model.ReactModuleInfo;
@@ -49,6 +50,7 @@ import java.util.Map;
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
@@ -94,6 +96,7 @@ import java.util.Map;
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
@@ -142,6 +145,8 @@ import java.util.Map;
return new DevSettingsModule(reactContext, mReactInstanceManager.getDevSupportManager());
case ExceptionsManagerModule.NAME:
return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager());
case LogBoxModule.NAME:
return new LogBoxModule(reactContext, mReactInstanceManager.getDevSupportManager());
case HeadlessJsTaskSupportModule.NAME:
return new HeadlessJsTaskSupportModule(reactContext);
case SourceCodeModule.NAME:
@@ -304,6 +304,27 @@ public class ReactInstanceManager {
public JavaScriptExecutorFactory getJavaScriptExecutorFactory() {
return ReactInstanceManager.this.getJSExecutorFactory();
}
@Override
public @Nullable View createRootView(String appKey) {
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
ReactRootView rootView = new ReactRootView(currentActivity);
rootView.startReactApplication(ReactInstanceManager.this, appKey, null);
return rootView;
}
return null;
}
@Override
public void destroyRootView(View rootView) {
if (rootView instanceof ReactRootView) {
((ReactRootView) rootView).unmountReactApplication();
}
}
};
}
@@ -19,6 +19,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.hardware.SensorManager;
import android.util.Pair;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -377,6 +378,14 @@ public class DevSupportManagerImpl
}
}
public @Nullable View createRootView(String appKey) {
return mReactInstanceManagerHelper.createRootView(appKey);
}
public void destroyRootView(View rootView) {
mReactInstanceManagerHelper.destroyRootView(rootView);
}
private void hideDevOptionsDialog() {
if (mDevOptionsDialog != null) {
mDevOptionsDialog.dismiss();
@@ -7,6 +7,7 @@
package com.facebook.react.devsupport;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
@@ -40,6 +41,14 @@ public class DisabledDevSupportManager implements DevSupportManager {
@Override
public void showNewJSError(String message, ReadableArray details, int errorCookie) {}
@Override
public @Nullable View createRootView(String appKey) {
return null;
}
@Override
public void destroyRootView(View rootView) {}
@Override
public void updateJSError(String message, ReadableArray details, int errorCookie) {}
@@ -0,0 +1,24 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.devsupport;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import android.view.Window;
import com.facebook.react.R;
/** Dialog for displaying JS errors in LogBox. */
public class LogBoxDialog extends Dialog {
public LogBoxDialog(Activity context, View reactRootView) {
super(context, R.style.Theme_Catalyst_LogBox);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(reactRootView);
}
}
@@ -0,0 +1,110 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.devsupport;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = LogBoxModule.NAME)
public class LogBoxModule extends ReactContextBaseJavaModule {
public static final String NAME = "LogBox";
private final DevSupportManager mDevSupportManager;
private @Nullable View mReactRootView;
private @Nullable LogBoxDialog mLogBoxDialog;
public LogBoxModule(ReactApplicationContext reactContext, DevSupportManager devSupportManager) {
super(reactContext);
mDevSupportManager = devSupportManager;
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mReactRootView == null) {
mReactRootView = mDevSupportManager.createRootView("LogBox");
if (mReactRootView == null) {
FLog.e(
ReactConstants.TAG,
"Unable to launch logbox because react was unable to create the root view");
}
}
}
});
}
@Override
public String getName() {
return NAME;
}
@ReactMethod
public void show() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mLogBoxDialog == null) {
Activity context = getCurrentActivity();
if (context == null || context.isFinishing()) {
FLog.e(
ReactConstants.TAG,
"Unable to launch logbox because react activity "
+ "is not available, here is the error that logbox would've displayed: ");
return;
}
mLogBoxDialog = new LogBoxDialog(context, mReactRootView);
mLogBoxDialog.setCancelable(false);
mLogBoxDialog.show();
}
}
});
}
@ReactMethod
public void hide() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mLogBoxDialog != null) {
if (mReactRootView.getParent() != null) {
((ViewGroup) mReactRootView.getParent()).removeView(mReactRootView);
}
mLogBoxDialog.dismiss();
mLogBoxDialog = null;
}
}
});
}
@Override
public void onCatalystInstanceDestroy() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mReactRootView != null) {
mDevSupportManager.destroyRootView(mReactRootView);
mReactRootView = null;
}
}
});
}
}
@@ -8,6 +8,7 @@
package com.facebook.react.devsupport;
import android.app.Activity;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
@@ -32,4 +33,9 @@ public interface ReactInstanceManagerDevHelper {
Activity getCurrentActivity();
JavaScriptExecutorFactory getJavaScriptExecutorFactory();
@Nullable
View createRootView(String appKey);
void destroyRootView(View rootView);
}
@@ -7,6 +7,7 @@
package com.facebook.react.devsupport.interfaces;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
@@ -25,6 +26,11 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
void addCustomDevOption(String optionName, DevOptionHandler optionHandler);
@Nullable
View createRootView(String appKey);
void destroyRootView(View rootView);
void showNewJSError(String message, ReadableArray details, int errorCookie);
void updateJSError(final String message, final ReadableArray details, final int errorCookie);
@@ -16,7 +16,7 @@ import java.util.Map;
public class ReactNativeVersion {
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
"major", 0,
"minor", 0,
"minor", 62,
"patch", 0,
"prerelease", null);
"prerelease", "rc.0");
}
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="catalyst_redbox_background">#eecc0000</color>
<color name="catalyst_logbox_background">#ffffffff</color>
</resources>
@@ -9,10 +9,22 @@
<item name="android:outAnimation">@android:anim/fade_out</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="Theme.Catalyst.LogBox">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@style/Animation.Catalyst.LogBox</item>
<item name="android:inAnimation">@android:anim/fade_in</item>
<item name="android:outAnimation">@android:anim/fade_out</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="Animation.Catalyst.RedBox" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/catalyst_push_up_in</item>
<item name="android:windowExitAnimation">@anim/catalyst_push_up_out</item>
</style>
<style name="Animation.Catalyst.LogBox" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/catalyst_push_up_in</item>
<item name="android:windowExitAnimation">@anim/catalyst_push_up_out</item>
</style>
<style name="redboxButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
+3
View File
@@ -438,6 +438,9 @@ module.exports = {
get unstable_RootTagContext(): RootTagContext {
return require('./Libraries/ReactNative/RootTagContext');
},
get unstable_enableLogBox(): () => void {
return require('./Libraries/YellowBox/YellowBox').__unstable_enableLogBox;
},
// Prop Types
get ColorPropType(): DeprecatedColorPropType {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "1000.0.0",
"version": "0.62.0-rc.0",
"bin": "./cli.js",
"description": "A framework for building native apps using React",
"license": "MIT",
@@ -174,4 +174,4 @@
}
}
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
},
"dependencies": {
"react": "16.11.0",
"react-native": "1000.0.0"
"react-native": "0.62.0-rc.0"
},
"devDependencies": {
"@babel/core": "^7.6.2",