diff --git a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj index 76c7963c0bf008..48808c4e4c8527 100644 --- a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj +++ b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj @@ -92,7 +92,7 @@ /* Begin PBXFileReference section */ 00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = ""; }; 00481BE91AC0C89D00671115 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; 00C302AF1ABCB8E700DB3ED1 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; diff --git a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js index d206716f26bb93..f26a6e051c147a 100644 --- a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js +++ b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js @@ -15,245 +15,133 @@ var React = require('react-native'); var { + PixelRatio, Navigator, - ScrollView, StyleSheet, - TabBarIOS, + ScrollView, Text, - View, TouchableHighlight, + TouchableOpacity, + View, } = React; -var SAMPLE_TEXT = 'Top Pushes. Middle Replaces. Bottom Pops.'; - var _getRandomRoute = function() { return { - backButtonTitle: 'Back' + ('' + 10 * Math.random()).substr(0, 1), - content: - SAMPLE_TEXT + '\nHere\'s a random number ' + Math.random(), - title: Math.random() > 0.5 ? 'Hello' : 'There', - rightButtonTitle: Math.random() > 0.5 ? 'Right' : 'Button', + title: '#' + Math.ceil(Math.random() * 1000), }; }; - -var SampleNavigationBarRouteMapper = { - rightContentForRoute: function(route, navigator) { - if (route.rightButtonTitle) { - return ( - - {route.rightButtonTitle} - - ); - } else { - return null; - } - }, - titleContentForRoute: function(route, navigator) { +class NavButton extends React.Component { + render() { return ( navigator.push(_getRandomRoute())}> - - {route.title} - - - ); - }, - iconForRoute: function(route, navigator) { - var onPress = - navigator.popToRoute.bind(navigator, route); - return ( - - - - ); - }, - separatorForRoute: function(route, navigator) { - return ( - - + style={styles.button} + underlayColor="#B5B5B5" + onPress={this.props.onPress}> + {this.props.text} ); } -}; - -var _delay = 400; // Just to test for race conditions with native nav. - -var renderScene = function(route, navigator) { - var content = route.content; - return ( - - - - - request push soon - - - - - {content} - - - - - {content} - - - - - {content} - - - - - {content} - - - - - {content} - - - - - request pop soon - - - - - Immediate set two routes - - - - - pop to top soon - - - - - ); -}; - -var _popToTopLater = function(popToTop) { - return () => setTimeout(popToTop, _delay); -}; - -var _pushRouteLater = function(push) { - return () => setTimeout( - () => push(_getRandomRoute()), - _delay - ); -}; - -var _immediatelySetTwoItemsLater = function(immediatelyResetRouteStack) { - return () => setTimeout( - () => immediatelyResetRouteStack([ - _getRandomRoute(), - _getRandomRoute(), - ]) - ); -}; - -var _popRouteLater = function(pop) { - return () => setTimeout(pop, _delay); -}; +} var BreadcrumbNavSample = React.createClass({ - getInitialState: function() { - return { - selectedTab: 0, + componentWillMount: function() { + this._navBarRouteMapper = { + rightContentForRoute: function(route, navigator) { + return null; + }, + titleContentForRoute: function(route, navigator) { + return ( + navigator.push(_getRandomRoute())}> + + {route.title} + + + ); + }, + iconForRoute: function(route, navigator) { + return ( + { + navigator.popToRoute(route); + }}> + + + ); + }, + separatorForRoute: function(route, navigator) { + return ( + + + + ); + } }; }, + _renderScene: function(route, navigator) { + return ( + + { navigator.push(_getRandomRoute()) }} + text="Push" + /> + { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]) }} + text="Reset w/ 2 scenes" + /> + { navigator.popToTop() }} + text="Pop to top" + /> + { navigator.replace(_getRandomRoute()) }} + text="Replace" + /> + { this.props.navigator.pop(); }} + text="Close breadcrumb example" + /> + + ); + }, + render: function() { - var initialRoute = { - backButtonTitle: 'Start', // no back button for initial scene - content: SAMPLE_TEXT, - title: 'Campaigns', - rightButtonTitle: 'Filter', - }; return ( - - - - } - /> - - - Navigator.SceneConfigs.FloatFromBottom} - debugOverlay={false} - style={[styles.appContainer]} - initialRoute={initialRoute} - renderScene={renderScene} - navigationBar={ - - } + - - + } + /> ); }, - onTabSelect: function(tab, event) { - if (this.state.selectedTab !== tab) { - this.setState({selectedTab: tab}); - } - }, + }); var styles = StyleSheet.create({ - navigationItem: { - backgroundColor: '#eeeeee', - }, scene: { paddingTop: 50, flex: 1, }, button: { - backgroundColor: '#cccccc', - margin: 50, - marginTop: 26, - padding: 10, + backgroundColor: 'white', + padding: 15, + borderBottomWidth: 1 / PixelRatio.get(), + borderBottomColor: '#CDCDCD', }, buttonText: { - fontSize: 12, - textAlign: 'center', + fontSize: 17, + fontWeight: '500', }, - appContainer: { + container: { overflow: 'hidden', backgroundColor: '#dddddd', flex: 1, @@ -262,13 +150,9 @@ var styles = StyleSheet.create({ fontSize: 18, color: '#666666', textAlign: 'center', - fontWeight: '500', + fontWeight: 'bold', lineHeight: 32, }, - filterText: { - color: '#5577ff', - }, - // TODO: Accept icons from route. crumbIconPlaceholder: { flex: 1, backgroundColor: '#666666', diff --git a/Examples/UIExplorer/Navigator/JumpingNavSample.js b/Examples/UIExplorer/Navigator/JumpingNavSample.js index 15586b65d429c4..7b0fa2122dd880 100644 --- a/Examples/UIExplorer/Navigator/JumpingNavSample.js +++ b/Examples/UIExplorer/Navigator/JumpingNavSample.js @@ -16,8 +16,10 @@ var React = require('react-native'); var { Navigator, + PixelRatio, StyleSheet, ScrollView, + TabBarIOS, Text, TouchableHighlight, View, @@ -25,178 +27,186 @@ var { var _getRandomRoute = function() { return { - randNumber: Math.random(), + randNumber: Math.ceil(Math.random() * 1000), }; }; -var INIT_ROUTE = _getRandomRoute(); +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + var ROUTE_STACK = [ _getRandomRoute(), _getRandomRoute(), - INIT_ROUTE, - _getRandomRoute(), _getRandomRoute(), ]; -var renderScene = function(route, navigator) { - return ( - - - {route.randNumber} - { - navigator.jumpBack(); - }}> - - jumpBack - - - { - navigator.jumpForward(); - }}> - - jumpForward - - - { - navigator.jumpTo(INIT_ROUTE); - }}> - - jumpTo initial route - - - { - navigator.push(_getRandomRoute()); - }}> - - destructive: push - - - { - navigator.replace(_getRandomRoute()); - }}> - - destructive: replace - - - { - navigator.pop(); - }}> - - destructive: pop - - - { - navigator.immediatelyResetRouteStack([ - _getRandomRoute(), - _getRandomRoute(), - ]); - }}> - - destructive: Immediate set two routes - - - { - navigator.popToTop(); - }}> - - destructive: pop to top - - - - - ); -}; +var INIT_ROUTE_INDEX = 1; class JumpingNavBar extends React.Component { render() { return ( - - {this.props.routeStack.map((route, index) => ( - { - this.props.navigator.jumpTo(route); - }}> - - - {index} - - - - ))} + + + { this.props.onTabIndex(0); }} + children={} + /> + { this.props.onTabIndex(1); }} + children={} + /> + { this.props.onTabIndex(2); }} + children={} + /> + ); } } var JumpingNavSample = React.createClass({ + getInitialState: function() { + return { + tabIndex: INIT_ROUTE_INDEX, + }; + }, render: function() { return ( { + this._navigator = navigator; + }} + initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]} initialRouteStack={ROUTE_STACK} - renderScene={renderScene} - navigationBar={} + renderScene={this.renderScene} + navigationBar={ + { + this.setState({ tabIndex: index }, () => { + this._navigator.jumpTo(ROUTE_STACK[index]); + }); + }} + /> + } + onWillFocus={(route) => { + this.setState({ + tabIndex: ROUTE_STACK.indexOf(route), + }); + }} shouldJumpOnBackstackPop={true} /> ); }, + renderScene: function(route, navigator) { + var backBtn; + var forwardBtn; + if (ROUTE_STACK.indexOf(route) !== 0) { + backBtn = ( + { + navigator.jumpBack(); + }} + text="jumpBack" + /> + ); + } + if (ROUTE_STACK.indexOf(route) !== ROUTE_STACK.length - 1) { + forwardBtn = ( + { + navigator.jumpForward(); + }} + text="jumpForward" + /> + ); + } + return ( + + #{route.randNumber} + {backBtn} + {forwardBtn} + { + navigator.jumpTo(ROUTE_STACK[1]); + }} + text="jumpTo middle route" + /> + { + this.props.navigator.pop(); + }} + text="Exit Navigation Example" + /> + { + this.props.navigator.push({ + message: 'Came from jumping example', + }); + }} + text="Nav Menu" + /> + + ); + }, }); var styles = StyleSheet.create({ - scene: { - backgroundColor: '#eeeeee', - }, - scroll: { - flex: 1, - }, button: { - backgroundColor: '#cccccc', - margin: 50, - marginTop: 26, - padding: 10, + backgroundColor: 'white', + padding: 15, + borderBottomWidth: 1 / PixelRatio.get(), + borderBottomColor: '#CDCDCD', }, buttonText: { - fontSize: 12, - textAlign: 'center', + fontSize: 17, + fontWeight: '500', }, appContainer: { overflow: 'hidden', backgroundColor: '#dddddd', flex: 1, }, - navBar: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - height: 90, - flexDirection: 'row', + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, }, - navButton: { + scene: { flex: 1, + paddingTop: 20, + backgroundColor: '#EAEAEA', }, - navButtonText: { - textAlign: 'center', - fontSize: 32, - marginTop: 25, - }, - navButtonActive: { - color: 'green', - }, + tabs: { + height: 50, + } }); module.exports = JumpingNavSample; diff --git a/Examples/UIExplorer/Navigator/NavigationBarSample.js b/Examples/UIExplorer/Navigator/NavigationBarSample.js index 67e6a8d9197d4b..ce080943611956 100644 --- a/Examples/UIExplorer/Navigator/NavigationBarSample.js +++ b/Examples/UIExplorer/Navigator/NavigationBarSample.js @@ -16,15 +16,30 @@ var React = require('react-native'); var { + PixelRatio, Navigator, + ScrollView, StyleSheet, Text, TouchableHighlight, + TouchableOpacity, View, } = React; var cssVar = require('cssVar'); +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} var NavigationBarRouteMapper = { @@ -35,26 +50,27 @@ var NavigationBarRouteMapper = { var previousRoute = navState.routeStack[index - 1]; return ( - navigator.pop()}> - + navigator.pop()}> + {previousRoute.title} - + ); }, RightButton: function(route, navigator, index, navState) { return ( - navigator.push(newRandomRoute())}> - + Next - + ); }, @@ -70,8 +86,7 @@ var NavigationBarRouteMapper = { function newRandomRoute() { return { - content: 'Hello World!', - title: 'Random ' + Math.round(Math.random() * 100), + title: '#' + Math.ceil(Math.random() * 1000), }; } @@ -79,37 +94,63 @@ var NavigationBarSample = React.createClass({ render: function() { return ( - - ( - - {route.content} - - )} - navigationBar={ - ( + + {route.content} + { + navigator.immediatelyResetRouteStack([ + newRandomRoute(), + newRandomRoute(), + newRandomRoute(), + ]); + }} + text="Reset w/ 3 scenes" + /> + { + this.props.navigator.pop(); + }} + text="Exit NavigationBar Example" /> - } - /> - + + )} + navigationBar={ + + } + /> ); }, }); var styles = StyleSheet.create({ - appContainer: { - overflow: 'hidden', - backgroundColor: '#ffffff', - flex: 1, + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, }, - scene: { - paddingTop: 50, - flex: 1, + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: 1 / PixelRatio.get(), + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + navBar: { + backgroundColor: 'white', }, navBarText: { fontSize: 16, @@ -120,9 +161,20 @@ var styles = StyleSheet.create({ fontWeight: '500', marginVertical: 9, }, + navBarLeftButton: { + paddingLeft: 10, + }, + navBarRightButton: { + paddingRight: 10, + }, navBarButtonText: { color: cssVar('fbui-accent-blue'), }, + scene: { + flex: 1, + paddingTop: 20, + backgroundColor: '#EAEAEA', + }, }); module.exports = NavigationBarSample; diff --git a/Examples/UIExplorer/Navigator/NavigatorExample.js b/Examples/UIExplorer/Navigator/NavigatorExample.js index d7e31c46e81c76..ef8001f94c6ee7 100644 --- a/Examples/UIExplorer/Navigator/NavigatorExample.js +++ b/Examples/UIExplorer/Navigator/NavigatorExample.js @@ -16,6 +16,7 @@ var React = require('react-native'); var { Navigator, + PixelRatio, ScrollView, StyleSheet, Text, @@ -25,30 +26,78 @@ var BreadcrumbNavSample = require('./BreadcrumbNavSample'); var NavigationBarSample = require('./NavigationBarSample'); var JumpingNavSample = require('./JumpingNavSample'); +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + class NavMenu extends React.Component { render() { return ( - { - this.props.navigator.push({ id: 'breadcrumbs' }); - }}> - Breadcrumbs Example - - { - this.props.navigator.push({ id: 'navbar' }); - }}> - Navbar Example - - { - this.props.navigator.push({ id: 'jumping' }); - }}> - Jumping Example - - { - this.props.onExampleExit(); - }}> - Exit Navigator Example - + {this.props.message} + { + this.props.navigator.push({ + message: 'Swipe right to dismiss', + sceneConfig: Navigator.SceneConfigs.FloatFromRight, + }); + }} + text="Float in from right" + /> + { + this.props.navigator.push({ + message: 'Swipe down to dismiss', + sceneConfig: Navigator.SceneConfigs.FloatFromBottom, + }); + }} + text="Float in from bottom" + /> + { + this.props.navigator.pop(); + }} + text="Pop" + /> + { + this.props.navigator.popToTop(); + }} + text="Pop to top" + /> + { + this.props.navigator.push({ id: 'navbar' }); + }} + text="Navbar Example" + /> + { + this.props.navigator.push({ id: 'jumping' }); + }} + text="Jumping Example" + /> + { + this.props.navigator.push({ id: 'breadcrumbs' }); + }} + text="Breadcrumbs Example" + /> + { + this.props.onExampleExit(); + }} + text="Exit Example" + /> ); } @@ -63,19 +112,20 @@ var TabBarExample = React.createClass({ renderScene: function(route, nav) { switch (route.id) { - case 'menu': + case 'navbar': + return ; + case 'breadcrumbs': + return ; + case 'jumping': + return ; + default: return ( ); - case 'navbar': - return ; - case 'breadcrumbs': - return ; - case 'jumping': - return ; } }, @@ -83,9 +133,14 @@ var TabBarExample = React.createClass({ return ( Navigator.SceneConfigs.FloatFromBottom} + configureScene={(route) => { + if (route.sceneConfig) { + return route.sceneConfig; + } + return Navigator.SceneConfigs.FloatFromBottom; + }} /> ); }, @@ -93,18 +148,30 @@ var TabBarExample = React.createClass({ }); var styles = StyleSheet.create({ + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, container: { flex: 1, }, button: { backgroundColor: 'white', padding: 15, + borderBottomWidth: 1 / PixelRatio.get(), + borderBottomColor: '#CDCDCD', }, buttonText: { + fontSize: 17, + fontWeight: '500', }, scene: { flex: 1, - paddingTop: 64, + paddingTop: 20, + backgroundColor: '#EAEAEA', } }); diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/UIExplorer/UIExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index e668abba37cfbc..00000000000000 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png index bdc9fc180483ad..044576efc9f6e7 100644 Binary files a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png differ diff --git a/Libraries/Animation/POPAnimationMixin.js b/Libraries/Animation/POPAnimationMixin.js index a1ded9f953553c..213023290b3696 100644 --- a/Libraries/Animation/POPAnimationMixin.js +++ b/Libraries/Animation/POPAnimationMixin.js @@ -11,14 +11,18 @@ */ 'use strict'; -var POPAnimation = require('POPAnimation'); +var POPAnimationOrNull = require('POPAnimation'); -if (!POPAnimation) { +if (!POPAnimationOrNull) { // POP animation isn't available in the OSS fork - this is a temporary // workaround to enable its availability to be determined at runtime. module.exports = (null : ?{}); } else { +// At this point, POPAnimationOrNull is guaranteed to be +// non-null. Bring it local to preserve type refinement. +var POPAnimation = POPAnimationOrNull; + var invariant = require('invariant'); var warning = require('warning'); diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js index 2bd2ebd03850ec..959bf238304f72 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule POPAnimation + * @flow */ 'use strict'; @@ -14,7 +15,7 @@ var RCTPOPAnimationManager = require('NativeModules').POPAnimationManager; if (!RCTPOPAnimationManager) { // POP animation isn't available in the OSS fork - this is a temporary // workaround to enable its availability to be determined at runtime. - module.exports = null; + module.exports = (null: ?Object); } else { var ReactPropTypes = require('ReactPropTypes'); @@ -64,6 +65,20 @@ var Types = { spring: RCTTypes.spring, }; +type Attrs = { + type?: $Enum; + property?: $Enum; + fromValue?: any; + toValue?: any; + duration?: any; + velocity?: any; + deceleration?: any; + springBounciness?: any; + dynamicsFriction?: any; + dynamicsMass?: any; + dynamicsTension?: any; +} + var POPAnimation = { Types: Types, Properties: Properties, @@ -83,11 +98,11 @@ var POPAnimation = { }), lastUsedTag: 0, - allocateTagForAnimation: function() { + allocateTagForAnimation: function(): number { return ++this.lastUsedTag; }, - createAnimation: function(typeName, attrs) { + createAnimation: function(typeName: number, attrs: Attrs): number { var tag = this.allocateTagForAnimation(); if (__DEV__) { @@ -107,35 +122,35 @@ var POPAnimation = { return tag; }, - createSpringAnimation: function(attrs) { + createSpringAnimation: function(attrs: Attrs): number { return this.createAnimation(this.Types.spring, attrs); }, - createDecayAnimation: function(attrs) { + createDecayAnimation: function(attrs: Attrs): number { return this.createAnimation(this.Types.decay, attrs); }, - createLinearAnimation: function(attrs) { + createLinearAnimation: function(attrs: Attrs): number { return this.createAnimation(this.Types.linear, attrs); }, - createEaseInAnimation: function(attrs) { + createEaseInAnimation: function(attrs: Attrs): number { return this.createAnimation(this.Types.easeIn, attrs); }, - createEaseOutAnimation: function(attrs) { + createEaseOutAnimation: function(attrs: Attrs): number { return this.createAnimation(this.Types.easeOut, attrs); }, - createEaseInEaseOutAnimation: function(attrs) { + createEaseInEaseOutAnimation: function(attrs: Attrs): number { return this.createAnimation(this.Types.easeInEaseOut, attrs); }, - addAnimation: function(nodeHandle, anim, callback) { + addAnimation: function(nodeHandle: any, anim: number, callback: Function) { RCTPOPAnimationManager.addAnimation(nodeHandle, anim, callback); }, - removeAnimation: function(nodeHandle, anim) { + removeAnimation: function(nodeHandle: any, anim: number) { RCTPOPAnimationManager.removeAnimation(nodeHandle, anim); }, }; diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index a8809ad554a9cf..621fd4bfc1a709 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -95,20 +95,20 @@ var styles = StyleSheet.create({ }); /** - * Use `ReactNavigator` to transition between different scenes in your app. To + * Use `Navigator` to transition between different scenes in your app. To * accomplish this, provide route objects to the navigator to identify each * scene, and also a `renderScene` function that the navigator can use to * render the scene for a given route. * * To change the animation or gesture properties of the scene, provide a * `configureScene` prop to get the config object for a given route. See - * `ReactNavigator.SceneConfigs` for default animations and more info on + * `Navigator.SceneConfigs` for default animations and more info on * scene config options. * * ### Basic Usage * * ``` - * * ReactNavigator.SceneConfigs.FloatFromRight + * (route) => Navigator.SceneConfigs.FloatFromRight * ``` */ configureScene: PropTypes.func, @@ -230,7 +230,7 @@ var Navigator = React.createClass({ navigationBar: PropTypes.node, /** - * Optionally provide the navigator object from a parent ReactNavigator + * Optionally provide the navigator object from a parent Navigator */ navigator: PropTypes.object, @@ -316,7 +316,7 @@ var Navigator = React.createClass({ parentNavigator: this.props.navigator, // We want to bubble focused routes to the top navigation stack. If we // are a child navigator, this allows us to call props.navigator.on*Focus - // of the topmost ReactNavigator + // of the topmost Navigator onWillFocus: this.props.onWillFocus, onDidFocus: this.props.onDidFocus, }; diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.ios.js similarity index 100% rename from Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.js rename to Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.ios.js diff --git a/Libraries/Geolocation/Geolocation.ios.js b/Libraries/Geolocation/Geolocation.ios.js index eb33fbbbeed589..6a022f76f5d686 100644 --- a/Libraries/Geolocation/Geolocation.ios.js +++ b/Libraries/Geolocation/Geolocation.ios.js @@ -37,7 +37,8 @@ var Geolocation = { getCurrentPosition: function( geo_success: Function, geo_error?: Function, - geo_options?: Object) { + geo_options?: Object + ) { invariant( typeof geo_success === 'function', 'Must provide a valid geo_success callback.' diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 1e2589723b3521..ed857584d2265c 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule Image + * @flow */ 'use strict'; @@ -106,7 +107,9 @@ var Image = React.createClass({ render: function() { var style = flattenStyle([styles.base, this.props.style]); + invariant(style, "style must be initialized"); var source = this.props.source; + invariant(source, "source must be initialized"); var isNetwork = source.uri && source.uri.match(/^https?:/); invariant( !(isNetwork && source.isStatic), diff --git a/Libraries/Image/ImageResizeMode.js b/Libraries/Image/ImageResizeMode.js index 368f4dfb85e68c..9a7a2f9541f9ff 100644 --- a/Libraries/Image/ImageResizeMode.js +++ b/Libraries/Image/ImageResizeMode.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ImageResizeMode + * @flow */ 'use strict'; diff --git a/Libraries/Image/ImageStylePropTypes.js b/Libraries/Image/ImageStylePropTypes.js index b48ac8653dfc29..4e361d9dea1293 100644 --- a/Libraries/Image/ImageStylePropTypes.js +++ b/Libraries/Image/ImageStylePropTypes.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ImageStylePropTypes + * @flow */ 'use strict'; @@ -39,8 +40,8 @@ var unsupportedProps = Object.keys({ paddingHorizontal: null, }); -for (var key in unsupportedProps) { - delete ImageStylePropTypes[key]; +for (var i = 0; i < unsupportedProps.length; i++) { + delete ImageStylePropTypes[unsupportedProps[i]]; } module.exports = ImageStylePropTypes; diff --git a/Libraries/Image/RCTNetworkImageView.m b/Libraries/Image/RCTNetworkImageView.m index 5ea0b64d51d3b0..c97b97cfdec7d9 100644 --- a/Libraries/Image/RCTNetworkImageView.m +++ b/Libraries/Image/RCTNetworkImageView.m @@ -75,6 +75,7 @@ - (void)setImageURL:(NSURL *)imageURL resetToDefaultImageWhileLoading:(BOOL)rese } else { _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) { if (image) { + [self.layer removeAnimationForKey:@"contents"]; self.layer.contentsScale = image.scale; self.layer.contents = (__bridge id)image.CGImage; } diff --git a/Libraries/Interaction/InteractionManager.js b/Libraries/Interaction/InteractionManager.js index 3a104bad2b1152..6aef690dd16a8b 100644 --- a/Libraries/Interaction/InteractionManager.js +++ b/Libraries/Interaction/InteractionManager.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule InteractionManager + * @flow */ 'use strict'; @@ -70,7 +71,7 @@ var InteractionManager = { /** * Schedule a function to run after all interactions have completed. */ - runAfterInteractions(callback) { + runAfterInteractions(callback: Function) { invariant( typeof callback === 'function', 'Must specify a function to schedule.' @@ -82,7 +83,7 @@ var InteractionManager = { /** * Notify manager that an interaction has started. */ - createInteractionHandle() { + createInteractionHandle(): number { scheduleUpdate(); var handle = ++_inc; _addInteractionSet.add(handle); @@ -92,7 +93,7 @@ var InteractionManager = { /** * Notify manager that an interaction has completed. */ - clearInteractionHandle(handle) { + clearInteractionHandle(handle: number) { invariant( !!handle, 'Must provide a handle to clear.' diff --git a/Libraries/Interaction/InteractionMixin.js b/Libraries/Interaction/InteractionMixin.js index 633159c1f4c3c3..b92120f90fe4bf 100644 --- a/Libraries/Interaction/InteractionMixin.js +++ b/Libraries/Interaction/InteractionMixin.js @@ -2,6 +2,7 @@ * Copyright 2004-present Facebook. All Rights Reserved. * * @providesModule InteractionMixin + * @flow */ 'use strict'; @@ -21,7 +22,7 @@ var InteractionMixin = { } }, - _interactionMixinHandles: [], + _interactionMixinHandles: ([]: Array), createInteractionHandle: function() { var handle = InteractionManager.createInteractionHandle(); @@ -29,7 +30,7 @@ var InteractionMixin = { return handle; }, - clearInteractionHandle: function(clearHandle) { + clearInteractionHandle: function(clearHandle: number) { InteractionManager.clearInteractionHandle(clearHandle); this._interactionMixinHandles = this._interactionMixinHandles.filter( handle => handle !== clearHandle @@ -41,7 +42,7 @@ var InteractionMixin = { * * @param {function} callback */ - runAfterInteractions: function(callback) { + runAfterInteractions: function(callback: Function) { InteractionManager.runAfterInteractions(callback); }, }; diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index 9b2a43bcc71bd6..7dadeb5bba795c 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ExceptionsManager + * @flow */ 'use strict'; @@ -18,7 +19,13 @@ var parseErrorStack = require('parseErrorStack'); var sourceMapPromise; -function handleException(e) { +type Exception = { + sourceURL: string; + line: number; + message: string; +} + +function handleException(e: Exception) { var stack = parseErrorStack(e); console.error( 'Error: ' + diff --git a/Libraries/LinkingIOS/LinkingIOS.js b/Libraries/LinkingIOS/LinkingIOS.js index 8e36ded711f335..473bd0fa36bdc8 100644 --- a/Libraries/LinkingIOS/LinkingIOS.js +++ b/Libraries/LinkingIOS/LinkingIOS.js @@ -21,74 +21,8 @@ var _initialURL = RCTLinkingManager && var DEVICE_NOTIF_EVENT = 'openURL'; -/** - * `LinkingIOS` gives you an interface to interact with both incoming and - * outgoing app links. - * - * ### Basic Usage - * - * #### Handling deep links - * - * If your app was launched from an external URL registered with your app, you can - * access and handle it from any component you want with the following: - * - * ``` - * componentDidMount() { - * var url = LinkingIOS.popInitialURL(); - * if (url) { ... } - * } - * ``` - * - * If you also want to listen to incoming app links during your app's - * execution you'll need to add the following lines to you `*AppDelegate.m`: - * - * ``` - * - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { - * return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; - * } - * ``` - * - * And in your React component, you'll then be able to listen to the events from - * `LinkingIOS` as follows - * - * ``` - * componentDidMount() { - * LinkingIOS.addEventListener('url', this._handleOpenURL); - * }, - * componentWillUnmount() { - * LinkingIOS.removeEventListener('url', this._handleOpenURL); - * }, - * _handleOpenURL(event) { - * console.log(event.url); - * } - * ``` - * - * #### Triggering App links - * - * To trigger an app link (browser, email, or custom schemes) you can call: - * - * ``` - * LinkingIOS.openURL(url) - * ``` - * - * If you want to check if a URL can be opened by an installed app on the system you can call - * - * ``` - * LinkingIOS.canOpenURL(url, (supported) => { - * if (!supported) { - * AlertIOS.alert('Can\'t handle url: ' + url); - * } else { - * LinkingIOS.openURL(url); - * } - * }); - * ``` - */ class LinkingIOS { - /** - * Add a handler to LinkingIOS changes by listening to the `url` event type - * and providing the handler - */ - static addEventListener(type: string, handler: Function) { + static addEventListener(type, handler) { invariant( type === 'url', 'LinkingIOS only supports `url` events' @@ -101,10 +35,7 @@ class LinkingIOS { ); } - /** - * Remove a handler by passing the `url` event type and the handler - */ - static removeEventListener(type: string, handler: Function ) { + static removeEventListener(type, handler) { invariant( type === 'url', 'LinkingIOS only supports `url` events' @@ -116,12 +47,7 @@ class LinkingIOS { _notifHandlers[handler] = null; } - /** - * Try to open the given `url` with any of the installed apps. - * If multiple applications can open `url`, the one that opens - * is undefined. - */ - static openURL(url: string) { + static openURL(url) { invariant( typeof url === 'string', 'Invalid url: should be a string' @@ -129,11 +55,7 @@ class LinkingIOS { RCTLinkingManager.openURL(url); } - /** - * Determine whether an installed app can handle a given `url`. - * The callback function will be called with `bool supported` as the only argument - */ - static canOpenURL(url: string, callback: Function) { + static canOpenURL(url, callback) { invariant( typeof url === 'string', 'Invalid url: should be a string' @@ -145,11 +67,7 @@ class LinkingIOS { RCTLinkingManager.canOpenURL(url, callback); } - /** - * If the app launch was triggered by an app link, it will pop the link URL, - * otherwise it will return `null` - */ - static popInitialURL(): ?string { + static popInitialURL() { var initialURL = _initialURL; _initialURL = null; return initialURL; diff --git a/Libraries/LinkingIOS/RCTLinkingManager.h b/Libraries/LinkingIOS/RCTLinkingManager.h index f2ec63215fb2b2..98e40beeed3e95 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.h +++ b/Libraries/LinkingIOS/RCTLinkingManager.h @@ -9,7 +9,7 @@ #import -#import "RCTBridgeModule.h" +#import "../../React/Base/RCTBridgeModule.h" @interface RCTLinkingManager : NSObject diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h index b60a646e416f0f..4e791c68002c1e 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h @@ -9,7 +9,7 @@ #import -#import "RCTBridgeModule.h" +#import "../../React/Base/RCTBridgeModule.h" @interface RCTPushNotificationManager : NSObject diff --git a/React.podspec b/React.podspec index f1d4df7aa591f3..4ad740e4ad3592 100644 --- a/React.podspec +++ b/React.podspec @@ -1,93 +1,78 @@ Pod::Spec.new do |s| - s.name = "React" - s.version = "0.1.0" - s.summary = "Build high quality mobile apps using React." - s.description = <<-DESC - React Native apps are built using the React JS - framework, and render directly to native UIKit - elements using a fully asynchronous architecture. - There is no browser and no HTML. We have picked what - we think is the best set of features from these and - other technologies to build what we hope to become - the best product development framework available, - with an emphasis on iteration speed, developer - delight, continuity of technology, and absolutely - beautiful and fast products with no compromises in - quality or capability. - DESC - s.homepage = "http://facebook.github.io/react-native/" - s.license = "BSD" - s.author = "Facebook" - s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "v#{s.version}" } - s.default_subspec = 'Core' - s.requires_arc = true - s.platform = :ios, "7.0" - s.prepare_command = 'npm install' - s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli" - - s.subspec 'Core' do |ss| - ss.libraries = 'icucore' - ss.source_files = "React/**/*.{c,h,m}" - ss.exclude_files = "**/__tests__/*", "IntegrationTests/*" - ss.frameworks = "JavaScriptCore" - end + s.name = "React" + s.version = "0.1.0" + s.summary = "Build high quality mobile apps using React." + s.description= <<-DESC + React Native apps are built using the React JS framework, + and render directly to native UIKit elements using a fully + asynchronous architecture. There is no browser and no HTML. + We have picked what we think is the best set of features from + these and other technologies to build what we hope to become + the best product development framework available, with an + emphasis on iteration speed, developer delight, continuity + of technology, and absolutely beautiful and fast products + with no compromises in quality or capability. + DESC + s.homepage = "http://facebook.github.io/react-native/" + s.license = "BSD" + s.author = "Facebook" + s.platform = :ios, "7.0" + s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "v#{s.version}" } + s.source_files = "React/**/*.{c,h,m}" + s.resources = "Resources/*.png" + s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli" + s.exclude_files = "**/__tests__/*", "IntegrationTests/*" + s.frameworks = "JavaScriptCore" + s.requires_arc = true + s.prepare_command = 'npm install' + s.libraries = 'icucore' s.subspec 'RCTActionSheet' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/ActionSheetIOS/*.{h,m}" + ss.source_files = "Libraries/ActionSheetIOS/*.{h,m}" ss.preserve_paths = "Libraries/ActionSheetIOS/*.js" end s.subspec 'RCTAdSupport' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/AdSupport/*.{h,m}" - ss.preserve_paths = "Libraries/AdSupport/*.js" + ss.source_files = "Libraries/RCTAdSupport/*.{h,m}" + ss.preserve_paths = "Libraries/RCTAdSupport/*.js" end s.subspec 'RCTAnimation' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/Animation/*.{h,m}" - ss.preserve_paths = "Libraries/Animation/*.js" + ss.source_files = "Libraries/Animation/*.{h,m}" + ss.preserve_paths = "Libraries/Animation/*.js" end s.subspec 'RCTGeolocation' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/Geolocation/*.{h,m}" - ss.preserve_paths = "Libraries/Geolocation/*.js" + ss.source_files = "Libraries/Geolocation/*.{h,m}" + ss.preserve_paths = "Libraries/Geolocation/*.js" end s.subspec 'RCTImage' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/Image/*.{h,m}" - ss.preserve_paths = "Libraries/Image/*.js" + ss.source_files = "Libraries/Image/*.{h,m}" + ss.preserve_paths = "Libraries/Image/*.js" end s.subspec 'RCTNetwork' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/Network/*.{h,m}" - ss.preserve_paths = "Libraries/Network/*.js" + ss.source_files = "Libraries/Network/*.{h,m}" + ss.preserve_paths = "Libraries/Network/*.js" end s.subspec 'RCTPushNotification' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/PushNotificationIOS/*.{h,m}" - ss.preserve_paths = "Libraries/PushNotificationIOS/*.js" + ss.source_files = "Libraries/PushNotificationIOS/*.{h,m}" + ss.preserve_paths = "Libraries/PushNotificationIOS/*.js" end s.subspec 'RCTWebSocketDebugger' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}" + ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}" end s.subspec 'RCTText' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/Text/*.{h,m}" - ss.preserve_paths = "Libraries/Text/*.js" + ss.source_files = "Libraries/Text/*.{h,m}" + ss.preserve_paths = "Libraries/Text/*.js" end s.subspec 'RCTVibration' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/Vibration/*.{h,m}" - ss.preserve_paths = "Libraries/Vibration/*.js" + ss.source_files = "Libraries/Vibration/*.{h,m}" + ss.preserve_paths = "Libraries/Vibration/*.js" end end diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index c8d6ecadab5501..ba8ef698687810 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -10,10 +10,11 @@ #import #import -#import "Layout.h" -#import "RCTAnimationType.h" +#import "../Layout/Layout.h" +#import "../Views/RCTAnimationType.h" +#import "../Views/RCTPointerEvents.h" + #import "RCTLog.h" -#import "RCTPointerEvents.h" /** * This class provides a collection of conversion functions for mapping diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 9bf7104c996b0d..344fc3102dcc6e 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -453,22 +453,21 @@ + (CGImageRef)CGImage:(id)json return [self UIImage:json].CGImage; } -#ifndef __IPHONE_8_2 - -// These constants are defined in iPhone SDK 8.2 -// They'll work fine in earlier iOS versions, but the app cannot be built with -// an SDK version < 8.2 unless we redefine them here. This will be removed -// in a future version of React, once 8.2 is more widely adopted. - -static const CGFloat UIFontWeightUltraLight = -0.8; -static const CGFloat UIFontWeightThin = -0.6; -static const CGFloat UIFontWeightLight = -0.4; -static const CGFloat UIFontWeightRegular = 0; -static const CGFloat UIFontWeightMedium = 0.23; -static const CGFloat UIFontWeightSemibold = 0.3; -static const CGFloat UIFontWeightBold = 0.4; -static const CGFloat UIFontWeightHeavy = 0.56; -static const CGFloat UIFontWeightBlack = 0.62; +#if !defined(__IPHONE_8_2) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_2 + +// These constants are defined in iPhone SDK 8.2, but the app cannot run on +// iOS < 8.2 unless we redefine them here. If you target iOS 8.2 or above +// as a base target, the standard constants will be used instead. + +#define UIFontWeightUltraLight -0.8 +#define UIFontWeightThin -0.6 +#define UIFontWeightLight -0.4 +#define UIFontWeightRegular 0 +#define UIFontWeightMedium 0.23 +#define UIFontWeightSemibold 0.3 +#define UIFontWeightBold 0.4 +#define UIFontWeightHeavy 0.56 +#define UIFontWeightBlack 0.62 #endif diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index 894849ffc7de11..c70dda9c63f3de 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -9,10 +9,10 @@ #import -#import "RCTBridge.h" -#import "RCTBridgeModule.h" -#import "RCTInvalidating.h" -#import "RCTViewManager.h" +#import "../Base/RCTBridge.h" +#import "../Base/RCTBridgeModule.h" +#import "../Base/RCTInvalidating.h" +#import "../Views/RCTViewManager.h" @protocol RCTScrollableProtocol; diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 8d68855f7f186a..6efb0c1d118967 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -9,7 +9,8 @@ #import -#import "Layout.h" +#import "../Layout/Layout.h" + #import "RCTViewNodeProtocol.h" @class RCTSparseArray; diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index 6f2188d3d04647..5c1c609d357475 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -9,9 +9,9 @@ #import -#import "RCTBridgeModule.h" -#import "RCTConvert.h" -#import "RCTLog.h" +#import "../Base/RCTBridgeModule.h" +#import "../Base/RCTConvert.h" +#import "../Base/RCTLog.h" @class RCTBridge; @class RCTEventDispatcher; diff --git a/packager/packager.js b/packager/packager.js index 9503df6ea42e51..119336edc9f572 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -69,11 +69,7 @@ if (options.assetRoots) { options.assetRoots = options.assetRoots.split(','); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { - options.assetRoots = [path.resolve(__dirname, '../../..')]; - } else { - options.assetRoots = [path.resolve(__dirname, '..')]; - } + options.assetRoots = [path.resolve(__dirname, '..')]; } console.log('\n' + diff --git a/packager/packager.sh b/packager/packager.sh index 969ca1b2df039a..93a017c358c0ac 100755 --- a/packager/packager.sh +++ b/packager/packager.sh @@ -10,4 +10,4 @@ ulimit -n 4096 THIS_DIR=$(dirname "$0") -node $THIS_DIR/packager.js "$@" +node "$THIS_DIR/packager.js" "$@" diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 962a069468e62c..69ed11e65723ce 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -821,6 +821,55 @@ describe('DependencyGraph', function() { }); }); + pit('updates module dependencies on asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!foo")' + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetRoots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher + }); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + }, + { id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + }, + ]); + }); + }); + }); + pit('runs changes through ignore filter', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index adb0128284eb8f..6f09c5e88af87e 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -86,7 +86,11 @@ DependecyGraph.prototype.load = function() { DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { var absolutePath = this._getAbsolutePath(entryPath); if (absolutePath == null) { - throw new Error('Cannot find entry file in any of the roots: ' + entryPath); + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); } var module = this._graph[absolutePath]; @@ -478,7 +482,12 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) { /** * Process a filewatcher change event. */ -DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) { +DependecyGraph.prototype._processFileChange = function( + eventType, + filePath, + root, + stat +) { var absPath = path.join(root, filePath); if (this._ignoreFilePath(absPath)) { return; @@ -486,6 +495,11 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root this._debugUpdateEvents.push({event: eventType, path: filePath}); + if (this._assetExts.indexOf(extname(filePath)) > -1) { + this._processAssetChange(eventType, absPath); + return; + } + var isPackage = path.basename(filePath) === 'package.json'; if (eventType === 'delete') { if (isPackage) { @@ -520,7 +534,8 @@ DependecyGraph.prototype.getDebugInfo = function() { }; /** - * Searches all roots for the file and returns the first one that has file of the same path. + * Searches all roots for the file and returns the first one that has file of + * the same path. */ DependecyGraph.prototype._getAbsolutePath = function(filePath) { if (isAbsolutePath(filePath)) { @@ -543,12 +558,43 @@ DependecyGraph.prototype._buildAssetMap = function() { return q(); } - var self = this; - return buildAssetMap(this._assetRoots, this._assetExts) - .then(function(map) { - self._assetMap = map; - return map; + this._assetMap = Object.create(null); + return buildAssetMap( + this._assetRoots, + this._processAsset.bind(this) + ); +}; + +DependecyGraph.prototype._processAsset = function(file) { + var ext = extname(file); + if (this._assetExts.indexOf(ext) !== -1) { + var name = assetName(file, ext); + if (this._assetMap[name] != null) { + debug('Conflcting assets', name); + } + + this._assetMap[name] = new ModuleDescriptor({ + id: 'image!' + name, + path: path.resolve(file), + isAsset: true, + dependencies: [], }); + } +}; + +DependecyGraph.prototype._processAssetChange = function(eventType, file) { + if (this._assetMap == null) { + return; + } + + var name = assetName(file, extname(file)); + if (eventType === 'change' || eventType === 'delete') { + delete this._assetMap[name]; + } + + if (eventType === 'change' || eventType === 'add') { + this._processAsset(file); + } }; /** @@ -623,15 +669,14 @@ function readAndStatDir(dir) { * Given a list of roots and list of extensions find all the files in * the directory with that extension and build a map of those assets. */ -function buildAssetMap(roots, exts) { +function buildAssetMap(roots, processAsset) { var queue = roots.slice(0); - var map = Object.create(null); function search() { var root = queue.shift(); if (root == null) { - return q(map); + return q(); } return readAndStatDir(root).spread(function(files, stats) { @@ -639,21 +684,7 @@ function buildAssetMap(roots, exts) { if (stats[i].isDirectory()) { queue.push(file); } else { - var ext = path.extname(file).replace(/^\./, ''); - if (exts.indexOf(ext) !== -1) { - var assetName = path.basename(file, '.' + ext) - .replace(/@[\d\.]+x/, ''); - if (map[assetName] != null) { - debug('Conflcting assets', assetName); - } - - map[assetName] = new ModuleDescriptor({ - id: 'image!' + assetName, - path: path.resolve(file), - isAsset: true, - dependencies: [], - }); - } + processAsset(file); } }); @@ -664,4 +695,24 @@ function buildAssetMap(roots, exts) { return search(); } +function assetName(file, ext) { + return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); +} + +function extname(name) { + return path.extname(name).replace(/^\./, ''); +} + + +function NotFoundError() { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + var msg = util.format.apply(util, arguments); + this.message = msg; + this.type = this.name = 'NotFoundError'; + this.status = 404; +} + +NotFoundError.__proto__ = Error.prototype; + module.exports = DependecyGraph; diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js index 2edb3b52c2a963..0e46d5e8a2701f 100644 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/index.js @@ -51,15 +51,15 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, + fileWatcher: { + type: 'object', + required: true, + }, }); function HasteDependencyResolver(options) { var opts = validateOpts(options); - this._fileWatcher = opts.nonPersistent - ? FileWatcher.createDummyWatcher() - : new FileWatcher(opts.projectRoots); - this._depGraph = new DependencyGraph({ roots: opts.projectRoots, assetRoots: opts.assetRoots, @@ -67,7 +67,7 @@ function HasteDependencyResolver(options) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, - fileWatcher: this._fileWatcher, + fileWatcher: opts.fileWatcher, }); @@ -164,10 +164,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { }); }; -HasteDependencyResolver.prototype.end = function() { - return this._fileWatcher.end(); -}; - HasteDependencyResolver.prototype.getDebugInfo = function() { return this._depGraph.getDebugInfo(); }; diff --git a/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 213033c5b47b7d..e24618dcfee917 100644 --- a/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -21,6 +21,7 @@ describe('FileWatcher', function() { var Watcher; beforeEach(function() { + require('mock-modules').dumpCache(); FileWatcher = require('../'); Watcher = require('sane').WatchmanWatcher; Watcher.prototype.once.mockImplementation(function(type, callback) { diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js index c1633683c27020..86ec962bd210e8 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/FileWatcher/index.js @@ -16,7 +16,7 @@ var exec = require('child_process').exec; var Promise = q.Promise; -var detectingWatcherClass = new Promise(function(resolve, reject) { +var detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { resolve(sane.NodeWatcher); @@ -30,14 +30,23 @@ module.exports = FileWatcher; var MAX_WAIT_TIME = 3000; -function FileWatcher(projectRoots) { - var self = this; +// Singleton +var fileWatcher = null; + +function FileWatcher(rootConfigs) { + if (fileWatcher) { + // This allows us to optimize watching in the future by merging roots etc. + throw new Error('FileWatcher can only be instantiated once'); + } + + fileWatcher = this; + this._loading = q.all( - projectRoots.map(createWatcher) + rootConfigs.map(createWatcher) ).then(function(watchers) { watchers.forEach(function(watcher) { watcher.on('all', function(type, filepath, root) { - self.emit('all', type, filepath, root); + fileWatcher.emit('all', type, filepath, root); }); }); return watchers; @@ -50,21 +59,14 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - delete watchersByRoot[watcher._root]; return q.ninvoke(watcher, 'close'); }); }); }; -var watchersByRoot = Object.create(null); - -function createWatcher(root) { - if (watchersByRoot[root] != null) { - return Promise.resolve(watchersByRoot[root]); - } - +function createWatcher(rootConfig) { return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(root, {glob: ['**/*.js', '**/package.json']}); + var watcher = new Watcher(rootConfig.dir, rootConfig.globs); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { @@ -77,8 +79,6 @@ function createWatcher(root) { watcher.once('ready', function() { clearTimeout(rejectTimeout); - watchersByRoot[root] = watcher; - watcher._root = root; resolve(watcher); }); }); diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index ac7bd2e2fac3e8..843efe75a6d9f0 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -56,6 +56,14 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, + fileWatcher: { + type: 'object', + required: true, + }, }); function Packager(options) { @@ -70,6 +78,7 @@ function Packager(options) { nonPersistent: opts.nonPersistent, moduleFormat: opts.moduleFormat, assetRoots: opts.assetRoots, + fileWatcher: opts.fileWatcher, }); this._transformer = new Transformer({ @@ -83,10 +92,7 @@ function Packager(options) { } Packager.prototype.kill = function() { - return q.all([ - this._transformer.kill(), - this._resolver.end(), - ]); + return this._transformer.kill(); }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index f40ebeeaf84f1d..23af55dbca47ae 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -55,18 +55,49 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, }); function Server(options) { var opts = validateOpts(options); + this._projectRoots = opts.projectRoots; this._packages = Object.create(null); - this._packager = new Packager(opts); this._changeWatchers = []; + var watchRootConfigs = opts.projectRoots.map(function(dir) { + return { + dir: dir, + globs: [ + '**/*.js', + '**/package.json', + ] + }; + }); + + if (opts.assetRoots != null) { + watchRootConfigs = watchRootConfigs.concat( + opts.assetRoots.map(function(dir) { + return { + dir: dir, + globs: opts.assetExts.map(function(ext) { + return '**/*.' + ext; + }), + }; + }) + ); + } + this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() - : new FileWatcher(options.projectRoots); + : new FileWatcher(watchRootConfigs); + + var packagerOpts = Object.create(opts); + packagerOpts.fileWatcher = this._fileWatcher; + this._packager = new Packager(packagerOpts); var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); @@ -246,6 +277,9 @@ Server.prototype.processRequest = function(req, res, next) { function getOptionsFromUrl(reqUrl) { // `true` to parse the query param as an object. var urlObj = url.parse(reqUrl, true); + // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 + urlObj.query = urlObj.query || {}; + var pathname = urlObj.pathname; // Backwards compatibility. Options used to be as added as '.' to the @@ -281,11 +315,11 @@ function getBoolOptionFromQuery(query, opt, defaultVal) { } function handleError(res, error) { - res.writeHead(500, { + res.writeHead(error.status || 500, { 'Content-Type': 'application/json; charset=UTF-8', }); - if (error.type === 'TransformError') { + if (error.type === 'TransformError' || error.type === 'NotFoundError') { res.end(JSON.stringify(error)); } else { console.error(error.stack || error);