Compare commits

...
Author SHA1 Message Date
Mike Grabowski 8a5b6a5a09 [0.52.3] Bump version numbers 2018-02-14 15:47:48 +01:00
Eric RozellandMike Grabowski 35b8519f15 Uses a single code path to link and unlink all platforms
Summary:
This commit removes special cases for linking iOS and Android platforms.

A previous commit opened up link and other commands for other platforms to provide their own behaviors. It left special cases in tact for iOS and Android. This PR removes the special case.

- Added jest tests related to the link command.
- Ran the `link` and `unlink` commands for iOS and Android and confirmed no changes.

https://github.com/facebook/react-native/pull/17745

<!--
Help reviewers and the release process by writing your own release notes

**INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.**

  CATEGORY
[----------]        TYPE
[ CLI      ]   [-------------]      LOCATION
[ DOCS     ]   [ BREAKING    ]   [-------------]
[ GENERAL  ]   [ BUGFIX      ]   [-{Component}-]
[ INTERNAL ]   [ ENHANCEMENT ]   [ {File}      ]
[ IOS      ]   [ FEATURE     ]   [ {Directory} ]   |-----------|
[ ANDROID  ]   [ MINOR       ]   [ {Framework} ] - | {Message} |
[----------]   [-------------]   [-------------]   |-----------|

