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
commit 0c83766e3b
37 changed files with 749 additions and 671 deletions

View File

@ -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 = "<group>"; };
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>"; };
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>"; };

View File

@ -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 (
<Text style={[styles.titleText, styles.filterText]}>
{route.rightButtonTitle}
</Text>
);
} else {
return null;
}
},
titleContentForRoute: function(route, navigator) {
class NavButton extends React.Component {
render() {
return (
<TouchableHighlight
onPress={() => navigator.push(_getRandomRoute())}>
<View>
<Text style={styles.titleText}>{route.title}</Text>
</View>
</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} />
style={styles.button}
underlayColor="#B5B5B5"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.props.text}</Text>
</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({
getInitialState: function() {
return {
selectedTab: 0,
componentWillMount: function() {
this._navBarRouteMapper = {
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() {
var initialRoute = {
backButtonTitle: 'Start', // no back button for initial scene
content: SAMPLE_TEXT,
title: 'Campaigns',
rightButtonTitle: 'Filter',
};
_renderScene: function(route, navigator) {
return (
<TabBarIOS>
<TabBarIOS.Item
selected={this.state.selectedTab === 0}
onPress={this.onTabSelect.bind(this, 0)}
icon={require('image!tabnav_list')}
title="One">
<Navigator
debugOverlay={false}
style={[styles.appContainer]}
initialRoute={initialRoute}
renderScene={renderScene}
navigationBar={
<Navigator.BreadcrumbNavigationBar
navigationBarRouteMapper={SampleNavigationBarRouteMapper}
/>
}
/>
</TabBarIOS.Item>
<TabBarIOS.Item
selected={this.state.selectedTab === 1}
onPress={this.onTabSelect.bind(this, 1)}
icon={require('image!tabnav_notification')}
title="Two">
<Navigator
configureScene={() => Navigator.SceneConfigs.FloatFromBottom}
debugOverlay={false}
style={[styles.appContainer]}
initialRoute={initialRoute}
renderScene={renderScene}
navigationBar={
<Navigator.BreadcrumbNavigationBar
navigationBarRouteMapper={SampleNavigationBarRouteMapper}
/>
}
/>
</TabBarIOS.Item>
</TabBarIOS>
<ScrollView style={styles.scene}>
<NavButton
onPress={() => { navigator.push(_getRandomRoute()) }}
text="Push"
/>
<NavButton
onPress={() => { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]) }}
text="Reset w/ 2 scenes"
/>
<NavButton
onPress={() => { navigator.popToTop() }}
text="Pop to top"
/>
<NavButton
onPress={() => { navigator.replace(_getRandomRoute()) }}
text="Replace"
/>
<NavButton
onPress={() => { this.props.navigator.pop(); }}
text="Close breadcrumb example"
/>
</ScrollView>
);
},
onTabSelect: function(tab, event) {
if (this.state.selectedTab !== tab) {
this.setState({selectedTab: tab});
}
render: function() {
return (
<Navigator
style={styles.container}
initialRoute={_getRandomRoute()}
renderScene={this._renderScene}
navigationBar={
<Navigator.BreadcrumbNavigationBar
navigationBarRouteMapper={this._navBarRouteMapper}
/>
}
/>
);
},
});
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',

View File

@ -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 (
<TouchableHighlight
style={styles.button}
underlayColor="#B5B5B5"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.props.text}</Text>
</TouchableHighlight>
);
}
}
var ROUTE_STACK = [
_getRandomRoute(),
_getRandomRoute(),
INIT_ROUTE,
_getRandomRoute(),
_getRandomRoute(),
];
var renderScene = function(route, navigator) {
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>
);
};
var INIT_ROUTE_INDEX = 1;
class JumpingNavBar extends React.Component {
render() {
return (
<View style={styles.navBar}>
{this.props.routeStack.map((route, index) => (
<TouchableHighlight onPress={() => {
this.props.navigator.jumpTo(route);
}}>
<View style={styles.navButton}>
<Text
style={[
styles.navButtonText,
this.props.navState.toIndex === index && styles.navButtonActive
]}>
{index}
</Text>
</View>
</TouchableHighlight>
))}
<View style={styles.tabs}>
<TabBarIOS
selectedTab={'tab_' + this.props.tabIndex}>
<TabBarIOS.Item
name="tab_0"
icon={require('image!tabnav_notification')}
selected={this.props.tabIndex === 0}
onPress={() => { this.props.onTabIndex(0); }}
children={<View />}
/>
<TabBarIOS.Item
name="tab_1"
icon={require('image!tabnav_list')}
selected={this.props.tabIndex === 1}
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>
);
}
}
var JumpingNavSample = React.createClass({
getInitialState: function() {
return {
tabIndex: INIT_ROUTE_INDEX,
};
},
render: function() {
return (
<Navigator
debugOverlay={false}
style={[styles.appContainer]}
initialRoute={INIT_ROUTE}
style={styles.appContainer}
ref={(navigator) => {
this._navigator = navigator;
}}
initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]}
initialRouteStack={ROUTE_STACK}
renderScene={renderScene}
navigationBar={<JumpingNavBar routeStack={ROUTE_STACK} />}
renderScene={this.renderScene}
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}
/>
);
},
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({
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;

View File

@ -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 (
<TouchableHighlight
style={styles.button}
underlayColor="#B5B5B5"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.props.text}</Text>
</TouchableHighlight>
);
}
}
var NavigationBarRouteMapper = {
@ -35,26 +50,27 @@ var NavigationBarRouteMapper = {
var previousRoute = navState.routeStack[index - 1];
return (
<TouchableHighlight onPress={() => navigator.pop()}>
<View>
<TouchableOpacity
onPress={() => navigator.pop()}>
<View style={styles.navBarLeftButton}>
<Text style={[styles.navBarText, styles.navBarButtonText]}>
{previousRoute.title}
</Text>
</View>
</TouchableHighlight>
</TouchableOpacity>
);
},
RightButton: function(route, navigator, index, navState) {
return (
<TouchableHighlight
<TouchableOpacity
onPress={() => navigator.push(newRandomRoute())}>
<View>
<View style={styles.navBarRightButton}>
<Text style={[styles.navBarText, styles.navBarButtonText]}>
Next
</Text>
</View>
</TouchableHighlight>
</TouchableOpacity>
);
},
@ -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 (
<View style={styles.appContainer}>
<Navigator
debugOverlay={false}
style={styles.appContainer}
initialRoute={newRandomRoute()}
renderScene={(route, navigator) => (
<View style={styles.scene}>
<Text>{route.content}</Text>
</View>
)}
navigationBar={
<Navigator.NavigationBar
navigationBarRouteMapper={NavigationBarRouteMapper}
<Navigator
debugOverlay={false}
style={styles.appContainer}
initialRoute={newRandomRoute()}
renderScene={(route, navigator) => (
<ScrollView style={styles.scene}>
<Text style={styles.messageText}>{route.content}</Text>
<NavButton
onPress={() => {
navigator.immediatelyResetRouteStack([
newRandomRoute(),
newRandomRoute(),
newRandomRoute(),
]);
}}
text="Reset w/ 3 scenes"
/>
}
/>
</View>
<NavButton
onPress={() => {
this.props.navigator.pop();
}}
text="Exit NavigationBar Example"
/>
</ScrollView>
)}
navigationBar={
<Navigator.NavigationBar
navigationBarRouteMapper={NavigationBarRouteMapper}
navigationBarStyles={styles.navBar}
/>
}
/>
);
},
});
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;

