diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index faa3267a1..b886bf861 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -21,10 +21,41 @@ var { StyleSheet, Text, View, + ActivityIndicatorIOS } = React; var ImageCapInsetsExample = require('./ImageCapInsetsExample'); +var NetworkImageExample = React.createClass({ + watchID: (null: ?number), + + getInitialState: function() { + return { + error: false, + loading: true, + progress: 0 + }; + }, + render: function() { + var loader = this.state.loading ? + + {this.state.progress}% + + : null; + return this.state.error ? + {this.state.error} : + this.setState({error: e.nativeEvent.error})} + onLoadProgress={(e) => this.setState({progress: Math.max(0, Math.round(100 * e.nativeEvent.written / e.nativeEvent.total))}) } + onLoadEnd={() => this.setState({loading: false, error: false})} + onLoadAbort={() => this.setState({error: 'Loading has aborted'})} > + {loader} + ; + } +}); + exports.displayName = (undefined: ?string); exports.framework = 'React'; exports.title = ''; @@ -59,6 +90,22 @@ exports.examples = [ ); }, }, + { + title: 'Error Handler', + render: function() { + return ( + + ); + }, + }, + { + title: 'Image Download Progress', + render: function() { + return ( + + ); + }, + }, { title: 'Border Color', render: function() { @@ -300,6 +347,12 @@ var styles = StyleSheet.create({ width: 38, height: 38, }, + progress: { + flex: 1, + alignItems: 'center', + flexDirection: 'row', + width: 100 + }, leftMargin: { marginLeft: 10, }, diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index cbc3e5b41..4aff00e73 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -369,25 +369,6 @@ exports.examples = [ ); }, -}, { - title: 'allowFontScaling attribute', - render: function() { - return ( - - - By default, text will respect Text Size accessibility setting on iOS. - It means that all font sizes will be increased or descreased depending on the value of Text Size setting in - {" "}Settings.app - Display & Brightness - Text Size - - - You can disable scaling for your Text component by passing {"\""}allowFontScaling={"{"}false{"}\""} prop. - - - This text will not scale. - - - ); - }, }]; var styles = StyleSheet.create({ diff --git a/Examples/UIExplorer/TransformExample.js b/Examples/UIExplorer/TransformExample.js index 9e848c3a5..7da9eb5ac 100644 --- a/Examples/UIExplorer/TransformExample.js +++ b/Examples/UIExplorer/TransformExample.js @@ -1,61 +1,85 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. * - * @providesModule TransformExample + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 'use strict'; -var React = require('React'); +var React = require('react-native'); var { + Animated, StyleSheet, + Text, View, } = React; -var TimerMixin = require('react-timer-mixin'); -var UIExplorerBlock = require('./UIExplorerBlock'); -var UIExplorerPage = require('./UIExplorerPage'); - -var TransformExample = React.createClass({ - - mixins: [TimerMixin], - +var Flip = React.createClass({ getInitialState() { return { - interval: this.setInterval(this._update, 800), - pulse: false, + theta: new Animated.Value(45), }; }, + componentDidMount() { + this._animate(); + }, + + _animate() { + this.state.theta.setValue(0); + Animated.timing(this.state.theta, { + toValue: 360, + duration: 5000, + }).start(this._animate); + }, + render() { return ( - - - - - - - - - - - - - + + + + This text is flipping great. + + + + + On the flip side... + + + ); - }, - - _update() { - this.setState({ - pulse: !this.state.pulse, - }); - }, - + } }); var styles = StyleSheet.create({ + container: { + height: 500, + }, box1: { left: 0, backgroundColor: 'green', @@ -88,7 +112,7 @@ var styles = StyleSheet.create({ }, box3step1: { left: 0, - backgroundColor: '#ffb6c1', // lightpink + backgroundColor: 'lightpink', height: 50, position: 'absolute', top: 0, @@ -99,7 +123,7 @@ var styles = StyleSheet.create({ }, box3step2: { left: 0, - backgroundColor: '#ff69b4', //hotpink + backgroundColor: 'hotpink', height: 50, opacity: 0.5, position: 'absolute', @@ -113,7 +137,7 @@ var styles = StyleSheet.create({ }, box3step3: { left: 0, - backgroundColor: '#ff1493', // deeppink + backgroundColor: 'deeppink', height: 50, opacity: 0.5, position: 'absolute', @@ -129,7 +153,7 @@ var styles = StyleSheet.create({ }, box4: { left: 0, - backgroundColor: '#ff8c00', // darkorange + backgroundColor: 'darkorange', height: 50, position: 'absolute', top: 0, @@ -141,7 +165,7 @@ var styles = StyleSheet.create({ width: 100, }, box5: { - backgroundColor: '#800000', // maroon + backgroundColor: 'maroon', height: 50, position: 'absolute', right: 0, @@ -155,7 +179,110 @@ var styles = StyleSheet.create({ {scale: 2}, ], }, + flipCardContainer: { + marginVertical: 40, + flex: 1, + alignSelf: 'center', + }, + flipCard: { + width: 200, + height: 200, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'blue', + backfaceVisibility: 'hidden', + }, + flipText: { + width: 90, + fontSize: 20, + color: 'white', + fontWeight: 'bold', + } }); - -module.exports = TransformExample; +exports.title = 'Transforms'; +exports.description = 'View transforms'; +exports.examples = [ + { + title: 'Perspective', + description: 'perspective: 850, rotateX: Animated.timing(0 -> 360)', + render(): ReactElement { return ; } + }, + { + title: 'Translate, Rotate, Scale', + description: "translateX: 100, translateY: 50, rotate: '30deg', scaleX: 2, scaleY: 2", + render() { + return ( + + + + ); + } + }, + { + title: 'Scale, Translate, Rotate, ', + description: "scaleX: 2, scaleY: 2, translateX: 100, translateY: 50, rotate: '30deg'", + render() { + return ( + + + + ); + } + }, + { + title: 'Rotate', + description: "rotate: '30deg'", + render() { + return ( + + + + ); + } + }, + { + title: 'Rotate, Scale', + description: "rotate: '30deg', scaleX: 2, scaleY: 2", + render() { + return ( + + + + ); + } + }, + { + title: 'Rotate, Scale, Translate ', + description: "rotate: '30deg', scaleX: 2, scaleY: 2, translateX: 100, translateY: 50", + render() { + return ( + + + + ); + } + }, + { + title: 'Translate, Scale, Rotate', + description: "translate: [200, 350], scale: 2.5, rotate: '-0.2rad'", + render() { + return ( + + + + ); + } + }, + { + title: 'Translate, Rotate, Scale', + description: "translate: [-50, 35], rotate: '50deg', scale: 2", + render() { + return ( + + + + ); + } + } +]; diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 6a5ad20e5..f19fdf364 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -43,7 +43,7 @@ * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` * to your Xcode project folder and run * - * $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle' -o main.jsbundle + * $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle' -o main.jsbundle * * then add the `main.jsbundle` file to your project and uncomment this line: */ diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 217797e26..20edd13b0 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -80,6 +80,7 @@ if (Platform.OS === 'ios') { require('./AlertIOSExample'), require('./AppStateIOSExample'), require('./AsyncStorageExample'), + require('./TransformExample'), require('./BorderExample'), require('./CameraRollExample.ios'), require('./NetInfoExample'), diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index 280aaf3b3..60f41747b 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -24,7 +24,7 @@ @property (nonatomic, strong, readonly) RCTBridge *batchedBridge; -- (void)_handleBuffer:(id)buffer context:(NSNumber *)context; +- (void)_handleBuffer:(id)buffer; - (void)setUp; @end @@ -57,7 +57,6 @@ RCT_EXPORT_MODULE() - (void)executeJSCall:(__unused NSString *)name method:(__unused NSString *)method arguments:(__unused NSArray *)arguments - context:(__unused NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete { onComplete(nil, nil); @@ -155,7 +154,7 @@ RCT_EXPORT_MODULE(TestModule) NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42]; NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567]; - [_bridge.batchedBridge _handleBuffer:buffer context:RCTGetExecutorID(executor)]; + [_bridge.batchedBridge _handleBuffer:buffer]; dispatch_sync(_methodQueue, ^{ // clear the queue diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index 02450fab6..18b4118ba 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -33,7 +33,6 @@ { [super setUp]; _executor = [[RCTContextExecutor alloc] init]; - RCTSetExecutorID(_executor); [_executor setUp]; } @@ -139,7 +138,6 @@ static uint64_t _get_time_nanoseconds(void) [_executor executeJSCall:@"module" method:@"method" arguments:params - context:RCTGetExecutorID(_executor) callback:^(id json, __unused NSError *unused) { XCTAssert([json isEqual:@YES], @"Invalid return"); }]; diff --git a/Libraries/ART/RCTConvert+ART.m b/Libraries/ART/RCTConvert+ART.m index e6bc09286..7a607a12c 100644 --- a/Libraries/ART/RCTConvert+ART.m +++ b/Libraries/ART/RCTConvert+ART.m @@ -87,7 +87,7 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{ } NSDictionary *fontDict = dict[@"font"]; - CTFontRef font = (__bridge CTFontRef)[self UIFont:nil withFamily:fontDict[@"fontFamily"] size:fontDict[@"fontSize"] weight:fontDict[@"fontWeight"] style:fontDict[@"fontStyle"] scaleMultiplier:1.0]; + CTFontRef font = (__bridge CTFontRef)[self UIFont:nil withFamily:fontDict[@"fontFamily"] size:fontDict[@"fontSize"] weight:fontDict[@"fontWeight"] style:fontDict[@"fontStyle"]]; if (!font) { return frame; } diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index c0b1981e1..141cefab0 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -411,7 +411,7 @@ var TextInput = React.createClass({ _renderIOS: function() { var textContainer; - var props = this.props; + var props = Object.assign({},this.props); props.style = [styles.input, this.props.style]; if (!props.multiline) { diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index ce147e096..64ace6d7b 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -21,6 +21,7 @@ var TransformPropTypes = require('TransformPropTypes'); var ViewStylePropTypes = { ...LayoutPropTypes, ...TransformPropTypes, + backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']), backgroundColor: ReactPropTypes.string, borderColor: ReactPropTypes.string, borderTopColor: ReactPropTypes.string, diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index bc5d65791..573223771 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -220,11 +220,6 @@ var Navigator = React.createClass({ */ onDidFocus: PropTypes.func, - /** - * Will be called with (ref, indexInStack, route) when the scene ref changes - */ - onItemRef: PropTypes.func, - /** * Optionally provide a navigation bar that persists across scene * transitions @@ -318,7 +313,6 @@ var Navigator = React.createClass({ onPanResponderMove: this._handlePanResponderMove, onPanResponderTerminate: this._handlePanResponderTerminate, }); - this._itemRefs = {}; this._interactionHandle = null; this._emitWillFocus(this.state.routeStack[this.state.presentedIndex]); }, @@ -1006,22 +1000,10 @@ var Navigator = React.createClass({ return this.state.routeStack.slice(); }, - _handleItemRef: function(itemId, route, ref) { - this._itemRefs[itemId] = ref; - var itemIndex = this.state.idStack.indexOf(itemId); - if (itemIndex === -1) { - return; - } - this.props.onItemRef && this.props.onItemRef(ref, itemIndex, route); - }, - _cleanScenesPastIndex: function(index) { var newStackLength = index + 1; // Remove any unneeded rendered routes. if (newStackLength < this.state.routeStack.length) { - this.state.idStack.slice(newStackLength).map((removingId) => { - this._itemRefs[removingId] = null; - }); this.setState({ sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength), idStack: this.state.idStack.slice(0, newStackLength), @@ -1031,38 +1013,22 @@ var Navigator = React.createClass({ }, _renderScene: function(route, i) { - var child = this.props.renderScene( - route, - this - ); var disabledSceneStyle = null; if (i !== this.state.presentedIndex) { disabledSceneStyle = styles.disabledScene; } - var originalRef = child.ref; - if (originalRef != null && typeof originalRef !== 'function') { - console.warn( - 'String refs are not supported for navigator scenes. Use a callback ' + - 'ref instead. Ignoring ref: ' + originalRef - ); - originalRef = null; - } return ( { return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null); }} style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}> - {React.cloneElement(child, { - ref: component => { - this._handleItemRef(this.state.idStack[i], route, component); - if (originalRef) { - originalRef(component); - } - } - })} + {this.props.renderScene( + route, + this + )} ); }, diff --git a/Libraries/Image/RCTDownloadTaskWrapper.m b/Libraries/Image/RCTDownloadTaskWrapper.m index b910cb476..e9a64369b 100644 --- a/Libraries/Image/RCTDownloadTaskWrapper.m +++ b/Libraries/Image/RCTDownloadTaskWrapper.m @@ -66,7 +66,6 @@ static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownload task.rct_completionBlock = completionBlock; task.rct_progressBlock = progressBlock; - [task resume]; return task; } diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 697214249..f32d895cb 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -14,7 +14,8 @@ #import "RCTLog.h" #import "RCTUtils.h" -typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error); +typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSURLResponse *response, + NSData *data, NSError *error); CGSize RCTTargetSizeForClipRect(CGRect); CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); @@ -80,24 +81,36 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); _pendingBlocks[cacheKey] = [NSMutableArray arrayWithObject:block]; __weak RCTImageDownloader *weakSelf = self; - RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) { + RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSURLResponse *response, NSData *data, NSError *error) { + + if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (httpResponse.statusCode != 200) { + data = nil; + error = [[NSError alloc] initWithDomain:NSURLErrorDomain + code:httpResponse.statusCode + userInfo:nil]; + } + } + dispatch_async(_processingQueue, ^{ RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; for (RCTCachedDataDownloadBlock downloadBlock in blocks) { - downloadBlock(cached, data, error); + downloadBlock(cached, response, data, error); } }); }; NSURLRequest *request = [NSURLRequest requestWithURL:url]; task = [_downloadTaskWrapper downloadData:url progressBlock:progressBlock completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { + if (!cancelled) { - runBlocks(NO, data, error); + runBlocks(NO, response, data, error); } - if (response) { + if (response && !error) { RCTImageDownloader *strongSelf = weakSelf; NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed]; [strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request]; @@ -111,7 +124,7 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); } if (cachedResponse) { - runBlocks(YES, cachedResponse.data, nil); + runBlocks(YES, cachedResponse.response, cachedResponse.data, nil); } else { [task resume]; } @@ -123,7 +136,7 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); - (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTDataDownloadBlock)block { - return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSData *data, NSError *error) { + return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSURLResponse *response, NSData *data, NSError *error) { block(data, error); }]; } diff --git a/Libraries/Image/RCTNetworkImageView.m b/Libraries/Image/RCTNetworkImageView.m index f8dc28157..20d297b46 100644 --- a/Libraries/Image/RCTNetworkImageView.m +++ b/Libraries/Image/RCTNetworkImageView.m @@ -86,6 +86,8 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) { if (![_imageURL isEqual:imageURL] && _downloadToken) { [_imageDownloader cancelDownload:_downloadToken]; + NSDictionary *event = @{ @"target": self.reactTag }; + [_eventDispatcher sendInputEventWithName:@"loadAbort" body:event]; _downloadToken = nil; } @@ -146,7 +148,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) loadEndHandler(); }); } else if (error) { - errorHandler([error description]); + errorHandler([error localizedDescription]); } }]; } else { @@ -170,7 +172,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) loadEndHandler(); }); } else if (error) { - errorHandler([error description]); + errorHandler([error localizedDescription]); } }]; } diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m index 29d990b27..f42ef48f1 100644 --- a/Libraries/Image/RCTNetworkImageViewManager.m +++ b/Libraries/Image/RCTNetworkImageViewManager.m @@ -47,7 +47,7 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTNetworkImageView) return @{ @"loadStart": @{ @"registrationName": @"onLoadStart" }, @"loadProgress": @{ @"registrationName": @"onLoadProgress" }, - @"loaded": @{ @"registrationName": @"onLoaded" }, + @"loaded": @{ @"registrationName": @"onLoadEnd" }, @"loadError": @{ @"registrationName": @"onLoadError" }, @"loadAbort": @{ @"registrationName": @"onLoadAbort" }, }; diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js index 6b1ac5789..6015bcf3f 100644 --- a/Libraries/Inspector/Inspector.js +++ b/Libraries/Inspector/Inspector.js @@ -20,10 +20,21 @@ var StyleSheet = require('StyleSheet'); var UIManager = require('NativeModules').UIManager; var View = require('View'); +var REACT_DEVTOOLS_HOOK: ?Object = typeof window !== 'undefined' ? window.__REACT_DEVTOOLS_BACKEND__ : null; + +if (REACT_DEVTOOLS_HOOK) { + // required for devtools to be able to edit react native styles + REACT_DEVTOOLS_HOOK.resolveRNStyle = require('flattenStyle'); +} + class Inspector extends React.Component { + _subs: ?Array<() => void>; + constructor(props: Object) { super(props); + this.state = { + devtoolsBackend: null, panelPos: 'bottom', inspecting: true, perfing: false, @@ -31,6 +42,63 @@ class Inspector extends React.Component { }; } + componentDidMount() { + if (REACT_DEVTOOLS_HOOK) { + this.attachToDevtools = this.attachToDevtools.bind(this); + REACT_DEVTOOLS_HOOK.addStartupListener(this.attachToDevtools); + // if devtools is already started + // TODO(jared): should addStartupListener just go ahead and call the + // listener if the devtools is already started? might be unexpected... + // is there some name other than `addStartupListener` that would be + // better? + if (REACT_DEVTOOLS_HOOK.backend) { + this.attachToDevtools(REACT_DEVTOOLS_HOOK.backend); + } + } + } + + componentWillUnmount() { + if (this._subs) { + this._subs.map(fn => fn()); + } + if (REACT_DEVTOOLS_HOOK) { + REACT_DEVTOOLS_HOOK.removeStartupListener(this.attachToDevtools); + } + } + + attachToDevtools(backend: Object) { + var _hideWait = null; + var hlSub = backend.sub('highlight', ({node, name, props}) => { + clearTimeout(_hideWait); + UIManager.measure(node, (x, y, width, height, left, top) => { + this.setState({ + hierarchy: [], + inspected: { + frame: {left, top, width, height}, + style: props ? props.style : {}, + }, + }); + }); + }); + var hideSub = backend.sub('hideHighlight', () => { + // we wait to actually hide in order to avoid flicker + _hideWait = setTimeout(() => { + this.setState({ + inspected: null, + }); + }, 100); + }); + this._subs = [hlSub, hideSub]; + + backend.on('shutdown', () => { + this.setState({devtoolsBackend: null}); + this._subs = null; + }); + this.setState({ + devtoolsBackend: backend, + }); + } + setSelection(i: number) { var instance = this.state.hierarchy[i]; var publicInstance = instance.getPublicInstance(); @@ -46,6 +114,9 @@ class Inspector extends React.Component { } onTouchInstance(instance: Object, frame: Object, pointerY: number) { + if (this.state.devtoolsBackend) { + this.state.devtoolsBackend.selectFromReactInstance(instance, true); + } var hierarchy = InspectorUtils.getOwnerHierarchy(instance); var publicInstance = instance.getPublicInstance(); var props = publicInstance.props || {}; @@ -88,6 +159,7 @@ class Inspector extends React.Component { />} - {contents} + {!this.props.devtoolsIsOpen && contents}