[CATEGORY] [TYPE] [LOCATION] - MESSAGE

 EXAMPLES:

 [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things
 [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput
 [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with
 [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word
 [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position
 [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see
-->

[CLI][FEATURE][local-cli/link/link.js] - Removes special cases for linking in iOS and Android.
Closes https://github.com/facebook/react-native/pull/17961

Differential Revision: D6975951

Pulled By: hramos

fbshipit-source-id: 8dd5da35619e2124ce4b3b18db8b694757792363
2018-02-14 15:47:31 +01:00
Eric RozellandMike Grabowski 0e9ed6f790 Enable platforms to configure CLI commands
Summary:
This change adds hooks via the `package.json` for a platform plugin to specify hooks for generating platform configurations. This change also adds hooks to allow platform plugins to participate in `link` and `unlink` commands. The goal is to move platform-specific code for platform plugins into their own repositories / node_modules.

<!--
Thank you for sending the PR! We appreciate you spending the time to work on these changes.

Help us understand your motivation by explaining why you decided to make this change.

You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html

Happy contributing!

-->

We need to be able to configure the CLI commands for plugin platforms (e.g., react-native-windows) in their own repositories.

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Bonus points for screenshots and videos!)

- All jest tests, including new tests, pass.
- `link` and `unlink` commands are successful on iOS and Android.

(If this PR adds or changes functionality, please take some time to update the docs at https://github.com/facebook/react-native-website, and link to your PR here.)

https://github.com/Microsoft/react-native-windows/pull/1601

<!--
Help reviewers and the release process by writing your own release notes

**INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.**

  CATEGORY
[----------]        TYPE
[ CLI      ]   [-------------]      LOCATION
[ DOCS     ]   [ BREAKING    ]   [-------------]
[ GENERAL  ]   [ BUGFIX      ]   [-{Component}-]
[ INTERNAL ]   [ ENHANCEMENT ]   [ {File}      ]
[ IOS      ]   [ FEATURE     ]   [ {Directory} ]   |-----------|
[ ANDROID  ]   [ MINOR       ]   [ {Framework} ] - | {Message} |
[----------]   [-------------]   [-------------]   |-----------|

[CATEGORY] [TYPE] [LOCATION] - MESSAGE

 EXAMPLES:

 [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things
 [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput
 [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with
 [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word
 [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position
 [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see
-->

[CLI][FEATURE][local-cli/core/index.js] - Allow platform plugins to contribute to the RNConfig.
[CLI][FEATURE][local-cli/link/link.js] - Allow platform plugins to participate in the `link` command.
[CLI][FEATURE][local-cli/link/unlink.js] - Allow platform plugins to participate in the `unlink` command.
Closes https://github.com/facebook/react-native/pull/17745

Differential Revision: D6883558

Pulled By: hramos

fbshipit-source-id: ea32fe21cedd4cc02c5c0d48229f2cdb2ac8142b
2018-02-14 15:47:31 +01:00
Mike Grabowski 4080141a93 [0.52.2] Bump version numbers 2018-01-26 11:57:48 +01:00
Mike Grabowski a7524c27cc Fix Yoga cherry-pick issues 2018-01-26 11:57:40 +01:00
Mike Grabowski 94c5a3412f [0.52.2] Bump version numbers 2018-01-26 11:16:42 +01:00
Pritesh NandgaonkarandMike Grabowski 468309bbae Fix warnings of casting and null pointer handling
Reviewed By: emilsjolander

Differential Revision: D6675111

fbshipit-source-id: 884659fabb05033b4d43d3aa6629e22481d39b7e
2018-01-26 11:04:51 +01:00
Mike Grabowski 7b861cf336 Revert "Fix warnings of casting and null pointer handling"
This reverts commit f6f8d7b158.
2018-01-26 11:03:10 +01:00
Pritesh NandgaonkarandMike Grabowski f6f8d7b158 Fix warnings of casting and null pointer handling
Reviewed By: emilsjolander

Differential Revision: D6675111

fbshipit-source-id: 884659fabb05033b4d43d3aa6629e22481d39b7e
2018-01-26 10:55:27 +01:00
James IdeandMike Grabowski 9a8784c241 [0.52.1] Bump version numbers 2018-01-18 12:13:18 -08:00
James Ide 6bab7356a6 Fix HMR syntax error messages (message instead of description)
Summary:
The code to display HMR errors on the client was reading the `description` field from Metro payloads. Metro does not include `description` in the body of its error payloads -- only in its `body.errors[]` items. This commit changes RN's HMR code to show `body.message` (set consistently with https://github.com/facebook/metro/pull/124) instead of the non-existent `body.description`.

Open a test RN app, enable HMR, and then introduce a syntax error in an app source file. See that the redbox provides information about the syntax error instead of just saying "TransformError undefined".

- https://github.com/facebook/metro/pull/124

[GENERAL][ENHANCEMENT][HMR] - Fix display of syntax error messages when HMR is enabled
Closes https://github.com/facebook/react-native/pull/17619

Differential Revision: D6726516

Pulled By: mjesun

fbshipit-source-id: b1d1008d6f1aa8f88ff8a2aa1374724a305c773b
2018-01-16 12:11:01 -08:00
DouglasandMike Grabowski 9954499df1 Import RCTTVRemoteHandler only on tvOS
Summary:
Fix issue #17027 (`RCTModalHostView` has a tvOS dependency that was not wrapped in `TARGET_OS_TV`)

Existing test automation should pass.

[GENERAL] [BUGFIX] [tvOS] Fix cocoapods compile issue in RCTModalHostView
Closes https://github.com/facebook/react-native/pull/17502

Differential Revision: D6688166

Pulled By: hramos

fbshipit-source-id: 38297f439f75a8303f59f83b92e004c6c73d9bf6
2018-01-10 13:12:50 +01:00
Mike Grabowski f465017fe8 [0.52.0] Bump version numbers 2018-01-08 13:45:14 +01:00
Mike Grabowski 02a61a8d8e [0.52.0-rc.0] Bump version numbers 2017-12-18 18:27:04 +01:00
Mike Grabowski 33650db050 Revert "Upgrade to 1.9.1"
This reverts commit 9f33fe2583.
2017-12-18 10:52:58 +01:00
Mike Grabowski cc1499e124 Dont use Node 8.x specific Stream.final
This has been recently added to Node 8.x. Since it makes our tests to fail, I decided to
do a workaround that works for all the versions.
2017-12-18 10:52:52 +01:00
Mike Grabowski 7cb831d164 Revert "Fixes for migrating to Buck's source-only ABI feature"
This reverts commit 71006f74cd.
2017-12-18 10:50:13 +01:00
Mike Grabowski e6c1fb7212 Fix Flow (2/2)
Define missing "beforeEach" so that Flow does not complain.

Facebook is probably having these defined in another manner which
makes their internal test suite pass.
2017-12-18 10:49:42 +01:00
Mike Grabowski e56089af1d Revert "Automated fixups for Instagram"
This reverts commit dd016f334c.
2017-12-18 10:49:41 +01:00
Mike Grabowski e258e5f214 Revert Buck 2017-12-18 10:49:41 +01:00
Mike Grabowski 3259353fce Fix Flow (1/2)
Define missing "before" function so that it does not cause flow
to fail.
2017-12-18 10:48:46 +01:00
75 changed files with 723 additions and 1427 deletions
+2 -2
View File
@@ -62,11 +62,11 @@ aliases:
- &restore-cache-buck
keys:
- v1-buck-{{ arch }}-v2017.11.16.01
- v1-buck-{{ arch }}-v2017.09.04.02
- &save-cache-buck
paths:
- ~/buck
key: v1-buck-{{ arch }}-v2017.11.16.01
key: v1-buck-{{ arch }}-v2017.09.04.02
- &restore-cache-watchman
keys:
@@ -528,14 +528,14 @@ module.exports = {
ValueXY: AnimatedValueXY,
/**
* Exported to use the Interpolation type in flow.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#interpolation
*/
Interpolation: AnimatedInterpolation,
/**
* Exported for ease of type checking. All animated values derive from this
* class.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#node
*/
Node: AnimatedNode,
@@ -557,7 +557,7 @@ module.exports = {
/**
* Animates a value according to an analytical spring model based on
* damped harmonic oscillation.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#spring
*/
spring,
@@ -565,7 +565,7 @@ module.exports = {
/**
* Creates a new Animated value composed from two Animated values added
* together.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#add
*/
add,
@@ -573,7 +573,7 @@ module.exports = {
/**
* Creates a new Animated value composed by dividing the first Animated value
* by the second Animated value.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#divide
*/
divide,
@@ -581,7 +581,7 @@ module.exports = {
/**
* Creates a new Animated value composed from two Animated values multiplied
* together.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#multiply
*/
multiply,
@@ -589,7 +589,7 @@ module.exports = {
/**
* Creates a new Animated value that is the (non-negative) modulo of the
* provided Animated value.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#modulo
*/
modulo,
@@ -598,14 +598,14 @@ module.exports = {
* Create a new Animated value that is limited between 2 values. It uses the
* difference between the last value so even if the value is far from the
* bounds it will start changing when the value starts getting closer again.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#diffclamp
*/
diffClamp,
/**
* Starts an animation after the given delay.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#delay
*/
delay,
@@ -613,7 +613,7 @@ module.exports = {
* Starts an array of animations in order, waiting for each to complete
* before starting the next. If the current running animation is stopped, no
* following animations will be started.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#sequence
*/
sequence,
@@ -621,44 +621,44 @@ module.exports = {
* Starts an array of animations all at the same time. By default, if one
* of the animations is stopped, they will all be stopped. You can override
* this with the `stopTogether` flag.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#parallel
*/
parallel,
/**
* Array of animations may run in parallel (overlap), but are started in
* sequence with successive delays. Nice for doing trailing effects.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#stagger
*/
stagger,
/**
* Loops a given animation continuously, so that each time it reaches the
* end, it resets and begins again from the start.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#loop
*/
*/
loop,
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls `setValue` on the mapped outputs.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#event
*/
event,
/**
* Make any React component Animatable. Used to create `Animated.View`, etc.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#createanimatedcomponent
*/
createAnimatedComponent,
/**
* Imperative API to attach an animated value to an event on a view. Prefer
* Imperative API to attach an animated value to an event on a view. Prefer
* using `Animated.event` with `useNativeDrive: true` if possible.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#attachnativeevent
*/
attachNativeEvent,
@@ -666,7 +666,7 @@ module.exports = {
/**
* Advanced imperative API for snooping on animated events that are passed in
* through props. Use values directly where possible.
*
*
* See http://facebook.github.io/react-native/docs/animated.html#forkevent
*/
forkEvent,
@@ -193,9 +193,7 @@ function validateTransform(configs: Array<Object>): void {
configs.forEach(config => {
if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) {
throw new Error(
`Property '${
config.property
}' is not supported by native animated module`,
`Property '${config.property}' is not supported by native animated module`,
);
}
});
+13 -13
View File
@@ -68,7 +68,7 @@ function _flush(rootNode: AnimatedValue): void {
* multiple properties in a synchronized fashion, but can only be driven by one
* mechanism at a time. Using a new mechanism (e.g. starting a new animation,
* or calling `setValue`) will stop any previous ones.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html
*/
class AnimatedValue extends AnimatedWithChildren {
@@ -108,7 +108,7 @@ class AnimatedValue extends AnimatedWithChildren {
/**
* Directly set the value. This will stop any animations running on the value
* and update all the bound properties.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#setvalue
*/
setValue(value: number): void {
@@ -129,7 +129,7 @@ class AnimatedValue extends AnimatedWithChildren {
* Sets an offset that is applied on top of whatever value is set, whether via
* `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#setoffset
*/
setOffset(offset: number): void {
@@ -142,7 +142,7 @@ class AnimatedValue extends AnimatedWithChildren {
/**
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#flattenoffset
*/
flattenOffset(): void {
@@ -156,7 +156,7 @@ class AnimatedValue extends AnimatedWithChildren {
/**
* Sets the offset value to the base value, and resets the base value to zero.
* The final output of the value is unchanged.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#extractoffset
*/
extractOffset(): void {
@@ -171,7 +171,7 @@ class AnimatedValue extends AnimatedWithChildren {
* Adds an asynchronous listener to the value so you can observe updates from
* animations. This is useful because there is no way to
* synchronously read the value because it might be driven natively.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener
*/
addListener(callback: ValueListenerCallback): string {
@@ -184,9 +184,9 @@ class AnimatedValue extends AnimatedWithChildren {
}
/**
* Unregister a listener. The `id` param shall match the identifier
* previously returned by `addListener()`.
*
* Unregister a listener. The `id` param shall match the identifier
* previously returned by `addListener()`.
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener
*/
removeListener(id: string): void {
@@ -198,7 +198,7 @@ class AnimatedValue extends AnimatedWithChildren {
/**
* Remove all registered listeners.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners
*/
removeAllListeners(): void {
@@ -239,7 +239,7 @@ class AnimatedValue extends AnimatedWithChildren {
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#stopanimation
*/
stopAnimation(callback?: ?(value: number) => void): void {
@@ -251,7 +251,7 @@ class AnimatedValue extends AnimatedWithChildren {
/**
* Stops any animation and resets the value to its original.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#resetanimation
*/
resetAnimation(callback?: ?(value: number) => void): void {
@@ -270,7 +270,7 @@ class AnimatedValue extends AnimatedWithChildren {
/**
* Typically only used internally, but could be used by a custom Animation
* class.
*
*
* See http://facebook.github.io/react-native/docs/animatedvalue.html#animate
*/
animate(animation: Animation, callback: ?EndCallback): void {
+24 -24
View File
@@ -22,9 +22,9 @@ type ValueXYListenerCallback = (value: {x: number, y: number}) => void;
let _uniqueId = 1;
/**
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
* API to normal `Animated.Value`, but multiplexed.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html
*/
class AnimatedValueXY extends AnimatedWithChildren {
@@ -55,7 +55,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
/**
* Directly set the value. This will stop any animations running on the value
* and update all the bound properties.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setvalue
*/
setValue(value: {x: number, y: number}) {
@@ -64,10 +64,10 @@ class AnimatedValueXY extends AnimatedWithChildren {
}
/**
* Sets an offset that is applied on top of whatever value is set, whether
* via `setValue`, an animation, or `Animated.event`. Useful for compensating
* Sets an offset that is applied on top of whatever value is set, whether
* via `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setoffset
*/
setOffset(offset: {x: number, y: number}) {
@@ -76,9 +76,9 @@ class AnimatedValueXY extends AnimatedWithChildren {
}
/**
* Merges the offset value into the base value and resets the offset to zero.
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#flattenoffset
*/
flattenOffset(): void {
@@ -87,9 +87,9 @@ class AnimatedValueXY extends AnimatedWithChildren {
}
/**
* Sets the offset value to the base value, and resets the base value to
* Sets the offset value to the base value, and resets the base value to
* zero. The final output of the value is unchanged.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#extractoffset
*/
extractOffset(): void {
@@ -106,7 +106,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
/**
* Stops any animation and resets the value to its original.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#resetanimation
*/
resetAnimation(callback?: (value: {x: number, y: number}) => void): void {
@@ -116,10 +116,10 @@ class AnimatedValueXY extends AnimatedWithChildren {
}
/**
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#stopanimation
*/
stopAnimation(callback?: (value: {x: number, y: number}) => void): void {
@@ -129,12 +129,12 @@ class AnimatedValueXY extends AnimatedWithChildren {
}
/**
* Adds an asynchronous listener to the value so you can observe updates from
* animations. This is useful because there is no way to synchronously read
* Adds an asynchronous listener to the value so you can observe updates from
* animations. This is useful because there is no way to synchronously read
* the value because it might be driven natively.
*
*
* Returns a string that serves as an identifier for the listener.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#addlistener
*/
addListener(callback: ValueXYListenerCallback): string {
@@ -150,9 +150,9 @@ class AnimatedValueXY extends AnimatedWithChildren {
}
/**
* Unregister a listener. The `id` param shall match the identifier
* previously returned by `addListener()`.
*
* Unregister a listener. The `id` param shall match the identifier
* previously returned by `addListener()`.
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removelistener
*/
removeListener(id: string): void {
@@ -163,7 +163,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
/**
* Remove all registered listeners.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removealllisteners
*/
removeAllListeners(): void {
@@ -174,7 +174,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
/**
* Converts `{x, y}` into `{left, top}` for use in style.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#getlayout
*/
getLayout(): {[key: string]: AnimatedValue} {
@@ -186,7 +186,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
/**
* Converts `{x, y}` into a useable translation transform.
*
*
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#gettranslatetransform
*/
getTranslateTransform(): Array<{[key: string]: AnimatedValue}> {
+1 -3
View File
@@ -59,9 +59,7 @@ class URL {
if (BLOB_URL_PREFIX === null) {
throw new Error('Cannot create URL for blob!');
}
return `${BLOB_URL_PREFIX}${blob.blobId}?offset=${blob.offset}&size=${
blob.size
}`;
return `${BLOB_URL_PREFIX}${blob.blobId}?offset=${blob.offset}&size=${blob.size}`;
}
static revokeObjectURL(url: string) {
-11
View File
@@ -35,7 +35,6 @@ class BugReporting {
static _extraSources: Map<string, SourceCallback> = new Map();
static _fileSources: Map<string, SourceCallback> = new Map();
static _subscription: ?EmitterSubscription = null;
static _redboxSubscription: ?EmitterSubscription = null;
static _maybeInit() {
if (!BugReporting._subscription) {
@@ -43,11 +42,6 @@ class BugReporting {
.addListener('collectBugExtraData', BugReporting.collectExtraData, null);
defaultExtras();
}
if (!BugReporting._redboxSubscription) {
BugReporting._redboxSubscription = RCTDeviceEventEmitter
.addListener('collectRedBoxExtraData', BugReporting.collectExtraData, null);
}
}
/**
@@ -104,11 +98,6 @@ class BugReporting {
BugReportingNativeModule.setExtraData &&
BugReportingNativeModule.setExtraData(extraData, fileData);
const RedBoxNativeModule = require('NativeModules').RedBox;
RedBoxNativeModule &&
RedBoxNativeModule.setExtraData &&
RedBoxNativeModule.setExtraData(extraData, 'From BugReporting.js');
return { extras: extraData, files: fileData };
}
}
+2 -2
View File
@@ -72,8 +72,8 @@ const getPhotosParamChecker = createStrictShapeTypeChecker({
groupName: PropTypes.string,
/**
* Specifies filter on asset type
*/
* Specifies filter on asset type
*/
assetType: PropTypes.oneOf(Object.keys(ASSET_TYPE_OPTIONS)),
/**
+2 -2
View File
@@ -14,7 +14,7 @@
exports.version = {
major: 0,
minor: 0,
patch: 0,
minor: 52,
patch: 3,
prerelease: null,
};
+11 -11
View File
@@ -60,17 +60,17 @@ export type ViewabilityConfig = {|
|};
/**
* A Utility class for calculating viewable items based on current metrics like scroll position and
* layout.
*
* An item is said to be in a "viewable" state when any of the following
* is true for longer than `minimumViewTime` milliseconds (after an interaction if `waitForInteraction`
* is true):
*
* - Occupying >= `viewAreaCoveragePercentThreshold` of the view area XOR fraction of the item
* visible in the view area >= `itemVisiblePercentThreshold`.
* - Entirely visible on screen
*/
* A Utility class for calculating viewable items based on current metrics like scroll position and
* layout.
*
* An item is said to be in a "viewable" state when any of the following
* is true for longer than `minimumViewTime` milliseconds (after an interaction if `waitForInteraction`
* is true):
*
* - Occupying >= `viewAreaCoveragePercentThreshold` of the view area XOR fraction of the item
* visible in the view area >= `itemVisiblePercentThreshold`.
* - Entirely visible on screen
*/
class ViewabilityHelper {
_config: ViewabilityConfig;
_hasInteracted: boolean = false;
+6 -6
View File
@@ -59,7 +59,7 @@ let wrapperComponentProvider: ?WrapperComponentProvider;
/**
* `AppRegistry` is the JavaScript entry point to running all React Native apps.
*
*
* See http://facebook.github.io/react-native/docs/appregistry.html
*/
const AppRegistry = {
@@ -89,7 +89,7 @@ const AppRegistry = {
/**
* Registers an app's root component.
*
*
* See http://facebook.github.io/react-native/docs/appregistry.html#registercomponent
*/
registerComponent(
@@ -155,7 +155,7 @@ const AppRegistry = {
/**
* Loads the JavaScript bundle and runs the app.
*
*
* See http://facebook.github.io/react-native/docs/appregistry.html#runapplication
*/
runApplication(appKey: string, appParameters: any): void {
@@ -197,8 +197,8 @@ const AppRegistry = {
},
/**
* Stops an application when a view should be destroyed.
*
* Stops an application when a view should be destroyed.
*
* See http://facebook.github.io/react-native/docs/appregistry.html#unmountapplicationcomponentatroottag
*/
unmountApplicationComponentAtRootTag(rootTag: number): void {
@@ -207,7 +207,7 @@ const AppRegistry = {
/**
* Register a headless task. A headless task is a bit of code that runs without a UI.
*
*
* See http://facebook.github.io/react-native/docs/appregistry.html#registerheadlesstask
*/
registerHeadlessTask(taskKey: string, task: TaskProvider): void {
+1 -2
View File
@@ -398,8 +398,7 @@ class YellowBox extends React.Component<
onDismissAll={() => this.dismissWarning(null)}
onMinimize={() => this.setState({inspecting: null})}
toggleStacktrace={() =>
this.setState({stacktraceVisible: !stacktraceVisible})
}
this.setState({stacktraceVisible: !stacktraceVisible})}
/>
) : null;
+1 -1
View File
@@ -121,7 +121,7 @@ Error: ${e.message}`
}
case 'error': {
HMRLoadingView.hide();
throw new Error(data.body.type + ' ' + data.body.description);
throw new Error(`${data.body.type}: ${data.body.message}`);
}
default: {
throw new Error(`Unexpected message: ${data}`);
+2 -4
View File
@@ -75,15 +75,13 @@ class EventCheckBoxExample extends React.Component<{}, $FlowFixMeState> {
<View>
<CheckBox
onValueChange={value =>
this.setState({eventCheckBoxRegressionIsOn: value})
}
this.setState({eventCheckBoxRegressionIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventCheckBoxRegressionIsOn}
/>
<CheckBox
onValueChange={value =>
this.setState({eventCheckBoxRegressionIsOn: value})
}
this.setState({eventCheckBoxRegressionIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventCheckBoxRegressionIsOn}
/>
+2 -2
View File
@@ -11,7 +11,7 @@
#define RCT_REACT_NATIVE_VERSION @{ \
@"major": @(0), \
@"minor": @(0), \
@"patch": @(0), \
@"minor": @(52), \
@"patch": @(3), \
@"prerelease": [NSNull null], \
}
+264 -316
View File
@@ -13,9 +13,7 @@
#import "RCTConvert.h"
#import "RCTDefines.h"
#import "RCTErrorInfo.h"
#import "RCTEventDispatcher.h"
#import "RCTJSStackFrame.h"
#import "RCTRedBoxExtraDataViewController.h"
#import "RCTUtils.h"
#if RCT_DEBUG
@@ -26,7 +24,6 @@
- (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame;
- (void)reloadFromRedBoxWindow:(RCTRedBoxWindow *)redBoxWindow;
- (void)loadExtraDataViewController;
@end
@@ -36,332 +33,308 @@
@implementation RCTRedBoxWindow
{
UITableView *_stackTraceTableView;
NSString *_lastErrorMessage;
NSArray<RCTJSStackFrame *> *_lastStackTrace;
UITableView *_stackTraceTableView;
NSString *_lastErrorMessage;
NSArray<RCTJSStackFrame *> *_lastStackTrace;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
if ((self = [super initWithFrame:frame])) {
#if TARGET_OS_TV
self.windowLevel = UIWindowLevelAlert + 1000;
self.windowLevel = UIWindowLevelAlert + 1000;
#else
self.windowLevel = UIWindowLevelStatusBar - 1;
self.windowLevel = UIWindowLevelStatusBar - 1;
#endif
self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
self.hidden = YES;
self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
self.hidden = YES;
UIViewController *rootController = [UIViewController new];
self.rootViewController = rootController;
UIView *rootView = rootController.view;
rootView.backgroundColor = [UIColor clearColor];
UIViewController *rootController = [UIViewController new];
self.rootViewController = rootController;
UIView *rootView = rootController.view;
rootView.backgroundColor = [UIColor clearColor];
const CGFloat buttonHeight = 60;
const CGFloat buttonHeight = 60;
CGRect detailsFrame = rootView.bounds;
detailsFrame.size.height -= buttonHeight;
CGRect detailsFrame = rootView.bounds;
detailsFrame.size.height -= buttonHeight;
_stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain];
_stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_stackTraceTableView.delegate = self;
_stackTraceTableView.dataSource = self;
_stackTraceTableView.backgroundColor = [UIColor clearColor];
_stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain];
_stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_stackTraceTableView.delegate = self;
_stackTraceTableView.dataSource = self;
_stackTraceTableView.backgroundColor = [UIColor clearColor];
#if !TARGET_OS_TV
_stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3];
_stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3];
_stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
#endif
_stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[rootView addSubview:_stackTraceTableView];
_stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[rootView addSubview:_stackTraceTableView];
#if TARGET_OS_SIMULATOR
NSString *reloadText = @"Reload JS (\u2318R)";
NSString *dismissText = @"Dismiss (ESC)";
NSString *copyText = @"Copy (\u2325\u2318C)";
NSString *extraText = @"Extra Info (\u2318E)";
#else
NSString *reloadText = @"Reload JS";
NSString *dismissText = @"Dismiss";
NSString *copyText = @"Copy";
NSString *extraText = @"Extra Info";
#endif
#if TARGET_OS_SIMULATOR
NSString *reloadText = @"Reload JS (\u2318R)";
NSString *dismissText = @"Dismiss (ESC)";
NSString *copyText = @"Copy (\u2325\u2318C)";
#else
NSString *reloadText = @"Reload JS";
NSString *dismissText = @"Dismiss";
NSString *copyText = @"Copy";
#endif
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin;
dismissButton.accessibilityIdentifier = @"redbox-dismiss";
dismissButton.titleLabel.font = [UIFont systemFontOfSize:13];
[dismissButton setTitle:dismissText forState:UIControlStateNormal];
[dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
[dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin;
dismissButton.accessibilityIdentifier = @"redbox-dismiss";
dismissButton.titleLabel.font = [UIFont systemFontOfSize:14];
[dismissButton setTitle:dismissText forState:UIControlStateNormal];
[dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
[dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
reloadButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
reloadButton.accessibilityIdentifier = @"redbox-reload";
reloadButton.titleLabel.font = [UIFont systemFontOfSize:13];
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
reloadButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
reloadButton.accessibilityIdentifier = @"redbox-reload";
reloadButton.titleLabel.font = [UIFont systemFontOfSize:14];
[reloadButton setTitle:reloadText forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
[reloadButton setTitle:reloadText forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
UIButton *copyButton = [UIButton buttonWithType:UIButtonTypeCustom];
copyButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
copyButton.accessibilityIdentifier = @"redbox-copy";
copyButton.titleLabel.font = [UIFont systemFontOfSize:13];
[copyButton setTitle:copyText forState:UIControlStateNormal];
[copyButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
[copyButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[copyButton addTarget:self action:@selector(copyStack) forControlEvents:UIControlEventTouchUpInside];
UIButton *copyButton = [UIButton buttonWithType:UIButtonTypeCustom];
copyButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
copyButton.accessibilityIdentifier = @"redbox-copy";
copyButton.titleLabel.font = [UIFont systemFontOfSize:14];
[copyButton setTitle:copyText forState:UIControlStateNormal];
[copyButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
[copyButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[copyButton addTarget:self action:@selector(copyStack) forControlEvents:UIControlEventTouchUpInside];
UIButton *extraButton = [UIButton buttonWithType:UIButtonTypeCustom];
extraButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
extraButton.accessibilityIdentifier = @"redbox-extra";
extraButton.titleLabel.font = [UIFont systemFontOfSize:13];
[extraButton setTitle:extraText forState:UIControlStateNormal];
[extraButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
[extraButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[extraButton addTarget:self action:@selector(showExtraDataViewController) forControlEvents:UIControlEventTouchUpInside];
CGFloat buttonWidth = self.bounds.size.width / 4;
dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
copyButton.frame = CGRectMake(buttonWidth * 2, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
extraButton.frame = CGRectMake(buttonWidth * 3, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
[rootView addSubview:dismissButton];
[rootView addSubview:reloadButton];
[rootView addSubview:copyButton];
[rootView addSubview:extraButton];
}
return self;
CGFloat buttonWidth = self.bounds.size.width / 3;
dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
copyButton.frame = CGRectMake(buttonWidth * 2, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
[rootView addSubview:dismissButton];
[rootView addSubview:reloadButton];
[rootView addSubview:copyButton];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)dealloc
{
_stackTraceTableView.dataSource = nil;
_stackTraceTableView.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
_stackTraceTableView.dataSource = nil;
_stackTraceTableView.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate
{
// Show if this is a new message, or if we're updating the previous message
if ((self.hidden && !isUpdate) || (!self.hidden && isUpdate && [_lastErrorMessage isEqualToString:message])) {
_lastStackTrace = stack;
// message is displayed using UILabel, which is unable to render text of
// unlimited length, so we truncate it
_lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)];
// Show if this is a new message, or if we're updating the previous message
if ((self.hidden && !isUpdate) || (!self.hidden && isUpdate && [_lastErrorMessage isEqualToString:message])) {
_lastStackTrace = stack;
// message is displayed using UILabel, which is unable to render text of
// unlimited length, so we truncate it
_lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)];
[_stackTraceTableView reloadData];
[_stackTraceTableView reloadData];
if (self.hidden) {
[_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:NO];
}
[self makeKeyAndVisible];
[self becomeFirstResponder];
if (self.hidden) {
[_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:NO];
}
[self makeKeyAndVisible];
[self becomeFirstResponder];
}
}
- (void)dismiss
{
self.hidden = YES;
[self resignFirstResponder];
[RCTSharedApplication().delegate.window makeKeyWindow];
self.hidden = YES;
[self resignFirstResponder];
[RCTSharedApplication().delegate.window makeKeyWindow];
}
- (void)reload
{
[_actionDelegate reloadFromRedBoxWindow:self];
}
- (void)showExtraDataViewController
{
[_actionDelegate loadExtraDataViewController];
[_actionDelegate reloadFromRedBoxWindow:self];
}
- (void)copyStack
{
NSMutableString *fullStackTrace;
NSMutableString *fullStackTrace;
if (_lastErrorMessage != nil) {
fullStackTrace = [_lastErrorMessage mutableCopy];
[fullStackTrace appendString:@"\n\n"];
}
else {
fullStackTrace = [NSMutableString string];
}
if (_lastErrorMessage != nil) {
fullStackTrace = [_lastErrorMessage mutableCopy];
[fullStackTrace appendString:@"\n\n"];
}
else {
fullStackTrace = [NSMutableString string];
}
for (RCTJSStackFrame *stackFrame in _lastStackTrace) {
[fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]];
if (stackFrame.file) {
[fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]];
}
for (RCTJSStackFrame *stackFrame in _lastStackTrace) {
[fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]];
if (stackFrame.file) {
[fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]];
}
}
#if !TARGET_OS_TV
UIPasteboard *pb = [UIPasteboard generalPasteboard];
[pb setString:fullStackTrace];
UIPasteboard *pb = [UIPasteboard generalPasteboard];
[pb setString:fullStackTrace];
#endif
}
- (NSString *)formatFrameSource:(RCTJSStackFrame *)stackFrame
{
NSString *fileName = RCTNilIfNull(stackFrame.file) ? [stackFrame.file lastPathComponent] : @"<unknown file>";
NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld",
fileName,
(long long)stackFrame.lineNumber];
NSString *fileName = RCTNilIfNull(stackFrame.file) ? [stackFrame.file lastPathComponent] : @"<unknown file>";
NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld",
fileName,
(long long)stackFrame.lineNumber];
if (stackFrame.column != 0) {
lineInfo = [lineInfo stringByAppendingFormat:@":%lld", (long long)stackFrame.column];
}
return lineInfo;
if (stackFrame.column != 0) {
lineInfo = [lineInfo stringByAppendingFormat:@":%lld", (long long)stackFrame.column];
}
return lineInfo;
}
#pragma mark - TableView
- (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView
{
return 2;
return 2;
}
- (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return section == 0 ? 1 : _lastStackTrace.count;
return section == 0 ? 1 : _lastStackTrace.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"];
return [self reuseCell:cell forErrorMessage:_lastErrorMessage];
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
NSUInteger index = indexPath.row;
RCTJSStackFrame *stackFrame = _lastStackTrace[index];
return [self reuseCell:cell forStackFrame:stackFrame];
if (indexPath.section == 0) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"];
return [self reuseCell:cell forErrorMessage:_lastErrorMessage];
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
NSUInteger index = indexPath.row;
RCTJSStackFrame *stackFrame = _lastStackTrace[index];
return [self reuseCell:cell forStackFrame:stackFrame];
}
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message
{
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"];
cell.textLabel.accessibilityIdentifier = @"redbox-error";
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.textLabel.numberOfLines = 0;
cell.detailTextLabel.textColor = [UIColor whiteColor];
cell.backgroundColor = [UIColor clearColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"];
cell.textLabel.accessibilityIdentifier = @"redbox-error";
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.textLabel.numberOfLines = 0;
cell.detailTextLabel.textColor = [UIColor whiteColor];
cell.backgroundColor = [UIColor clearColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = message;
cell.textLabel.text = message;
return cell;
return cell;
}
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(RCTJSStackFrame *)stackFrame
{
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
cell.textLabel.textColor = [UIColor colorWithWhite:1 alpha:0.9];
cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14];
cell.textLabel.lineBreakMode = NSLineBreakByCharWrapping;
cell.textLabel.numberOfLines = 2;
cell.detailTextLabel.textColor = [UIColor colorWithWhite:1 alpha:0.7];
cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11];
cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
cell.backgroundColor = [UIColor clearColor];
cell.selectedBackgroundView = [UIView new];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
}
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
cell.textLabel.textColor = [UIColor colorWithWhite:1 alpha:0.9];
cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14];
cell.textLabel.lineBreakMode = NSLineBreakByCharWrapping;
cell.textLabel.numberOfLines = 2;
cell.detailTextLabel.textColor = [UIColor colorWithWhite:1 alpha:0.7];
cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11];
cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
cell.backgroundColor = [UIColor clearColor];
cell.selectedBackgroundView = [UIView new];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
}
cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
if (stackFrame.file) {
cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
} else {
cell.detailTextLabel.text = @"";
}
return cell;
cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
if (stackFrame.file) {
cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
} else {
cell.detailTextLabel.text = @"";
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
if (indexPath.section == 0) {
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16],
NSParagraphStyleAttributeName: paragraphStyle};
CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
return ceil(boundingRect.size.height) + 40;
} else {
return 50;
}
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16],
NSParagraphStyleAttributeName: paragraphStyle};
CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
return ceil(boundingRect.size.height) + 40;
} else {
return 50;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1) {
NSUInteger row = indexPath.row;
RCTJSStackFrame *stackFrame = _lastStackTrace[row];
[_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.section == 1) {
NSUInteger row = indexPath.row;
RCTJSStackFrame *stackFrame = _lastStackTrace[row];
[_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - Key commands
- (NSArray<UIKeyCommand *> *)keyCommands
{
// NOTE: We could use RCTKeyCommands for this, but since
// we control this window, we can use the standard, non-hacky
// mechanism instead
// NOTE: We could use RCTKeyCommands for this, but since
// we control this window, we can use the standard, non-hacky
// mechanism instead
return @[
// Dismiss red box
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
modifierFlags:0
action:@selector(dismiss)],
return @[
// Dismiss red box
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
modifierFlags:0
action:@selector(dismiss)],
// Reload
[UIKeyCommand keyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:@selector(reload)],
// Reload
[UIKeyCommand keyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:@selector(reload)],
// Copy = Cmd-Option C since Cmd-C in the simulator copies the pasteboard from
// the simulator to the desktop pasteboard.
[UIKeyCommand keyCommandWithInput:@"c"
modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate
action:@selector(copyStack)],
// Copy = Cmd-Option C since Cmd-C in the simulator copies the pasteboard from
// the simulator to the desktop pasteboard.
[UIKeyCommand keyCommandWithInput:@"c"
modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate
action:@selector(copyStack)]
// Extra data
[UIKeyCommand keyCommandWithInput:@"e"
modifierFlags:UIKeyModifierCommand
action:@selector(showExtraDataViewController)]
];
];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
return YES;
}
@end
@interface RCTRedBox () <RCTInvalidating, RCTRedBoxWindowActionDelegate, RCTRedBoxExtraDataActionDelegate>
@interface RCTRedBox () <RCTInvalidating, RCTRedBoxWindowActionDelegate>
@end
@implementation RCTRedBox
{
RCTRedBoxWindow *_window;
NSMutableArray<id<RCTErrorCustomizer>> *_errorCustomizers;
RCTRedBoxExtraDataViewController *_extraDataViewController;
RCTRedBoxWindow *_window;
NSMutableArray<id<RCTErrorCustomizer>> *_errorCustomizers;
}
@synthesize bridge = _bridge;
@@ -370,165 +343,140 @@ RCT_EXPORT_MODULE()
- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_errorCustomizers) {
self->_errorCustomizers = [NSMutableArray array];
}
if (![self->_errorCustomizers containsObject:errorCustomizer]) {
[self->_errorCustomizers addObject:errorCustomizer];
}
});
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_errorCustomizers) {
self->_errorCustomizers = [NSMutableArray array];
}
if (![self->_errorCustomizers containsObject:errorCustomizer]) {
[self->_errorCustomizers addObject:errorCustomizer];
}
});
}
// WARNING: Should only be called from the main thread/dispatch queue.
- (RCTErrorInfo *)_customizeError:(RCTErrorInfo *)error
{
RCTAssertMainQueue();
if (!self->_errorCustomizers) {
return error;
}
for (id<RCTErrorCustomizer> customizer in self->_errorCustomizers) {
RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error];
if (newInfo) {
error = newInfo;
}
}
RCTAssertMainQueue();
if (!self->_errorCustomizers) {
return error;
}
for (id<RCTErrorCustomizer> customizer in self->_errorCustomizers) {
RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error];
if (newInfo) {
error = newInfo;
}
}
return error;
}
- (void)showError:(NSError *)error
{
[self showErrorMessage:error.localizedDescription
withDetails:error.localizedFailureReason
stack:error.userInfo[RCTJSStackTraceKey]];
[self showErrorMessage:error.localizedDescription
withDetails:error.localizedFailureReason
stack:error.userInfo[RCTJSStackTraceKey]];
}
- (void)showErrorMessage:(NSString *)message
{
[self showErrorMessage:message withParsedStack:nil isUpdate:NO];
[self showErrorMessage:message withParsedStack:nil isUpdate:NO];
}
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
{
[self showErrorMessage:message withDetails:details stack:nil];
[self showErrorMessage:message withDetails:details stack:nil];
}
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray<RCTJSStackFrame *> *)stack {
NSString *combinedMessage = message;
if (details) {
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
}
[self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO];
NSString *combinedMessage = message;
if (details) {
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
}
[self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO];
}
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
{
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
{
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:NO];
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:NO];
}
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
{
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:YES];
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:YES];
}
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
{
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
}
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
{
[self showErrorMessage:message withParsedStack:stack isUpdate:YES];
[self showErrorMessage:message withParsedStack:stack isUpdate:YES];
}
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_extraDataViewController == nil) {
self->_extraDataViewController = [RCTRedBoxExtraDataViewController new];
self->_extraDataViewController.actionDelegate = self;
}
[self->_bridge.eventDispatcher sendDeviceEventWithName:@"collectRedBoxExtraData" body:nil];
if (!self->_window) {
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self->_window.actionDelegate = self;
}
RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message
stack:stack];
errorInfo = [self _customizeError:errorInfo];
[self->_window showErrorMessage:errorInfo.errorMessage
withStack:errorInfo.stack
isUpdate:isUpdate];
});
}
- (void)loadExtraDataViewController {
dispatch_async(dispatch_get_main_queue(), ^{
// Make sure the CMD+E shortcut doesn't call this twice
if (self->_extraDataViewController != nil && ![self->_window.rootViewController presentedViewController]) {
[self->_window.rootViewController presentViewController:self->_extraDataViewController animated:YES completion:nil];
}
});
}
RCT_EXPORT_METHOD(setExtraData:(NSDictionary *)extraData forIdentifier:(NSString *)identifier) {
[_extraDataViewController addExtraData:extraData forIdentifier:identifier];
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_window) {
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self->_window.actionDelegate = self;
}
RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message
stack:stack];
errorInfo = [self _customizeError:errorInfo];
[self->_window showErrorMessage:errorInfo.errorMessage
withStack:errorInfo.stack
isUpdate:isUpdate];
});
}
RCT_EXPORT_METHOD(dismiss)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self->_window dismiss];
});
dispatch_async(dispatch_get_main_queue(), ^{
[self->_window dismiss];
});
}
- (void)invalidate
{
[self dismiss];
[self dismiss];
}
- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame
{
NSURL *const bundleURL = _overrideBundleURL ?: _bridge.bundleURL;
if (![bundleURL.scheme hasPrefix:@"http"]) {
RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager.");
return;
}
NSURL *const bundleURL = _overrideBundleURL ?: _bridge.bundleURL;
if (![bundleURL.scheme hasPrefix:@"http"]) {
RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager.");
return;
}
NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
NSMutableURLRequest *request = [NSMutableURLRequest new];
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL];
request.HTTPMethod = @"POST";
request.HTTPBody = stackFrameJSON;
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
NSMutableURLRequest *request = [NSMutableURLRequest new];
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL];
request.HTTPMethod = @"POST";
request.HTTPBody = stackFrameJSON;
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
}
- (void)reload
{
// Window is not used and can be nil
[self reloadFromRedBoxWindow:nil];
[[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
}
- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow
{
if (_overrideReloadAction) {
_overrideReloadAction();
} else {
[_bridge reload];
}
[self dismiss];
if (_overrideReloadAction) {
_overrideReloadAction();
} else {
[_bridge reload];
}
[self dismiss];
}
@end
@@ -537,7 +485,7 @@ RCT_EXPORT_METHOD(dismiss)
- (RCTRedBox *)redBox
{
return [self moduleForClass:[RCTRedBox class]];
return [self moduleForClass:[RCTRedBox class]];
}
@end
@@ -1,22 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@protocol RCTRedBoxExtraDataActionDelegate <NSObject>
- (void)reload;
@end
@interface RCTRedBoxExtraDataViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, weak) id<RCTRedBoxExtraDataActionDelegate> actionDelegate;
- (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier;
@end
@@ -1,288 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTRedBoxExtraDataViewController.h"
@interface RCTRedBoxExtraDataCell : UITableViewCell
@property (nonatomic, strong) UILabel *keyLabel;
@property (nonatomic, strong) UILabel *valueLabel;
@end
@implementation RCTRedBoxExtraDataCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.backgroundColor = [UIColor colorWithRed:0.8
green:0 blue:0
alpha:1];
UILayoutGuide *contentLayout = self.contentView.layoutMarginsGuide;
self.keyLabel = [UILabel new];
[self.contentView addSubview:self.keyLabel];
self.keyLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.keyLabel.leadingAnchor
constraintEqualToAnchor:contentLayout.leadingAnchor].active = YES;
[self.keyLabel.topAnchor
constraintEqualToAnchor:contentLayout.topAnchor].active = YES;
[self.keyLabel.bottomAnchor
constraintEqualToAnchor:contentLayout.bottomAnchor].active = YES;
[self.keyLabel.widthAnchor
constraintEqualToAnchor:contentLayout.widthAnchor
multiplier:0.3].active = YES;
self.keyLabel.textColor = [UIColor whiteColor];
self.keyLabel.numberOfLines = 0;
self.keyLabel.lineBreakMode = UILineBreakModeWordWrap;
self.keyLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:12.0f];
self.valueLabel = [UILabel new];
[self.contentView addSubview:self.valueLabel];
self.valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.valueLabel.leadingAnchor
constraintEqualToAnchor:self.keyLabel.trailingAnchor
constant:10.f].active = YES;
[self.valueLabel.trailingAnchor
constraintEqualToAnchor:contentLayout.trailingAnchor].active = YES;
[self.valueLabel.topAnchor
constraintEqualToAnchor:contentLayout.topAnchor].active = YES;
[self.valueLabel.bottomAnchor
constraintEqualToAnchor:contentLayout.bottomAnchor].active = YES;
self.valueLabel.textColor = [UIColor whiteColor];
self.valueLabel.numberOfLines = 0;
self.valueLabel.lineBreakMode = UILineBreakModeWordWrap;
self.valueLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:12.0f];
}
return self;
}
@end
@interface RCTRedBoxExtraDataViewController ()
@end
@implementation RCTRedBoxExtraDataViewController
{
UITableView *_tableView;
NSMutableArray *_extraDataTitle;
NSMutableArray *_extraData;
}
@synthesize actionDelegate = _actionDelegate;
- (instancetype)init
{
if (self = [super init]) {
_extraData = [NSMutableArray new];
_extraDataTitle = [NSMutableArray new];
self.view.backgroundColor = [UIColor colorWithRed:0.8
green:0
blue:0
alpha:1];
_tableView = [UITableView new];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.estimatedRowHeight = 200;
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.rowHeight = UITableViewAutomaticDimension;
_tableView.allowsSelection = NO;
#if TARGET_OS_SIMULATOR
NSString *reloadText = @"Reload JS (\u2318R)";
NSString *dismissText = @"Dismiss (ESC)";
#else
NSString *reloadText = @"Reload JS";
NSString *dismissText = @"Dismiss";
#endif
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
dismissButton.translatesAutoresizingMaskIntoConstraints = NO;
dismissButton.accessibilityIdentifier = @"redbox-extra-data-dismiss";
dismissButton.titleLabel.font = [UIFont systemFontOfSize:13];
[dismissButton setTitle:dismissText forState:UIControlStateNormal];
[dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5]
forState:UIControlStateNormal];
[dismissButton setTitleColor:[UIColor whiteColor]
forState:UIControlStateHighlighted];
[dismissButton addTarget:self
action:@selector(dismiss)
forControlEvents:UIControlEventTouchUpInside];
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
reloadButton.accessibilityIdentifier = @"redbox-reload";
reloadButton.titleLabel.font = [UIFont systemFontOfSize:13];
[reloadButton setTitle:reloadText forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5]
forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor whiteColor]
forState:UIControlStateHighlighted];
[reloadButton addTarget:self
action:@selector(reload)
forControlEvents:UIControlEventTouchUpInside];
UIStackView *buttonStackView = [UIStackView new];
buttonStackView.axis = UILayoutConstraintAxisHorizontal;
buttonStackView.distribution = UIStackViewDistributionEqualSpacing;
buttonStackView.alignment = UIStackViewAlignmentFill;
buttonStackView.spacing = 20;
[buttonStackView addArrangedSubview:dismissButton];
[buttonStackView addArrangedSubview:reloadButton];
buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
UIStackView *mainStackView = [UIStackView new];
mainStackView.axis = UILayoutConstraintAxisVertical;
mainStackView.backgroundColor = [UIColor colorWithRed:0.8
green:0 blue:0
alpha:1];
[mainStackView addArrangedSubview:_tableView];
[mainStackView addArrangedSubview:buttonStackView];
mainStackView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:mainStackView];
CGFloat tableHeight = self.view.bounds.size.height - 60.f;
[_tableView.heightAnchor constraintEqualToConstant:tableHeight].active = YES;
[_tableView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor].active = YES;
CGFloat buttonWidth = self.view.bounds.size.width / 4;
[dismissButton.heightAnchor constraintEqualToConstant:60].active = YES;
[dismissButton.widthAnchor
constraintEqualToConstant:buttonWidth].active = YES;
[reloadButton.heightAnchor constraintEqualToConstant:60].active = YES;
[reloadButton.widthAnchor
constraintEqualToConstant:buttonWidth].active = YES;
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[_tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[_extraData objectAtIndex:section] count];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 40;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [UIView new];
view.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1];
UILabel *header = [UILabel new];
[view addSubview:header];
header.translatesAutoresizingMaskIntoConstraints = NO;
[header.leadingAnchor
constraintEqualToAnchor:view.leadingAnchor constant:5].active = YES;
[header.trailingAnchor
constraintEqualToAnchor:view.trailingAnchor].active = YES;
[header.topAnchor
constraintEqualToAnchor:view.topAnchor].active = YES;
[header.bottomAnchor
constraintEqualToAnchor:view.bottomAnchor].active = YES;
header.textColor = [UIColor whiteColor];
header.font = [UIFont fontWithName:@"Menlo-Bold" size:14.0f];
header.text = [_extraDataTitle[section] uppercaseString];
return view;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *reuseIdentifier = @"RedBoxExtraData";
RCTRedBoxExtraDataCell *cell =
(RCTRedBoxExtraDataCell *)[tableView
dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil) {
cell = [[RCTRedBoxExtraDataCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:reuseIdentifier];
}
NSArray *dataKVPair = _extraData[indexPath.section][indexPath.row];
cell.keyLabel.text = dataKVPair[0];
cell.valueLabel.text = dataKVPair[1];
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _extraDataTitle.count;
}
- (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier
{
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *newData = [NSMutableArray new];
for (id key in data) {
[newData addObject:@[[NSString stringWithFormat:@"%@", key],
[NSString stringWithFormat:@"%@", [data objectForKey:key]]]];
}
NSInteger idx = [self->_extraDataTitle indexOfObject:identifier];
if (idx == NSNotFound) {
[self->_extraDataTitle addObject:identifier];
[self->_extraData addObject:newData];
} else {
[self->_extraData replaceObjectAtIndex:idx withObject:newData];
}
[self->_tableView reloadData];
});
}
- (void)dismiss
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)reload
{
[_actionDelegate reload];
}
#pragma mark - Key commands
- (NSArray<UIKeyCommand *> *)keyCommands
{
return @[
// Dismiss
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
modifierFlags:0
action:@selector(dismiss)],
// Reload
[UIKeyCommand keyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:@selector(reload)]
];
}
@end
-8
View File
@@ -1192,8 +1192,6 @@
EBF21BFE1FC499840052F4D5 /* InspectorInterfaces.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = EBF21BBA1FC498270052F4D5 /* InspectorInterfaces.h */; };
EBF21BFF1FC4998E0052F4D5 /* InspectorInterfaces.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EBF21BBB1FC498270052F4D5 /* InspectorInterfaces.cpp */; };
EBF21C001FC499A80052F4D5 /* InspectorInterfaces.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EBF21BBB1FC498270052F4D5 /* InspectorInterfaces.cpp */; };
FEFAAC9E1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FEFAAC9C1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.m */; };
FEFAAC9F1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = FEFAAC9D1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.h */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -2270,8 +2268,6 @@
EBF21BBB1FC498270052F4D5 /* InspectorInterfaces.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorInterfaces.cpp; sourceTree = "<group>"; };
EBF21BDC1FC498900052F4D5 /* libjsinspector.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjsinspector.a; sourceTree = BUILT_PRODUCTS_DIR; };
EBF21BFA1FC4989A0052F4D5 /* libjsinspector-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libjsinspector-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
FEFAAC9C1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRedBoxExtraDataViewController.m; sourceTree = "<group>"; };
FEFAAC9D1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRedBoxExtraDataViewController.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -2480,8 +2476,6 @@
5960C1B41F0804A00066FD5B /* RCTLayoutAnimationGroup.m */,
13F17A831B8493E5007D4C75 /* RCTRedBox.h */,
13F17A841B8493E5007D4C75 /* RCTRedBox.m */,
FEFAAC9D1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.h */,
FEFAAC9C1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.m */,
000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */,
000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */,
13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */,
@@ -3470,7 +3464,6 @@
3D80DA591DF820620028D040 /* RCTTiming.h in Headers */,
3D80DA5A1DF820620028D040 /* RCTUIManager.h in Headers */,
3D80DA5B1DF820620028D040 /* RCTFPSGraph.h in Headers */,
FEFAAC9F1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.h in Headers */,
3D80DA5D1DF820620028D040 /* RCTMacros.h in Headers */,
3D80DA5E1DF820620028D040 /* RCTProfile.h in Headers */,
3D80DA5F1DF820620028D040 /* RCTActivityIndicatorView.h in Headers */,
@@ -4417,7 +4410,6 @@
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
59A7B9FE1E577DBF0068EDBF /* RCTRootContentView.m in Sources */,
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
FEFAAC9E1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.m in Sources */,
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
CF2731C11E7B8DE40044CA4F /* RCTDeviceInfo.m in Sources */,
3D7AA9C41E548CD5001955CF /* NSDataBigString.mm in Sources */,
+2
View File
@@ -18,7 +18,9 @@
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+React.h"
#if TARGET_OS_TV
#import "RCTTVRemoteHandler.h"
#endif
@implementation RCTModalHostView
{
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=1000.0.0-master
VERSION_NAME=0.52.3
GROUP=com.facebook.react
POM_NAME=ReactNative
@@ -12,7 +12,6 @@ android_library(
provided_deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -16,7 +16,6 @@ android_library(
provided_deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -9,14 +9,17 @@
package com.facebook.react.devsupport;
import android.os.Handler;
import android.os.Looper;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.JavaJSExecutor;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.JavaJSExecutor;
/**
* Executes JS remotely via the react nodejs server as a proxy to a browser on the host machine.
@@ -157,7 +160,8 @@ public class WebsocketJavaScriptExecutor implements JavaJSExecutor {
}
@Override
public void loadApplicationScript(String sourceURL) throws JavaJSExecutor.ProxyExecutorException {
public void loadApplicationScript(String sourceURL)
throws ProxyExecutorException {
JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
Assertions.assertNotNull(mWebSocketClient).loadApplicationScript(
sourceURL,
@@ -172,7 +176,7 @@ public class WebsocketJavaScriptExecutor implements JavaJSExecutor {
@Override
public @Nullable String executeJSCall(String methodName, String jsonArgsArray)
throws JavaJSExecutor.ProxyExecutorException {
throws ProxyExecutorException {
JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
Assertions.assertNotNull(mWebSocketClient).executeJSCall(
methodName,
@@ -9,7 +9,6 @@
package com.facebook.react.flat;
import com.facebook.react.uimanager.UIViewOperationQueue;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
@@ -35,7 +34,7 @@ import javax.annotation.Nullable;
private final FlatNativeViewHierarchyManager mNativeViewHierarchyManager;
private final ProcessLayoutRequests mProcessLayoutRequests = new ProcessLayoutRequests();
private final class ProcessLayoutRequests implements UIViewOperationQueue.UIOperation {
private final class ProcessLayoutRequests implements UIOperation {
@Override
public void execute() {
FlatViewGroup.processLayoutRequests();
@@ -45,7 +44,7 @@ import javax.annotation.Nullable;
/**
* UIOperation that updates DrawCommands for a View defined by reactTag.
*/
private final class UpdateMountState implements UIViewOperationQueue.UIOperation {
private final class UpdateMountState implements UIOperation {
private final int mReactTag;
private final @Nullable DrawCommand[] mDrawCommands;
@@ -76,7 +75,7 @@ import javax.annotation.Nullable;
/**
* UIOperation that updates DrawCommands for a View defined by reactTag.
*/
private final class UpdateClippingMountState implements UIViewOperationQueue.UIOperation {
private final class UpdateClippingMountState implements UIOperation {
private final int mReactTag;
private final @Nullable DrawCommand[] mDrawCommands;
@@ -128,7 +127,7 @@ import javax.annotation.Nullable;
}
}
private final class UpdateViewGroup implements UIViewOperationQueue.UIOperation {
private final class UpdateViewGroup implements UIOperation {
private final int mReactTag;
private final int[] mViewsToAdd;
@@ -149,7 +148,7 @@ import javax.annotation.Nullable;
/**
* UIOperation that updates View bounds for a View defined by reactTag.
*/
public final class UpdateViewBounds implements UIViewOperationQueue.UIOperation {
public final class UpdateViewBounds implements UIOperation {
private final int mReactTag;
private final int mLeft;
@@ -171,7 +170,7 @@ import javax.annotation.Nullable;
}
}
private final class SetPadding implements UIViewOperationQueue.UIOperation {
private final class SetPadding implements UIOperation {
private final int mReactTag;
private final int mPaddingLeft;
@@ -203,7 +202,7 @@ import javax.annotation.Nullable;
}
}
private final class DropViews implements UIViewOperationQueue.UIOperation {
private final class DropViews implements UIOperation {
private final SparseIntArray mViewsToDrop;
@@ -221,7 +220,7 @@ import javax.annotation.Nullable;
}
}
private final class MeasureVirtualView implements UIViewOperationQueue.UIOperation {
private final class MeasureVirtualView implements UIOperation {
private final int mReactTag;
private final float mScaledX;
@@ -285,7 +284,7 @@ import javax.annotation.Nullable;
}
}
public final class DetachAllChildrenFromViews implements UIViewOperationQueue.UIOperation {
public final class DetachAllChildrenFromViews implements UIOperation {
private @Nullable int[] mViewsToDetachAllChildrenFrom;
public void setViewsToDetachAllChildrenFrom(int[] viewsToDetachAllChildrenFrom) {
@@ -298,7 +297,7 @@ import javax.annotation.Nullable;
}
}
private final class FindTargetForTouchOperation implements UIViewOperationQueue.UIOperation {
private final class FindTargetForTouchOperation implements UIOperation {
private final int mReactTag;
private final float mTargetX;
@@ -374,7 +373,7 @@ import javax.annotation.Nullable;
* Used to delay view manager command dispatch until after the view hierarchy is updated.
* Mirrors command operation dispatch, but is only used in Nodes for view manager commands.
*/
public final class ViewManagerCommand implements UIViewOperationQueue.UIOperation {
public final class ViewManagerCommand implements UIOperation {
private final int mReactTag;
private final int mCommand;
@@ -469,7 +468,7 @@ import javax.annotation.Nullable;
return new ViewManagerCommand(reactTag, command, args);
}
/* package */ void enqueueFlatUIOperation(UIViewOperationQueue.UIOperation operation) {
/* package */ void enqueueFlatUIOperation(UIOperation operation) {
enqueueUIOperation(operation);
}
@@ -3,7 +3,6 @@ include_defs("//ReactAndroid/DEFS")
android_library(
name = "annotations",
srcs = glob(["**/*.java"]),
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -18,7 +18,7 @@ import java.util.Map;
public class ReactNativeVersion {
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
"major", 0,
"minor", 0,
"patch", 0,
"minor", 52,
"patch", 3,
"prerelease", null);
}
@@ -14,7 +14,6 @@ android_library(
provided_deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -3,7 +3,6 @@ include_defs("//ReactAndroid/DEFS")
android_library(
name = "annotations",
srcs = glob(["*.java"]),
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -12,7 +12,7 @@ import android.view.animation.Transformation;
*/
/* package */ class OpacityAnimation extends Animation {
static class OpacityAnimationListener implements Animation.AnimationListener {
static class OpacityAnimationListener implements AnimationListener {
private final View mView;
private boolean mLayerTypeChanged = false;
@@ -11,7 +11,6 @@ android_library(
react_native_dep("third-party/android/support-annotations:android-support-annotations"),
react_native_dep("third-party/android/support/v4:lib-support-v4"),
],
required_for_source_only_abi = True,
visibility = ["PUBLIC"],
deps = [
react_native_dep("third-party/java/jsr-305:jsr-305"),
@@ -3,7 +3,6 @@ include_defs("//ReactAndroid/DEFS")
android_library(
name = "modal",
srcs = glob(["*.java"]),
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -3,7 +3,6 @@ include_defs("//ReactAndroid/DEFS")
android_library(
name = "text",
srcs = glob(["*.java"]),
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -6,7 +6,6 @@ android_library(
provided_deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -54,13 +54,13 @@ public class ReactViewGroup extends ViewGroup implements
/**
* This listener will be set for child views when removeClippedSubview property is enabled. When
* children layout is updated, it will call {@link #updateSubviewClipStatus} to notify parent view
* about that fact so that view can be attached/detached if necessary.
* children layout is updated, it will call {@link #updateSubviewClipStatus} to notify parent
* view about that fact so that view can be attached/detached if necessary.
*
* <p>TODO(7728005): Attach/detach views in batch - once per frame in case when multiple children
* TODO(7728005): Attach/detach views in batch - once per frame in case when multiple children
* update their layout.
*/
private static final class ChildrenLayoutChangeListener implements View.OnLayoutChangeListener {
private static final class ChildrenLayoutChangeListener implements OnLayoutChangeListener {
private final ReactViewGroup mParent;
@@ -422,7 +422,7 @@ public class ReactViewGroup extends ViewGroup implements
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
public void addView(View child, int index, LayoutParams params) {
// This will get called for every overload of addView so there is not need to override every method.
mDrawingOrderHelper.handleAddView(child);
setChildrenDrawingOrderEnabled(mDrawingOrderHelper.shouldEnableCustomDrawingOrder());
@@ -494,8 +494,7 @@ public class ReactViewGroup extends ViewGroup implements
addViewWithSubviewClippingEnabled(child, index, sDefaultLayoutParam);
}
/*package*/ void addViewWithSubviewClippingEnabled(
View child, int index, ViewGroup.LayoutParams params) {
/*package*/ void addViewWithSubviewClippingEnabled(View child, int index, LayoutParams params) {
Assertions.assertCondition(mRemoveClippedSubviews);
Assertions.assertNotNull(mClippingRect);
Assertions.assertNotNull(mAllChildren);
@@ -6,7 +6,6 @@ android_library(
provided_deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
@@ -9,17 +9,19 @@
package com.facebook.react.views.viewpager;
import java.util.ArrayList;
import java.util.List;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.NativeGestureUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Wrapper view for {@link ViewPager}. It's forwarding calls to {@link ViewGroup#addView} to add
@@ -116,7 +118,7 @@ public class ReactViewPager extends ViewPager {
}
}
private class PageChangeListener implements ViewPager.OnPageChangeListener {
private class PageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+1 -1
View File
@@ -205,7 +205,7 @@ void YGNodeToString(
}
appendFormatedString(str, ">");
const uint32_t childCount = node->children.size();
const uint32_t childCount = static_cast<uint32_t>(node->children.size());
if (options & YGPrintOptionsChildren && childCount > 0) {
for (uint32_t i = 0; i < childCount; i++) {
appendFormatedString(str, "\n");
+6 -4
View File
@@ -288,7 +288,9 @@ YGConfigRef YGConfigGetDefault() {
YGConfigRef YGConfigNew(void) {
const YGConfigRef config = (const YGConfigRef)malloc(sizeof(YGConfig));
YGAssert(config != nullptr, "Could not allocate memory for config");
if (config == nullptr) {
abort();
}
gConfigInstanceCount++;
memcpy(config, &gYGConfigDefaults, sizeof(YGConfig));
return config;
@@ -469,7 +471,7 @@ YGNodeRef YGNodeGetParent(const YGNodeRef node) {
}
uint32_t YGNodeGetChildCount(const YGNodeRef node) {
return node->children.size();
return static_cast<uint32_t>(node->children.size());
}
void YGNodeMarkDirty(const YGNodeRef node) {
@@ -1853,7 +1855,7 @@ static void YGNodelayoutImpl(const YGNodeRef node,
return;
}
const uint32_t childCount = node->children.size();
const uint32_t childCount = YGNodeGetChildCount(node);
if (childCount == 0) {
YGNodeEmptyContainerSetMeasuredDimensions(node,
availableWidth,
@@ -3378,7 +3380,7 @@ static void YGRoundToPixelGrid(const YGNodeRef node,
(textRounding && !hasFractionalHeight)) -
YGRoundValueToPixelGrid(absoluteNodeTop, pointScaleFactor, false, textRounding);
const uint32_t childCount = node->children.size();
const uint32_t childCount = YGNodeGetChildCount(node);
for (uint32_t i = 0; i < childCount; i++) {
YGRoundToPixelGrid(YGNodeGetChild(node, i), pointScaleFactor, absoluteNodeLeft, absoluteNodeTop);
}
+5 -3
View File
@@ -347,9 +347,11 @@ fs.createWriteStream.mockImplementation(filePath => {
const writeStream = new stream.Writable({
write(chunk, encoding, callback) {
this.__chunks.push(chunk);
callback();
},
final(callback) {
/**
* We can't use `final` as that has been added to Node 8.x.
* For backwards compatibility and minimal code paths,
* we update the content of a file immediately
*/
node[path.basename(filePath)] = this.__chunks.join('');
callback();
},
+2
View File
@@ -17,6 +17,8 @@
declare var jest: any;
declare var describe: any;
declare var beforeEach: any;
declare var expect: any;
declare var it: any;
jest.mock('fs');
+20 -8
View File
@@ -27,18 +27,27 @@ describe('findPlugins', () => {
jest.mock(pjsonPath, () => ({
dependencies: {'rnpm-plugin-test': '*'},
}));
expect(findPlugins([ROOT])).toHaveLength(1);
expect(findPlugins([ROOT])[0]).toBe('rnpm-plugin-test');
expect(findPlugins([ROOT])).toHaveProperty('commands');
expect(findPlugins([ROOT])).toHaveProperty('platforms');
expect(findPlugins([ROOT]).commands).toHaveLength(1);
expect(findPlugins([ROOT]).commands[0]).toBe('rnpm-plugin-test');
expect(findPlugins([ROOT]).platforms).toHaveLength(0);
});
it('returns an empty array if there are no plugins in this folder', () => {
jest.mock(pjsonPath, () => ({}));
expect(findPlugins([ROOT])).toHaveLength(0);
expect(findPlugins([ROOT])).toHaveProperty('commands');
expect(findPlugins([ROOT])).toHaveProperty('platforms');
expect(findPlugins([ROOT]).commands).toHaveLength(0);
expect(findPlugins([ROOT]).platforms).toHaveLength(0);
});
it('returns an empty array if there is no package.json in the supplied folder', () => {
expect(Array.isArray(findPlugins(['fake-path']))).toBeTruthy();
expect(findPlugins(['fake-path'])).toHaveLength(0);
it('returns an object with empty arrays if there is no package.json in the supplied folder', () => {
expect(findPlugins(['fake-path'])).toHaveProperty('commands');
expect(findPlugins(['fake-path'])).toHaveProperty('platforms');
expect(findPlugins(['fake-path']).commands).toHaveLength(0);
expect(findPlugins(['fake-path']).platforms).toHaveLength(0);
});
it('returns plugins from both dependencies and dev dependencies', () => {
@@ -46,7 +55,10 @@ describe('findPlugins', () => {
dependencies: {'rnpm-plugin-test': '*'},
devDependencies: {'rnpm-plugin-test-2': '*'},
}));
expect(findPlugins([ROOT])).toHaveLength(2);
expect(findPlugins([ROOT])).toHaveProperty('commands');
expect(findPlugins([ROOT])).toHaveProperty('platforms');
expect(findPlugins([ROOT]).commands).toHaveLength(2);
expect(findPlugins([ROOT]).platforms).toHaveLength(0);
});
it('returns unique list of plugins', () => {
@@ -54,6 +66,6 @@ describe('findPlugins', () => {
dependencies: {'rnpm-plugin-test': '*'},
devDependencies: {'rnpm-plugin-test': '*'},
}));
expect(findPlugins([ROOT])).toHaveLength(1);
expect(findPlugins([ROOT]).commands).toHaveLength(1);
});
});
+2
View File
@@ -126,3 +126,5 @@ exports.dependencyConfig = function dependencyConfigAndroid(folder, userConfig)
return { sourceDir, folder, manifest, packageImportPath, packageInstance };
};
exports.linkConfig = require('../../link/android');
+23 -9
View File
@@ -37,11 +37,19 @@ const findPluginsInReactNativePackage = (pjson) => {
return path.join(pjson.name, pjson.rnpm.plugin);
};
const findPlatformsInPackage = (pjson) => {
if (!pjson.rnpm || !pjson.rnpm.platform) {
return [];
}
return path.join(pjson.name, pjson.rnpm.platform);
};
const findPluginInFolder = (folder) => {
const pjson = readPackage(folder);
if (!pjson) {
return [];
return {commands: [], platforms: []};
}
const deps = union(
@@ -51,27 +59,33 @@ const findPluginInFolder = (folder) => {
return deps.reduce(
(acc, pkg) => {
let commands = acc.commands;
let platforms = acc.platforms;
if (isRNPMPlugin(pkg)) {
return acc.concat(pkg);
commands = commands.concat(pkg);
}
if (isReactNativePlugin(pkg)) {
const pkgJson = readPackage(path.join(folder, 'node_modules', pkg));
if (!pkgJson) {
return acc;
if (pkgJson) {
commands = commands.concat(findPluginsInReactNativePackage(pkgJson));
platforms = platforms.concat(findPlatformsInPackage(pkgJson));
}
return acc.concat(findPluginsInReactNativePackage(pkgJson));
}
return acc;
return {commands: commands, platforms: platforms};
},
[]
{commands: [], platforms: []}
);
};
/**
* Find plugins in package.json of the given folder
* @param {String} folder Path to the folder to get the package.json from
* @type {Array} Array of plugins or an empty array if no package.json found
* @type {Object} Object of commands and platform plugins
*/
module.exports = function findPlugins(folders) {
return uniq(flatten(folders.map(findPluginInFolder)));
const plugins = folders.map(findPluginInFolder);
return {
commands: uniq(flatten(plugins.map(p => p.commands))),
platforms: uniq(flatten(plugins.map(p => p.platforms)))
};
};
+42 -12
View File
@@ -15,7 +15,6 @@ const Config = require('../util/Config');
const findPlugins = require('./findPlugins');
const findAssets = require('./findAssets');
const ios = require('./ios');
const windows = require('./windows');
const wrapCommands = require('./wrapCommands');
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
@@ -33,6 +32,10 @@ import type {ConfigT} from 'metro';
export type RNConfig = {
...ConfigT,
/**
* Returns an object with all platform configurations.
*/
getPlatformConfig(): Object,
/**
* Returns an array of project commands used by the CLI to load
*/
@@ -55,10 +58,21 @@ const attachPackage = (command, pkg) => Array.isArray(command)
? command.map(cmd => attachPackage(cmd, pkg))
: { ...command, pkg };
const appRoot = process.cwd();
const plugins = findPlugins([appRoot]);
const pluginPlatforms = plugins
.platforms
.reduce((acc, pathToPlatforms) => {
// $FlowFixMe non-literal require
return Object.assign(acc, require(path.join(appRoot, 'node_modules', pathToPlatforms)));
},
{});
const defaultRNConfig = {
getProjectCommands(): Array<CommandT> {
const appRoot = process.cwd();
const plugins = findPlugins([appRoot])
const commands = plugins
.commands
.map(pathToCommands => {
const name = pathToCommands.split(path.sep)[0];
@@ -70,35 +84,51 @@ const defaultRNConfig = {
);
});
return flatten(plugins);
return flatten(commands);
},
getPlatformConfig(): Object {
return {
ios,
android,
...pluginPlatforms
};
},
getProjectConfig(): Object {
const platforms = this.getPlatformConfig();
const folder = process.cwd();
const rnpm = getRNPMConfig(folder);
return Object.assign({}, rnpm, {
ios: ios.projectConfig(folder, rnpm.ios || {}),
android: android.projectConfig(folder, rnpm.android || {}),
windows: windows.projectConfig(folder, rnpm.windows || {}),
let config = Object.assign({}, rnpm, {
assets: findAssets(folder, rnpm.assets),
});
Object.keys(platforms).forEach(key => {
config[key] = platforms[key].projectConfig(folder, rnpm[key] || {});
});
return config;
},
getDependencyConfig(packageName: string) {
const platforms = this.getPlatformConfig();
const folder = path.join(process.cwd(), 'node_modules', packageName);
const rnpm = getRNPMConfig(
path.join(process.cwd(), 'node_modules', packageName)
);
return Object.assign({}, rnpm, {
ios: ios.dependencyConfig(folder, rnpm.ios || {}),
android: android.dependencyConfig(folder, rnpm.android || {}),
windows: windows.dependencyConfig(folder, rnpm.windows || {}),
let config = Object.assign({}, rnpm, {
assets: findAssets(folder, rnpm.assets),
commands: wrapCommands(rnpm.commands),
params: rnpm.params || [],
});
Object.keys(platforms).forEach(key => {
config[key] = platforms[key].dependencyConfig(folder, rnpm[key] || {});
});
return config;
},
};
+2
View File
@@ -57,3 +57,5 @@ exports.projectConfig = function projectConfigIOS(folder, userConfig) {
};
exports.dependencyConfig = exports.projectConfig;
exports.linkConfig = require('../../link/ios');
-30
View File
@@ -1,30 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const fs = require('fs');
const glob = require('glob');
const path = require('path');
/**
* Gets package's namespace
* by searching for its declaration in all C# files present in the folder
*
* @param {String} folder Folder to find C# files
*/
module.exports = function getNamespace(folder) {
const files = glob.sync('**/*.cs', { cwd: folder });
const packages = files
.map(filePath => fs.readFileSync(path.join(folder, filePath), 'utf8'))
.map(file => file.match(/namespace (.*)[\s\S]+IReactPackage/))
.filter(match => match);
return packages.length ? packages[0][1] : null;
};
@@ -1,30 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const fs = require('fs');
const glob = require('glob');
const path = require('path');
/**
* Gets package's class name (class that implements IReactPackage)
* by searching for its declaration in all C# files present in the folder
*
* @param {String} folder Folder to find C# files
*/
module.exports = function getPackageClassName(folder) {
const files = glob.sync('**/*.cs', { cwd: folder });
const packages = files
.map(filePath => fs.readFileSync(path.join(folder, filePath), 'utf8'))
.map(file => file.match(/class (.*) : IReactPackage/))
.filter(match => match);
return packages.length ? packages[0][1] : null;
};
-27
View File
@@ -1,27 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const glob = require('glob');
const path = require('path');
/**
* Find an C# project file
*
* @param {String} folder Name of the folder where to seek
* @return {String}
*/
module.exports = function findManifest(folder) {
const csprojPath = glob.sync(path.join('**', '*.csproj'), {
cwd: folder,
ignore: ['node_modules/**', '**/build/**', 'Examples/**', 'examples/**'],
})[0];
return csprojPath ? path.join(folder, csprojPath) : null;
};
@@ -1,60 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const glob = require('glob');
const path = require('path');
/**
* Glob pattern to look for solution file
*/
const GLOB_PATTERN = '**/*.sln';
/**
* Regexp matching all test projects
*/
const TEST_PROJECTS = /test|example|sample/i;
/**
* Base windows folder
*/
const WINDOWS_BASE = 'windows';
/**
* These folders will be excluded from search to speed it up
*/
const GLOB_EXCLUDE_PATTERN = ['**/@(node_modules)/**'];
/**
* Finds windows project by looking for all .sln files
* in given folder.
*
* Returns first match if files are found or null
*
* Note: `./windows/*.sln` are returned regardless of the name
*/
module.exports = function findSolution(folder) {
const projects = glob
.sync(GLOB_PATTERN, {
cwd: folder,
ignore: GLOB_EXCLUDE_PATTERN,
})
.filter(project => {
return path.dirname(project) === WINDOWS_BASE || !TEST_PROJECTS.test(project);
})
.sort((projectA, projectB) => {
return path.dirname(projectA) === WINDOWS_BASE ? -1 : 1;
});
if (projects.length === 0) {
return null;
}
return projects[0];
};
-10
View File
@@ -1,10 +0,0 @@
const s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
module.exports = function generateGUID() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
};
-114
View File
@@ -1,114 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const findWindowsSolution = require('./findWindowsSolution');
const findNamespace = require('./findNamespace');
const findProject = require('./findProject');
const findPackageClassName = require('./findPackageClassName');
const path = require('path');
const generateGUID = require('./generateGUID');
const relativeProjectPath = (fullProjPath) => {
const windowsPath = fullProjPath
.substring(fullProjPath.lastIndexOf('node_modules') - 1, fullProjPath.length)
.replace(/\//g, '\\');
return '..' + windowsPath;
};
const getProjectName = (fullProjPath) => {
return fullProjPath.split('/').slice(-1)[0].replace(/\.csproj/i, '');
};
/**
* Gets windows project config by analyzing given folder and taking some
* defaults specified by user into consideration
*/
exports.projectConfig = function projectConfigWindows(folder, userConfig) {
const csSolution = userConfig.csSolution || findWindowsSolution(folder);
if (!csSolution) {
return null;
}
// expects solutions to be named the same as project folders
const solutionPath = path.join(folder, csSolution);
const windowsAppFolder = csSolution.substring(0, csSolution.lastIndexOf('.sln'));
const src = userConfig.sourceDir || windowsAppFolder;
const sourceDir = path.join(folder, src);
const mainPage = path.join(sourceDir, 'MainPage.cs');
const projectPath = userConfig.projectPath || findProject(folder);
return {
sourceDir,
solutionPath,
projectPath,
mainPage,
folder,
userConfig,
};
};
/**
* Same as projectConfigWindows except it returns
* different config that applies to packages only
*/
exports.dependencyConfig = function dependencyConfigWindows(folder, userConfig) {
const csSolution = userConfig.csSolution || findWindowsSolution(folder);
if (!csSolution) {
return null;
}
// expects solutions to be named the same as project folders
const windowsAppFolder = csSolution.substring(0, csSolution.lastIndexOf('.sln'));
const src = userConfig.sourceDir || windowsAppFolder;
if (!src) {
return null;
}
const sourceDir = path.join(folder, src);
const packageClassName = findPackageClassName(sourceDir);
const namespace = userConfig.namespace || findNamespace(sourceDir);
const csProj = userConfig.csProj || findProject(folder);
/**
* This module has no package to export or no namespace
*/
if (!packageClassName || !namespace) {
return null;
}
const packageUsingPath = userConfig.packageUsingPath ||
`using ${namespace};`;
const packageInstance = userConfig.packageInstance ||
`new ${packageClassName}()`;
const projectGUID = generateGUID();
const pathGUID = generateGUID();
const projectName = getProjectName(csProj);
const relativeProjPath = relativeProjectPath(csProj);
return {
sourceDir,
packageUsingPath,
packageInstance,
projectName,
csProj,
folder,
projectGUID,
pathGUID,
relativeProjPath,
};
};
+67 -1
View File
@@ -39,6 +39,7 @@ describe('link', () => {
it('should accept a name of a dependency to link', (done) => {
const config = {
getPlatformConfig: () => ({ios: {}, android: {}}),
getProjectConfig: () => ({ assets: [] }),
getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
};
@@ -54,6 +55,7 @@ describe('link', () => {
it('should read dependencies from package.json when name not provided', (done) => {
const config = {
getPlatformConfig: () => ({ios: {}, android: {}}),
getProjectConfig: () => ({ assets: [] }),
getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
};
@@ -79,7 +81,10 @@ describe('link', () => {
it('should register native module when android/ios projects are present', (done) => {
const registerNativeModule = sinon.stub();
const dependencyConfig = {android: {}, ios: {}, assets: [], commands: {}};
const androidLinkConfig = require('../android');
const iosLinkConfig = require('../ios');
const config = {
getPlatformConfig: () => ({ios: { linkConfig: iosLinkConfig }, android: { linkConfig: androidLinkConfig }}),
getProjectConfig: () => ({android: {}, ios: {}, assets: []}),
getDependencyConfig: sinon.stub().returns(dependencyConfig),
};
@@ -116,6 +121,7 @@ describe('link', () => {
const registerNativeModule = sinon.stub();
const dependencyConfig = {ios: {}, android: {}, assets: [], commands: {}};
const config = {
getPlatformConfig: () => ({ios: {}, android: {}}),
getProjectConfig: () => ({ ios: {}, android: {}, assets: [] }),
getDependencyConfig: sinon.stub().returns(dependencyConfig),
};
@@ -148,6 +154,62 @@ describe('link', () => {
});
});
it('should register native modules for plugins', (done) => {
const registerNativeModule = sinon.stub();
const dependencyConfig = {ios: {}, android: {}, test: {}, assets: [], commands: {}};
const linkPluginConfig = { isInstalled: () => false, register: registerNativeModule };
const config = {
getPlatformConfig: () => ({ ios: {}, android: {}, test: { linkConfig: () => linkPluginConfig }}),
getProjectConfig: () => ({ ios: {}, android: {}, test: {}, assets: [] }),
getDependencyConfig: sinon.stub().returns(dependencyConfig),
};
jest.setMock(
'../ios/isInstalled.js',
sinon.stub().returns(true)
);
jest.setMock(
'../android/isInstalled.js',
sinon.stub().returns(true)
);
const link = require('../link').func;
link(['react-native-blur'], config).then(() => {
expect(registerNativeModule.calledOnce).toBeTruthy();
done();
});
});
it('should not register native modules for plugins when already installed', (done) => {
const registerNativeModule = sinon.stub();
const dependencyConfig = {ios: {}, android: {}, test: {}, assets: [], commands: {}};
const linkPluginConfig = { isInstalled: () => true, register: registerNativeModule};
const config = {
getPlatformConfig: () => ({ ios: {}, android: {}, test: { linkConfig: () => linkPluginConfig }}),
getProjectConfig: () => ({ ios: {}, android: {}, test: {}, assets: [] }),
getDependencyConfig: sinon.stub().returns(dependencyConfig),
};
jest.setMock(
'../ios/isInstalled.js',
sinon.stub().returns(true)
);
jest.setMock(
'../android/isInstalled.js',
sinon.stub().returns(true)
);
const link = require('../link').func;
link(['react-native-blur'], config).then(() => {
expect(registerNativeModule.callCount).toEqual(0);
done();
});
});
it('should run prelink and postlink commands at the appropriate times', (done) => {
const registerNativeModule = sinon.stub();
const prelink = sinon.stub().yieldsAsync();
@@ -163,7 +225,9 @@ describe('link', () => {
sinon.stub().returns(false)
);
const linkConfig = require('../ios');
const config = {
getPlatformConfig: () => ({ ios: { linkConfig: linkConfig }}),
getProjectConfig: () => ({ ios: {}, assets: [] }),
getDependencyConfig: sinon.stub().returns({
ios: {}, assets: [], commands: { prelink, postlink },
@@ -181,7 +245,7 @@ describe('link', () => {
it('should copy assets from both project and dependencies projects', (done) => {
const dependencyAssets = ['Fonts/Font.ttf'];
const dependencyConfig = {assets: dependencyAssets, commands: {}};
const dependencyConfig = {assets: dependencyAssets, ios: {}, commands: {}};
const projectAssets = ['Fonts/FontC.ttf'];
const copyAssets = sinon.stub();
@@ -190,7 +254,9 @@ describe('link', () => {
copyAssets
);
const linkConfig = require('../ios');
const config = {
getPlatformConfig: () => ({ ios: { linkConfig: linkConfig } }),
getProjectConfig: () => ({ ios: {}, assets: projectAssets }),
getDependencyConfig: sinon.stub().returns(dependencyConfig),
};
+2 -2
View File
@@ -8,10 +8,10 @@ const groupFilesByType = require('../groupFilesByType');
* For now, the only types of files that are handled are:
* - Fonts (otf, ttf) - copied to targetPath/fonts under original name
*/
module.exports = function copyAssetsAndroid(files, targetPath) {
module.exports = function copyAssetsAndroid(files, project) {
const assets = groupFilesByType(files);
(assets.font || []).forEach(asset =>
fs.copySync(asset, path.join(targetPath, 'fonts', path.basename(asset)))
fs.copySync(asset, path.join(project.assetsPath, 'fonts', path.basename(asset)))
);
};
+9
View File
@@ -0,0 +1,9 @@
module.exports = function() {
return {
isInstalled: require('./isInstalled'),
register: require('./registerNativeModule'),
unregister: require('./unregisterNativeModule'),
copyAssets: require('./copyAssets'),
unlinkAssets: require('./unlinkAssets')
};
};
+2 -2
View File
@@ -8,11 +8,11 @@ const groupFilesByType = require('../groupFilesByType');
* For now, the only types of files that are handled are:
* - Fonts (otf, ttf) - copied to targetPath/fonts under original name
*/
module.exports = function unlinkAssetsAndroid(files, targetPath) {
module.exports = function unlinkAssetsAndroid(files, project) {
const assets = groupFilesByType(files);
(assets.font || []).forEach((file) => {
const filePath = path.join(targetPath, 'fonts', path.basename(file));
const filePath = path.join(project.assetsPath, 'fonts', path.basename(file));
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
+6
View File
@@ -0,0 +1,6 @@
const isInstalledIOS = require('../isInstalled');
const isInstalledPods = require('../../pods/isInstalled');
module.exports = function isInstalled(config, name) {
return isInstalledIOS(config, name) || isInstalledPods(config, name);
};
@@ -0,0 +1,16 @@
const registerDependencyIOS = require('../registerNativeModule');
const registerDependencyPods = require('../../pods/registerNativeModule');
module.exports = function registerNativeModule(
name,
dependencyConfig,
params,
projectConfig
) {
if (projectConfig.podfile && dependencyConfig.podspec) {
registerDependencyPods(name, dependencyConfig, projectConfig);
}
else {
registerDependencyIOS(dependencyConfig, projectConfig);
}
};
@@ -0,0 +1,22 @@
const compact = require('lodash').compact;
const isInstalledIOS = require('../isInstalled');
const isInstalledPods = require('../../pods/isInstalled');
const unregisterDependencyIOS = require('../registerNativeModule');
const unregisterDependencyPods = require('../../pods/registerNativeModule');
module.exports = function unregisterNativeModule(
name,
dependencyConfig,
projectConfig,
otherDependencies
) {
const isIosInstalled = isInstalledIOS(projectConfig, dependencyConfig);
const isPodInstalled = isInstalledPods(projectConfig, dependencyConfig);
if (isIosInstalled) {
const iOSDependencies = compact(otherDependencies.map(d => d.config.ios));
unregisterDependencyIOS(dependencyConfig, projectConfig, iOSDependencies);
}
else if (isPodInstalled) {
unregisterDependencyPods(dependencyConfig, projectConfig);
}
};
+9
View File
@@ -0,0 +1,9 @@
module.exports = function() {
return {
isInstalled: require('./common/isInstalled'),
register: require('./common/registerNativeModule'),
unregister: require('./common/unregisterNativeModule'),
copyAssets: require('./copyAssets'),
unlinkAssets: require('./unlinkAssets')
};
};
+44 -93
View File
@@ -26,16 +26,6 @@ const chalk = require('chalk');
* run Flow. */
const isEmpty = require('lodash').isEmpty;
const promiseWaterfall = require('./promiseWaterfall');
const registerDependencyAndroid = require('./android/registerNativeModule');
const registerDependencyWindows = require('./windows/registerNativeModule');
const registerDependencyIOS = require('./ios/registerNativeModule');
const registerDependencyPods = require('./pods/registerNativeModule');
const isInstalledAndroid = require('./android/isInstalled');
const isInstalledWindows = require('./windows/isInstalled');
const isInstalledIOS = require('./ios/isInstalled');
const isInstalledPods = require('./pods/isInstalled');
const copyAssetsAndroid = require('./android/copyAssets');
const copyAssetsIOS = require('./ios/copyAssets');
const getProjectDependencies = require('./getProjectDependencies');
const getDependencyConfig = require('./getDependencyConfig');
const pollParams = require('./pollParams');
@@ -49,95 +39,55 @@ log.heading = 'rnpm-link';
const dedupeAssets = (assets) => uniqBy(assets, asset => path.basename(asset));
const linkDependency = (platforms, project, dependency) => {
Object.keys(platforms || {})
.forEach(platform => {
if (!project[platform] || !dependency.config[platform]) {
return null;
}
const linkDependencyAndroid = (androidProject, dependency) => {
if (!androidProject || !dependency.config.android) {
return null;
}
const linkConfig = platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig();
if (!linkConfig || !linkConfig.isInstalled || !linkConfig.register) {
return null;
}
const isInstalled = isInstalledAndroid(androidProject, dependency.name);
const isInstalled = linkConfig.isInstalled(project[platform], dependency.config[platform]);
if (isInstalled) {
log.info(chalk.grey(`Android module ${dependency.name} is already linked`));
return null;
}
if (isInstalled) {
log.info(chalk.grey(`Platform '${platform}' module ${dependency.name} is already linked`));
return null;
}
return pollParams(dependency.config.params).then(params => {
log.info(`Linking ${dependency.name} android dependency`);
return pollParams(dependency.config.params).then(params => {
log.info(`Linking ${dependency.name} ${platform} dependency`);
registerDependencyAndroid(
dependency.name,
dependency.config.android,
params,
androidProject
);
linkConfig.register(
dependency.name,
dependency.config[platform],
params,
project[platform]
);
log.info(`Android module ${dependency.name} has been successfully linked`);
});
log.info(`Platform '${platform}' module ${dependency.name} has been successfully linked`);
});
});
};
const linkDependencyWindows = (windowsProject, dependency) => {
if (!windowsProject || !dependency.config.windows) {
return null;
}
const isInstalled = isInstalledWindows(windowsProject, dependency.config.windows);
if (isInstalled) {
log.info(chalk.grey(`Windows module ${dependency.name} is already linked`));
return null;
}
return pollParams(dependency.config.params).then(params => {
log.info(`Linking ${dependency.name} windows dependency`);
registerDependencyWindows(
dependency.name,
dependency.config.windows,
params,
windowsProject
);
log.info(`Windows module ${dependency.name} has been successfully linked`);
});
};
const linkDependencyIOS = (iOSProject, dependency) => {
if (!iOSProject || !dependency.config.ios) {
return;
}
const isInstalled = isInstalledIOS(iOSProject, dependency.config.ios) || isInstalledPods(iOSProject, dependency.config.ios);
if (isInstalled) {
log.info(chalk.grey(`iOS module ${dependency.name} is already linked`));
return;
}
log.info(`Linking ${dependency.name} ios dependency`);
if (iOSProject.podfile && dependency.config.ios.podspec) {
registerDependencyPods(dependency, iOSProject);
}
else {
registerDependencyIOS(dependency.config.ios, iOSProject);
}
log.info(`iOS module ${dependency.name} has been successfully linked`);
};
const linkAssets = (project, assets) => {
const linkAssets = (platforms, project, assets) => {
if (isEmpty(assets)) {
return;
}
if (project.ios) {
log.info('Linking assets to ios project');
copyAssetsIOS(assets, project.ios);
}
Object.keys(platforms || {})
.forEach(platform => {
const linkConfig = platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig();
if (!linkConfig || !linkConfig.copyAssets) {
return;
}
if (project.android) {
log.info('Linking assets to android project');
copyAssetsAndroid(assets, project.android.assetsPath);
}
log.info(`Linking assets to ${platform} project`);
linkConfig.copyAssets(assets, project[platform]);
});
log.info('Assets have been successfully linked to your project');
};
@@ -150,9 +100,11 @@ const linkAssets = (project, assets) => {
* @param config CLI config, see local-cli/core/index.js
*/
function link(args: Array<string>, config: RNConfig) {
var project;
let project;
let platforms;
try {
project = config.getProjectConfig();
platforms = config.getPlatformConfig();
} catch (err) {
log.error(
'ERRPACKAGEJSON',
@@ -161,7 +113,8 @@ function link(args: Array<string>, config: RNConfig) {
return Promise.reject(err);
}
if (!project.android && !project.ios && !project.windows && findReactNativeScripts()) {
const hasProjectConfig = Object.keys(platforms).reduce((acc, key) => acc || key in project, false);
if (!hasProjectConfig && findReactNativeScripts()) {
throw new Error(
'`react-native link` can not be used in Create React Native App projects. ' +
'If you need to include a library that relies on custom native code, ' +
@@ -183,19 +136,17 @@ function link(args: Array<string>, config: RNConfig) {
);
const assets = dedupeAssets(dependencies.reduce(
(assets, dependency) => assets.concat(dependency.config.assets),
(acc, dependency) => acc.concat(dependency.config.assets),
project.assets
));
const tasks = flatten(dependencies.map(dependency => [
() => promisify(dependency.config.commands.prelink || commandStub),
() => linkDependencyAndroid(project.android, dependency),
() => linkDependencyIOS(project.ios, dependency),
() => linkDependencyWindows(project.windows, dependency),
() => linkDependency(platforms, project, dependency),
() => promisify(dependency.config.commands.postlink || commandStub),
]));
tasks.push(() => linkAssets(project, assets));
tasks.push(() => linkAssets(platforms, project, assets));
return promiseWaterfall(tasks).catch(err => {
log.error(
+2 -2
View File
@@ -7,10 +7,10 @@ const findMarkedLinesInPodfile = require('./findMarkedLinesInPodfile');
const addPodEntry = require('./addPodEntry');
const savePodFile = require('./savePodFile');
module.exports = function registerNativeModulePods(dependency, iOSProject) {
module.exports = function registerNativeModulePods(name, dependencyConfig, iOSProject) {
const podLines = readPodfile(iOSProject.podfile);
const linesToAddEntry = getLinesToAddEntry(podLines, iOSProject);
addPodEntry(podLines, linesToAddEntry, dependency.config.ios.podspec, dependency.name);
addPodEntry(podLines, linesToAddEntry, dependencyConfig.podspec, name);
savePodFile(iOSProject.podfile, podLines);
};
+39 -79
View File
@@ -1,18 +1,7 @@
const log = require('npmlog');
const getProjectDependencies = require('./getProjectDependencies');
const unregisterDependencyAndroid = require('./android/unregisterNativeModule');
const unregisterDependencyWindows = require('./windows/unregisterNativeModule');
const unregisterDependencyIOS = require('./ios/unregisterNativeModule');
const unregisterDependencyPods = require('./pods/unregisterNativeModule');
const isInstalledAndroid = require('./android/isInstalled');
const isInstalledWindows = require('./windows/isInstalled');
const isInstalledIOS = require('./ios/isInstalled');
const isInstalledPods = require('./pods/isInstalled');
const unlinkAssetsAndroid = require('./android/unlinkAssets');
const unlinkAssetsIOS = require('./ios/unlinkAssets');
const getDependencyConfig = require('./getDependencyConfig');
const compact = require('lodash').compact;
const difference = require('lodash').difference;
const filter = require('lodash').filter;
const flatten = require('lodash').flatten;
@@ -23,66 +12,37 @@ const promisify = require('./promisify');
log.heading = 'rnpm-link';
const unlinkDependencyAndroid = (androidProject, dependency, packageName) => {
if (!androidProject || !dependency.android) {
return;
}
const unlinkDependency = (platforms, project, dependency, packageName, otherDependencies) => {
const isInstalled = isInstalledAndroid(androidProject, packageName);
Object.keys(platforms || {})
.forEach(platform => {
if (!project[platform] || !dependency[platform]) {
return;
}
if (!isInstalled) {
log.info(`Android module ${packageName} is not installed`);
return;
}
const linkConfig = platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig();
if (!linkConfig || !linkConfig.isInstalled || !linkConfig.unregister) {
return;
}
log.info(`Unlinking ${packageName} android dependency`);
const isInstalled = linkConfig.isInstalled(project[platform], dependency[platform]);
unregisterDependencyAndroid(packageName, dependency.android, androidProject);
if (!isInstalled) {
log.info(`Platform '${platform}' module ${packageName} is not installed`);
return;
}
log.info(`Android module ${packageName} has been successfully unlinked`);
};
log.info(`Unlinking ${packageName} ${platform} dependency`);
const unlinkDependencyWindows = (windowsProject, dependency, packageName) => {
if (!windowsProject || !dependency.windows) {
return;
}
linkConfig.unregister(
packageName,
dependency[platform],
project[platform],
otherDependencies
);
const isInstalled = isInstalledWindows(windowsProject, dependency.windows);
if (!isInstalled) {
log.info(`Windows module ${packageName} is not installed`);
return;
}
log.info(`Unlinking ${packageName} windows dependency`);
unregisterDependencyWindows(packageName, dependency.windows, windowsProject);
log.info(`Windows module ${packageName} has been successfully unlinked`);
};
const unlinkDependencyIOS = (iOSProject, dependency, packageName, iOSDependencies) => {
if (!iOSProject || !dependency.ios) {
return;
}
const isIosInstalled = isInstalledIOS(iOSProject, dependency.ios);
const isPodInstalled = isInstalledPods(iOSProject, dependency.ios);
if (!isIosInstalled && !isPodInstalled) {
log.info(`iOS module ${packageName} is not installed`);
return;
}
log.info(`Unlinking ${packageName} ios dependency`);
if (isIosInstalled) {
unregisterDependencyIOS(dependency.ios, iOSProject, iOSDependencies);
}
else if (isPodInstalled) {
unregisterDependencyPods(dependency.ios, iOSProject);
}
log.info(`iOS module ${packageName} has been successfully unlinked`);
log.info(`Platform '${platform}' module ${dependency.name} has been successfully unlinked`);
});
};
/**
@@ -94,10 +54,12 @@ const unlinkDependencyIOS = (iOSProject, dependency, packageName, iOSDependencie
function unlink(args, config) {
const packageName = args[0];
var project;
var dependency;
let platforms;
let project;
let dependency;
try {
platforms = config.getPlatformConfig();
project = config.getProjectConfig();
} catch (err) {
log.error(
@@ -119,13 +81,10 @@ function unlink(args, config) {
const allDependencies = getDependencyConfig(config, getProjectDependencies());
const otherDependencies = filter(allDependencies, d => d.name !== packageName);
const iOSDependencies = compact(otherDependencies.map(d => d.config.ios));
const tasks = [
() => promisify(dependency.commands.preunlink || commandStub),
() => unlinkDependencyAndroid(project.android, dependency, packageName),
() => unlinkDependencyIOS(project.ios, dependency, packageName, iOSDependencies),
() => unlinkDependencyWindows(project.windows, dependency, packageName),
() => unlinkDependency(platforms, project, dependency, packageName, otherDependencies),
() => promisify(dependency.commands.postunlink || commandStub)
];
@@ -142,15 +101,16 @@ function unlink(args, config) {
return Promise.resolve();
}
if (project.ios) {
log.info('Unlinking assets from ios project');
unlinkAssetsIOS(assets, project.ios);
}
if (project.android) {
log.info('Unlinking assets from android project');
unlinkAssetsAndroid(assets, project.android.assetsPath);
}
Object.keys(platforms || {})
.forEach(platform => {
const linkConfig = platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig();
if (!linkConfig || !linkConfig.unlinkAssets) {
return;
}
log.info(`Unlinking assets from ${platform} project`);
linkConfig.unlinkAssets(assets, project[platform]);
});
log.info(
`${packageName} assets has been successfully unlinked from your project`
-8
View File
@@ -1,8 +0,0 @@
const fs = require('fs');
const makeUsingPatch = require('./patches/makeUsingPatch');
module.exports = function isInstalled(config, dependencyConfig) {
return fs
.readFileSync(config.mainPage)
.indexOf(makeUsingPatch(dependencyConfig.packageUsingPath).patch) > -1;
};
@@ -1,23 +0,0 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
const toCamelCase = require('lodash').camelCase;
module.exports = function applyParams(str, params, prefix) {
return str.replace(
/\$\{(\w+)\}/g,
(pattern, param) => {
const name = toCamelCase(prefix) + '_' + param;
return params[param]
? `getResources().getString(R.string.${name})`
: null;
}
);
};
@@ -1,11 +0,0 @@
const fs = require('fs');
module.exports = function applyPatch(file, patch, flip = false) {
fs.writeFileSync(file, fs
.readFileSync(file, 'utf8')
.replace(patch.pattern, match => {
return flip ? `${patch.patch}${match}` : `${match}${patch.patch}`;
})
);
};
@@ -1,10 +0,0 @@
const applyParams = require('./applyParams');
module.exports = function makePackagePatch(packageInstance, params, prefix) {
const processedInstance = applyParams(packageInstance, params, prefix);
return {
pattern: 'new MainReactPackage()',
patch: ',\n ' + processedInstance,
};
};
@@ -1,14 +0,0 @@
module.exports = function makeProjectPatch(windowsConfig) {
const projectInsert = `<ProjectReference Include="..\\${windowsConfig.relativeProjPath}">
<Project>{${windowsConfig.pathGUID}}</Project>
<Name>${windowsConfig.projectName}</Name>
</ProjectReference>
`;
return {
pattern: '<ProjectReference Include="..\\..\\node_modules\\react-native-windows\\ReactWindows\\ReactNative\\ReactNative.csproj">',
patch: projectInsert,
unpatch: new RegExp(`<ProjectReference.+\\s+.+\\s+.+${windowsConfig.projectName}.+\\s+.+\\s`),
};
};
@@ -1,12 +0,0 @@
module.exports = function makeSolutionPatch(windowsConfig) {
const solutionInsert = `Project("{${windowsConfig.projectGUID.toUpperCase()}}") = "${windowsConfig.projectName}", "${windowsConfig.relativeProjPath}", "{${windowsConfig.pathGUID.toUpperCase()}}"
EndProject
`;
return {
pattern: 'Global',
patch: solutionInsert,
unpatch: new RegExp(`Project.+${windowsConfig.projectName}.+\\s+EndProject\\s+`),
};
};
@@ -1,6 +0,0 @@
module.exports = function makeUsingPatch(packageImportPath) {
return {
pattern: 'using ReactNative.Modules.Core;',
patch: '\n' + packageImportPath,
};
};
@@ -1,9 +0,0 @@
const fs = require('fs');
module.exports = function revokePatch(file, patch) {
const unpatch = patch.unpatch || patch.patch;
fs.writeFileSync(file, fs
.readFileSync(file, 'utf8')
.replace(unpatch, '')
);
};
@@ -1,26 +0,0 @@
const applyPatch = require('./patches/applyPatch');
const makeProjectPatch = require('./patches/makeProjectPatch');
const makeSolutionPatch = require('./patches/makeSolutionPatch');
const makeUsingPatch = require('./patches/makeUsingPatch');
const makePackagePatch = require('./patches/makePackagePatch');
module.exports = function registerNativeWindowsModule(
name,
windowsConfig,
params,
projectConfig
) {
applyPatch(projectConfig.projectPath, makeProjectPatch(windowsConfig), true);
applyPatch(projectConfig.solutionPath, makeSolutionPatch(windowsConfig), true);
applyPatch(
projectConfig.mainPage,
makePackagePatch(windowsConfig.packageInstance, params, name)
);
applyPatch(
projectConfig.mainPage,
makeUsingPatch(windowsConfig.packageUsingPath)
);
};
@@ -1,27 +0,0 @@
const fs = require('fs');
const toCamelCase = require('lodash').camelCase;
const revokePatch = require('./patches/revokePatch');
const makeProjectPatch = require('./patches/makeProjectPatch');
const makeSolutionPatch = require('./patches/makeSolutionPatch');
const makeUsingPatch = require('./patches/makeUsingPatch');
const makePackagePatch = require('./patches/makePackagePatch');
module.exports = function unregisterNativeWindowsModule(
name,
windowsConfig,
projectConfig
) {
revokePatch(projectConfig.projectPath, makeProjectPatch(windowsConfig));
revokePatch(projectConfig.solutionPath, makeSolutionPatch(windowsConfig));
revokePatch(
projectConfig.mainPage,
makePackagePatch(windowsConfig.packageInstance, {}, name)
);
revokePatch(
projectConfig.mainPage,
makeUsingPatch(windowsConfig.packageUsingPath)
);
};
@@ -10,14 +10,14 @@
*/
/**
* This file is a copy of the reference `DeltaPatcher`, located in
* metro. The reason to not reuse that file is that in this context
* we cannot have flow annotations or CJS syntax (since this file is directly)
* injected into a static HTML page.
*
* TODO: Find a simple and lightweight way to compile `DeltaPatcher` to avoid
* having this duplicated file.
*/
* This file is a copy of the reference `DeltaPatcher`, located in
* metro. The reason to not reuse that file is that in this context
* we cannot have flow annotations or CJS syntax (since this file is directly)
* injected into a static HTML page.
*
* TODO: Find a simple and lightweight way to compile `DeltaPatcher` to avoid
* having this duplicated file.
*/
(function(global) {
'use strict';
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "1000.0.0",
"version": "0.52.3",
"description": "A framework for building native apps using React",
"license": "BSD-3-Clause",
"repository": {
@@ -209,10 +209,10 @@
"eslint-plugin-react": "^7.2.1",
"flow-bin": "^0.61.0",
"jest": "21.3.0-beta.13",
"prettier": "1.9.1",
"prettier": "1.7.0",
"react": "16.2.0",
"react-test-renderer": "16.2.0",
"shelljs": "^0.7.8",
"sinon": "^2.2.0"
}
}
}