View File

@ -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 (
<TouchableHighlight
style={styles.button}
underlayColor="#B5B5B5"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.props.text}</Text>
</TouchableHighlight>
);
}
}
class NavMenu extends React.Component {
render() {
return (
<ScrollView style={styles.scene}>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.navigator.push({ id: 'breadcrumbs' });
}}>
<Text style={styles.buttonText}>Breadcrumbs Example</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.navigator.push({ id: 'navbar' });
}}>
<Text style={styles.buttonText}>Navbar Example</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.navigator.push({ id: 'jumping' });
}}>
<Text style={styles.buttonText}>Jumping Example</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.onExampleExit();
}}>
<Text style={styles.buttonText}>Exit Navigator Example</Text>
</TouchableHighlight>
<Text style={styles.messageText}>{this.props.message}</Text>
<NavButton
onPress={() => {
this.props.navigator.push({
message: 'Swipe right to dismiss',
sceneConfig: Navigator.SceneConfigs.FloatFromRight,
});
}}
text="Float in from right"
/>
<NavButton
onPress={() => {
this.props.navigator.push({
message: 'Swipe down to dismiss',
sceneConfig: Navigator.SceneConfigs.FloatFromBottom,
});
}}
text="Float in from bottom"
/>
<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>
);
}
@ -63,19 +112,20 @@ var TabBarExample = React.createClass({
renderScene: function(route, nav) {
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 (
<NavMenu
message={route.message}
navigator={nav}
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 (
<Navigator
style={styles.container}
initialRoute={{ id: 'menu', }}
initialRoute={{ message: "First Scene", }}
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({
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',
}
});

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';
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');

View File

@ -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<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 = {
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);
},
};

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
* 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
* <Navigator
* initialRoute={{name: 'My First Scene', index: 0}}
* renderScene={(route, navigator) =>
* <MySceneComponent
@ -132,7 +132,7 @@ var styles = StyleSheet.create({
*
* ### 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:
*
* - `jumpBack()` - Jump backward without unmounting the current scene
@ -174,7 +174,7 @@ var Navigator = React.createClass({
* configuration object
*
* ```
* (route) => 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,
};

View File

@ -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.'

View File

@ -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),

View File

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

View File

@ -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;

View File

@ -75,6 +75,7 @@
} 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;
}

View File

@ -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.'

View File

@ -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<number>),
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);
},
};

View File

@ -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: ' +

View File

@ -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;

View File

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

View File

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

View File

@ -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

View File

@ -10,10 +10,11 @@
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#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

View File

@ -453,22 +453,21 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
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
// 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.
// 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.
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;
#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

View File

@ -9,10 +9,10 @@
#import <UIKit/UIKit.h>
#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;

View File

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

View File

@ -9,9 +9,9 @@
#import <UIKit/UIKit.h>
#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;

View File

@ -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' +

View File

@ -10,4 +10,4 @@
ulimit -n 4096
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() {
var root = '/root';
var filesystem = fs.__setMockFilesystem({

View File

@ -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;

View File

@ -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();
};

View File

@ -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) {

View File

@ -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);
});
});

View File

@ -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) {

View File

@ -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);