This commit is contained in:
Christopher Chedeau 2015-04-17 08:42:57 -07:00
Родитель 0e07407ce2 1883ba535c
Коммит 014065b277
48 изменённых файлов: 1282 добавлений и 293 удалений

Просмотреть файл

@ -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 */; };
00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* SampleAppTests.m */; };
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 */; };
@ -74,6 +75,13 @@
remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
remoteInfo = RCTVibration;
};
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = SampleApp;
};
146834031AC3E56700842450 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
@ -106,6 +114,9 @@
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; };
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
00E356EE1AD99517003FC87E /* SampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* SampleAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleAppTests.m; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = iOS/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = iOS/AppDelegate.m; sourceTree = "<group>"; };
@ -119,6 +130,13 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
00E356EB1AD99517003FC87E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -195,6 +213,23 @@
name = Products;
sourceTree = "<group>";
};
00E356EF1AD99517003FC87E /* SampleAppTests */ = {
isa = PBXGroup;
children = (
00E356F21AD99517003FC87E /* SampleAppTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = SampleAppTests;
sourceTree = "<group>";
};
00E356F01AD99517003FC87E /* Supporting Files */ = {
isa = PBXGroup;
children = (
00E356F11AD99517003FC87E /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
13B07FAE1A68108700A75B9A /* SampleApp */ = {
isa = PBXGroup;
children = (
@ -255,6 +290,7 @@
children = (
13B07FAE1A68108700A75B9A /* SampleApp */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* SampleAppTests */,
83CBBA001A601CBA00E9B192 /* Products */,
);
indentWidth = 2;
@ -265,6 +301,7 @@
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* SampleApp.app */,
00E356EE1AD99517003FC87E /* SampleAppTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -272,6 +309,24 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
00E356ED1AD99517003FC87E /* SampleAppTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "SampleAppTests" */;
buildPhases = (
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
);
buildRules = (
);
dependencies = (
00E356F51AD99517003FC87E /* PBXTargetDependency */,
);
name = SampleAppTests;
productName = SampleAppTests;
productReference = 00E356EE1AD99517003FC87E /* SampleAppTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
13B07F861A680F5B00A75B9A /* SampleApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */;
@ -297,6 +352,12 @@
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */;
compatibilityVersion = "Xcode 3.2";
@ -354,6 +415,7 @@
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* SampleApp */,
00E356ED1AD99517003FC87E /* SampleAppTests */,
);
};
/* End PBXProject section */
@ -432,6 +494,13 @@
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
00E356EC1AD99517003FC87E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -445,6 +514,14 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
00E356EA1AD99517003FC87E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -456,6 +533,14 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* SampleApp */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = {
isa = PBXVariantGroup;
@ -469,6 +554,43 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = SampleAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp";
};
name = Debug;
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
INFOPLIST_FILE = SampleAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -590,6 +712,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "SampleAppTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00E356F61AD99517003FC87E /* Debug */,
00E356F71AD99517003FC87E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (

Просмотреть файл

@ -20,6 +20,20 @@
ReferencedContainer = "container:SampleApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "SampleAppTests.xctest"
BlueprintName = "SampleAppTests"
ReferencedContainer = "container:SampleApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@ -28,6 +42,16 @@
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "SampleAppTests.xctest"
BlueprintName = "SampleAppTests"
ReferencedContainer = "container:SampleApp.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference

Просмотреть файл

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

Просмотреть файл

@ -0,0 +1,68 @@
/**
* 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 <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "RCTAssert.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#define TIMEOUT_SECONDS 240
#define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
@interface SampleAppTests : XCTestCase
@end
@implementation SampleAppTests
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
- (void)testRendersWelcomeScreen {
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
NSString *redboxError = nil;
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
if ([view respondsToSelector:@selector(attributedText)]) {
NSString *text = [(id)view attributedText].string;
if ([text isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
}
return NO;
}];
}
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Cound't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}
@end

Просмотреть файл

@ -226,10 +226,8 @@ exports.examples = [
Contain
</Text>
<Image
style={[
styles.resizeMode,
{resizeMode: Image.resizeMode.contain}
]}
style={styles.resizeMode}
resizeMode={Image.resizeMode.contain}
source={fullImage}
/>
</View>
@ -238,10 +236,8 @@ exports.examples = [
Cover
</Text>
<Image
style={[
styles.resizeMode,
{resizeMode: Image.resizeMode.cover}
]}
style={styles.resizeMode}
resizeMode={Image.resizeMode.cover}
source={fullImage}
/>
</View>
@ -250,10 +246,8 @@ exports.examples = [
Stretch
</Text>
<Image
style={[
styles.resizeMode,
{resizeMode: Image.resizeMode.stretch}
]}
style={styles.resizeMode}
resizeMode={Image.resizeMode.stretch}
source={fullImage}
/>
</View>

Просмотреть файл

@ -74,7 +74,8 @@ var BatchedBridgeFactory = {
setLoggingEnabled: messageQueue.setLoggingEnabled.bind(messageQueue),
getLoggedOutgoingItems: messageQueue.getLoggedOutgoingItems.bind(messageQueue),
getLoggedIncomingItems: messageQueue.getLoggedIncomingItems.bind(messageQueue),
replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue)
replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue),
processBatch: messageQueue.processBatch.bind(messageQueue),
};
}
};

Просмотреть файл

@ -17,7 +17,6 @@ var PointPropType = require('PointPropType');
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
var RCTScrollViewConsts = RCTScrollView.Constants;
var React = require('React');
var ReactIOSTagHandles = require('ReactIOSTagHandles');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var RCTUIManager = require('NativeModules').UIManager;
var ScrollResponder = require('ScrollResponder');
@ -32,6 +31,7 @@ var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var pointsDiffer = require('pointsDiffer');
var requireNativeComponent = require('requireNativeComponent');
var PropTypes = React.PropTypes;
@ -73,6 +73,12 @@ var ScrollView = React.createClass({
* the `alwaysBounce*` props are true. The default value is true.
*/
bounces: PropTypes.bool,
/**
* When true, gestures can drive zoom past min/max and the zoom will animate
* to the min/max value at gesture end, otherwise the zoom will not exceed
* the limits.
*/
bouncesZoom: PropTypes.bool,
/**
* When true, the scroll view bounces horizontally when it reaches the end
* even if the content is smaller than the scroll view itself. The default
@ -120,6 +126,16 @@ var ScrollView = React.createClass({
* instead of vertically in a column. The default value is false.
*/
horizontal: PropTypes.bool,
/**
* When true, the ScrollView will try to lock to only vertical or horizontal
* scrolling while dragging. The default value is false.
*/
directionalLockEnabled: PropTypes.bool,
/**
* When false, once tracking starts, won't try to drag if the touch moves.
* The default value is true.
*/
canCancelContentTouches: PropTypes.bool,
/**
* Determines whether the keyboard gets dismissed in response to a drag.
* - 'none' (the default), drags do not dismiss the keyboard.
@ -359,6 +375,7 @@ if (Platform.OS === 'android') {
validAttributes: validAttributes,
uiViewClassName: 'RCTScrollView',
});
var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView);
}
module.exports = ScrollView;

Просмотреть файл

@ -12,6 +12,7 @@
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
@ -21,6 +22,7 @@ var View = require('View');
var createReactIOSNativeComponentClass =
require('createReactIOSNativeComponentClass');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
type Event = Object;
@ -96,16 +98,20 @@ var styles = StyleSheet.create({
},
});
var validAttributes = {
...ReactIOSViewAttributes.UIView,
value: true,
minimumValue: true,
maximumValue: true,
};
if (Platform.OS === 'ios') {
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
} else {
var validAttributes = {
...ReactIOSViewAttributes.UIView,
value: true,
minimumValue: true,
maximumValue: true,
};
var RCTSlider = createReactIOSNativeComponentClass({
validAttributes: validAttributes,
uiViewClassName: 'RCTSlider',
});
var RCTSlider = createReactIOSNativeComponentClass({
validAttributes: validAttributes,
uiViewClassName: 'RCTSlider',
});
}
module.exports = SliderIOS;

Просмотреть файл

@ -11,7 +11,6 @@
'use strict';
var Dimensions = require('Dimensions');
var React = require('React');
var View = require('View');
var StyleSheet = require('StyleSheet');
@ -33,8 +32,10 @@ var styles = StyleSheet.create({
tab: {
// TODO(5405356): Implement overflow: visible so position: absolute isn't useless
// position: 'absolute',
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
top: 0,
right: 0,
bottom: 0,
left: 0,
borderColor: 'red',
borderWidth: 1,
}

Просмотреть файл

@ -14,7 +14,6 @@
var Image = require('Image');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var Dimensions = require('Dimensions');
var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
var View = require('View');
@ -134,8 +133,10 @@ var TabBarItemIOS = React.createClass({
var styles = StyleSheet.create({
tab: {
position: 'absolute',
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
top: 0,
right: 0,
bottom: 0,
left: 0,
}
});

Просмотреть файл

@ -72,7 +72,7 @@ var notMultiline = {
onSubmitEditing: true,
};
var TextInputAndroidAttributes = {
var AndroidTextInputAttributes = {
autoCapitalize: true,
autoCorrect: true,
autoFocus: true,
@ -80,14 +80,19 @@ var TextInputAndroidAttributes = {
multiline: true,
password: true,
placeholder: true,
value: true,
text: true,
testID: true,
};
var AndroidTextInput = createReactIOSNativeComponentClass({
validAttributes: TextInputAndroidAttributes,
var viewConfigIOS = {
uiViewClassName: 'RCTTextField',
validAttributes: RCTTextFieldAttributes,
};
var viewConfigAndroid = {
uiViewClassName: 'AndroidTextInput',
});
validAttributes: AndroidTextInputAttributes,
};
var crossPlatformKeyboardTypeMap = {
'numeric': 'decimal-pad',
@ -293,10 +298,8 @@ var TextInput = React.createClass({
*/
mixins: [NativeMethodsMixin, TimerMixin],
viewConfig: {
uiViewClassName: 'RCTTextField',
validAttributes: RCTTextFieldAttributes,
},
viewConfig: ((Platform.OS === 'ios' ? viewConfigIOS :
(Platform.OS === 'android' ? viewConfigAndroid : {})) : Object),
isFocused: function(): boolean {
return TextInputState.currentlyFocusedField() ===
@ -521,7 +524,7 @@ var TextInput = React.createClass({
onSubmitEditing={this.props.onSubmitEditing}
password={this.props.password || this.props.secureTextEntry}
placeholder={this.props.placeholder}
value={this.state.bufferedValue}
text={this.state.bufferedValue}
/>;
return (
@ -591,4 +594,9 @@ var RCTTextField = createReactIOSNativeComponentClass({
uiViewClassName: 'RCTTextField',
});
var AndroidTextInput = createReactIOSNativeComponentClass({
validAttributes: AndroidTextInputAttributes,
uiViewClassName: 'AndroidTextInput',
});
module.exports = TextInput;

Просмотреть файл

@ -0,0 +1,37 @@
/**
* Common implementation for a simple stubbed view. Simply applies the view's styles to the inner
* View component and renders its children.
*
* @providesModule UnimplementedView
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');
var UnimplementedView = React.createClass({
setNativeProps: function() {
// Do nothing.
// This method is required in order to use this view as a Touchable* child.
// See ensureComponentIsNative.js for more info
},
render: function() {
return (
<View style={[styles.unimplementedView, this.props.style]}>
{this.props.children}
</View>
);
},
});
var styles = StyleSheet.create({
unimplementedView: {
borderWidth: 1,
borderColor: 'red',
alignSelf: 'flex-start',
}
});
module.exports = UnimplementedView;

Просмотреть файл

@ -35,6 +35,7 @@ var NavigatorNavigationBar = require('NavigatorNavigationBar');
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
var NavigatorStaticContextContainer = require('NavigatorStaticContextContainer');
var PanResponder = require('PanResponder');
var Platform = require('Platform');
var React = require('React');
var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
@ -287,9 +288,10 @@ var Navigator = React.createClass({
),
idStack: routeStack.map(() => getuid()),
routeStack,
// These are tracked to avoid rendering everything all the time.
updatingRangeStart: initialRouteIndex,
updatingRangeLength: initialRouteIndex + 1,
// `updatingRange*` allows us to only render the visible or staged scenes
// On first render, we will render every scene in the initialRouteStack
updatingRangeStart: 0,
updatingRangeLength: routeStack.length,
// Either animating or gesturing.
isAnimating: false,
jumpToIndex: routeStack.length - 1,
@ -397,22 +399,22 @@ var Navigator = React.createClass({
this._emitDidFocus(this.state.routeStack[this.state.presentedIndex]);
if (this.parentNavigator) {
this.parentNavigator.setHandler(this._handleRequest);
} else {
} else if (Platform.OS === 'android') {
// There is no navigator in our props or context, so this is the
// top-level navigator. We will handle back button presses here
BackAndroid.addEventListener('hardwareBackPress', this._handleBackPress);
BackAndroid.addEventListener('hardwareBackPress', this._handleAndroidBackPress);
}
},
componentWillUnmount: function() {
if (this.parentNavigator) {
this.parentNavigator.setHandler(null);
} else {
BackAndroid.removeEventListener('hardwareBackPress', this._handleBackPress);
} else if (Platform.OS === 'android') {
BackAndroid.removeEventListener('hardwareBackPress', this._handleAndroidBackPress);
}
},
_handleBackPress: function() {
_handleAndroidBackPress: function() {
var didPop = this.pop();
if (!didPop) {
BackAndroid.exitApp();

Просмотреть файл

@ -14,6 +14,7 @@
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var ImageResizeMode = require('ImageResizeMode');
var ImageStylePropTypes = require('ImageStylePropTypes');
@ -27,7 +28,9 @@ var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var warning = require('warning');
var verifyPropTypes = require('verifyPropTypes');
/**
* A react component for displaying different types of images,
@ -64,6 +67,13 @@ var Image = React.createClass({
source: PropTypes.shape({
uri: PropTypes.string,
}),
/**
* A static image to display while downloading the final image off the
* network.
*/
defaultSource: PropTypes.shape({
uri: PropTypes.string,
}),
/**
* Whether this element should be revealed as an accessible element.
*/
@ -80,6 +90,11 @@ var Image = React.createClass({
* [Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets)
*/
capInsets: EdgeInsetsPropType,
/**
* Determines how to resize the image when the frame doesn't match the raw
* image dimensions.
*/
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
style: StyleSheetPropType(ImageStylePropTypes),
/**
* A unique identifier for this element to be used in UI Automation
@ -104,6 +119,12 @@ var Image = React.createClass({
},
render: function() {
for (var prop in nativeOnlyProps) {
if (this.props[prop] !== undefined) {
console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
'not be set directly on Image.');
}
}
var style = flattenStyle([styles.base, this.props.style]);
invariant(style, "style must be initialized");
var source = this.props.source;
@ -119,28 +140,36 @@ var Image = React.createClass({
if (this.props.style && this.props.style.tintColor) {
warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
}
var resizeMode = this.props.resizeMode || style.resizeMode;
var contentModes = NativeModules.UIManager.UIView.ContentMode;
var resizeMode;
if (style.resizeMode === ImageResizeMode.stretch) {
resizeMode = contentModes.ScaleToFill;
} else if (style.resizeMode === ImageResizeMode.contain) {
resizeMode = contentModes.ScaleAspectFit;
var contentMode;
if (resizeMode === ImageResizeMode.stretch) {
contentMode = contentModes.ScaleToFill;
} else if (resizeMode === ImageResizeMode.contain) {
contentMode = contentModes.ScaleAspectFit;
} else { // ImageResizeMode.cover or undefined
resizeMode = contentModes.ScaleAspectFill;
contentMode = contentModes.ScaleAspectFill;
}
var nativeProps = merge(this.props, {
style,
resizeMode,
contentMode,
tintColor: style.tintColor,
});
if (Platform.OS === 'android') {
// TODO: update android native code to not need this
nativeProps.resizeMode = contentMode;
delete nativeProps.contentMode;
}
if (isStored) {
nativeProps.imageTag = source.uri;
} else {
nativeProps.src = source.uri;
}
if (this.props.defaultSource) {
nativeProps.defaultImageSrc = this.props.defaultSource.uri;
}
return <RawImage {...nativeProps} />;
}
});
@ -151,24 +180,39 @@ var styles = StyleSheet.create({
},
});
var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
accessible: true,
accessibilityLabel: true,
capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
imageTag: true,
resizeMode: true,
if (Platform.OS === 'android') {
var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
accessible: true,
accessibilityLabel: true,
capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
imageTag: true,
resizeMode: true,
src: true,
testID: PropTypes.string,
});
var RCTStaticImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
uiViewClassName: 'RCTStaticImage',
});
var RCTNetworkImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
uiViewClassName: 'RCTNetworkImageView',
});
} else {
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
}
var nativeOnlyProps = {
src: true,
testID: PropTypes.string,
});
var RCTStaticImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
uiViewClassName: 'RCTStaticImage',
});
var RCTNetworkImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
uiViewClassName: 'RCTNetworkImageView',
});
defaultImageSrc: true,
imageTag: true,
contentMode: true,
};
if (__DEV__) {
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
verifyPropTypes(Image, RCTNetworkImage.viewConfig, nativeOnlyProps);
}
module.exports = Image;

Просмотреть файл

@ -29,6 +29,6 @@ RCT_EXPORT_MODULE()
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(contentMode, UIViewContentMode)
@end

Просмотреть файл

@ -26,7 +26,7 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage)
{
if (json) {

Просмотреть файл

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule loadSourceMap
*
* -- disabled flow due to mysterious validation errors --
*/

Просмотреть файл

@ -10,22 +10,23 @@
* @flow
*/
"use strict";
'use strict';
var ImageStylePropTypes = require('ImageStylePropTypes');
var TextStylePropTypes = require('TextStylePropTypes');
var ViewStylePropTypes = require('ViewStylePropTypes');
var deepDiffer = require('deepDiffer');
var keyMirror = require('keyMirror');
var matricesDiffer = require('matricesDiffer');
var merge = require('merge');
var sizesDiffer = require('sizesDiffer');
var ReactIOSStyleAttributes = merge(
keyMirror(ViewStylePropTypes),
keyMirror(TextStylePropTypes)
);
var ReactIOSStyleAttributes = {
...keyMirror(ViewStylePropTypes),
...keyMirror(TextStylePropTypes),
...keyMirror(ImageStylePropTypes),
};
ReactIOSStyleAttributes.transformMatrix = { diff: matricesDiffer };
ReactIOSStyleAttributes.shadowOffset = { diff: deepDiffer };
ReactIOSStyleAttributes.shadowOffset = { diff: sizesDiffer };
module.exports = ReactIOSStyleAttributes;

Просмотреть файл

@ -0,0 +1,74 @@
/**
* 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.
*
* @providesModule requireNativeComponent
* @flow
*/
'use strict';
var RCTUIManager = require('NativeModules').UIManager;
var UnimplementedView = require('UnimplementedView');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var deepDiffer = require('deepDiffer');
var insetsDiffer = require('insetsDiffer');
var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var sizesDiffer = require('sizesDiffer');
var verifyPropTypes = require('verifyPropTypes');
/**
* Used to create React components that directly wrap native component
* implementations. Config information is extracted from data exported from the
* RCTUIManager module. You should also wrap the native component in a
* hand-written component with full propTypes definitions and other
* documentation - pass the hand-written component in as `wrapperComponent` to
* verify all the native props are documented via `propTypes`.
*
* If some native props shouldn't be exposed in the wrapper interface, you can
* pass null for `wrapperComponent` and call `verifyPropTypes` directly
* with `nativePropsToIgnore`;
*
* Common types are lined up with the appropriate prop differs with
* `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`.
*/
function requireNativeComponent(
viewName: string,
wrapperComponent: ?Function
): Function {
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs[viewName];
if (!viewConfig) {
return UnimplementedView;
}
var nativeProps = {
...RCTUIManager.viewConfigs.RCTView.nativeProps,
...viewConfig.nativeProps,
};
viewConfig.validAttributes = {};
for (var key in nativeProps) {
// TODO: deep diff by default in diffRawProperties instead of setting it here
var differ = TypeToDifferMap[nativeProps[key].type] || deepDiffer;
viewConfig.validAttributes[key] = {diff: differ};
}
if (__DEV__) {
wrapperComponent && verifyPropTypes(wrapperComponent, viewConfig);
}
return createReactIOSNativeComponentClass(viewConfig);
}
var TypeToDifferMap = {
// iOS Types
CATransform3D: matricesDiffer,
CGPoint: pointsDiffer,
CGSize: sizesDiffer,
UIEdgeInsets: insetsDiffer,
// Android Types
// (not yet implemented)
};
module.exports = requireNativeComponent;

Просмотреть файл

@ -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.
*
* @providesModule verifyPropTypes
* @flow
*/
'use strict';
var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes');
var View = require('View');
function verifyPropTypes(
component: Function,
viewConfig: Object,
nativePropsToIgnore?: Object
) {
if (!viewConfig) {
return; // This happens for UnimplementedView.
}
var nativeProps = viewConfig.nativeProps;
for (var prop in viewConfig.nativeProps) {
if (!component.propTypes[prop] &&
!View.propTypes[prop] &&
!ReactIOSStyleAttributes[prop] &&
(!nativePropsToIgnore || !nativePropsToIgnore[prop])) {
throw new Error(
'`' + component.displayName + '` has no propType for native prop `' +
viewConfig.uiViewClassName + '.' + prop + '` of native type `' +
nativeProps[prop].type + '`'
);
}
}
}
module.exports = verifyPropTypes;

Просмотреть файл

@ -17,6 +17,24 @@ var invariant = require('invariant');
var dimensions = NativeModules.UIManager.Dimensions;
// We calculate the window dimensions in JS so that we don't encounter loss of
// precision in transferring the dimensions (which could be non-integers) over
// the bridge.
if (dimensions.windowPhysicalPixels) {
// parse/stringify => Clone hack
dimensions = JSON.parse(JSON.stringify(dimensions));
var windowPhysicalPixels = dimensions.windowPhysicalPixels;
dimensions.window = {
width: windowPhysicalPixels.width / windowPhysicalPixels.scale,
height: windowPhysicalPixels.height / windowPhysicalPixels.scale,
scale: windowPhysicalPixels.scale,
};
// delete so no callers rely on this existing
delete dimensions.windowPhysicalPixels;
}
class Dimensions {
/**
* This should only be called from native code.

Просмотреть файл

@ -307,6 +307,25 @@ var MessageQueueMixin = {
);
},
processBatch: function (batch) {
var self = this;
batch.forEach(function (call) {
invariant(
call.module === 'BatchedBridge',
'All the calls should pass through the BatchedBridge module'
);
if (call.method === 'callFunctionReturnFlushedQueue') {
self.callFunction.apply(self, call.args);
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
self.invokeCallback.apply(self, call.args);
} else {
throw new Error(
'Unrecognized method called on BatchedBridge: ' + call.method);
}
});
return this.flushedQueue();
},
setLoggingEnabled: function(enabled) {
this._enableLogging = enabled;
this._loggedIncomingItems = [];

Просмотреть файл

@ -0,0 +1,19 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule sizesDiffer
*/
'use strict';
var dummySize = {w: undefined, h: undefined};
var sizesDiffer = function(one, two) {
one = one || dummySize;
two = two || dummySize;
return one !== two && (
one.w !== two.w ||
one.h !== two.h
);
};
module.exports = sizesDiffer;

1
Libraries/react-native/react-native.js поставляемый
Просмотреть файл

@ -59,6 +59,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
// Plugins
DeviceEventEmitter: require('RCTDeviceEventEmitter'),
NativeModules: require('NativeModules'),
requireNativeComponent: require('requireNativeComponent'),
addons: {
LinkedStateMixin: require('LinkedStateMixin'),

Просмотреть файл

@ -42,6 +42,45 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis
};
/**
* Temporarily allow to turn on and off the call batching in case someone wants
* to profile both
*/
#define BATCHED_BRIDGE 1
#ifdef DEBUG
#define RCT_PROFILE_START() \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
NSTimeInterval __rct_profile_start = CACurrentMediaTime() \
_Pragma("clang diagnostic pop")
#define RCT_PROFILE_END(cat, args, profileName...) \
do { \
if (_profile) { \
[_profileLock lock]; \
[_profile addObject:@{ \
@"name": [@[profileName] componentsJoinedByString: @"_"], \
@"cat": @ #cat, \
@"ts": @((NSUInteger)((__rct_profile_start - _startingTime) * 1e6)), \
@"dur": @((NSUInteger)((CACurrentMediaTime() - __rct_profile_start) * 1e6)), \
@"ph": @"X", \
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
@"tid": [[NSThread currentThread] description], \
@"args": args ?: [NSNull null], \
}]; \
[_profileLock unlock]; \
} \
} while(0)
#else
#define RCT_PROFILE_START(...)
#define RCT_PROFILE_END(...)
#endif
#ifdef __LP64__
typedef uint64_t RCTHeaderValue;
typedef struct section_64 RCTHeaderSection;
@ -191,10 +230,16 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
@interface RCTBridge ()
@property (nonatomic, copy, readonly) NSArray *profile;
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args;
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args;
@end
/**
@ -754,7 +799,13 @@ static NSDictionary *RCTLocalModulesConfig()
RCTBridgeModuleProviderBlock _moduleProvider;
RCTDisplayLink *_displayLink;
NSMutableSet *_frameUpdateObservers;
NSMutableArray *_scheduledCalls;
NSMutableArray *_scheduledCallbacks;
BOOL _loading;
NSUInteger _startingTime;
NSMutableArray *_profile;
NSLock *_profileLock;
}
static id<RCTJavaScriptExecutor> _latestJSExecutor;
@ -782,6 +833,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
_frameUpdateObservers = [[NSMutableSet alloc] init];
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[NSMutableArray alloc] init];
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
@ -1005,20 +1058,54 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}
/**
* Private hack to support `setTimeout(fn, 0)`
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer
{
NSString *moduleDotMethod = @"RCTJSTimers.callTimers";
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
[[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
if (!_loading) {
#if BATCHED_BRIDGE
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]];
#else
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]];
#endif
}
}
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCT_PROFILE_START();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script");
if (scriptLoadError) {
onComplete(scriptLoadError);
return;
}
RCT_PROFILE_START();
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
method:@"flushedQueue"
arguments:@[]
callback:^(id json, NSError *error) {
RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue");
RCT_PROFILE_START();
[self _handleBuffer:json];
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
onComplete(error);
}];
}];
@ -1028,11 +1115,46 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
{
#if BATCHED_BRIDGE
RCT_PROFILE_START();
if ([module isEqualToString:@"RCTEventEmitter"]) {
for (NSDictionary *call in _scheduledCalls) {
if ([call[@"module"] isEqualToString:module] && [call[@"method"] isEqualToString:method] && [call[@"args"][0] isEqualToString:args[0]]) {
[_scheduledCalls removeObject:call];
}
}
}
id call = @{
@"module": module,
@"method": method,
@"args": args,
};
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
[_scheduledCallbacks addObject:call];
} else {
[_scheduledCalls addObject:call];
}
RCT_PROFILE_END(js_call, args, @"schedule", module, method);
}
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
{
#endif
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
NSString *moduleDotMethod = [NSString stringWithFormat:@"%@.%@", module, method];
RCT_PROFILE_START();
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
RCT_PROFILE_END(js_call, args, moduleDotMethod);
RCT_PROFILE_START();
[self _handleBuffer:json];
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
};
[_javaScriptExecutor executeJSCall:module
@ -1151,12 +1273,34 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_update:(CADisplayLink *)displayLink
{
RCT_PROFILE_START();
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
[observer didUpdateFrame:frameUpdate];
}
}
[self _runScheduledCalls];
RCT_PROFILE_END(display_link, nil, @"main_thread");
}
- (void)_runScheduledCalls
{
#if BATCHED_BRIDGE
NSArray *calls = [_scheduledCallbacks arrayByAddingObjectsFromArray:_scheduledCalls];
if (calls.count > 0) {
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[NSMutableArray alloc] init];
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"processBatch"
arguments:@[calls]];
}
#endif
}
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
@ -1194,4 +1338,42 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
callback:^(id json, NSError *error) {}];
}
- (void)startProfiling
{
if (![_bundleURL.scheme isEqualToString:@"http"]) {
RCTLogError(@"To run the profiler you must be running from the dev server");
return;
}
_profileLock = [[NSLock alloc] init];
_startingTime = CACurrentMediaTime();
[_profileLock lock];
_profile = [[NSMutableArray alloc] init];
[_profileLock unlock];
}
- (void)stopProfiling
{
[_profileLock lock];
NSArray *profile = _profile;
_profile = nil;
[_profileLock unlock];
_profileLock = nil;
NSString *log = RCTJSONStringify(profile, NULL);
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port];
NSURL *URL = [NSURL URLWithString:URLString];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
URLRequest.HTTPMethod = @"POST";
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
RCTLogError(@"%@", error.localizedDescription);
}
}];
[task resume];
}
@end

Просмотреть файл

@ -14,6 +14,15 @@
#import "RCTSourceCode.h"
#import "RCTWebViewExecutor.h"
@interface RCTBridge (RCTDevMenu)
@property (nonatomic, copy, readonly) NSArray *profile;
- (void)startProfiling;
- (void)stopProfiling;
@end
@interface RCTDevMenu () <UIActionSheetDelegate>
@end
@ -37,11 +46,12 @@
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling";
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, nil];
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
}
@ -61,6 +71,12 @@
} else if (buttonIndex == 3) {
_liveReload = !_liveReload;
[self _pollAndReload];
} else if (buttonIndex == 4) {
if (_bridge.profile) {
[_bridge stopProfiling];
} else {
[_bridge startProfiling];
}
}
}

Просмотреть файл

@ -68,7 +68,6 @@
_bridge = bridge;
_moduleName = moduleName;
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bundleFinishedLoading)
@ -105,7 +104,6 @@
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_touchHandler invalidate];
if (_contentView) {
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[_contentView.reactTag]];
@ -148,7 +146,6 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
* NOTE: Since the bridge persists, the RootViews might be reused, so now
* the react tag is assigned every time we load new content.
*/
[_touchHandler invalidate];
[_contentView removeFromSuperview];
_contentView = [[UIView alloc] initWithFrame:self.bounds];
_contentView.reactTag = [_bridge.uiManager allocateRootTag];

Просмотреть файл

@ -9,14 +9,12 @@
#import <UIKit/UIKit.h>
#import "RCTInvalidating.h"
#import "RCTFrameUpdate.h"
@class RCTBridge;
@interface RCTTouchHandler : UIGestureRecognizer<RCTInvalidating>
@interface RCTTouchHandler : UIGestureRecognizer
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)startOrResetInteractionTiming;
- (NSDictionary *)endAndResetInteractionTiming;
@end

Просмотреть файл

@ -20,39 +20,6 @@
// TODO: this class behaves a lot like a module, and could be implemented as a
// module if we were to assume that modules and RootViews had a 1:1 relationship
@interface RCTTouchEvent : NSObject
@property (nonatomic, assign, readonly) NSUInteger id;
@property (nonatomic, copy, readonly) NSString *eventName;
@property (nonatomic, copy, readonly) NSArray *touches;
@property (nonatomic, copy, readonly) NSArray *changedIndexes;
@property (nonatomic, assign, readonly) CFTimeInterval originatingTime;
@end
@implementation RCTTouchEvent
+ (instancetype)touchWithEventName:(NSString *)eventName touches:(NSArray *)touches changedIndexes:(NSArray *)changedIndexes originatingTime:(CFTimeInterval)originatingTime
{
RCTTouchEvent *touchEvent = [[self alloc] init];
touchEvent->_id = [self newTaskID];
touchEvent->_eventName = [eventName copy];
touchEvent->_touches = [touches copy];
touchEvent->_changedIndexes = [changedIndexes copy];
touchEvent->_originatingTime = originatingTime;
return touchEvent;
}
+ (NSUInteger)newTaskID
{
static NSUInteger taskID = 0;
return ++taskID;
}
@end
@implementation RCTTouchHandler
{
__weak RCTBridge *_bridge;
@ -69,7 +36,6 @@
BOOL _recordingInteractionTiming;
CFTimeInterval _mostRecentEnqueueJS;
CADisplayLink *_displayLink;
NSMutableArray *_pendingTouches;
NSMutableArray *_bridgeInteractionTiming;
}
@ -86,12 +52,9 @@
_reactTouches = [[NSMutableArray alloc] init];
_touchViews = [[NSMutableArray alloc] init];
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
_pendingTouches = [[NSMutableArray alloc] init];
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
// level components not build using RCT, will fail to recognize gestures.
self.cancelsTouchesInView = NO;
@ -99,17 +62,6 @@
return self;
}
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
[_displayLink invalidate];
_displayLink = nil;
}
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
RCTTouchEventTypeStart,
RCTTouchEventTypeMove,
@ -216,7 +168,6 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime
{
// Update touches
CFTimeInterval enqueueTime = CACurrentMediaTime();
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
for (UITouch *touch in touches) {
NSInteger index = [_nativeTouches indexOfObject:touch];
@ -239,71 +190,8 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
[reactTouches addObject:[touch copy]];
}
RCTTouchEvent *touch = [RCTTouchEvent touchWithEventName:eventName
touches:reactTouches
changedIndexes:changedIndexes
originatingTime:originatingTime];
[_pendingTouches addObject:touch];
if (_recordingInteractionTiming) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(touch.originatingTime),
@"operation": @"taskOriginated",
@"taskID": @(touch.id),
}];
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(enqueueTime),
@"operation": @"taskEnqueuedPending",
@"taskID": @(touch.id),
}];
}
}
- (void)_update:(CADisplayLink *)sender
{
// Dispatch touch event
NSUInteger pendingCount = _pendingTouches.count;
for (RCTTouchEvent *touch in _pendingTouches) {
_mostRecentEnqueueJS = CACurrentMediaTime();
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
args:@[touch.eventName, touch.touches, touch.changedIndexes]];
}
if (_recordingInteractionTiming) {
for (RCTTouchEvent *touch in _pendingTouches) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(sender.timestamp),
@"operation": @"frameAlignedDispatch",
@"taskID": @(touch.id),
}];
}
if (pendingCount > 0 || sender.timestamp - _mostRecentEnqueueJS < 0.1) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(sender.timestamp),
@"operation": @"mainThreadDisplayLink",
@"taskID": @([RCTTouchEvent newTaskID]),
}];
}
}
[_pendingTouches removeAllObjects];
}
- (void)startOrResetInteractionTiming
{
RCTAssertMainThread();
[_bridgeInteractionTiming removeAllObjects];
_recordingInteractionTiming = YES;
}
- (NSDictionary *)endAndResetInteractionTiming
{
RCTAssertMainThread();
_recordingInteractionTiming = NO;
NSArray *_prevInteractionTimingData = _bridgeInteractionTiming;
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
return @{ @"interactionTiming": _prevInteractionTimingData };
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
args:@[eventName, reactTouches, changedIndexes]];
}
#pragma mark - Gesture Recognizer Delegate Callbacks

