Merge pull request #792 from facebook/10-apr

Updates from Fri 10 Apr
This commit is contained in:
Amjad Masad 2015-04-10 12:27:10 -07:00
commit d18a233fad
25 changed files with 608 additions and 128 deletions

View File

@ -15,6 +15,7 @@
00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; };
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
@ -80,6 +81,13 @@
remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192;
remoteInfo = React;
};
78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTLinking;
};
832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
@ -91,7 +99,6 @@
/* 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; };
008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = iOS/main.jsbundle; sourceTree = "<group>"; };
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>"; };
@ -107,6 +114,7 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = iOS/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = iOS/main.m; sourceTree = "<group>"; };
146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -121,6 +129,7 @@
00C302E61ABCBA2D00DB3ED1 /* libRCTAdSupport.a in Frameworks */,
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */,
00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */,
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */,
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */,
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */,
@ -208,9 +217,18 @@
name = Products;
sourceTree = "<group>";
};
78C398B11ACF4ADC00677621 /* Products */ = {
isa = PBXGroup;
children = (
78C398B91ACF4ADC00677621 /* libRCTLinking.a */,
);
name = Products;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
@ -220,7 +238,6 @@
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */,
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */,
00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */,
00481BE91AC0C89D00671115 /* libicucore.dylib */,
);
name = Libraries;
sourceTree = "<group>";
@ -309,6 +326,10 @@
ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */;
ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
},
{
ProductGroup = 78C398B11ACF4ADC00677621 /* Products */;
ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
},
{
ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */;
ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
@ -394,6 +415,13 @@
remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTLinking.a;
remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
832341B51AAA6A8300B99B32 /* libRCTText.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@ -251,7 +251,7 @@ exports.examples = [
return (
<View>
<WithLabel label="true">
<TextInput secureTextEntry={true} style={styles.default} value="abc" />
<TextInput password={true} style={styles.default} value="abc" />
</WithLabel>
</View>
);

View File

@ -50,6 +50,7 @@ var RCTNavigatorItem = createReactIOSNativeComponentClass({
rightButtonTitle: true,
onNavRightButtonTap: true,
tintColor: true,
navigationBarHidden: true,
backButtonTitle: true,
titleTextColor: true,
style: true,
@ -235,6 +236,11 @@ var NavigatorIOS = React.createClass({
}).isRequired,
/**
* A Boolean value that indicates whether the navigation bar is hidden
*/
navigationBarHidden: PropTypes.bool,
/**
* The default wrapper style for components in the navigator.
* A common use case is to set the backgroundColor for every page
@ -547,6 +553,7 @@ var NavigatorIOS = React.createClass({
backButtonTitle={route.backButtonTitle}
rightButtonTitle={route.rightButtonTitle}
onNavRightButtonTap={route.onRightButtonPress}
navigationBarHidden={this.props.navigationBarHidden}
tintColor={this.props.tintColor}>
<Component
navigator={this.navigator}

View File

@ -198,12 +198,6 @@ var TextInput = React.createClass({
* automatically enables it when there is text. Default value is false.
*/
enablesReturnKeyAutomatically: PropTypes.bool,
/**
* If true, the text input obscures the text entered so that sensitive text
* like passwords stay secure. Default value is false.
*/
secureTextEntry: PropTypes.bool,
/**
* If true, the text input can be multiple lines. Default value is false.
*/
@ -221,11 +215,17 @@ var TextInput = React.createClass({
*/
onChange: PropTypes.func,
onChangeText: PropTypes.func,
/**
* Callback that is called when text input ends.
*/
onEndEditing: PropTypes.func,
/**
* Callback that is called when the text input's submit button is pressed.
*/
onSubmitEditing: PropTypes.func,
/**
* If true, the TextInput will be a password field. Default value is false.
* If true, the text input obscures the text entered so that sensitive text
* like passwords stay secure. Default value is false.
*/
password: PropTypes.bool,
/**
@ -419,7 +419,7 @@ var TextInput = React.createClass({
keyboardType={keyboardType}
returnKeyType={returnKeyType}
enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically}
secureTextEntry={this.props.secureTextEntry}
secureTextEntry={this.props.password || this.props.secureTextEntry}
onFocus={this._onFocus}
onBlur={this._onBlur}
onChange={this._onChange}
@ -464,7 +464,7 @@ var TextInput = React.createClass({
keyboardType={keyboardType}
returnKeyType={returnKeyType}
enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically}
secureTextEntry={this.props.secureTextEntry}
secureTextEntry={this.props.password || this.props.secureTextEntry}
onFocus={this._onFocus}
onBlur={this._onBlur}
onChange={this._onChange}
@ -505,7 +505,7 @@ var TextInput = React.createClass({
onChange={this._onChange}
onEndEditing={this.props.onEndEditing}
onSubmitEditing={this.props.onSubmitEditing}
password={this.props.password}
password={this.props.password || this.props.secureTextEntry}
placeholder={this.props.placeholder}
value={this.state.bufferedValue}
/>;

View File

@ -34,8 +34,8 @@ var WebViewState = keyMirror({
var WebView = React.createClass({
propTypes: {
renderError: PropTypes.func.isRequired, // view to show if there's an error
renderLoading: PropTypes.func.isRequired, // loading indicator to show
renderError: PropTypes.func, // view to show if there's an error
renderLoading: PropTypes.func, // loading indicator to show
url: PropTypes.string.isRequired,
automaticallyAdjustContentInsets: PropTypes.bool,
contentInset: EdgeInsetsPropType,
@ -66,10 +66,10 @@ var WebView = React.createClass({
var otherView = null;
if (this.state.viewState === WebViewState.LOADING) {
otherView = this.props.renderLoading();
otherView = this.props.renderLoading && this.props.renderLoading();
} else if (this.state.viewState === WebViewState.ERROR) {
var errorEvent = this.state.lastErrorEvent;
otherView = this.props.renderError(
otherView = this.props.renderError && this.props.renderError(
errorEvent.domain,
errorEvent.code,
errorEvent.description);

View File

@ -13,6 +13,9 @@
@interface RCTLinkingManager : NSObject <RCTBridgeModule>
+ (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation;
@end

View File

@ -37,11 +37,11 @@ RCT_EXPORT_MODULE()
}
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
NSDictionary *payload = @{@"url": [url absoluteString]};
NSDictionary *payload = @{@"url": [URL absoluteString]};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenURLNotification
object:self
userInfo:payload];
@ -54,23 +54,22 @@ RCT_EXPORT_MODULE()
body:[notification userInfo]];
}
RCT_EXPORT_METHOD(openURL:(NSURL *)url)
RCT_EXPORT_METHOD(openURL:(NSURL *)URL)
{
[[UIApplication sharedApplication] openURL:url];
[[UIApplication sharedApplication] openURL:URL];
}
RCT_EXPORT_METHOD(canOpenURL:(NSURL *)url
RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
callback:(RCTResponseSenderBlock)callback)
{
BOOL supported = [[UIApplication sharedApplication] canOpenURL:url];
callback(@[@(supported)]);
BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL];
callback(@[@(canOpen)]);
}
- (NSDictionary *)constantsToExport
{
return @{
@"initialURL": [[_bridge.launchOptions objectForKey:UIApplicationLaunchOptionsURLKey] absoluteString] ?: [NSNull null]
};
NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
return @{@"initialURL": [initialURL absoluteString] ?: [NSNull null]};
}
@end

View File

@ -10,6 +10,7 @@
#import "RCTDataManager.h"
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@ -22,27 +23,18 @@ RCT_EXPORT_MODULE()
* The responseSender block won't be called on same thread as called.
*/
RCT_EXPORT_METHOD(queryData:(NSString *)queryType
withQuery:(id)query
withQuery:(NSDictionary *)query
queryHash:(__unused NSString *)queryHash
responseSender:(RCTResponseSenderBlock)responseSender)
{
if ([queryType isEqualToString:@"http"]) {
// Parse query
NSDictionary *queryDict = query;
if ([query isKindOfClass:[NSString class]]) {
// TODO: it would be more efficient just to send a dictionary
queryDict = RCTJSONParse(query, NULL);
}
// Build request
NSURL *url = [NSURL URLWithString:queryDict[@"url"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = queryDict[@"method"] ?: @"GET";
request.allHTTPHeaderFields = queryDict[@"headers"];
if ([queryDict[@"data"] isKindOfClass:[NSString class]]) {
request.HTTPBody = [queryDict[@"data"] dataUsingEncoding:NSUTF8StringEncoding];
}
NSURL *URL = [RCTConvert NSURL:query[@"url"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.HTTPMethod = [RCTConvert NSString:query[@"method"]] ?: @"GET";
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
request.HTTPBody = [RCTConvert NSData:query[@"data"]];
// Build data task
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *connectionError) {
@ -50,18 +42,27 @@ RCT_EXPORT_METHOD(queryData:(NSString *)queryType
// Build response
NSDictionary *responseJSON;
if (connectionError == nil) {
NSStringEncoding encoding;
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
} else {
encoding = NSUTF8StringEncoding;
}
int responseCode = (int)[((NSHTTPURLResponse *)response) statusCode];
NSString *returnData = [[NSString alloc] initWithData:data encoding:encoding];
responseJSON = @{@"status": @(responseCode), @"responseText": returnData};
NSHTTPURLResponse *httpResponse = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
// Might be a local file request
httpResponse = (NSHTTPURLResponse *)response;
}
responseJSON = @{
@"status": @([httpResponse statusCode] ?: 200),
@"responseHeaders": [httpResponse allHeaderFields] ?: @{},
@"responseText": [[NSString alloc] initWithData:data encoding:encoding] ?: @""
};
} else {
responseJSON = @{@"status": @0, @"responseText": [connectionError localizedDescription]};
responseJSON = @{
@"status": @0,
@"responseHeaders": @{},
@"responseText": [connectionError localizedDescription]
};
}
// Send response (won't be sent on same thread as caller)

View File

@ -22,12 +22,13 @@ class XMLHttpRequest extends XMLHttpRequestBase {
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
RCTDataManager.queryData(
'http',
JSON.stringify({
{
method: method,
url: url,
data: data,
headers: headers,
}),
},
// TODO: Do we need this? is it used anywhere?
'h' + crc32(method + '|' + url + '|' + data),
(result) => {
result = JSON.parse(result);

View File

@ -85,51 +85,31 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback
RCT_EXPORT_METHOD(requestPermissions)
{
Class _UIUserNotificationSettings;
if ((_UIUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings"))) {
UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert;
UIUserNotificationSettings *notificationSettings = [_UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
} else {
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
// if we are targeting iOS 7, *and* the new UIUserNotificationSettings
// class is not available, then register using the old mechanism
if (![UIUserNotificationSettings class]) {
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert];
return;
}
#endif
}
UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert;
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
}
RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert
#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge
#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound
#endif
NSUInteger types;
if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) {
types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
} else {
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
#endif
}
NSMutableDictionary *permissions = [[NSMutableDictionary alloc] init];
permissions[@"alert"] = @((types & UIUserNotificationTypeAlert) > 0);
permissions[@"badge"] = @((types & UIUserNotificationTypeBadge) > 0);
permissions[@"sound"] = @((types & UIUserNotificationTypeSound) > 0);
UIUserNotificationType types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
permissions[@"alert"] = @((BOOL)(types & UIUserNotificationTypeAlert));
permissions[@"badge"] = @((BOOL)(types & UIUserNotificationTypeBadge));
permissions[@"sound"] = @((BOOL)(types & UIUserNotificationTypeSound));
callback(@[permissions]);
}

View File

@ -47,7 +47,7 @@ var StyleSheetValidation = require('StyleSheetValidation');
* Code quality:
*
* - By moving styles away from the render function, you're making the code
* code easier to understand.
* easier to understand.
* - Naming the styles is a good way to add meaning to the low level components
* in the render function.
*

View File

@ -49,6 +49,9 @@ typedef struct section RCTHeaderSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader
#endif
NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
/**
* This function returns the module name for a given class.
*/
@ -926,10 +929,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"JS_PERF_ENQUEUE" object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"JS_PERF_DEQUEUE" object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[self _handleBuffer:json];
};
@ -1038,6 +1041,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
@throw;
}
}
});

View File

@ -38,6 +38,7 @@
+ (NSDictionary *)NSDictionary:(id)json;
+ (NSString *)NSString:(id)json;
+ (NSNumber *)NSNumber:(id)json;
+ (NSData *)NSData:(id)json;
+ (NSURL *)NSURL:(id)json;
+ (NSURLRequest *)NSURLRequest:(id)json;

View File

@ -50,6 +50,12 @@ RCT_CONVERTER(NSString *, NSString, description)
return nil;
}
+ (NSData *)NSData:(id)json
{
// TODO: should we automatically decode base64 data? Probably not...
return [[self NSString:json] dataUsingEncoding:NSUTF8StringEncoding];
}
+ (NSURL *)NSURL:(id)json
{
if (![json isKindOfClass:[NSString class]]) {

View File

@ -97,6 +97,12 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
@"x": @(scrollView.contentOffset.x),
@"y": @(scrollView.contentOffset.y)
},
@"contentInset": @{
@"top": @(scrollView.contentInset.top),
@"left": @(scrollView.contentInset.left),
@"bottom": @(scrollView.contentInset.bottom),
@"right": @(scrollView.contentInset.right)
},
@"contentSize": @{
@"width": @(scrollView.contentSize.width),
@"height": @(scrollView.contentSize.height)

View File

@ -21,4 +21,6 @@
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@property (nonatomic, assign) NSUInteger maxReloadAttempts;
@end

View File

@ -9,19 +9,27 @@
#import "RCTExceptionsManager.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
@implementation RCTExceptionsManager
{
__weak id<RCTExceptionsManagerDelegate> _delegate;
NSUInteger _reloadRetries;
}
#ifndef DEBUG
static NSUInteger RCTReloadRetries = 0;
#endif
RCT_EXPORT_MODULE()
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate
{
if ((self = [super init])) {
_delegate = delegate;
_maxReloadAttempts = 0;
}
return self;
}
@ -36,9 +44,40 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
{
if (_delegate) {
[_delegate unhandledJSExceptionWithMessage:message stack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
return;
}
#ifdef DEBUG
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
#else
if (RCTReloadRetries < _maxReloadAttempts) {
RCTReloadRetries++;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
});
} else {
NSError *error;
const NSUInteger MAX_SANITIZED_LENGTH = 75;
// Filter out numbers so the same base errors are mapped to the same categories independent of incorrect values.
NSString *pattern = @"[+-]?\\d+[,.]?\\d*";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
RCTAssert(error == nil, @"Bad regex pattern: %@", pattern);
NSString *sanitizedMessage = [regex stringByReplacingMatchesInString:message
options:0
range:NSMakeRange(0, message.length)
withTemplate:@"<num>"];
if (sanitizedMessage.length > MAX_SANITIZED_LENGTH) {
sanitizedMessage = [[sanitizedMessage substringToIndex:MAX_SANITIZED_LENGTH] stringByAppendingString:@"..."];
}
NSMutableString *prettyStack = [@"\n" mutableCopy];
for (NSDictionary *frame in stack) {
[prettyStack appendFormat:@"%@@%@:%@\n", frame[@"methodName"], frame[@"lineNumber"], frame[@"column"]];
}
NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:sanitizedMessage];
[NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack];
}
#endif
}
RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message

View File

@ -14,6 +14,7 @@
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *rightButtonTitle;
@property (nonatomic, copy) NSString *backButtonTitle;
@property (nonatomic, assign) BOOL navigationBarHidden;
@property (nonatomic, copy) UIColor *tintColor;
@property (nonatomic, copy) UIColor *barTintColor;
@property (nonatomic, copy) UIColor *titleTextColor;

View File

@ -24,6 +24,7 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString);
RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString);
RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL);
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor);

