Updates for Fri 10 Jul

This commit is contained in:
Nick Lockwood 2015-07-10 17:48:12 +01:00
commit 7fa08e5c3f
34 changed files with 567 additions and 127 deletions

View File

@ -130,10 +130,11 @@ var TouchableFeedbackEvents = React.createClass({
},
render: function() {
return (
<View>
<View testID="touchable_feedback_events">
<View style={[styles.row, {justifyContent: 'center'}]}>
<TouchableOpacity
style={styles.wrapper}
testID="touchable_feedback_events_button"
onPress={() => this._appendEvent('press')}
onPressIn={() => this._appendEvent('pressIn')}
onPressOut={() => this._appendEvent('pressOut')}
@ -143,7 +144,7 @@ var TouchableFeedbackEvents = React.createClass({
</Text>
</TouchableOpacity>
</View>
<View style={styles.eventLogBox}>
<View testID="touchable_feedback_events_console" style={styles.eventLogBox}>
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
</View>
</View>
@ -165,10 +166,11 @@ var TouchableDelayEvents = React.createClass({
},
render: function() {
return (
<View>
<View testID="touchable_delay_events">
<View style={[styles.row, {justifyContent: 'center'}]}>
<TouchableOpacity
style={styles.wrapper}
testID="touchable_delay_events_button"
onPress={() => this._appendEvent('press')}
delayPressIn={400}
onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
@ -181,7 +183,7 @@ var TouchableDelayEvents = React.createClass({
</Text>
</TouchableOpacity>
</View>
<View style={styles.eventLogBox}>
<View style={styles.eventLogBox} testID="touchable_delay_events_console">
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
</View>
</View>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -190,6 +190,7 @@ class UIExplorerList extends React.Component {
onChangeText={this._search.bind(this)}
placeholder="Search..."
style={[styles.searchTextInput, platformTextInputStyle]}
testID="explorer_search"
value={this.state.searchText}
/>
</View>);

View File

@ -14,25 +14,25 @@
XCTAssertEqualObjects(font1, font2); \
}
- (void)DISABLED_testWeight // task #7118691
- (void)testWeight
{
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14];
UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightBold];
UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"bold"}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Medium" size:14];
UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"500"}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:14];
UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightUltraLight];
UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"100"}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14];
UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular];
UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"normal"}];
RCTAssertEqualFonts(expected, result);
}
@ -41,7 +41,7 @@
- (void)testSize
{
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:18.5];
UIFont *expected = [UIFont systemFontOfSize:18.5];
UIFont *result = [RCTConvert UIFont:@{@"fontSize": @18.5}];
RCTAssertEqualFonts(expected, result);
}
@ -69,32 +69,47 @@
- (void)testStyle
{
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Italic" size:14];
UIFont *font = [UIFont systemFontOfSize:14];
UIFontDescriptor *fontDescriptor = [font fontDescriptor];
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
symbolicTraits |= UIFontDescriptorTraitItalic;
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
UIFont *expected = [UIFont fontWithDescriptor:fontDescriptor size:14];
UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic"}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14];
UIFont *expected = [UIFont systemFontOfSize:14];
UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"normal"}];
RCTAssertEqualFonts(expected, result);
}
}
- (void)DISABLED_testStyleAndWeight // task #7118691
- (void)testStyleAndWeight
{
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLightItalic" size:14];
UIFont *font = [UIFont systemFontOfSize:14 weight:UIFontWeightUltraLight];
UIFontDescriptor *fontDescriptor = [font fontDescriptor];
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
symbolicTraits |= UIFontDescriptorTraitItalic;
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
UIFont *expected = [UIFont fontWithDescriptor:fontDescriptor size:14];
UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic", @"fontWeight": @"100"}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-BoldItalic" size:14];
UIFont *font = [UIFont systemFontOfSize:14 weight:UIFontWeightBold];
UIFontDescriptor *fontDescriptor = [font fontDescriptor];
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
symbolicTraits |= UIFontDescriptorTraitItalic;
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
UIFont *expected = [UIFont fontWithDescriptor:fontDescriptor size:14];
UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic", @"fontWeight": @"bold"}];
RCTAssertEqualFonts(expected, result);
}
}
- (void)DISABLED_testFamilyAndWeight // task #7118691
- (void)testFamilyAndWeight
{
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14];
@ -111,11 +126,6 @@
UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"700"}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont fontWithName:@"Cochin" size:14];
UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"500"}]; // regular Cochin is actually medium bold
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont fontWithName:@"Cochin" size:14];
UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"100"}];
@ -137,7 +147,7 @@
}
}
- (void)DISABLED_testFamilyStyleAndWeight // task #7118691
- (void)testFamilyStyleAndWeight
{
{
UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLightItalic" size:14];
@ -156,4 +166,18 @@
}
}
- (void)testInvalidFont
{
{
UIFont *expected = [UIFont systemFontOfSize:14];
UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"foobar"}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont boldSystemFontOfSize:14];
UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"foobar", @"fontWeight": @"bold"}];
RCTAssertEqualFonts(expected, result);
}
}
@end