Просмотреть файл

@ -218,7 +218,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
[self invalidate];
}
- (void)executeJSCall:(NSString *)name

Просмотреть файл

@ -15,6 +15,15 @@
#import "RCTSparseArray.h"
#import "RCTUtils.h"
@interface RCTBridge (Private)
/**
* Allow super fast, one time, timers to skip the queue and be directly executed
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer;
@end
@interface RCTTimer : NSObject
@property (nonatomic, strong, readonly) NSDate *target;
@ -160,7 +169,7 @@ RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID
{
if (jsDuration == 0 && repeats == NO) {
// For super fast, one-off timers, just enqueue them immediately rather than waiting a frame.
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[@[callbackID]]];
[_bridge _immediatelyCallTimer:callbackID];
return;
}

Просмотреть файл

@ -193,6 +193,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
NSMutableDictionary *_defaultShadowViews; // RCT thread only
NSMutableDictionary *_defaultViews; // Main thread only
NSDictionary *_viewManagers;
NSDictionary *_viewConfigs;
NSUInteger _rootTag;
}
@ -219,6 +220,28 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
return name;
}
static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewName)
{
NSMutableDictionary *nativeProps = [[NSMutableDictionary alloc] init];
static const char *prefix = "getPropConfig";
static const NSUInteger prefixLength = sizeof("getPropConfig") - 1;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(objc_getMetaClass(class_getName(managerClass)), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL getInfo = method_getName(method);
const char *selName = sel_getName(getInfo);
if (strlen(selName) > prefixLength && strncmp(selName, prefix, prefixLength) == 0) {
NSDictionary *info = ((NSDictionary *(*)(id, SEL))method_getImplementation(method))(managerClass, getInfo);
nativeProps[info[@"name"]] = info;
}
}
return @{
@"uiViewClassName": viewName,
@"nativeProps": nativeProps
};
}
/**
* This private constructor should only be called when creating
* isolated UIImanager instances for testing. Normal initialization
@ -292,13 +315,17 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
// Get view managers from bridge
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
NSMutableDictionary *viewConfigs = [[NSMutableDictionary alloc] init];
[_bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, RCTViewManager *manager, BOOL *stop) {
if ([manager isKindOfClass:[RCTViewManager class]]) {
viewManagers[RCTViewNameForModuleName(moduleName)] = manager;
NSString *viewName = RCTViewNameForModuleName(moduleName);
viewManagers[viewName] = manager;
viewConfigs[viewName] = RCTViewConfigForModule([manager class], viewName);
}
}];
_viewManagers = [viewManagers copy];
_viewConfigs = [viewConfigs copy];
}
- (void)registerRootView:(UIView *)rootView;
@ -650,14 +677,12 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
// TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient
// Figure out what to insert - merge temporary inserts and adds
NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
for (NSInteger index = 0; index < temporarilyRemovedChildren.count; index++) {
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
}
for (NSInteger index = 0; index < addAtIndices.count; index++) {
for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
id view = registry[addChildReactTags[index]];
if (view) {
destinationsToChildrenToAdd[addAtIndices[index]] = view;
@ -1374,7 +1399,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
allJSConstants[name] = [constantsNamespace copy];
}
}];
allJSConstants[@"viewConfigs"] = _viewConfigs;
return allJSConstants;
}
@ -1393,41 +1418,6 @@ RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config
callback:callback];
}
RCT_EXPORT_METHOD(startOrResetInteractionTiming)
{
NSSet *rootViewTags = [_rootViewTags copy];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (NSNumber *reactTag in rootViewTags) {
UIView *rootView = viewRegistry[reactTag];
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
[handler startOrResetInteractionTiming];
break;
}
}
}
}];
}
RCT_EXPORT_METHOD(endAndResetInteractionTiming:(RCTResponseSenderBlock)onSuccess
onError:(RCTResponseSenderBlock)onError)
{
NSSet *rootViewTags = [_rootViewTags copy];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
NSMutableDictionary *timingData = [[NSMutableDictionary alloc] init];
for (NSNumber *reactTag in rootViewTags) {
UIView *rootView = viewRegistry[reactTag];
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
[handler endAndResetInteractionTiming];
break;
}
}
}
onSuccess(@[timingData]);
}];
}
static UIView *_jsResponder;
+ (UIView *)JSResponder

Просмотреть файл

@ -47,27 +47,27 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
[_regionChangeObserveTimer invalidate];
}
- (void)reactSetFrame:(CGRect)frame
{
self.frame = frame;
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Force resize subviews - only the layer is resized by default
CGRect mapFrame = self.frame;
self.frame = CGRectZero;
self.frame = mapFrame;
if (_legalLabel) {
dispatch_async(dispatch_get_main_queue(), ^{
CGRect frame = _legalLabel.frame;
if (_legalLabelInsets.left) {
frame.origin.x = _legalLabelInsets.left;
} else if (_legalLabelInsets.right) {
frame.origin.x = mapFrame.size.width - _legalLabelInsets.right - frame.size.width;
frame.origin.x = self.frame.size.width - _legalLabelInsets.right - frame.size.width;
}
if (_legalLabelInsets.top) {
frame.origin.y = _legalLabelInsets.top;
} else if (_legalLabelInsets.bottom) {
frame.origin.y = mapFrame.size.height - _legalLabelInsets.bottom - frame.size.height;
frame.origin.y = self.frame.size.height - _legalLabelInsets.bottom - frame.size.height;
}
_legalLabel.frame = frame;
});
@ -93,7 +93,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
}
}
- (void)setRegion:(MKCoordinateRegion)region
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
{
// If location is invalid, abort
if (!CLLocationCoordinate2DIsValid(region.center)) {
@ -109,7 +109,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
}
// Animate to new position
[super setRegion:region animated:YES];
[super setRegion:region animated:animated];
}
- (void)setAnnotations:(MKShapeArray *)annotations

Просмотреть файл

@ -41,9 +41,11 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(maxDelta, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
RCT_EXPORT_VIEW_PROPERTY(annotations, MKShapeArray)
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
{
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}
#pragma mark MKMapViewDelegate

Просмотреть файл

@ -109,6 +109,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* within the view or shadowView.
*/
#define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \
RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
@ -117,6 +118,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
}
#define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \
RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forShadowView:(id)view withDefaultView:(id)defaultView { \
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
@ -130,9 +132,11 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* refer to "json", "view" and "defaultView" to implement the required logic.
*/
#define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \
RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView
#define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \
RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView
/**
@ -160,4 +164,17 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
[self set_##newName:json forView:view withDefaultView:defaultView]; \
}
/**
* PROP_CONFIG macros should only be paired with property setters.
*/
#define RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
+ (NSDictionary *)getPropConfigView_##name { \
return @{@"name": @#name, @"type": @#type}; \
}
#define RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
+ (NSDictionary *)getPropConfigShadow_##name { \
return @{@"name": @#name, @"type": @#type}; \
}
@end

Просмотреть файл

@ -61,7 +61,6 @@ RCT_EXPORT_MODULE()
#pragma mark - View properties
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel, NSString)
RCT_EXPORT_VIEW_PROPERTY(hidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement, BOOL)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier, NSString)