View File

@ -35,6 +35,13 @@
return self;
}
- (void)setText:(NSString *)text
{
if (![text isEqualToString:self.text]) {
[super setText:text];
}
}
- (NSArray *)reactSubviews
{
// TODO: do we support subviews of textfield in React?

View File

@ -65,7 +65,10 @@
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
[self.navigationController setNavigationBarHidden:!_navItem animated:animated];
[self.navigationController
setNavigationBarHidden:_navItem.navigationBarHidden
animated:animated];
if (!_navItem) {
return;
}

View File

@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "0.3.9",
"version": "0.3.7",
"description": "A framework for building native apps using React",
"repository": {
"type": "git",

View File

@ -8,8 +8,11 @@
*/
'use strict';
var chalk = require('chalk');
var exec = require('child_process').exec;
var hasWarned = {};
function getFlowTypeCheckMiddleware(options) {
return function(req, res, next) {
if (options.skipflow) {
@ -18,13 +21,19 @@ function getFlowTypeCheckMiddleware(options) {
if (options.flowroot || options.projectRoots.length === 1) {
var flowroot = options.flowroot || options.projectRoots[0];
} else {
console.warn('flow: No suitable root');
if (!hasWarned.noRoot) {
hasWarned.noRoot = true;
console.warn('flow: No suitable root');
}
return next();
}
exec('command -v flow >/dev/null 2>&1', function(error, stdout) {
if (error) {
console.warn('flow: Skipping because not installed. Install with ' +
'`brew install flow`.');
if (!hasWarned.noFlow) {
hasWarned.noFlow = true;
console.warn(chalk.yellow('flow: Skipping because not installed. Install with ' +
'`brew install flow`.'));
}
return next();
} else {
return doFlowTypecheck(res, flowroot, next);
@ -34,16 +43,21 @@ function getFlowTypeCheckMiddleware(options) {
}
function doFlowTypecheck(res, flowroot, next) {
// vjeux: big hack to make it work on the sample app because we don't generate a
// .flowconfig in the init script right now.
return next();
var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20';
var start = Date.now();
console.log('flow: Running static typechecks.');
exec(flowCmd, function(flowError, stdout) {
// Log start message if flow is slow to let user know something is happening.
var flowSlow = setTimeout(
function() {
console.log(chalk.gray('flow: Running static typechecks.'));
},
500
);
exec(flowCmd, function(flowError, stdout, stderr) {
clearTimeout(flowSlow);
if (!flowError) {
console.log('flow: Typechecks passed (' + (Date.now() - start) + 'ms).');
console.log(chalk.gray(
'flow: Typechecks passed (' + (Date.now() - start) + 'ms).')
);
return next();
} else {
try {
@ -65,24 +79,38 @@ function doFlowTypecheck(res, flowroot, next) {
});
errorNum++;
});
var message = 'Flow found type errors. If you think these are wrong, ' +
'make sure flow is up to date, or disable with --skipflow.';
var error = {
status: 500,
message: 'Flow found type errors. If you think these are wrong, ' +
'make sure your flow bin and .flowconfig are up to date, or ' +
'disable with --skipflow.',
type: 'FlowError',
errors: errors,
};
console.error(chalk.yellow('flow: Error running command `' + flowCmd +
'`:\n' + JSON.stringify(error))
);
res.writeHead(error.status, {
'Content-Type': 'application/json; charset=UTF-8',
});
res.end(JSON.stringify(error));
} catch (e) {
var message =
'Flow failed to provide parseable output:\n\n`' + stdout + '`';
console.error(message, '\nException: `', e, '`\n\n');
if (stderr.match(/Could not find a \.flowconfig/)) {
if (!hasWarned.noConfig) {
hasWarned.noConfig = true;
console.warn(chalk.yellow('flow: ' + stderr));
}
} else {
if (!hasWarned.brokenFlow) {
hasWarned.brokenFlow = true;
console.warn(chalk.yellow(
'Flow failed to provide parseable output:\n\n`' + stdout +
'`.\n' + 'stderr: `' + stderr + '`'
));
}
}
return next();
}
var error = {
status: 500,
message: message,
type: 'FlowError',
errors: errors,
};
console.error('flow: Error running command `' + flowCmd + '`:\n', error);
res.writeHead(error.status, {
'Content-Type': 'application/json; charset=UTF-8',
});
res.end(JSON.stringify(error));
}
});
}

View File

@ -64,6 +64,42 @@ describe('DependencyGraph', function() {
});
});
pit('should get dependencies with the correct extensions', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("a")'
].join('\n'),
'a.js': [
'/**',
' * @providesModule a',
' */',
].join('\n'),
'a.js.orig': [
'/**',
' * @providesModule a',
' */',
].join('\n'),
}
});
var dgraph = new DependencyGraph({
roots: [root],
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: ['a']},
{id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []},
]);
});
});
pit('should get dependencies with deprecated assets', function() {
var root = '/root';
fs.__setMockFilesystem({
@ -674,6 +710,296 @@ describe('DependencyGraph', function() {
]);
});
});
pit('should support simple browser field in packages', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js',
browser: 'client.js',
}),
'main.js': 'some other code',
'client.js': 'some code',
}
}
});
var dgraph = new DependencyGraph({
roots: [root],
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: ['aPackage']
},
{ id: 'aPackage/client',
path: '/root/aPackage/client.js',
dependencies: []
},
]);
});
});
pit('should supportbrowser field in packages w/o .js ext', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js',
browser: 'client',
}),
'main.js': 'some other code',
'client.js': 'some code',
}
}
});
var dgraph = new DependencyGraph({
roots: [root],
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: ['aPackage']
},
{ id: 'aPackage/client',
path: '/root/aPackage/client.js',
dependencies: []
},
]);
});
});
pit('should support mapping main in browser field json', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: './main.js',
browser: {
'./main.js': './client.js',
},
}),
'main.js': 'some other code',
'client.js': 'some code',
}
}
});
var dgraph = new DependencyGraph({
roots: [root],
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: ['aPackage']
},
{ id: 'aPackage/client',
path: '/root/aPackage/client.js',
dependencies: []
},
]);
});
});
pit('should work do correct browser mapping w/o js ext', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: './main.js',
browser: {
'./main': './client.js',
},
}),
'main.js': 'some other code',
'client.js': 'some code',
}
}
});
var dgraph = new DependencyGraph({
roots: [root],
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: ['aPackage']
},
{ id: 'aPackage/client',
path: '/root/aPackage/client.js',
dependencies: []
},
]);
});
});
pit('should support browser mapping of files', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: './main.js',
browser: {
'./main': './client.js',
'./node.js': './not-node.js',
'./not-browser': './browser.js',
'./dir/server.js': './dir/client',
},
}),
'main.js': 'some other code',
'client.js': 'require("./node")\nrequire("./dir/server.js")',
'not-node.js': 'require("./not-browser")',
'not-browser.js': 'require("./dir/server")',
'browser.js': 'some browser code',
'dir': {
'server.js': 'some node code',
'client.js': 'some browser code',
}
}
}
});
var dgraph = new DependencyGraph({
roots: [root],
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: ['aPackage']
},
{ id: 'aPackage/client',
path: '/root/aPackage/client.js',
dependencies: ['./node', './dir/server.js']
},
{ id: 'aPackage/not-node',
path: '/root/aPackage/not-node.js',
dependencies: ['./not-browser']
},
{ id: 'aPackage/browser',
path: '/root/aPackage/browser.js',
dependencies: []
},
{ id: 'aPackage/dir/client',
path: '/root/aPackage/dir/client.js',
dependencies: []
},
]);
});
});
pit('should support browser mapping for packages', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
browser: {
'node-package': 'browser-package',
}
}),
'index.js': 'require("node-package")',
'node-package': {
'package.json': JSON.stringify({
'name': 'node-package',
}),
'index.js': 'some node code',
},
'browser-package': {
'package.json': JSON.stringify({
'name': 'browser-package',
}),
'index.js': 'some browser code',
},
}
}
});
var dgraph = new DependencyGraph({
roots: [root],
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: ['aPackage']
},
{ id: 'aPackage/index',
path: '/root/aPackage/index.js',
dependencies: ['node-package']
},
{ id: 'browser-package/index',
path: '/root/aPackage/browser-package/index.js',
dependencies: []
},
]);
});
});
});
describe('file watch updating', function() {

View File

@ -65,7 +65,7 @@ function DependecyGraph(options) {
this._debugUpdateEvents = [];
this._moduleExtPattern = new RegExp(
'.' + ['js'].concat(this._assetExts).join('|') + '$'
'\.(' + ['js'].concat(this._assetExts).join('|') + ')$'
);
// Kick off the search process to precompute the dependency graph.
@ -168,15 +168,22 @@ DependecyGraph.prototype.resolveDependency = function(
// Package relative modules starts with '.' or '..'.
if (depModuleId[0] !== '.') {
// 1. `depModuleId` is simply a top-level `providesModule`.
// 2. `depModuleId` is a package module but given the full path from the
// package, i.e. package_name/module_name
// Check if we need to map the dependency to something else via the
// `browser` field in package.json
var fromPackageJson = this._lookupPackage(fromModule.path);
if (fromPackageJson && fromPackageJson.browser &&
fromPackageJson.browser[depModuleId]) {
depModuleId = fromPackageJson.browser[depModuleId];
}
// `depModuleId` is simply a top-level `providesModule`.
// `depModuleId` is a package module but given the full path from the
// package, i.e. package_name/module_name
if (this._moduleById[sansExtJs(depModuleId)]) {
return this._moduleById[sansExtJs(depModuleId)];
}
// 3. `depModuleId` is a package and it's depending on the "main"
// resolution.
// `depModuleId` is a package and it's depending on the "main" resolution.
packageJson = this._packagesById[depModuleId];
// We are being forgiving here and raising an error because we could be
@ -190,7 +197,25 @@ DependecyGraph.prototype.resolveDependency = function(
return null;
}
var main = packageJson.main || 'index';
var main;
// We prioritize the `browser` field if it's a module path.
if (typeof packageJson.browser === 'string') {
main = packageJson.browser;
} else {
main = packageJson.main || 'index';
}
// If there is a mapping for main in the `browser` field.
if (packageJson.browser && typeof packageJson.browser === 'object') {
var tmpMain = packageJson.browser[main] ||
packageJson.browser[withExtJs(main)] ||
packageJson.browser[sansExtJs(main)];
if (tmpMain) {
main = tmpMain;
}
}
modulePath = withExtJs(path.join(packageJson._root, main));
dep = this._graph[modulePath];
@ -207,8 +232,7 @@ DependecyGraph.prototype.resolveDependency = function(
return dep;
} else {
// 4. `depModuleId` is a module defined in a package relative to
// `fromModule`.
// `depModuleId` is a module defined in a package relative to `fromModule`.
packageJson = this._lookupPackage(fromModule.path);
if (packageJson == null) {
@ -224,12 +248,23 @@ DependecyGraph.prototype.resolveDependency = function(
var dir = path.dirname(fromModule.path);
modulePath = path.join(dir, depModuleId);
if (packageJson.browser && typeof packageJson.browser === 'object') {
var relPath = './' + path.relative(packageJson._root, modulePath);
var tmpModulePath = packageJson.browser[withExtJs(relPath)] ||
packageJson.browser[sansExtJs(relPath)];
if (tmpModulePath) {
modulePath = path.join(packageJson._root, tmpModulePath);
}
}
// JS modules can be required without extensios.
if (this._assetExts.indexOf(extname(modulePath)) === -1) {
modulePath = withExtJs(modulePath);
}
dep = this._graph[modulePath];
// Maybe the dependency is a directory and there is an index.js inside it.
if (dep == null) {
modulePath = path.join(dir, depModuleId, 'index.js');
}