View File

@ -382,7 +382,7 @@
XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4]));
}
- (void)testScenario1
- (void)DISABLED_testScenario1 // t7660646
{
RCTUIManager *uiManager = [[RCTUIManager alloc] init];
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil];
@ -454,7 +454,7 @@
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testScenario2
- (void)DISABLED_testScenario2 // t7660646
{
RCTUIManager *uiManager = [[RCTUIManager alloc] init];
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil];

View File

@ -19,15 +19,22 @@
_alignment = alignment;
}
static void ARTFreeTextFrame(ARTTextFrame frame)
{
if (frame.count) {
// We must release each line before freeing up this struct
for (int i = 0; i < frame.count; i++) {
CFRelease(frame.lines[i]);
}
free(frame.lines);
free(frame.widths);
}
}
- (void)setTextFrame:(ARTTextFrame)frame
{
if (frame.lines != _textFrame.lines && _textFrame.count) {
// We must release each line before overriding the old one
for (int i = 0; i < _textFrame.count; i++) {
CFRelease(_textFrame.lines[0]);
}
free(_textFrame.lines);
free(_textFrame.widths);
if (frame.lines != _textFrame.lines) {
ARTFreeTextFrame(_textFrame);
}
[self invalidate];
_textFrame = frame;
@ -35,14 +42,7 @@
- (void)dealloc
{
if (_textFrame.count) {
// We must release each line before freeing up this struct
for (int i = 0; i < _textFrame.count; i++) {
CFRelease(_textFrame.lines[0]);
}
free(_textFrame.lines);
free(_textFrame.widths);
}
ARTFreeTextFrame(_textFrame);
}
- (void)renderLayerTo:(CGContextRef)context

View File

@ -243,7 +243,7 @@ function checkValidInputRange(arr: Array<number>) {
* mean this implicit string conversion, you can do something like
* String(myThing)
*/
'inputRange must be monolithically increasing ' + arr
'inputRange must be monotonically increasing ' + arr
);
}
}

View File