Просмотреть файл

@ -0,0 +1,85 @@
'use strict';
jest
.autoMockOff()
.mock('../../lib/declareOpts')
.mock('fs');
var fs = require('fs');
var AssetServer = require('../');
var Promise = require('bluebird');
describe('AssetServer', function() {
pit('should work for the simple case', function() {
var server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.png': 'b image',
'b@2x.png': 'b2 image',
}
}
});
return Promise.all([
server.get('imgs/b.png'),
server.get('imgs/b@1x.png'),
]).then(function(resp) {
resp.forEach(function(data) {
expect(data).toBe('b image');
});
});
});
pit.only('should pick the bigger one', function() {
var server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
'b@4x.png': 'b4 image',
'b@4.5x.png': 'b4.5 image',
}
}
});
return server.get('imgs/b@3x.png').then(function(data) {
expect(data).toBe('b4 image');
});
});
pit('should support multiple project roots', function() {
var server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.png': 'b image',
},
'root2': {
'newImages': {
'imgs': {
'b@1x.png': 'b1 image',
},
},
},
}
});
return server.get('newImages/imgs/b.png').then(function(data) {
expect(data).toBe('b1 image');
});
});
});

132
packager/react-packager/src/AssetServer/index.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,132 @@
/**
* 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 declareOpts = require('../lib/declareOpts');
var extractAssetResolution = require('../lib/extractAssetResolution');
var path = require('path');
var Promise = require('bluebird');
var fs = require('fs');
var lstat = Promise.promisify(fs.lstat);
var readDir = Promise.promisify(fs.readdir);
var readFile = Promise.promisify(fs.readFile);
module.exports = AssetServer;
var validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
assetExts: {
type: 'array',
default: ['png'],
},
});
function AssetServer(options) {
var opts = validateOpts(options);
this._roots = opts.projectRoots;
this._assetExts = opts.assetExts;
}
/**
* Given a request for an image by path. That could contain a resolution
* postfix, we need to find that image (or the closest one to it's resolution)
* in one of the project roots:
*
* 1. We first parse the directory of the asset
* 2. We check to find a matching directory in one of the project roots
* 3. We then build a map of all assets and their resolutions in this directory
* 4. Then pick the closest resolution (rounding up) to the requested one
*/
AssetServer.prototype.get = function(assetPath) {
var filename = path.basename(assetPath);
return findRoot(
this._roots,
path.dirname(assetPath)
).then(function(dir) {
return [
dir,
readDir(dir),
];
}).spread(function(dir, files) {
// Easy case. File exactly what the client requested.
var index = files.indexOf(filename);
if (index > -1) {
return readFile(path.join(dir, filename));
}
var assetData = extractAssetResolution(filename);
var map = buildAssetMap(dir, files);
var record = map[assetData.assetName];
if (!record) {
throw new Error('Asset not found');
}
for (var i = 0; i < record.resolutions.length; i++) {
if (record.resolutions[i] >= assetData.resolution) {
return readFile(record.files[i]);
}
}
return readFile(record.files[record.files.length - 1]);
});
};
function findRoot(roots, dir) {
return Promise.some(
roots.map(function(root) {
var absPath = path.join(root, dir);
return lstat(absPath).then(function(stat) {
if (!stat.isDirectory()) {
throw new Error('Looking for dirs');
}
stat._path = absPath;
return stat;
});
}),
1
).spread(
function(stat) {
return stat._path;
}
);
}
function buildAssetMap(dir, files) {
var assets = files.map(extractAssetResolution);
var map = Object.create(null);
assets.forEach(function(asset, i) {
var file = files[i];
var record = map[asset.assetName];
if (!record) {
record = map[asset.assetName] = {
resolutions: [],
files: [],
};
}
var insertIndex;
var length = record.resolutions.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.resolutions[insertIndex]) {
break;
}
}
record.resolutions.splice(insertIndex, 0, asset.resolution);
record.files.splice(insertIndex, 0, path.join(dir, file));
});
return map;
}

