Merge pull request #357 from facebook/updates-mar-27

Update from Friday March 27
This commit is contained in:
Amjad Masad
2015-03-27 12:15:11 -07:00
37 changed files with 749 additions and 671 deletions

View File

@@ -92,7 +92,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = "<group>"; }; 00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = "<group>"; };
00481BE91AC0C89D00671115 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; 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 = "<absolute>"; }; 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = "<group>"; };
00C302AF1ABCB8E700DB3ED1 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; }; 00C302AF1ABCB8E700DB3ED1 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; }; 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; }; 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; };

View File

@@ -15,245 +15,133 @@
var React = require('react-native'); var React = require('react-native');
var { var {
PixelRatio,
Navigator, Navigator,
ScrollView,
StyleSheet, StyleSheet,
TabBarIOS, ScrollView,
Text, Text,
View,
TouchableHighlight, TouchableHighlight,
TouchableOpacity,
View,
} = React; } = React;
var SAMPLE_TEXT = 'Top Pushes. Middle Replaces. Bottom Pops.';
var _getRandomRoute = function() { var _getRandomRoute = function() {
return { return {
backButtonTitle: 'Back' + ('' + 10 * Math.random()).substr(0, 1), title: '#' + Math.ceil(Math.random() * 1000),
content:
SAMPLE_TEXT + '\nHere\'s a random number ' + Math.random(),
title: Math.random() > 0.5 ? 'Hello' : 'There',
rightButtonTitle: Math.random() > 0.5 ? 'Right' : 'Button',
}; };
}; };
class NavButton extends React.Component {
var SampleNavigationBarRouteMapper = { render() {
rightContentForRoute: function(route, navigator) {
if (route.rightButtonTitle) {
return (
<Text style={[styles.titleText, styles.filterText]}>
{route.rightButtonTitle}
</Text>
);
} else {
return null;
}
},
titleContentForRoute: function(route, navigator) {
return ( return (
<TouchableHighlight <TouchableHighlight
onPress={() => navigator.push(_getRandomRoute())}> style={styles.button}
<View> underlayColor="#B5B5B5"
<Text style={styles.titleText}>{route.title}</Text> onPress={this.props.onPress}>
</View> <Text style={styles.buttonText}>{this.props.text}</Text>
</TouchableHighlight>
);
},
iconForRoute: function(route, navigator) {
var onPress =
navigator.popToRoute.bind(navigator, route);
return (
<TouchableHighlight onPress={onPress}>
<View style={styles.crumbIconPlaceholder} />
</TouchableHighlight>
);
},
separatorForRoute: function(route, navigator) {
return (
<TouchableHighlight onPress={navigator.pop}>
<View style={styles.crumbSeparatorPlaceholder} />
</TouchableHighlight> </TouchableHighlight>
); );
} }
}; }
var _delay = 400; // Just to test for race conditions with native nav.
var renderScene = function(route, navigator) {
var content = route.content;
return (
<ScrollView>
<View style={styles.scene}>
<TouchableHighlight
onPress={_pushRouteLater(navigator.push)}>
<View style={styles.button}>
<Text style={styles.buttonText}>request push soon</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_pushRouteLater(navigator.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_pushRouteLater(navigator.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_pushRouteLater(navigator.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_pushRouteLater(navigator.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_pushRouteLater(navigator.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_popRouteLater(navigator.pop)}>
<View style={styles.button}>
<Text style={styles.buttonText}>request pop soon</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={
_immediatelySetTwoItemsLater(
navigator.immediatelyResetRouteStack
)
}>
<View style={styles.button}>
<Text style={styles.buttonText}>Immediate set two routes</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_popToTopLater(navigator.popToTop)}>
<View style={styles.button}>
<Text style={styles.buttonText}>pop to top soon</Text>
</View>
</TouchableHighlight>
</View>
</ScrollView>
);
};
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({ var BreadcrumbNavSample = React.createClass({
getInitialState: function() { componentWillMount: function() {
return { this._navBarRouteMapper = {
selectedTab: 0, rightContentForRoute: function(route, navigator) {
return null;
},
titleContentForRoute: function(route, navigator) {
return (
<TouchableOpacity
onPress={() => navigator.push(_getRandomRoute())}>
<View>
<Text style={styles.titleText}>{route.title}</Text>
</View>
</TouchableOpacity>
);
},
iconForRoute: function(route, navigator) {
return (
<TouchableOpacity onPress={() => {
navigator.popToRoute(route);
}}>
<View style={styles.crumbIconPlaceholder} />
</TouchableOpacity>
);
},
separatorForRoute: function(route, navigator) {
return (
<TouchableOpacity onPress={navigator.pop}>
<View style={styles.crumbSeparatorPlaceholder} />
</TouchableOpacity>
);
}
}; };
}, },
render: function() { _renderScene: function(route, navigator) {
var initialRoute = {
backButtonTitle: 'Start', // no back button for initial scene
content: SAMPLE_TEXT,
title: 'Campaigns',
rightButtonTitle: 'Filter',
};
return ( return (
<TabBarIOS> <ScrollView style={styles.scene}>
<TabBarIOS.Item <NavButton
selected={this.state.selectedTab === 0} onPress={() => { navigator.push(_getRandomRoute()) }}
onPress={this.onTabSelect.bind(this, 0)} text="Push"
icon={require('image!tabnav_list')} />
title="One"> <NavButton
<Navigator onPress={() => { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]) }}
debugOverlay={false} text="Reset w/ 2 scenes"
style={[styles.appContainer]} />
initialRoute={initialRoute} <NavButton
renderScene={renderScene} onPress={() => { navigator.popToTop() }}
navigationBar={ text="Pop to top"
<Navigator.BreadcrumbNavigationBar />
navigationBarRouteMapper={SampleNavigationBarRouteMapper} <NavButton
/> onPress={() => { navigator.replace(_getRandomRoute()) }}
} text="Replace"
/> />
</TabBarIOS.Item> <NavButton
<TabBarIOS.Item onPress={() => { this.props.navigator.pop(); }}
selected={this.state.selectedTab === 1} text="Close breadcrumb example"
onPress={this.onTabSelect.bind(this, 1)} />
icon={require('image!tabnav_notification')} </ScrollView>
title="Two">
<Navigator
configureScene={() => Navigator.SceneConfigs.FloatFromBottom}
debugOverlay={false}
style={[styles.appContainer]}
initialRoute={initialRoute}
renderScene={renderScene}
navigationBar={
<Navigator.BreadcrumbNavigationBar
navigationBarRouteMapper={SampleNavigationBarRouteMapper}
/>
}
/>
</TabBarIOS.Item>
</TabBarIOS>
); );
}, },
onTabSelect: function(tab, event) { render: function() {
if (this.state.selectedTab !== tab) { return (
this.setState({selectedTab: tab}); <Navigator
} style={styles.container}
initialRoute={_getRandomRoute()}
renderScene={this._renderScene}
navigationBar={
<Navigator.BreadcrumbNavigationBar
navigationBarRouteMapper={this._navBarRouteMapper}
/>
}
/>
);
}, },
}); });
var styles = StyleSheet.create({ var styles = StyleSheet.create({
navigationItem: {
backgroundColor: '#eeeeee',
},
scene: { scene: {
paddingTop: 50, paddingTop: 50,
flex: 1, flex: 1,
}, },
button: { button: {
backgroundColor: '#cccccc', backgroundColor: 'white',
margin: 50, padding: 15,
marginTop: 26, borderBottomWidth: 1 / PixelRatio.get(),
padding: 10, borderBottomColor: '#CDCDCD',
}, },
buttonText: { buttonText: {
fontSize: 12, fontSize: 17,
textAlign: 'center', fontWeight: '500',
}, },
appContainer: { container: {
overflow: 'hidden', overflow: 'hidden',
backgroundColor: '#dddddd', backgroundColor: '#dddddd',
flex: 1, flex: 1,
@@ -262,13 +150,9 @@ var styles = StyleSheet.create({
fontSize: 18, fontSize: 18,
color: '#666666', color: '#666666',
textAlign: 'center', textAlign: 'center',
fontWeight: '500', fontWeight: 'bold',
lineHeight: 32, lineHeight: 32,
}, },
filterText: {
color: '#5577ff',
},
// TODO: Accept icons from route.
crumbIconPlaceholder: { crumbIconPlaceholder: {
flex: 1, flex: 1,
backgroundColor: '#666666', backgroundColor: '#666666',

View File

@@ -16,8 +16,10 @@
var React = require('react-native'); var React = require('react-native');
var { var {
Navigator, Navigator,
PixelRatio,
StyleSheet, StyleSheet,
ScrollView, ScrollView,
TabBarIOS,
Text, Text,
TouchableHighlight, TouchableHighlight,
View, View,
@@ -25,178 +27,186 @@ var {
var _getRandomRoute = function() { var _getRandomRoute = function() {
return { return {
randNumber: Math.random(), randNumber: Math.ceil(Math.random() * 1000),
}; };
}; };
var INIT_ROUTE = _getRandomRoute(); class NavButton extends React.Component {
render() {
return (
<TouchableHighlight
style={styles.button}
underlayColor="#B5B5B5"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.props.text}</Text>
</TouchableHighlight>
);
}
}
var ROUTE_STACK = [ var ROUTE_STACK = [
_getRandomRoute(), _getRandomRoute(),
_getRandomRoute(), _getRandomRoute(),
INIT_ROUTE,
_getRandomRoute(),
_getRandomRoute(), _getRandomRoute(),
]; ];
var renderScene = function(route, navigator) { var INIT_ROUTE_INDEX = 1;
return (
<ScrollView style={styles.scene}>
<View style={styles.scroll}>
<Text>{route.randNumber}</Text>
<TouchableHighlight
onPress={() => {
navigator.jumpBack();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>jumpBack</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
navigator.jumpForward();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>jumpForward</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
navigator.jumpTo(INIT_ROUTE);
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>jumpTo initial route</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
navigator.push(_getRandomRoute());
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: push</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
navigator.replace(_getRandomRoute());
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: replace</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
navigator.pop();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: pop</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
navigator.immediatelyResetRouteStack([
_getRandomRoute(),
_getRandomRoute(),
]);
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: Immediate set two routes</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
navigator.popToTop();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: pop to top</Text>
</View>
</TouchableHighlight>
</View>
</ScrollView>
);
};
class JumpingNavBar extends React.Component { class JumpingNavBar extends React.Component {
render() { render() {
return ( return (
<View style={styles.navBar}> <View style={styles.tabs}>
{this.props.routeStack.map((route, index) => ( <TabBarIOS
<TouchableHighlight onPress={() => { selectedTab={'tab_' + this.props.tabIndex}>
this.props.navigator.jumpTo(route); <TabBarIOS.Item
}}> name="tab_0"
<View style={styles.navButton}> icon={require('image!tabnav_notification')}
<Text selected={this.props.tabIndex === 0}
style={[ onPress={() => { this.props.onTabIndex(0); }}
styles.navButtonText, children={<View />}
this.props.navState.toIndex === index && styles.navButtonActive />
]}> <TabBarIOS.Item
{index} name="tab_1"
</Text> icon={require('image!tabnav_list')}
</View> selected={this.props.tabIndex === 1}
</TouchableHighlight> onPress={() => { this.props.onTabIndex(1); }}
))} children={<View />}
/>
<TabBarIOS.Item
name="tab_2"
icon={require('image!tabnav_settings')}
selected={this.props.tabIndex === 2}
onPress={() => { this.props.onTabIndex(2); }}
children={<View />}
/>
</TabBarIOS>
</View> </View>
); );
} }
} }
var JumpingNavSample = React.createClass({ var JumpingNavSample = React.createClass({
getInitialState: function() {
return {
tabIndex: INIT_ROUTE_INDEX,
};
},
render: function() { render: function() {
return ( return (
<Navigator <Navigator
debugOverlay={false} debugOverlay={false}
style={[styles.appContainer]} style={styles.appContainer}
initialRoute={INIT_ROUTE} ref={(navigator) => {
this._navigator = navigator;
}}
initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]}
initialRouteStack={ROUTE_STACK} initialRouteStack={ROUTE_STACK}
renderScene={renderScene} renderScene={this.renderScene}
navigationBar={<JumpingNavBar routeStack={ROUTE_STACK} />} navigationBar={
<JumpingNavBar
routeStack={ROUTE_STACK}
tabIndex={this.state.tabIndex}
onTabIndex={(index) => {
this.setState({ tabIndex: index }, () => {
this._navigator.jumpTo(ROUTE_STACK[index]);
});
}}
/>
}
onWillFocus={(route) => {
this.setState({
tabIndex: ROUTE_STACK.indexOf(route),
});
}}
shouldJumpOnBackstackPop={true} shouldJumpOnBackstackPop={true}
/> />
); );
}, },
renderScene: function(route, navigator) {
var backBtn;
var forwardBtn;
if (ROUTE_STACK.indexOf(route) !== 0) {
backBtn = (
<NavButton
onPress={() => {
navigator.jumpBack();
}}
text="jumpBack"
/>
);
}
if (ROUTE_STACK.indexOf(route) !== ROUTE_STACK.length - 1) {
forwardBtn = (
<NavButton
onPress={() => {
navigator.jumpForward();
}}
text="jumpForward"
/>
);
}
return (
<ScrollView style={styles.scene}>
<Text style={styles.messageText}>#{route.randNumber}</Text>
{backBtn}
{forwardBtn}
<NavButton
onPress={() => {
navigator.jumpTo(ROUTE_STACK[1]);
}}
text="jumpTo middle route"
/>
<NavButton
onPress={() => {
this.props.navigator.pop();
}}
text="Exit Navigation Example"
/>
<NavButton
onPress={() => {
this.props.navigator.push({
message: 'Came from jumping example',
});
}}
text="Nav Menu"
/>
</ScrollView>
);
},
}); });
var styles = StyleSheet.create({ var styles = StyleSheet.create({
scene: {
backgroundColor: '#eeeeee',
},
scroll: {
flex: 1,
},
button: { button: {
backgroundColor: '#cccccc', backgroundColor: 'white',
margin: 50, padding: 15,
marginTop: 26, borderBottomWidth: 1 / PixelRatio.get(),
padding: 10, borderBottomColor: '#CDCDCD',
}, },
buttonText: { buttonText: {
fontSize: 12, fontSize: 17,
textAlign: 'center', fontWeight: '500',
}, },
appContainer: { appContainer: {
overflow: 'hidden', overflow: 'hidden',
backgroundColor: '#dddddd', backgroundColor: '#dddddd',
flex: 1, flex: 1,
}, },
navBar: { messageText: {
position: 'absolute', fontSize: 17,
bottom: 0, fontWeight: '500',
left: 0, padding: 15,
right: 0, marginTop: 50,
height: 90, marginLeft: 15,
flexDirection: 'row',
}, },
navButton: { scene: {
flex: 1, flex: 1,
paddingTop: 20,
backgroundColor: '#EAEAEA',
}, },
navButtonText: { tabs: {
textAlign: 'center', height: 50,
fontSize: 32, }
marginTop: 25,
},
navButtonActive: {
color: 'green',
},
}); });
module.exports = JumpingNavSample; module.exports = JumpingNavSample;

View File

@@ -16,15 +16,30 @@
var React = require('react-native'); var React = require('react-native');
var { var {
PixelRatio,
Navigator, Navigator,
ScrollView,
StyleSheet, StyleSheet,
Text, Text,
TouchableHighlight, TouchableHighlight,
TouchableOpacity,
View, View,
} = React; } = React;
var cssVar = require('cssVar'); var cssVar = require('cssVar');
class NavButton extends React.Component {
render() {
return (
<TouchableHighlight
style={styles.button}
underlayColor="#B5B5B5"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.props.text}</Text>
</TouchableHighlight>
);
}
}
var NavigationBarRouteMapper = { var NavigationBarRouteMapper = {
@@ -35,26 +50,27 @@ var NavigationBarRouteMapper = {
var previousRoute = navState.routeStack[index - 1]; var previousRoute = navState.routeStack[index - 1];
return ( return (
<TouchableHighlight onPress={() => navigator.pop()}> <TouchableOpacity
<View> onPress={() => navigator.pop()}>
<View style={styles.navBarLeftButton}>
<Text style={[styles.navBarText, styles.navBarButtonText]}> <Text style={[styles.navBarText, styles.navBarButtonText]}>
{previousRoute.title} {previousRoute.title}
</Text> </Text>
</View> </View>
</TouchableHighlight> </TouchableOpacity>
); );
}, },
RightButton: function(route, navigator, index, navState) { RightButton: function(route, navigator, index, navState) {
return ( return (
<TouchableHighlight <TouchableOpacity
onPress={() => navigator.push(newRandomRoute())}> onPress={() => navigator.push(newRandomRoute())}>
<View> <View style={styles.navBarRightButton}>
<Text style={[styles.navBarText, styles.navBarButtonText]}> <Text style={[styles.navBarText, styles.navBarButtonText]}>
Next Next
</Text> </Text>
</View> </View>
</TouchableHighlight> </TouchableOpacity>
); );
}, },
@@ -70,8 +86,7 @@ var NavigationBarRouteMapper = {
function newRandomRoute() { function newRandomRoute() {
return { return {
content: 'Hello World!', title: '#' + Math.ceil(Math.random() * 1000),
title: 'Random ' + Math.round(Math.random() * 100),
}; };
} }
@@ -79,37 +94,63 @@ var NavigationBarSample = React.createClass({
render: function() { render: function() {
return ( return (
<View style={styles.appContainer}> <Navigator
<Navigator debugOverlay={false}
debugOverlay={false} style={styles.appContainer}
style={styles.appContainer} initialRoute={newRandomRoute()}
initialRoute={newRandomRoute()} renderScene={(route, navigator) => (
renderScene={(route, navigator) => ( <ScrollView style={styles.scene}>
<View style={styles.scene}> <Text style={styles.messageText}>{route.content}</Text>
<Text>{route.content}</Text> <NavButton
</View> onPress={() => {
)} navigator.immediatelyResetRouteStack([
navigationBar={ newRandomRoute(),
<Navigator.NavigationBar newRandomRoute(),
navigationBarRouteMapper={NavigationBarRouteMapper} newRandomRoute(),
]);
}}
text="Reset w/ 3 scenes"
/> />
} <NavButton
/> onPress={() => {
</View> this.props.navigator.pop();
}}
text="Exit NavigationBar Example"
/>
</ScrollView>
)}
navigationBar={
<Navigator.NavigationBar
navigationBarRouteMapper={NavigationBarRouteMapper}
navigationBarStyles={styles.navBar}
/>
}
/>
); );
}, },
}); });
var styles = StyleSheet.create({ var styles = StyleSheet.create({
appContainer: { messageText: {
overflow: 'hidden', fontSize: 17,
backgroundColor: '#ffffff', fontWeight: '500',
flex: 1, padding: 15,
marginTop: 50,
marginLeft: 15,
}, },
scene: { button: {
paddingTop: 50, backgroundColor: 'white',
flex: 1, padding: 15,
borderBottomWidth: 1 / PixelRatio.get(),
borderBottomColor: '#CDCDCD',
},
buttonText: {
fontSize: 17,
fontWeight: '500',
},
navBar: {
backgroundColor: 'white',
}, },
navBarText: { navBarText: {
fontSize: 16, fontSize: 16,
@@ -120,9 +161,20 @@ var styles = StyleSheet.create({
fontWeight: '500', fontWeight: '500',
marginVertical: 9, marginVertical: 9,
}, },
navBarLeftButton: {
paddingLeft: 10,
},
navBarRightButton: {
paddingRight: 10,
},
navBarButtonText: { navBarButtonText: {
color: cssVar('fbui-accent-blue'), color: cssVar('fbui-accent-blue'),
}, },
scene: {
flex: 1,
paddingTop: 20,
backgroundColor: '#EAEAEA',
},
}); });
module.exports = NavigationBarSample; module.exports = NavigationBarSample;

View File

@@ -16,6 +16,7 @@
var React = require('react-native'); var React = require('react-native');
var { var {
Navigator, Navigator,
PixelRatio,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
Text, Text,
@@ -25,30 +26,78 @@ var BreadcrumbNavSample = require('./BreadcrumbNavSample');
var NavigationBarSample = require('./NavigationBarSample'); var NavigationBarSample = require('./NavigationBarSample');
var JumpingNavSample = require('./JumpingNavSample'); var JumpingNavSample = require('./JumpingNavSample');
class NavButton extends React.Component {
render() {
return (
<TouchableHighlight
style={styles.button}
underlayColor="#B5B5B5"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.props.text}</Text>
</TouchableHighlight>
);
}
}
class NavMenu extends React.Component { class NavMenu extends React.Component {
render() { render() {
return ( return (
<ScrollView style={styles.scene}> <ScrollView style={styles.scene}>
<TouchableHighlight style={styles.button} onPress={() => { <Text style={styles.messageText}>{this.props.message}</Text>
this.props.navigator.push({ id: 'breadcrumbs' }); <NavButton
}}> onPress={() => {
<Text style={styles.buttonText}>Breadcrumbs Example</Text> this.props.navigator.push({
</TouchableHighlight> message: 'Swipe right to dismiss',
<TouchableHighlight style={styles.button} onPress={() => { sceneConfig: Navigator.SceneConfigs.FloatFromRight,
this.props.navigator.push({ id: 'navbar' }); });
}}> }}
<Text style={styles.buttonText}>Navbar Example</Text> text="Float in from right"
</TouchableHighlight> />
<TouchableHighlight style={styles.button} onPress={() => { <NavButton
this.props.navigator.push({ id: 'jumping' }); onPress={() => {
}}> this.props.navigator.push({
<Text style={styles.buttonText}>Jumping Example</Text> message: 'Swipe down to dismiss',
</TouchableHighlight> sceneConfig: Navigator.SceneConfigs.FloatFromBottom,
<TouchableHighlight style={styles.button} onPress={() => { });
this.props.onExampleExit(); }}
}}> text="Float in from bottom"
<Text style={styles.buttonText}>Exit Navigator Example</Text> />
</TouchableHighlight> <NavButton
onPress={() => {
this.props.navigator.pop();
}}
text="Pop"
/>
<NavButton
onPress={() => {
this.props.navigator.popToTop();
}}
text="Pop to top"
/>
<NavButton
onPress={() => {
this.props.navigator.push({ id: 'navbar' });
}}
text="Navbar Example"
/>
<NavButton
onPress={() => {
this.props.navigator.push({ id: 'jumping' });
}}
text="Jumping Example"
/>
<NavButton
onPress={() => {
this.props.navigator.push({ id: 'breadcrumbs' });
}}
text="Breadcrumbs Example"
/>
<NavButton
onPress={() => {
this.props.onExampleExit();
}}
text="Exit <Navigator> Example"
/>
</ScrollView> </ScrollView>
); );
} }
@@ -63,19 +112,20 @@ var TabBarExample = React.createClass({
renderScene: function(route, nav) { renderScene: function(route, nav) {
switch (route.id) { switch (route.id) {
case 'menu': case 'navbar':
return <NavigationBarSample navigator={nav} />;
case 'breadcrumbs':
return <BreadcrumbNavSample navigator={nav} />;
case 'jumping':
return <JumpingNavSample navigator={nav} />;
default:
return ( return (
<NavMenu <NavMenu
message={route.message}
navigator={nav} navigator={nav}
onExampleExit={this.props.onExampleExit} onExampleExit={this.props.onExampleExit}
/> />
); );
case 'navbar':
return <NavigationBarSample />;
case 'breadcrumbs':
return <BreadcrumbNavSample />;
case 'jumping':
return <JumpingNavSample />;
} }
}, },
@@ -83,9 +133,14 @@ var TabBarExample = React.createClass({
return ( return (
<Navigator <Navigator
style={styles.container} style={styles.container}
initialRoute={{ id: 'menu', }} initialRoute={{ message: "First Scene", }}
renderScene={this.renderScene} renderScene={this.renderScene}
configureScene={(route) => 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({ var styles = StyleSheet.create({
messageText: {
fontSize: 17,
fontWeight: '500',
padding: 15,
marginTop: 50,
marginLeft: 15,
},
container: { container: {
flex: 1, flex: 1,
}, },
button: { button: {
backgroundColor: 'white', backgroundColor: 'white',
padding: 15, padding: 15,
borderBottomWidth: 1 / PixelRatio.get(),
borderBottomColor: '#CDCDCD',
}, },
buttonText: { buttonText: {
fontSize: 17,
fontWeight: '500',
}, },
scene: { scene: {
flex: 1, flex: 1,
paddingTop: 64, paddingTop: 20,
backgroundColor: '#EAEAEA',
} }
}); });

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:UIExplorer.xcodeproj">
</FileRef>
</Workspace>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -11,14 +11,18 @@
*/ */
'use strict'; '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 // POP animation isn't available in the OSS fork - this is a temporary
// workaround to enable its availability to be determined at runtime. // workaround to enable its availability to be determined at runtime.
module.exports = (null : ?{}); module.exports = (null : ?{});
} else { } 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 invariant = require('invariant');
var warning = require('warning'); var warning = require('warning');

View File

@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule POPAnimation * @providesModule POPAnimation
* @flow
*/ */
'use strict'; 'use strict';
@@ -14,7 +15,7 @@ var RCTPOPAnimationManager = require('NativeModules').POPAnimationManager;
if (!RCTPOPAnimationManager) { if (!RCTPOPAnimationManager) {
// POP animation isn't available in the OSS fork - this is a temporary // POP animation isn't available in the OSS fork - this is a temporary
// workaround to enable its availability to be determined at runtime. // workaround to enable its availability to be determined at runtime.
module.exports = null; module.exports = (null: ?Object);
} else { } else {
var ReactPropTypes = require('ReactPropTypes'); var ReactPropTypes = require('ReactPropTypes');
@@ -64,6 +65,20 @@ var Types = {
spring: RCTTypes.spring, spring: RCTTypes.spring,
}; };
type Attrs = {
type?: $Enum<typeof Types>;
property?: $Enum<typeof Properties>;
fromValue?: any;
toValue?: any;
duration?: any;
velocity?: any;
deceleration?: any;
springBounciness?: any;
dynamicsFriction?: any;
dynamicsMass?: any;
dynamicsTension?: any;
}
var POPAnimation = { var POPAnimation = {
Types: Types, Types: Types,
Properties: Properties, Properties: Properties,
@@ -83,11 +98,11 @@ var POPAnimation = {
}), }),
lastUsedTag: 0, lastUsedTag: 0,
allocateTagForAnimation: function() { allocateTagForAnimation: function(): number {
return ++this.lastUsedTag; return ++this.lastUsedTag;
}, },
createAnimation: function(typeName, attrs) { createAnimation: function(typeName: number, attrs: Attrs): number {
var tag = this.allocateTagForAnimation(); var tag = this.allocateTagForAnimation();
if (__DEV__) { if (__DEV__) {
@@ -107,35 +122,35 @@ var POPAnimation = {
return tag; return tag;
}, },
createSpringAnimation: function(attrs) { createSpringAnimation: function(attrs: Attrs): number {
return this.createAnimation(this.Types.spring, attrs); return this.createAnimation(this.Types.spring, attrs);
}, },
createDecayAnimation: function(attrs) { createDecayAnimation: function(attrs: Attrs): number {
return this.createAnimation(this.Types.decay, attrs); return this.createAnimation(this.Types.decay, attrs);
}, },
createLinearAnimation: function(attrs) { createLinearAnimation: function(attrs: Attrs): number {
return this.createAnimation(this.Types.linear, attrs); return this.createAnimation(this.Types.linear, attrs);
}, },
createEaseInAnimation: function(attrs) { createEaseInAnimation: function(attrs: Attrs): number {
return this.createAnimation(this.Types.easeIn, attrs); return this.createAnimation(this.Types.easeIn, attrs);
}, },
createEaseOutAnimation: function(attrs) { createEaseOutAnimation: function(attrs: Attrs): number {
return this.createAnimation(this.Types.easeOut, attrs); return this.createAnimation(this.Types.easeOut, attrs);
}, },
createEaseInEaseOutAnimation: function(attrs) { createEaseInEaseOutAnimation: function(attrs: Attrs): number {
return this.createAnimation(this.Types.easeInEaseOut, attrs); 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); RCTPOPAnimationManager.addAnimation(nodeHandle, anim, callback);
}, },
removeAnimation: function(nodeHandle, anim) { removeAnimation: function(nodeHandle: any, anim: number) {
RCTPOPAnimationManager.removeAnimation(nodeHandle, anim); RCTPOPAnimationManager.removeAnimation(nodeHandle, anim);
}, },
}; };

View File

@@ -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 * accomplish this, provide route objects to the navigator to identify each
* scene, and also a `renderScene` function that the navigator can use to * scene, and also a `renderScene` function that the navigator can use to
* render the scene for a given route. * render the scene for a given route.
* *
* To change the animation or gesture properties of the scene, provide a * To change the animation or gesture properties of the scene, provide a
* `configureScene` prop to get the config object for a given route. See * `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. * scene config options.
* *
* ### Basic Usage * ### Basic Usage
* *
* ``` * ```
* <ReactNavigator * <Navigator
* initialRoute={{name: 'My First Scene', index: 0}} * initialRoute={{name: 'My First Scene', index: 0}}
* renderScene={(route, navigator) => * renderScene={(route, navigator) =>
* <MySceneComponent * <MySceneComponent
@@ -132,7 +132,7 @@ var styles = StyleSheet.create({
* *
* ### Navigation Methods * ### Navigation Methods
* *
* `ReactNavigator` can be told to navigate in two ways. If you have a ref to * `Navigator` can be told to navigate in two ways. If you have a ref to
* the element, you can invoke several methods on it to trigger navigation: * the element, you can invoke several methods on it to trigger navigation:
* *
* - `jumpBack()` - Jump backward without unmounting the current scene * - `jumpBack()` - Jump backward without unmounting the current scene
@@ -174,7 +174,7 @@ var Navigator = React.createClass({
* configuration object * configuration object
* *
* ``` * ```
* (route) => ReactNavigator.SceneConfigs.FloatFromRight * (route) => Navigator.SceneConfigs.FloatFromRight
* ``` * ```
*/ */
configureScene: PropTypes.func, configureScene: PropTypes.func,
@@ -230,7 +230,7 @@ var Navigator = React.createClass({
navigationBar: PropTypes.node, navigationBar: PropTypes.node,
/** /**
* Optionally provide the navigator object from a parent ReactNavigator * Optionally provide the navigator object from a parent Navigator
*/ */
navigator: PropTypes.object, navigator: PropTypes.object,
@@ -316,7 +316,7 @@ var Navigator = React.createClass({
parentNavigator: this.props.navigator, parentNavigator: this.props.navigator,
// We want to bubble focused routes to the top navigation stack. If we // 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 // 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, onWillFocus: this.props.onWillFocus,
onDidFocus: this.props.onDidFocus, onDidFocus: this.props.onDidFocus,
}; };

View File

@@ -37,7 +37,8 @@ var Geolocation = {
getCurrentPosition: function( getCurrentPosition: function(
geo_success: Function, geo_success: Function,
geo_error?: Function, geo_error?: Function,
geo_options?: Object) { geo_options?: Object
) {
invariant( invariant(
typeof geo_success === 'function', typeof geo_success === 'function',
'Must provide a valid geo_success callback.' 'Must provide a valid geo_success callback.'

View File

@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule Image * @providesModule Image
* @flow
*/ */
'use strict'; 'use strict';
@@ -106,7 +107,9 @@ var Image = React.createClass({
render: function() { render: function() {
var style = flattenStyle([styles.base, this.props.style]); var style = flattenStyle([styles.base, this.props.style]);
invariant(style, "style must be initialized");
var source = this.props.source; var source = this.props.source;
invariant(source, "source must be initialized");
var isNetwork = source.uri && source.uri.match(/^https?:/); var isNetwork = source.uri && source.uri.match(/^https?:/);
invariant( invariant(
!(isNetwork && source.isStatic), !(isNetwork && source.isStatic),

View File

@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule ImageResizeMode * @providesModule ImageResizeMode
* @flow
*/ */
'use strict'; 'use strict';

View File

@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule ImageStylePropTypes * @providesModule ImageStylePropTypes
* @flow
*/ */
'use strict'; 'use strict';
@@ -39,8 +40,8 @@ var unsupportedProps = Object.keys({
paddingHorizontal: null, paddingHorizontal: null,
}); });
for (var key in unsupportedProps) { for (var i = 0; i < unsupportedProps.length; i++) {
delete ImageStylePropTypes[key]; delete ImageStylePropTypes[unsupportedProps[i]];
} }
module.exports = ImageStylePropTypes; module.exports = ImageStylePropTypes;

View File

@@ -75,6 +75,7 @@
} else { } else {
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) { _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
if (image) { if (image) {
[self.layer removeAnimationForKey:@"contents"];
self.layer.contentsScale = image.scale; self.layer.contentsScale = image.scale;
self.layer.contents = (__bridge id)image.CGImage; self.layer.contents = (__bridge id)image.CGImage;
} }

View File

@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule InteractionManager * @providesModule InteractionManager
* @flow
*/ */
'use strict'; 'use strict';
@@ -70,7 +71,7 @@ var InteractionManager = {
/** /**
* Schedule a function to run after all interactions have completed. * Schedule a function to run after all interactions have completed.
*/ */
runAfterInteractions(callback) { runAfterInteractions(callback: Function) {
invariant( invariant(
typeof callback === 'function', typeof callback === 'function',
'Must specify a function to schedule.' 'Must specify a function to schedule.'
@@ -82,7 +83,7 @@ var InteractionManager = {
/** /**
* Notify manager that an interaction has started. * Notify manager that an interaction has started.
*/ */
createInteractionHandle() { createInteractionHandle(): number {
scheduleUpdate(); scheduleUpdate();
var handle = ++_inc; var handle = ++_inc;
_addInteractionSet.add(handle); _addInteractionSet.add(handle);
@@ -92,7 +93,7 @@ var InteractionManager = {
/** /**
* Notify manager that an interaction has completed. * Notify manager that an interaction has completed.
*/ */
clearInteractionHandle(handle) { clearInteractionHandle(handle: number) {
invariant( invariant(
!!handle, !!handle,
'Must provide a handle to clear.' 'Must provide a handle to clear.'

View File

@@ -2,6 +2,7 @@
* Copyright 2004-present Facebook. All Rights Reserved. * Copyright 2004-present Facebook. All Rights Reserved.
* *
* @providesModule InteractionMixin * @providesModule InteractionMixin
* @flow
*/ */
'use strict'; 'use strict';
@@ -21,7 +22,7 @@ var InteractionMixin = {
} }
}, },
_interactionMixinHandles: [], _interactionMixinHandles: ([]: Array<number>),
createInteractionHandle: function() { createInteractionHandle: function() {
var handle = InteractionManager.createInteractionHandle(); var handle = InteractionManager.createInteractionHandle();
@@ -29,7 +30,7 @@ var InteractionMixin = {
return handle; return handle;
}, },
clearInteractionHandle: function(clearHandle) { clearInteractionHandle: function(clearHandle: number) {
InteractionManager.clearInteractionHandle(clearHandle); InteractionManager.clearInteractionHandle(clearHandle);
this._interactionMixinHandles = this._interactionMixinHandles.filter( this._interactionMixinHandles = this._interactionMixinHandles.filter(
handle => handle !== clearHandle handle => handle !== clearHandle
@@ -41,7 +42,7 @@ var InteractionMixin = {
* *
* @param {function} callback * @param {function} callback
*/ */
runAfterInteractions: function(callback) { runAfterInteractions: function(callback: Function) {
InteractionManager.runAfterInteractions(callback); InteractionManager.runAfterInteractions(callback);
}, },
}; };

View File

@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule ExceptionsManager * @providesModule ExceptionsManager
* @flow
*/ */
'use strict'; 'use strict';
@@ -18,7 +19,13 @@ var parseErrorStack = require('parseErrorStack');
var sourceMapPromise; var sourceMapPromise;
function handleException(e) { type Exception = {
sourceURL: string;
line: number;
message: string;
}
function handleException(e: Exception) {
var stack = parseErrorStack(e); var stack = parseErrorStack(e);
console.error( console.error(
'Error: ' + 'Error: ' +

View File

@@ -21,74 +21,8 @@ var _initialURL = RCTLinkingManager &&
var DEVICE_NOTIF_EVENT = 'openURL'; 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 { class LinkingIOS {
/** static addEventListener(type, handler) {
* Add a handler to LinkingIOS changes by listening to the `url` event type
* and providing the handler
*/
static addEventListener(type: string, handler: Function) {
invariant( invariant(
type === 'url', type === 'url',
'LinkingIOS only supports `url` events' 'LinkingIOS only supports `url` events'
@@ -101,10 +35,7 @@ class LinkingIOS {
); );
} }
/** static removeEventListener(type, handler) {
* Remove a handler by passing the `url` event type and the handler
*/
static removeEventListener(type: string, handler: Function ) {
invariant( invariant(
type === 'url', type === 'url',
'LinkingIOS only supports `url` events' 'LinkingIOS only supports `url` events'
@@ -116,12 +47,7 @@ class LinkingIOS {
_notifHandlers[handler] = null; _notifHandlers[handler] = null;
} }
/** static openURL(url) {
* 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) {
invariant( invariant(
typeof url === 'string', typeof url === 'string',
'Invalid url: should be a string' 'Invalid url: should be a string'
@@ -129,11 +55,7 @@ class LinkingIOS {
RCTLinkingManager.openURL(url); RCTLinkingManager.openURL(url);
} }
/** static canOpenURL(url, callback) {
* 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) {
invariant( invariant(
typeof url === 'string', typeof url === 'string',
'Invalid url: should be a string' 'Invalid url: should be a string'
@@ -145,11 +67,7 @@ class LinkingIOS {
RCTLinkingManager.canOpenURL(url, callback); RCTLinkingManager.canOpenURL(url, callback);
} }
/** static popInitialURL() {
* If the app launch was triggered by an app link, it will pop the link URL,
* otherwise it will return `null`
*/
static popInitialURL(): ?string {
var initialURL = _initialURL; var initialURL = _initialURL;
_initialURL = null; _initialURL = null;
return initialURL; return initialURL;

View File

@@ -9,7 +9,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTBridgeModule.h" #import "../../React/Base/RCTBridgeModule.h"
@interface RCTLinkingManager : NSObject <RCTBridgeModule> @interface RCTLinkingManager : NSObject <RCTBridgeModule>

View File

@@ -9,7 +9,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTBridgeModule.h" #import "../../React/Base/RCTBridgeModule.h"
@interface RCTPushNotificationManager : NSObject <RCTBridgeModule> @interface RCTPushNotificationManager : NSObject <RCTBridgeModule>

View File

@@ -1,93 +1,78 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "React" s.name = "React"
s.version = "0.1.0" s.version = "0.1.0"
s.summary = "Build high quality mobile apps using React." s.summary = "Build high quality mobile apps using React."
s.description = <<-DESC s.description= <<-DESC
React Native apps are built using the React JS React Native apps are built using the React JS framework,
framework, and render directly to native UIKit and render directly to native UIKit elements using a fully
elements using a fully asynchronous architecture. asynchronous architecture. There is no browser and no HTML.
There is no browser and no HTML. We have picked what We have picked what we think is the best set of features from
we think is the best set of features from these and these and other technologies to build what we hope to become
other technologies to build what we hope to become the best product development framework available, with an
the best product development framework available, emphasis on iteration speed, developer delight, continuity
with an emphasis on iteration speed, developer of technology, and absolutely beautiful and fast products
delight, continuity of technology, and absolutely with no compromises in quality or capability.
beautiful and fast products with no compromises in DESC
quality or capability. s.homepage = "http://facebook.github.io/react-native/"
DESC s.license = "BSD"
s.homepage = "http://facebook.github.io/react-native/" s.author = "Facebook"
s.license = "BSD" s.platform = :ios, "7.0"
s.author = "Facebook" s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "v#{s.version}" }
s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "v#{s.version}" } s.source_files = "React/**/*.{c,h,m}"
s.default_subspec = 'Core' s.resources = "Resources/*.png"
s.requires_arc = true s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli"
s.platform = :ios, "7.0" s.exclude_files = "**/__tests__/*", "IntegrationTests/*"
s.prepare_command = 'npm install' s.frameworks = "JavaScriptCore"
s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli" s.requires_arc = true
s.prepare_command = 'npm install'
s.subspec 'Core' do |ss| s.libraries = 'icucore'
ss.libraries = 'icucore'
ss.source_files = "React/**/*.{c,h,m}"
ss.exclude_files = "**/__tests__/*", "IntegrationTests/*"
ss.frameworks = "JavaScriptCore"
end
s.subspec 'RCTActionSheet' do |ss| 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" ss.preserve_paths = "Libraries/ActionSheetIOS/*.js"
end end
s.subspec 'RCTAdSupport' do |ss| s.subspec 'RCTAdSupport' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/RCTAdSupport/*.{h,m}"
ss.source_files = "Libraries/AdSupport/*.{h,m}" ss.preserve_paths = "Libraries/RCTAdSupport/*.js"
ss.preserve_paths = "Libraries/AdSupport/*.js"
end end
s.subspec 'RCTAnimation' do |ss| s.subspec 'RCTAnimation' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/Animation/*.{h,m}"
ss.source_files = "Libraries/Animation/*.{h,m}" ss.preserve_paths = "Libraries/Animation/*.js"
ss.preserve_paths = "Libraries/Animation/*.js"
end end
s.subspec 'RCTGeolocation' do |ss| s.subspec 'RCTGeolocation' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/Geolocation/*.{h,m}"
ss.source_files = "Libraries/Geolocation/*.{h,m}" ss.preserve_paths = "Libraries/Geolocation/*.js"
ss.preserve_paths = "Libraries/Geolocation/*.js"
end end
s.subspec 'RCTImage' do |ss| s.subspec 'RCTImage' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/Image/*.{h,m}"
ss.source_files = "Libraries/Image/*.{h,m}" ss.preserve_paths = "Libraries/Image/*.js"
ss.preserve_paths = "Libraries/Image/*.js"
end end
s.subspec 'RCTNetwork' do |ss| s.subspec 'RCTNetwork' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/Network/*.{h,m}"
ss.source_files = "Libraries/Network/*.{h,m}" ss.preserve_paths = "Libraries/Network/*.js"
ss.preserve_paths = "Libraries/Network/*.js"
end end
s.subspec 'RCTPushNotification' do |ss| s.subspec 'RCTPushNotification' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/PushNotificationIOS/*.{h,m}"
ss.source_files = "Libraries/PushNotificationIOS/*.{h,m}" ss.preserve_paths = "Libraries/PushNotificationIOS/*.js"
ss.preserve_paths = "Libraries/PushNotificationIOS/*.js"
end end
s.subspec 'RCTWebSocketDebugger' do |ss| s.subspec 'RCTWebSocketDebugger' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}"
ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}"
end end
s.subspec 'RCTText' do |ss| s.subspec 'RCTText' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/Text/*.{h,m}"
ss.source_files = "Libraries/Text/*.{h,m}" ss.preserve_paths = "Libraries/Text/*.js"
ss.preserve_paths = "Libraries/Text/*.js"
end end
s.subspec 'RCTVibration' do |ss| s.subspec 'RCTVibration' do |ss|
ss.dependency 'React/Core' ss.source_files = "Libraries/Vibration/*.{h,m}"
ss.source_files = "Libraries/Vibration/*.{h,m}" ss.preserve_paths = "Libraries/Vibration/*.js"
ss.preserve_paths = "Libraries/Vibration/*.js"
end end
end end

View File

@@ -10,10 +10,11 @@
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "Layout.h" #import "../Layout/Layout.h"
#import "RCTAnimationType.h" #import "../Views/RCTAnimationType.h"
#import "../Views/RCTPointerEvents.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTPointerEvents.h"
/** /**
* This class provides a collection of conversion functions for mapping * This class provides a collection of conversion functions for mapping

View File

@@ -453,22 +453,21 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
return [self UIImage:json].CGImage; return [self UIImage:json].CGImage;
} }
#ifndef __IPHONE_8_2 #if !defined(__IPHONE_8_2) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_2
// These constants are defined in iPhone SDK 8.2 // These constants are defined in iPhone SDK 8.2, but the app cannot run on
// They'll work fine in earlier iOS versions, but the app cannot be built with // iOS < 8.2 unless we redefine them here. If you target iOS 8.2 or above
// an SDK version < 8.2 unless we redefine them here. This will be removed // as a base target, the standard constants will be used instead.
// in a future version of React, once 8.2 is more widely adopted.
static const CGFloat UIFontWeightUltraLight = -0.8; #define UIFontWeightUltraLight -0.8
static const CGFloat UIFontWeightThin = -0.6; #define UIFontWeightThin -0.6
static const CGFloat UIFontWeightLight = -0.4; #define UIFontWeightLight -0.4
static const CGFloat UIFontWeightRegular = 0; #define UIFontWeightRegular 0
static const CGFloat UIFontWeightMedium = 0.23; #define UIFontWeightMedium 0.23
static const CGFloat UIFontWeightSemibold = 0.3; #define UIFontWeightSemibold 0.3
static const CGFloat UIFontWeightBold = 0.4; #define UIFontWeightBold 0.4
static const CGFloat UIFontWeightHeavy = 0.56; #define UIFontWeightHeavy 0.56
static const CGFloat UIFontWeightBlack = 0.62; #define UIFontWeightBlack 0.62
#endif #endif

View File

@@ -9,10 +9,10 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTBridge.h" #import "../Base/RCTBridge.h"
#import "RCTBridgeModule.h" #import "../Base/RCTBridgeModule.h"
#import "RCTInvalidating.h" #import "../Base/RCTInvalidating.h"
#import "RCTViewManager.h" #import "../Views/RCTViewManager.h"
@protocol RCTScrollableProtocol; @protocol RCTScrollableProtocol;

View File

@@ -9,7 +9,8 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "Layout.h" #import "../Layout/Layout.h"
#import "RCTViewNodeProtocol.h" #import "RCTViewNodeProtocol.h"
@class RCTSparseArray; @class RCTSparseArray;

View File

@@ -9,9 +9,9 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTBridgeModule.h" #import "../Base/RCTBridgeModule.h"
#import "RCTConvert.h" #import "../Base/RCTConvert.h"
#import "RCTLog.h" #import "../Base/RCTLog.h"
@class RCTBridge; @class RCTBridge;
@class RCTEventDispatcher; @class RCTEventDispatcher;

View File

@@ -69,11 +69,7 @@ if (options.assetRoots) {
options.assetRoots = options.assetRoots.split(','); options.assetRoots = options.assetRoots.split(',');
} }
} else { } else {
if (__dirname.match(/node_modules\/react-native\/packager$/)) { options.assetRoots = [path.resolve(__dirname, '..')];
options.assetRoots = [path.resolve(__dirname, '../../..')];
} else {
options.assetRoots = [path.resolve(__dirname, '..')];
}
} }
console.log('\n' + console.log('\n' +

View File

@@ -10,4 +10,4 @@
ulimit -n 4096 ulimit -n 4096
THIS_DIR=$(dirname "$0") THIS_DIR=$(dirname "$0")
node $THIS_DIR/packager.js "$@" node "$THIS_DIR/packager.js" "$@"

View File

@@ -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() { pit('runs changes through ignore filter', function() {
var root = '/root'; var root = '/root';
var filesystem = fs.__setMockFilesystem({ var filesystem = fs.__setMockFilesystem({

View File

@@ -86,7 +86,11 @@ DependecyGraph.prototype.load = function() {
DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { DependecyGraph.prototype.getOrderedDependencies = function(entryPath) {
var absolutePath = this._getAbsolutePath(entryPath); var absolutePath = this._getAbsolutePath(entryPath);
if (absolutePath == null) { 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]; var module = this._graph[absolutePath];
@@ -478,7 +482,12 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) {
/** /**
* Process a filewatcher change event. * 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); var absPath = path.join(root, filePath);
if (this._ignoreFilePath(absPath)) { if (this._ignoreFilePath(absPath)) {
return; return;
@@ -486,6 +495,11 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root
this._debugUpdateEvents.push({event: eventType, path: filePath}); 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'; var isPackage = path.basename(filePath) === 'package.json';
if (eventType === 'delete') { if (eventType === 'delete') {
if (isPackage) { 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) { DependecyGraph.prototype._getAbsolutePath = function(filePath) {
if (isAbsolutePath(filePath)) { if (isAbsolutePath(filePath)) {
@@ -543,12 +558,43 @@ DependecyGraph.prototype._buildAssetMap = function() {
return q(); return q();
} }
var self = this; this._assetMap = Object.create(null);
return buildAssetMap(this._assetRoots, this._assetExts) return buildAssetMap(
.then(function(map) { this._assetRoots,
self._assetMap = map; this._processAsset.bind(this)
return map; );
};
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 * 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. * 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 queue = roots.slice(0);
var map = Object.create(null);
function search() { function search() {
var root = queue.shift(); var root = queue.shift();
if (root == null) { if (root == null) {
return q(map); return q();
} }
return readAndStatDir(root).spread(function(files, stats) { return readAndStatDir(root).spread(function(files, stats) {
@@ -639,21 +684,7 @@ function buildAssetMap(roots, exts) {
if (stats[i].isDirectory()) { if (stats[i].isDirectory()) {
queue.push(file); queue.push(file);
} else { } else {
var ext = path.extname(file).replace(/^\./, ''); processAsset(file);
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: [],
});
}
} }
}); });
@@ -664,4 +695,24 @@ function buildAssetMap(roots, exts) {
return search(); 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; module.exports = DependecyGraph;

View File

@@ -51,15 +51,15 @@ var validateOpts = declareOpts({
type: 'array', type: 'array',
default: [], default: [],
}, },
fileWatcher: {
type: 'object',
required: true,
},
}); });
function HasteDependencyResolver(options) { function HasteDependencyResolver(options) {
var opts = validateOpts(options); var opts = validateOpts(options);
this._fileWatcher = opts.nonPersistent
? FileWatcher.createDummyWatcher()
: new FileWatcher(opts.projectRoots);
this._depGraph = new DependencyGraph({ this._depGraph = new DependencyGraph({
roots: opts.projectRoots, roots: opts.projectRoots,
assetRoots: opts.assetRoots, assetRoots: opts.assetRoots,
@@ -67,7 +67,7 @@ function HasteDependencyResolver(options) {
return filepath.indexOf('__tests__') !== -1 || return filepath.indexOf('__tests__') !== -1 ||
(opts.blacklistRE && opts.blacklistRE.test(filepath)); (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() { HasteDependencyResolver.prototype.getDebugInfo = function() {
return this._depGraph.getDebugInfo(); return this._depGraph.getDebugInfo();
}; };

View File

@@ -21,6 +21,7 @@ describe('FileWatcher', function() {
var Watcher; var Watcher;
beforeEach(function() { beforeEach(function() {
require('mock-modules').dumpCache();
FileWatcher = require('../'); FileWatcher = require('../');
Watcher = require('sane').WatchmanWatcher; Watcher = require('sane').WatchmanWatcher;
Watcher.prototype.once.mockImplementation(function(type, callback) { Watcher.prototype.once.mockImplementation(function(type, callback) {

View File

@@ -16,7 +16,7 @@ var exec = require('child_process').exec;
var Promise = q.Promise; var Promise = q.Promise;
var detectingWatcherClass = new Promise(function(resolve, reject) { var detectingWatcherClass = new Promise(function(resolve) {
exec('which watchman', function(err, out) { exec('which watchman', function(err, out) {
if (err || out.length === 0) { if (err || out.length === 0) {
resolve(sane.NodeWatcher); resolve(sane.NodeWatcher);
@@ -30,14 +30,23 @@ module.exports = FileWatcher;
var MAX_WAIT_TIME = 3000; var MAX_WAIT_TIME = 3000;
function FileWatcher(projectRoots) { // Singleton
var self = this; 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( this._loading = q.all(
projectRoots.map(createWatcher) rootConfigs.map(createWatcher)
).then(function(watchers) { ).then(function(watchers) {
watchers.forEach(function(watcher) { watchers.forEach(function(watcher) {
watcher.on('all', function(type, filepath, root) { watcher.on('all', function(type, filepath, root) {
self.emit('all', type, filepath, root); fileWatcher.emit('all', type, filepath, root);
}); });
}); });
return watchers; return watchers;
@@ -50,21 +59,14 @@ util.inherits(FileWatcher, EventEmitter);
FileWatcher.prototype.end = function() { FileWatcher.prototype.end = function() {
return this._loading.then(function(watchers) { return this._loading.then(function(watchers) {
watchers.forEach(function(watcher) { watchers.forEach(function(watcher) {
delete watchersByRoot[watcher._root];
return q.ninvoke(watcher, 'close'); return q.ninvoke(watcher, 'close');
}); });
}); });
}; };
var watchersByRoot = Object.create(null); function createWatcher(rootConfig) {
function createWatcher(root) {
if (watchersByRoot[root] != null) {
return Promise.resolve(watchersByRoot[root]);
}
return detectingWatcherClass.then(function(Watcher) { 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) { return new Promise(function(resolve, reject) {
var rejectTimeout = setTimeout(function() { var rejectTimeout = setTimeout(function() {
@@ -77,8 +79,6 @@ function createWatcher(root) {
watcher.once('ready', function() { watcher.once('ready', function() {
clearTimeout(rejectTimeout); clearTimeout(rejectTimeout);
watchersByRoot[root] = watcher;
watcher._root = root;
resolve(watcher); resolve(watcher);
}); });
}); });

View File

@@ -56,6 +56,14 @@ var validateOpts = declareOpts({
type: 'array', type: 'array',
required: false, required: false,
}, },
assetExts: {
type: 'array',
default: ['png'],
},
fileWatcher: {
type: 'object',
required: true,
},
}); });
function Packager(options) { function Packager(options) {
@@ -70,6 +78,7 @@ function Packager(options) {
nonPersistent: opts.nonPersistent, nonPersistent: opts.nonPersistent,
moduleFormat: opts.moduleFormat, moduleFormat: opts.moduleFormat,
assetRoots: opts.assetRoots, assetRoots: opts.assetRoots,
fileWatcher: opts.fileWatcher,
}); });
this._transformer = new Transformer({ this._transformer = new Transformer({
@@ -83,10 +92,7 @@ function Packager(options) {
} }
Packager.prototype.kill = function() { Packager.prototype.kill = function() {
return q.all([ return this._transformer.kill();
this._transformer.kill(),
this._resolver.end(),
]);
}; };
Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {

View File

@@ -55,18 +55,49 @@ var validateOpts = declareOpts({
type: 'array', type: 'array',
required: false, required: false,
}, },
assetExts: {
type: 'array',
default: ['png'],
},
}); });
function Server(options) { function Server(options) {
var opts = validateOpts(options); var opts = validateOpts(options);
this._projectRoots = opts.projectRoots; this._projectRoots = opts.projectRoots;
this._packages = Object.create(null); this._packages = Object.create(null);
this._packager = new Packager(opts);
this._changeWatchers = []; 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 this._fileWatcher = options.nonPersistent
? FileWatcher.createDummyWatcher() ? 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); var onFileChange = this._onFileChange.bind(this);
this._fileWatcher.on('all', onFileChange); this._fileWatcher.on('all', onFileChange);
@@ -246,6 +277,9 @@ Server.prototype.processRequest = function(req, res, next) {
function getOptionsFromUrl(reqUrl) { function getOptionsFromUrl(reqUrl) {
// `true` to parse the query param as an object. // `true` to parse the query param as an object.
var urlObj = url.parse(reqUrl, true); 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; var pathname = urlObj.pathname;
// Backwards compatibility. Options used to be as added as '.' to the // Backwards compatibility. Options used to be as added as '.' to the
@@ -281,11 +315,11 @@ function getBoolOptionFromQuery(query, opt, defaultVal) {
} }
function handleError(res, error) { function handleError(res, error) {
res.writeHead(500, { res.writeHead(error.status || 500, {
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
}); });
if (error.type === 'TransformError') { if (error.type === 'TransformError' || error.type === 'NotFoundError') {
res.end(JSON.stringify(error)); res.end(JSON.stringify(error));
} else { } else {
console.error(error.stack || error); console.error(error.stack || error);