@ -105,6 +105,32 @@ var Image = React.createClass({
* {nativeEvent: { layout: {x, y, width, height}}}.
*/
onLayout: PropTypes.func,
/**
* Invoked on load start
*/
onLoadStart: PropTypes.func,
/**
* Invoked on download progress with
*
* {nativeEvent: { written, total}}.
*/
onLoadProgress: PropTypes.func,
/**
* Invoked on load abort
*/
onLoadAbort: PropTypes.func,
/**
* Invoked on load error
*
* {nativeEvent: { error}}.
*/
onLoadError: PropTypes.func,
/**
* Invoked on load end
*
*/
onLoaded: PropTypes.func
},
statics: {
@ -161,6 +187,7 @@ var Image = React.createClass({
if (this.props.defaultSource) {
nativeProps.defaultImageSrc = this.props.defaultSource.uri;
}
nativeProps.progressHandlerRegistered = isNetwork && this.props.onLoadProgress;
return <RawImage {...nativeProps} />;
}
});
@ -178,6 +205,7 @@ var nativeOnlyProps = {
src: true,
defaultImageSrc: true,
imageTag: true,
progressHandlerRegistered: true
};
if (__DEV__) {
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
typedef void (^RCTDataCompletionBlock)(NSURLResponse *response, NSData *data, NSError *error);
typedef void (^RCTDataProgressBlock)(int64_t written, int64_t total);
@interface RCTDownloadTaskWrapper : NSObject <NSURLSessionDownloadDelegate>
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue;
- (NSURLSessionDownloadTask *)downloadData:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock;
@end

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDownloadTaskWrapper.h"
#import <objc/runtime.h>
static void *const RCTDownloadTaskWrapperCompletionBlockKey = (void *)&RCTDownloadTaskWrapperCompletionBlockKey;
static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownloadTaskWrapperProgressBlockKey;
@interface NSURLSessionTask (RCTDownloadTaskWrapper)
@property (nonatomic, copy, setter=rct_setCompletionBlock:) RCTDataCompletionBlock rct_completionBlock;
@property (nonatomic, copy, setter=rct_setProgressBlock:) RCTDataProgressBlock rct_progressBlock;
@end
@implementation NSURLSessionTask (RCTDownloadTaskWrapper)
- (RCTDataCompletionBlock)rct_completionBlock
{
return objc_getAssociatedObject(self, RCTDownloadTaskWrapperCompletionBlockKey);
}
- (void)rct_setCompletionBlock:(RCTDataCompletionBlock)completionBlock
{
objc_setAssociatedObject(self, RCTDownloadTaskWrapperCompletionBlockKey, completionBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (RCTDataProgressBlock)rct_progressBlock
{
return objc_getAssociatedObject(self, RCTDownloadTaskWrapperProgressBlockKey);
}
- (void)rct_setProgressBlock:(RCTDataProgressBlock)progressBlock
{
objc_setAssociatedObject(self, RCTDownloadTaskWrapperProgressBlockKey, progressBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
@implementation RCTDownloadTaskWrapper
{
NSURLSession *_URLSession;
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue
{
if ((self = [super init])) {
_URLSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
}
return self;
}
- (NSURLSessionDownloadTask *)downloadData:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock
{
NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url completionHandler:nil];
task.rct_completionBlock = completionBlock;
task.rct_progressBlock = progressBlock;
[task resume];
return task;
}
#pragma mark - NSURLSessionTaskDelegate methods
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
if (downloadTask.rct_completionBlock) {
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
downloadTask.rct_completionBlock(downloadTask.response, data, nil);
});
}
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)didWriteData totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
{
if (downloadTask.rct_progressBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
downloadTask.rct_progressBlock(totalBytesWritten, totalBytesExpectedToWrite);
});
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error && task.rct_completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
task.rct_completionBlock(nil, nil, error);
});
}
}
@end

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */; };
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
@ -32,6 +33,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTaskWrapper.h; sourceTree = "<group>"; };
03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTaskWrapper.m; sourceTree = "<group>"; };
1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImage.h; sourceTree = "<group>"; };
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImage.m; sourceTree = "<group>"; };
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImageManager.h; sourceTree = "<group>"; };
@ -71,6 +74,8 @@
children = (
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */,
03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
@ -168,6 +173,7 @@
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -9,8 +9,11 @@
#import <UIKit/UIKit.h>
#import "RCTDownloadTaskWrapper.h"
typedef void (^RCTDataDownloadBlock)(NSData *data, NSError *error);
typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
typedef void (^RCTImageDownloadCancellationBlock)(void);
@interface RCTImageDownloader : NSObject
@ -21,7 +24,8 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
* will not be executed on the same thread you called the method from, nor on
* the main thread. Returns a token that can be used to cancel the download.
*/
- (id)downloadDataForURL:(NSURL *)url
- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url
progressBlock:(RCTDataProgressBlock)progressBlock
block:(RCTDataDownloadBlock)block;
/**
@ -30,11 +34,12 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
* will not be executed on the same thread you called the method from, nor on
* the main thread. Returns a token that can be used to cancel the download.
*/
- (id)downloadImageForURL:(NSURL *)url
- (RCTImageDownloadCancellationBlock)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
progressBlock:(RCTDataProgressBlock)progressBlock
block:(RCTImageDownloadBlock)block;
/**
@ -42,6 +47,6 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
* same image, only the request that relates to the token passed will be
* cancelled.
*/
- (void)cancelDownload:(id)downloadToken;
- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken;
@end

View File

@ -9,6 +9,7 @@
#import "RCTImageDownloader.h"
#import "RCTDownloadTaskWrapper.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@ -22,6 +23,7 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
NSURLCache *_cache;
dispatch_queue_t _processingQueue;
NSMutableDictionary *_pendingBlocks;
RCTDownloadTaskWrapper *_downloadTaskWrapper;
}
+ (RCTImageDownloader *)sharedInstance
@ -40,19 +42,22 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
_cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 diskCapacity:200 * 1024 * 1024 diskPath:@"React/RCTImageDownloader"];
_processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL);
_pendingBlocks = [[NSMutableDictionary alloc] init];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_downloadTaskWrapper = [[RCTDownloadTaskWrapper alloc] initWithSessionConfiguration:config delegateQueue:nil];
}
return self;
}
- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url progressBlock:progressBlock block:(RCTCachedDataDownloadBlock)block
{
NSString *cacheKey = url.absoluteString;
NSString *const cacheKey = url.absoluteString;
__block BOOL cancelled = NO;
__block NSURLSessionDataTask *task = nil;
__block NSURLSessionDownloadTask *task = nil;
dispatch_block_t cancel = ^{
RCTImageDownloadCancellationBlock cancel = ^{
cancelled = YES;
dispatch_async(_processingQueue, ^{
@ -85,7 +90,8 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
});
};
task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
task = [_downloadTaskWrapper downloadData:url progressBlock:progressBlock completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!cancelled) {
runBlocks(NO, data, error);
}
@ -93,12 +99,12 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
if (response) {
RCTImageDownloader *strongSelf = weakSelf;
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
[strongSelf->_cache storeCachedResponse:cachedResponse forDataTask:task];
[strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request];
}
task = nil;
}];
[_cache getCachedResponseForDataTask:task completionHandler:^(NSCachedURLResponse *cachedResponse) {
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
if (cancelled) {
return;
}
@ -108,28 +114,29 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
} else {
[task resume];
}
}];
}
});
return [cancel copy];
}
- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTDataDownloadBlock)block
{
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSData *data, NSError *error) {
block(data, error);
}];
}
- (id)downloadImageForURL:(NSURL *)url
- (RCTImageDownloadCancellationBlock)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
progressBlock:(RCTDataProgressBlock)progressBlock
block:(RCTImageDownloadBlock)block
{
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
return [self downloadDataForURL:url progressBlock:progressBlock block:^(NSData *data, NSError *error) {
if (!data || error) {
block(nil, error);
return;
@ -176,10 +183,10 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
}];
}
- (void)cancelDownload:(id)downloadToken
- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken
{
if (downloadToken) {
((dispatch_block_t)downloadToken)();
downloadToken();
}
}

View File

@ -121,7 +121,7 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
return;
}
[[RCTImageDownloader sharedInstance] downloadDataForURL:url block:^(NSData *data, NSError *error) {
[[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
if (error) {
RCTDispatchCallbackOnMainQueue(callback, error, nil);
} else {

View File

@ -9,11 +9,13 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@class RCTImageDownloader;
@interface RCTNetworkImageView : UIView
- (instancetype)initWithImageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
imageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER;
/**
* An image that will appear while the view is loading the image from the network,

View File

@ -14,23 +14,27 @@
#import "RCTGIFImage.h"
#import "RCTImageDownloader.h"
#import "RCTUtils.h"
#import "RCTBridgeModule.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
@implementation RCTNetworkImageView
{
BOOL _deferred;
BOOL _progressHandlerRegistered;
NSURL *_imageURL;
NSURL *_deferredImageURL;
NSUInteger _deferSentinel;
RCTImageDownloader *_imageDownloader;
id _downloadToken;
RCTEventDispatcher *_eventDispatcher;
}
- (instancetype)initWithImageDownloader:(RCTImageDownloader *)imageDownloader
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher imageDownloader:(RCTImageDownloader *)imageDownloader
{
RCTAssertParam(imageDownloader);
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_progressHandlerRegistered = NO;
_deferSentinel = 0;
_imageDownloader = imageDownloader;
self.userInteractionEnabled = NO;
@ -56,6 +60,11 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
[self _updateImage];
}
- (void)setProgressHandlerRegistered:(BOOL)progressHandlerRegistered
{
_progressHandlerRegistered = progressHandlerRegistered;
}
- (void)reactSetFrame:(CGRect)frame
{
[super reactSetFrame:frame];
@ -89,8 +98,34 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
self.layer.minificationFilter = kCAFilterTrilinear;
self.layer.magnificationFilter = kCAFilterTrilinear;
}
[_eventDispatcher sendInputEventWithName:@"loadStart" body:@{ @"target": self.reactTag }];
RCTDataProgressBlock progressHandler = ^(int64_t written, int64_t total) {
if (_progressHandlerRegistered) {
NSDictionary *event = @{
@"target": self.reactTag,
@"written": @(written),
@"total": @(total),
};
[_eventDispatcher sendInputEventWithName:@"loadProgress" body:event];
}
};
void (^errorHandler)(NSString *errorDescription) = ^(NSString *errorDescription) {
NSDictionary *event = @{
@"target": self.reactTag,
@"error": errorDescription,
};
[_eventDispatcher sendInputEventWithName:@"loadError" body:event];
};
void (^loadEndHandler)(void) = ^(void) {
NSDictionary *event = @{ @"target": self.reactTag };
[_eventDispatcher sendInputEventWithName:@"loaded" body:event];
};
if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
_downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) {
_downloadToken = [_imageDownloader downloadDataForURL:imageURL progressBlock:progressHandler block:^(NSData *data, NSError *error) {
if (data) {
dispatch_async(dispatch_get_main_queue(), ^{
if (imageURL != self.imageURL) {
@ -102,13 +137,16 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
self.layer.minificationFilter = kCAFilterLinear;
self.layer.magnificationFilter = kCAFilterLinear;
[self.layer addAnimation:animation forKey:@"contents"];
loadEndHandler();
});
} else if (error) {
RCTLogWarn(@"Unable to download image data. Error: %@", error);
errorHandler([error description]);
}
}];
} else {
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() resizeMode:self.contentMode backgroundColor:self.backgroundColor block:^(UIImage *image, NSError *error) {
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale()
resizeMode:self.contentMode backgroundColor:self.backgroundColor
progressBlock:progressHandler block:^(UIImage *image, NSError *error) {
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
if (imageURL != self.imageURL) {
@ -118,9 +156,10 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
[self.layer removeAnimationForKey:@"contents"];
self.layer.contentsScale = image.scale;
self.layer.contents = (__bridge id)image.CGImage;
loadEndHandler();
});
} else if (error) {
RCTLogWarn(@"Unable to download image. Error: %@", error);
errorHandler([error description]);
}
}];
}

View File

@ -9,24 +9,38 @@
#import "RCTNetworkImageViewManager.h"
#import "RCTNetworkImageView.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTUtils.h"
#import "RCTImageDownloader.h"
#import "RCTNetworkImageView.h"
#import "RCTUtils.h"
@implementation RCTNetworkImageViewManager
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
- (UIView *)view
{
return [[RCTNetworkImageView alloc] initWithImageDownloader:[RCTImageDownloader sharedInstance]];
return [[RCTNetworkImageView alloc] initWithEventDispatcher:self.bridge.eventDispatcher imageDownloader:[RCTImageDownloader sharedInstance]];
}
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_EXPORT_VIEW_PROPERTY(progressHandlerRegistered, BOOL)
- (NSDictionary *)customDirectEventTypes
{
return @{
@"loadStart": @{ @"registrationName": @"onLoadStart" },
@"loadProgress": @{ @"registrationName": @"onLoadProgress" },
@"loaded": @{ @"registrationName": @"onLoaded" },
@"loadError": @{ @"registrationName": @"onLoadError" },
@"loadAbort": @{ @"registrationName": @"onLoadAbort" },
};
}
@end

View File

@ -222,7 +222,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
- (UIFont *)defaultPlaceholderFont
{
return [UIFont fontWithName:@"Helvetica" size:17];
return [UIFont systemFontOfSize:17];
}
- (UIColor *)defaultPlaceholderTextColor

View File

@ -26,6 +26,8 @@ let MODULE_IDS = 0;
let METHOD_IDS = 1;
let PARAMS = 2;
let SPY_MODE = false;
let MethodTypes = keyMirror({
local: null,
remote: null,
@ -120,6 +122,10 @@ class MessageQueue {
(this._debugInfo[this._callbackID >> 5] = null);
this._debugInfo[this._callbackID >> 1] = [module, method];
if (SPY_MODE && isFinite(module)) {
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
}
}
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
@ -137,6 +143,9 @@ class MessageQueue {
method = this._methodTable[module][method];
module = this._moduleTable[module];
}
if (__DEV__ && SPY_MODE) {
console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
}
module = this._require(module);
module[method].apply(module, args);
BridgeProfiling.profileEnd();
@ -146,11 +155,15 @@ class MessageQueue {
BridgeProfiling.profile(
() => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`);
let callback = this._callbacks[cbID];
if (__DEV__ && !callback) {
if (__DEV__) {
let debug = this._debugInfo[cbID >> 1];
let module = this._remoteModuleTable[debug[0]];
let method = this._remoteMethodTable[debug[0]][debug[1]];
if (!callback) {
console.error(`Callback with id ${cbID}: ${module}.${method}() not found`);
} else if (SPY_MODE) {
console.log('N->JS : <callback for ' + module + '.' + method + '>(' + JSON.stringify(args) + ')');
}
}
this._callbacks[cbID & ~1] = null;
this._callbacks[cbID | 1] = null;

View File

@ -38,7 +38,6 @@ typedef void (^RCTPromiseResolveBlock)(id result);
*/
typedef void (^RCTPromiseRejectBlock)(NSError *error);
/**
* This constant can be returned from +methodQueue to force module
* methods to be called on the JavaScript thread. This can have serious

View File

@ -39,6 +39,7 @@
+ (NSUInteger)NSUInteger:(id)json;
+ (NSArray *)NSArray:(id)json;
+ (NSSet *)NSSet:(id)json;
+ (NSDictionary *)NSDictionary:(id)json;
+ (NSString *)NSString:(id)json;
+ (NSNumber *)NSNumber:(id)json;

View File

@ -35,6 +35,7 @@ RCT_NUMBER_CONVERTER(NSInteger, integerValue)
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)
RCT_CUSTOM_CONVERTER(NSArray *, NSArray, [NSArray arrayWithArray:json])
RCT_CUSTOM_CONVERTER(NSSet *, NSSet, [NSSet setWithArray:json])
RCT_CUSTOM_CONVERTER(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CONVERTER(NSString *, NSString, description)
@ -671,8 +672,8 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
NSURL *url = [NSURL URLWithString:path];
NSData *imageData = [NSData dataWithContentsOfURL:url];
image = [UIImage imageWithData:imageData];
} else if ([path isAbsolutePath]) {
image = [UIImage imageWithContentsOfFile:path];
} else if ([path isAbsolutePath] || [path hasPrefix:@"~"]) {
image = [UIImage imageWithContentsOfFile:path.stringByExpandingTildeInPath];
} else {
image = [UIImage imageNamed:path];
if (!image) {
@ -788,7 +789,8 @@ static BOOL RCTFontIsCondensed(UIFont *font)
size:(id)size weight:(id)weight style:(id)style
{
// Defaults
NSString *const RCTDefaultFontFamily = @"Helvetica Neue";
NSString *const RCTDefaultFontFamily = @"System";
NSString *const RCTIOS8SystemFontFamily = @"Helvetica Neue";
const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular;
const CGFloat RCTDefaultFontSize = 14;
@ -807,11 +809,36 @@ static BOOL RCTFontIsCondensed(UIFont *font)
isCondensed = RCTFontIsCondensed(font);
}
// Get font size
// Get font attributes
fontSize = [self CGFloat:size] ?: fontSize;
// Get font family
familyName = [self NSString:family] ?: familyName;
isItalic = style ? [self RCTFontStyle:style] : isItalic;
fontWeight = weight ? [self RCTFontWeight:weight] : fontWeight;
// Handle system font as special case. This ensures that we preserve
// the specific metrics of the standard system font as closely as possible.
if ([familyName isEqual:RCTDefaultFontFamily]) {
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) {
font = [UIFont systemFontOfSize:fontSize weight:fontWeight];
if (isItalic || isCondensed) {
UIFontDescriptor *fontDescriptor = [font fontDescriptor];
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
if (isItalic) {
symbolicTraits |= UIFontDescriptorTraitItalic;
}
if (isCondensed) {
symbolicTraits |= UIFontDescriptorTraitCondensed;
}
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize];
}
return font;
} else {
// systemFontOfSize:weight: isn't available prior to iOS 8.2, so we
// fall back to finding the correct font manually, by linear search.
familyName = RCTIOS8SystemFontFamily;
}
}
// Gracefully handle being given a font name rather than font family, for
// example: "Helvetica Light Oblique" rather than just "Helvetica".
@ -821,30 +848,25 @@ static BOOL RCTFontIsCondensed(UIFont *font)
// It's actually a font name, not a font family name,
// but we'll do what was meant, not what was said.
familyName = font.familyName;
fontWeight = RCTWeightOfFont(font);
isItalic = RCTFontIsItalic(font);
fontWeight = weight ? fontWeight : RCTWeightOfFont(font);
isItalic = style ? isItalic : RCTFontIsItalic(font);
isCondensed = RCTFontIsCondensed(font);
} else {
// Not a valid font or family
RCTLogError(@"Unrecognized font family '%@'", familyName);
familyName = RCTDefaultFontFamily;
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) {
font = [UIFont systemFontOfSize:fontSize weight:fontWeight];
} else if (fontWeight > UIFontWeightRegular) {
font = [UIFont boldSystemFontOfSize:fontSize];
} else {
font = [UIFont systemFontOfSize:fontSize];
}
}
// Get font style
if (style) {
isItalic = [self RCTFontStyle:style];
}
// Get font weight
if (weight) {
fontWeight = [self RCTFontWeight:weight];
}
// Get the closest font that matches the given weight for the fontFamily
UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize];
UIFont *bestMatch = font;
CGFloat closestWeight = INFINITY;
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
UIFont *match = [UIFont fontWithName:name size:fontSize];
if (isItalic == RCTFontIsItalic(match) &&
@ -857,14 +879,6 @@ static BOOL RCTFontIsCondensed(UIFont *font)
}
}
// Safety net
if (!bestMatch) {
RCTLogError(@"Could not find font with family: '%@', size: %@, \
weight: %@, style: %@", family, size, weight, style);
bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:familyName] firstObject]
size:fontSize];
}
return bestMatch;
}

View File

@ -32,6 +32,8 @@
NSArray *_argumentBlocks;
}
RCT_NOT_IMPLEMENTED(-init)
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName
moduleClass:(Class)moduleClass

View File

@ -64,6 +64,8 @@ RCT_EXPORT_MODULE()
- (void)sendTimespans
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_bridge enqueueJSCall:@"PerformanceLogger.addTimespans" args:@[
RCTPerformanceLoggerOutput(),
@[

View File

@ -64,10 +64,12 @@
"react-tools": "0.13.2",
"rebound": "^0.0.12",
"sane": "^1.1.2",
"semver": "^4.3.6",
"source-map": "0.1.31",
"stacktrace-parser": "frantic/stacktrace-parser#493c5e5638",
"uglify-js": "~2.4.16",
"underscore": "1.7.0",
"wordwrap": "^1.0.0",
"worker-farm": "^1.3.1",
"ws": "0.4.31",
"yargs": "1.3.2"

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
var chalk = require('chalk');
var semver = require('semver');
var formatBanner = require('./formatBanner');
function checkNodeVersion() {
if (!semver.satisfies(process.version, '>=2.0.0')) {
var engine = semver.lt(process.version, '1.0.0') ? 'Node' : 'io.js';
var message = 'You are currently running ' + engine + ' ' +
process.version + '.\n' +
'\n' +
'React Native is moving to io.js 2.x. There are several ways to upgrade' +
'to io.js depending on your preference.\n' +
'\n' +
'nvm: nvm install iojs && nvm alias default iojs\n' +
'Homebrew: brew unlink node; brew install iojs && brew ln iojs --force\n' +
'Installer: download the Mac .pkg from https://iojs.org/\n' +
'\n' +
'About io.js: https://iojs.org\n' +
'Follow along at: https://github.com/facebook/react-native/issues/1737';
console.log(formatBanner(message, {
chalkFunction: chalk.green,
marginLeft: 1,
marginRight: 1,
paddingBottom: 1,
}));
}
}
module.exports = checkNodeVersion;

108
packager/formatBanner.js Normal file
View File

@ -0,0 +1,108 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
var _ = require('underscore');
var wordwrap = require('wordwrap');
var HORIZONTAL_LINE = '\u2500';
var VERTICAL_LINE = '\u2502';
var TOP_LEFT = '\u250c';
var TOP_RIGHT = '\u2510';
var BOTTOM_LEFT = '\u2514';
var BOTTOM_RIGHT = '\u2518';
/**
* Prints a banner with a border around it containing the given message. The
* following options are supported:
*
* type Options = {
* // A function to apply to each line of text to decorate it
* chalkFunction: (string: message) => string;
* // The total width (max line length) of the banner, including margin and
* // padding (default = 80)
* width: number;
* // How much leading space to prepend to each line (default = 0)
* marginLeft: number;
* // How much trailing space to append to each line (default = 0)
* marginRight: number;
* // Space between the top banner border and the text (default = 0)
* paddingTop: number;
* // Space between the bottom banner border and the text (default = 0)
* paddingBottom: number;
* // Space between the left banner border and the text (default = 2)
* paddingLeft: number;
* // Space between the right banner border and the text (default = 2)
* paddingRight: number;
* };
*/
function formatBanner(message, options) {
options = options || {};
_.defaults(options, {
chalkFunction: _.identity,
width: 80,
marginLeft: 0,
marginRight: 0,
paddingTop: 0,
paddingBottom: 0,
paddingLeft: 2,
paddingRight: 2,
});
var width = options.width;
var marginLeft = options.marginLeft;
var marginRight = options.marginRight;
var paddingTop = options.paddingTop;
var paddingBottom = options.paddingBottom;
var paddingLeft = options.paddingLeft;
var paddingRight = options.paddingRight;
var horizSpacing = marginLeft + paddingLeft + paddingRight + marginRight;
// 2 for the banner borders
var maxLineWidth = width - horizSpacing - 2;
var wrap = wordwrap(maxLineWidth);
var body = wrap(message);
var left = spaces(marginLeft) + VERTICAL_LINE + spaces(paddingLeft);
var right = spaces(paddingRight) + VERTICAL_LINE + spaces(marginRight);
var bodyLines = _.flatten([
arrayOf('', paddingTop),
body.split('\n'),
arrayOf('', paddingBottom),
]).map(function(line) {
var padding = spaces(Math.max(0, maxLineWidth - line.length));
return left + options.chalkFunction(line) + padding + right;
});
var horizontalBorderLine = repeatString(
HORIZONTAL_LINE,
width - marginLeft - marginRight - 2
);
var top = spaces(marginLeft) + TOP_LEFT + horizontalBorderLine + TOP_RIGHT +
spaces(marginRight);
var bottom = spaces(marginLeft) + BOTTOM_LEFT + horizontalBorderLine +
BOTTOM_RIGHT + spaces(marginRight);
return _.flatten([top, bodyLines, bottom]).join('\n');
}
function spaces(number) {
return repeatString(' ', number);
}
function repeatString(string, number) {
return new Array(number + 1).join(string);
}
function arrayOf(value, number) {
return _.range(number).map(function() {
return value;
});
}
module.exports = formatBanner;

View File

@ -23,7 +23,9 @@
"lint": "node linter.js Examples/",
"start": "./packager/packager.sh"
},
"dependencies": {},
"dependencies": {
"wordwrap": "^1.0.0"
},
"devDependencies": {
"jest-cli": "git://github.com/facebook/jest#0.5.x",
"eslint": "0.9.2"

View File

@ -30,6 +30,8 @@ var chalk = require('chalk');
var connect = require('connect');
var ReactPackager = require('./react-packager');
var blacklist = require('./blacklist.js');
var checkNodeVersion = require('./checkNodeVersion');
var formatBanner = require('./formatBanner');
var launchEditor = require('./launchEditor.js');
var parseCommandLine = require('./parseCommandLine.js');
var webSocketProxy = require('./webSocketProxy.js');
@ -108,16 +110,19 @@ if (options.assetRoots) {
}
}
console.log('\n' +
' ===============================================================\n' +
' | Running packager on port ' + options.port + '. \n' +
' | Keep this packager running while developing on any JS \n' +
' | projects. Feel free to close this tab and run your own \n' +
' | packager instance if you prefer. \n' +
' | \n' +
' | https://github.com/facebook/react-native \n' +
' | \n' +
' ===============================================================\n'
checkNodeVersion();
console.log(formatBanner(
'Running packager on port ' + options.port + '.\n'+
'\n' +
'Keep this packager running while developing on any JS projects. Feel free ' +
'to close this tab and run your own packager instance if you prefer.\n' +
'\n' +
'https://github.com/facebook/react-native', {
marginLeft: 1,
marginRight: 1,
paddingBottom: 1,
})
);
console.log(