Просмотреть файл

@ -14,6 +14,7 @@ jest
.dontMock('absolute-path')
.dontMock('../docblock')
.dontMock('../../replacePatterns')
.dontMock('../../../../lib/extractAssetResolution')
.setMock('../../../ModuleDescriptor', function(data) {return data;});
describe('DependencyGraph', function() {

Просмотреть файл

@ -18,6 +18,7 @@ var isAbsolutePath = require('absolute-path');
var debug = require('debug')('DependecyGraph');
var util = require('util');
var declareOpts = require('../../../lib/declareOpts');
var extractAssetResolution = require('../../../lib/extractAssetResolution');
var readFile = Promise.promisify(fs.readFile);
var readDir = Promise.promisify(fs.readdir);
@ -421,7 +422,7 @@ DependecyGraph.prototype._processModule = function(modulePath) {
var module;
if (this._assetExts.indexOf(extname(modulePath)) > -1) {
var assetData = extractResolutionPostfix(this._lookupName(modulePath));
var assetData = extractAssetResolution(this._lookupName(modulePath));
moduleData.id = assetData.assetName;
moduleData.resolution = assetData.resolution;
moduleData.isAsset = true;
@ -772,28 +773,6 @@ function extname(name) {
return path.extname(name).replace(/^\./, '');
}
function extractResolutionPostfix(filename) {
var ext = extname(filename);
var re = new RegExp('@([\\d\\.]+)x\\.' + ext + '$');
var match = filename.match(re);
var resolution;
if (!(match && match[1])) {
resolution = 1;
} else {
resolution = parseFloat(match[1], 10);
if (isNaN(resolution)) {
resolution = 1;
}
}
return {
resolution: resolution,
assetName: match ? filename.replace(re, '.' + ext) : filename,
};
}
function NotFoundError() {
Error.call(this);
Error.captureStackTrace(this, this.constructor);

Просмотреть файл

@ -23,7 +23,7 @@
get: function() {
console.error(
'Your code is broken! Do not iterate over arrays with ' +
'for...in. See https://fburl.com/31944000 for more information.'
'for...in.'
);
}
}
@ -49,9 +49,6 @@
* - Use a regular for loop with index.
* - Use one of the array methods: a.forEach, a.map, etc.
* - Guard your body of your loop with a `arr.hasOwnProperty(key)` check.
*
* More info:
* https://our.intern.facebook.com/intern/dex/qa/669736606441771/
*/
if (this == null) {
throw new TypeError(
@ -92,9 +89,6 @@
* - Use a regular for loop with index.
* - Use one of the array methods: a.forEach, a.map, etc.
* - Guard your body of your loop with a `arr.hasOwnProperty(key)` check.
*
* More info:
* https://our.intern.facebook.com/intern/dex/qa/669736606441771/
*/
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');

Просмотреть файл

@ -111,7 +111,7 @@ describe('Packager', function() {
var imgModule = {
isStatic: true,
path: '/root/img/new_image.png',
uri: 'img/new_image.png',
uri: 'assets/img/new_image.png',
width: 25,
height: 50,
};

Просмотреть файл

@ -194,7 +194,7 @@ function generateAssetModule(module, relPath) {
var img = {
isStatic: true,
path: module.path, //TODO(amasad): this should be path inside tar file.
uri: relPath,
uri: path.join('assets', relPath),
width: dimensions.width / module.resolution,
height: dimensions.height / module.resolution,
};

Просмотреть файл

@ -229,4 +229,32 @@ describe('processRequest', function() {
expect(res.end).not.toBeCalled();
});
});
describe.only('/assets endpoint', function() {
var AssetServer;
beforeEach(function() {
AssetServer = require('../../AssetServer');
});
it('should serve simple case', function() {
var req = {
url: '/assets/imgs/a.png',
};
var res = {
end: jest.genMockFn(),
};
AssetServer.prototype.get.mockImpl(function() {
return Promise.resolve('i am image');
});
server.processRequest(req, res);
jest.runAllTimers();
expect(res.end).toBeCalledWith('i am image');
});
it('should return 404', function() {
});
});
});

66
packager/react-packager/src/Server/index.js поставляемый
Просмотреть файл

@ -14,8 +14,11 @@ var declareOpts = require('../lib/declareOpts');
var FileWatcher = require('../FileWatcher');
var Packager = require('../Packager');
var Activity = require('../Activity');
var AssetServer = require('../AssetServer');
var Promise = require('bluebird');
var _ = require('underscore');
var exec = require('child_process').exec;
var fs = require('fs');
module.exports = Server;
@ -99,6 +102,11 @@ function Server(options) {
packagerOpts.fileWatcher = this._fileWatcher;
this._packager = new Packager(packagerOpts);
this._assetServer = new AssetServer({
projectRoots: opts.projectRoots,
assetExts: opts.assetExts,
});
var onFileChange = this._onFileChange.bind(this);
this._fileWatcher.on('all', onFileChange);
@ -230,6 +238,58 @@ Server.prototype._processOnChangeRequest = function(req, res) {
});
};
Server.prototype._processAssetsRequest = function(req, res) {
var urlObj = url.parse(req.url, true);
var assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
this._assetServer.get(assetPath[1])
.then(
function(data) {
res.end(data);
},
function(error) {
console.error(error.stack);
res.writeHead('404');
res.end('Asset not found');
}
).done();
};
Server.prototype._processProfile = function(req, res) {
console.log('Dumping profile information...');
var dumpName = '/tmp/dump_' + Date.now() + '.json';
var prefix = process.env.TRACE_VIEWER_PATH || '';
var cmd = path.join(prefix, 'trace2html') + ' ' + dumpName;
fs.writeFileSync(dumpName, req.rawBody);
exec(cmd, function (error) {
if (error) {
if (error.code === 127) {
console.error(
'\n** Failed executing `' + cmd + '` **\n\n' +
'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' +
'You can get it at:\n\n' +
' https://github.com/google/trace-viewer\n\n' +
'If it\'s not in your path, you can set a custom path with:\n\n' +
' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' +
'NOTE: Your profile data was kept at:\n\n' +
' ' + dumpName
);
} else {
console.error('Unknown error', error);
}
res.end();
return;
} else {
exec('rm ' + dumpName);
exec('open ' + dumpName.replace(/json$/, 'html'), function (error) {
if (error) {
console.error(error);
}
res.end();
});
}
});
};
Server.prototype.processRequest = function(req, res, next) {
var urlObj = url.parse(req.url, true);
var pathname = urlObj.pathname;
@ -245,6 +305,12 @@ Server.prototype.processRequest = function(req, res, next) {
} else if (pathname.match(/^\/onchange\/?$/)) {
this._processOnChangeRequest(req, res);
return;
} else if (pathname.match(/^\/assets\//)) {
this._processAssetsRequest(req, res);
return;
} else if (pathname.match(/^\/profile\/?$/)) {
this._processProfile(req, res);
return;
} else {
next();
return;

Просмотреть файл

@ -42,6 +42,11 @@ fs.readdir.mockImpl(function(filepath, callback) {
});
fs.readFile.mockImpl(function(filepath, encoding, callback) {
if (arguments.length === 2) {
callback = encoding;
encoding = null;
}
try {
var node = getToNode(filepath);
// dir check

Просмотреть файл

@ -0,0 +1,42 @@
'use strict';
jest.autoMockOff();
var extractAssetResolution = require('../extractAssetResolution');
describe('extractAssetResolution', function() {
it('should extract resolution simple case', function() {
var data = extractAssetResolution('test@2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 2,
});
});
it('should default resolution to 1', function() {
var data = extractAssetResolution('test.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1,
});
});
it('should support float', function() {
var data = extractAssetResolution('test@1.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1.1,
});
data = extractAssetResolution('test@.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.1,
});
data = extractAssetResolution('test@0.2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.2,
});
});
});

28
packager/react-packager/src/lib/extractAssetResolution.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,28 @@
'use strict';
var path = require('path');
function extractAssetResolution(filename) {
var ext = path.extname(filename);
var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$');
var match = filename.match(re);
var resolution;
if (!(match && match[1])) {
resolution = 1;
} else {
resolution = parseFloat(match[1], 10);
if (isNaN(resolution)) {
resolution = 1;
}
}
return {
resolution: resolution,
assetName: match ? filename.replace(re, ext) : filename,
};
}
module.exports = extractAssetResolution;