Updates from Tue 8 Sep

This commit is contained in:
Christopher Chedeau 2015-09-08 16:54:44 -07:00
commit a9607901e2
93 changed files with 4018 additions and 772 deletions

View File

@ -36,7 +36,7 @@
* on the same Wi-Fi network. * on the same Wi-Fi network.
*/ */
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios"]; jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios&dev=true"];
/** /**
* OPTION 2 * OPTION 2

View File

@ -216,7 +216,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = { 83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0610; LastUpgradeCheck = 0700;
ORGANIZATIONNAME = Facebook; ORGANIZATIONNAME = Facebook;
}; };
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Movies" */; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Movies" */;
@ -358,6 +358,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = Movies; PRODUCT_NAME = Movies;
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**"; USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**";
}; };
@ -376,6 +377,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = Movies; PRODUCT_NAME = Movies;
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**"; USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**";
}; };
@ -401,6 +403,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0610" LastUpgradeVersion = "0700"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -38,6 +38,8 @@
ReferencedContainer = "container:Movies.xcodeproj"> ReferencedContainer = "container:Movies.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
@ -47,8 +49,10 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable> <BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
@ -66,7 +70,8 @@
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "Release" buildConfiguration = "Release"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES">
<BuildableProductRunnable> <BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"

View File

@ -37,7 +37,7 @@
* on the same Wi-Fi network. * on the same Wi-Fi network.
*/ */
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle"]; jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.ios.bundle?platform=ios&dev=true"];
/** /**
* OPTION 2 * OPTION 2

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.facebook.$(PRODUCT_NAME:rfc1034identifier)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
@ -33,6 +33,11 @@
<string>1</string> <string>1</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
@ -47,11 +52,5 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>NSAppTransportSecurity</key>
<dict>
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict> </dict>
</plist> </plist>

View File

@ -22,7 +22,9 @@ var {
var MAX_VALUE = 200; var MAX_VALUE = 200;
function getStyleFromScore(score: number): {color: string} { import type { StyleObj } from 'StyleSheetTypes';
function getStyleFromScore(score: number): StyleObj {
if (score < 0) { if (score < 0) {
return styles.noScore; return styles.noScore;
} }

View File

@ -31,7 +31,7 @@
* on the same Wi-Fi network. * on the same Wi-Fi network.
*/ */
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle"]; jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle?platform=ios&dev=true"];
/** /**
* OPTION 2 * OPTION 2

View File

@ -36,7 +36,7 @@
* on the same Wi-Fi network. * on the same Wi-Fi network.
*/ */
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios"]; jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios&dev=true"];
/** /**
* OPTION 2 * OPTION 2

View File

@ -0,0 +1,230 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react-native');
var {
Animated,
Easing,
StyleSheet,
Text,
View,
} = React;
var UIExplorerButton = require('./UIExplorerButton');
exports.framework = 'React';
exports.title = 'Animated - Examples';
exports.description = 'Animated provides a powerful ' +
'and easy-to-use API for building modern, ' +
'interactive user experiences.';
exports.examples = [
{
title: 'FadeInView',
description: 'Uses a simple timing animation to ' +
'bring opacity from 0 to 1 when the component ' +
'mounts.',
render: function() {
class FadeInView extends React.Component {
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), // opacity 0
};
}
componentDidMount() {
Animated.timing( // Uses easing functions
this.state.fadeAnim, // The value to drive
{
toValue: 1, // Target
duration: 2000, // Configuration
},
).start(); // Don't forget start!
}
render() {
return (
<Animated.View // Special animatable View
style={{
opacity: this.state.fadeAnim, // Binds
}}>
{this.props.children}
</Animated.View>
);
}
}
class FadeInExample extends React.Component {
constructor(props) {
super(props);
this.state = {
show: true,
};
}
render() {
return (
<View>
<UIExplorerButton onPress={() => {
this.setState((state) => (
{show: !state.show}
));
}}>
Press to {this.state.show ?
'Hide' : 'Show'}
</UIExplorerButton>
{this.state.show && <FadeInView>
<View style={styles.content}>
<Text>FadeInView</Text>
</View>
</FadeInView>}
</View>
);
}
}
return <FadeInExample />;
},
},
{
title: 'Transform Bounce',
description: 'One `Animated.Value` is driven by a ' +
'spring with custom constants and mapped to an ' +
'ordered set of transforms. Each transform has ' +
'an interpolation to convert the value into the ' +
'right range and units.',
render: function() {
this.anim = this.anim || new Animated.Value(0);
return (
<View>
<UIExplorerButton onPress={() => {
Animated.spring(this.anim, {
toValue: 0, // Returns to the start
velocity: 3, // Velocity makes it move
tension: -10, // Slow
friction: 1, // Oscillate a lot
}).start(); }}>
Press to Fling it!
</UIExplorerButton>
<Animated.View
style={[styles.content, {
transform: [ // Array order matters
{scale: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 4],
})},
{translateX: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 500],
})},
{rotate: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [
'0deg', '360deg' // 'deg' or 'rad'
],
})},
]}
]}>
<Text>Transforms!</Text>
</Animated.View>
</View>
);
},
},
{
title: 'Composite Animations with Easing',
description: 'Sequence, parallel, delay, and ' +
'stagger with different easing functions.',
render: function() {
this.anims = this.anims || [1,2,3].map(
() => new Animated.Value(0)
);
return (
<View>
<UIExplorerButton onPress={() => {
var timing = Animated.timing;
Animated.sequence([ // One after the other
timing(this.anims[0], {
toValue: 200,
easing: Easing.linear,
}),
Animated.delay(400), // Use with sequence
timing(this.anims[0], {
toValue: 0,
easing: Easing.elastic(2), // Springy
}),
Animated.delay(400),
Animated.stagger(200,
this.anims.map((anim) => timing(
anim, {toValue: 200}
)).concat(
this.anims.map((anim) => timing(
anim, {toValue: 0}
))),
),
Animated.delay(400),
Animated.parallel([
Easing.inOut(Easing.quad), // Symmetric
Easing.back(1.5), // Goes backwards first
Easing.ease // Default bezier
].map((easing, ii) => (
timing(this.anims[ii], {
toValue: 320, easing, duration: 3000,
})
))),
Animated.delay(400),
Animated.stagger(200,
this.anims.map((anim) => timing(anim, {
toValue: 0,
easing: Easing.bounce, // Like a ball
duration: 2000,
})),
),
]).start(); }}>
Press to Animate
</UIExplorerButton>
{['Composite', 'Easing', 'Animations!'].map(
(text, ii) => (
<Animated.View
style={[styles.content, {
left: this.anims[ii]
}]}>
<Text>{text}</Text>
</Animated.View>
)
)}
</View>
);
},
},
{
title: 'Continuous Interactions',
description: 'Gesture events, chaining, 2D ' +
'values, interrupting and transitioning ' +
'animations, etc.',
render: () => (
<Text>Checkout the Gratuitous Animation App!</Text>
),
}
];
var styles = StyleSheet.create({
content: {
backgroundColor: 'deepskyblue',
borderWidth: 1,
borderColor: 'dodgerblue',
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
},
});

View File

@ -325,6 +325,18 @@ exports.examples = [
); );
}, },
}, },
{
title: 'Animated GIF',
render: function() {
return (
<Image
style={styles.gif}
source={{uri: 'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'}}
/>
);
},
platform: 'ios',
},
{ {
title: 'Cap Insets', title: 'Cap Insets',
description: description:
@ -384,5 +396,9 @@ var styles = StyleSheet.create({
}, },
horizontal: { horizontal: {
flexDirection: 'row', flexDirection: 'row',
} },
gif: {
flex: 1,
height: 200,
},
}); });

View File

@ -18,27 +18,12 @@
var React = require('react-native'); var React = require('react-native');
var { var {
AlertIOS, AlertIOS,
StyleSheet,
Text, Text,
TouchableHighlight, TouchableHighlight,
View, View,
} = React; } = React;
var TimerMixin = require('react-timer-mixin'); var TimerMixin = require('react-timer-mixin');
var UIExplorerButton = require('./UIExplorerButton');
var Button = React.createClass({
render: function() {
return (
<TouchableHighlight
onPress={this.props.onPress}
style={styles.button}
underlayColor="#eeeeee">
<Text>
{this.props.children}
</Text>
</TouchableHighlight>
);
},
});
var TimerTester = React.createClass({ var TimerTester = React.createClass({
mixins: [TimerMixin], mixins: [TimerMixin],
@ -52,9 +37,9 @@ var TimerTester = React.createClass({
render: function() { render: function() {
var args = 'fn' + (this.props.dt !== undefined ? ', ' + this.props.dt : ''); var args = 'fn' + (this.props.dt !== undefined ? ', ' + this.props.dt : '');
return ( return (
<Button onPress={this._run}> <UIExplorerButton onPress={this._run}>
Measure: {this.props.type}({args}) - {this._ii || 0} Measure: {this.props.type}({args}) - {this._ii || 0}
</Button> </UIExplorerButton>
); );
}, },
@ -112,18 +97,6 @@ var TimerTester = React.createClass({
}, },
}); });
var styles = StyleSheet.create({
button: {
borderColor: 'gray',
borderRadius: 8,
borderWidth: 1,
padding: 10,
margin: 5,
alignItems: 'center',
justifyContent: 'center',
},
});
exports.framework = 'React'; exports.framework = 'React';
exports.title = 'Timers, TimerMixin'; exports.title = 'Timers, TimerMixin';
exports.description = 'The TimerMixin provides timer functions for executing ' + exports.description = 'The TimerMixin provides timer functions for executing ' +
@ -183,9 +156,9 @@ exports.examples = [
if (this.state.showTimer) { if (this.state.showTimer) {
var timer = [ var timer = [
<TimerTester ref="interval" dt={25} type="setInterval" />, <TimerTester ref="interval" dt={25} type="setInterval" />,
<Button onPress={() => this.refs.interval.clear() }> <UIExplorerButton onPress={() => this.refs.interval.clear() }>
Clear interval Clear interval
</Button> </UIExplorerButton>
]; ];
var toggleText = 'Unmount timer'; var toggleText = 'Unmount timer';
} else { } else {
@ -195,9 +168,9 @@ exports.examples = [
return ( return (
<View> <View>
{timer} {timer}
<Button onPress={this._toggleTimer}> <UIExplorerButton onPress={this._toggleTimer}>
{toggleText} {toggleText}
</Button> </UIExplorerButton>
</View> </View>
); );
}, },

View File

@ -15,6 +15,7 @@
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; }; 138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; };
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; }; 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */; };
1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; }; 1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; };
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; }; 139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
@ -96,6 +97,13 @@
remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTGeolocation; remoteInfo = RCTGeolocation;
}; };
138DEE081B9EDDDB007F4EA5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
remoteInfo = RCTImage;
};
139FDED81B0651EA00C62182 /* PBXContainerItemProxy */ = { 139FDED81B0651EA00C62182 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */; containerPortal = 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */;
@ -171,6 +179,7 @@
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; }; 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = "<group>"; }; 138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = "<group>"; };
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; }; 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = ../../Libraries/CameraRoll/RCTCameraRoll.xcodeproj; sourceTree = "<group>"; };
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; }; 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; }; 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -254,6 +263,7 @@
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */, 14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */,
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */, 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */,
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */, 134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */,
138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */,
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */, 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */,
13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */, 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */,
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */, 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */,
@ -281,13 +291,14 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
14AADEFF1AC3DB95002390C9 /* React.xcodeproj */, 14AADEFF1AC3DB95002390C9 /* React.xcodeproj */,
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */,
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */, 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */,
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */, 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */,
138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */,
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
13417FE31AA91428003F314A /* RCTImage.xcodeproj */, 13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */, 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */,
134180261AA91779003F314A /* RCTNetwork.xcodeproj */, 134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */,
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */, 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */,
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */, 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */,
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
@ -337,6 +348,14 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
138DEE031B9EDDDB007F4EA5 /* Products */ = {
isa = PBXGroup;
children = (
138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */,
);
name = Products;
sourceTree = "<group>";
};
139FDECB1B0651EA00C62182 /* Products */ = { 139FDECB1B0651EA00C62182 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -624,6 +643,10 @@
ProductGroup = 134454561AAFCAAE003F0779 /* Products */; ProductGroup = 134454561AAFCAAE003F0779 /* Products */;
ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */; ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */;
}, },
{
ProductGroup = 138DEE031B9EDDDB007F4EA5 /* Products */;
ProjectRef = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
},
{ {
ProductGroup = 134A8A211AACED6A00945AAE /* Products */; ProductGroup = 134A8A211AACED6A00945AAE /* Products */;
ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
@ -714,6 +737,13 @@
remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */; remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR; sourceTree = BUILT_PRODUCTS_DIR;
}; };
138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTCameraRoll.a;
remoteRef = 138DEE081B9EDDDB007F4EA5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
139FDED91B0651EA00C62182 /* libRCTWebSocket.a */ = { 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */ = {
isa = PBXReferenceProxy; isa = PBXReferenceProxy;
fileType = archive.ar; fileType = archive.ar;

View File

@ -59,7 +59,7 @@
* on the same Wi-Fi network. * on the same Wi-Fi network.
*/ */
sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?dev=true"]; sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?platform=ios&dev=true"];
/** /**
* OPTION 2 * OPTION 2

View File

@ -0,0 +1,56 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react-native');
var {
StyleSheet,
Text,
TouchableHighlight,
} = React;
var UIExplorerButton = React.createClass({
propTypes: {
onPress: React.PropTypes.func,
},
render: function() {
return (
<TouchableHighlight
onPress={this.props.onPress}
style={styles.button}
underlayColor="grey">
<Text>
{this.props.children}
</Text>
</TouchableHighlight>
);
},
});
var styles = StyleSheet.create({
button: {
borderColor: 'dimgray',
borderRadius: 8,
borderWidth: 1,
padding: 10,
margin: 5,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'lightgrey',
},
});
module.exports = UIExplorerButton;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -14,11 +14,17 @@
#import "RCTAssert.h" #import "RCTAssert.h"
@interface IntegrationTests : XCTestCase #define RCT_TEST(name) \
- (void)test##name \
{ \
[_runner runTest:_cmd module:@#name]; \
}
@interface UIExplorerIntegrationTests : XCTestCase
@end @end
@implementation IntegrationTests @implementation UIExplorerIntegrationTests
{ {
RCTTestRunner *_runner; RCTTestRunner *_runner;
} }
@ -36,11 +42,6 @@
#pragma mark Logic Tests #pragma mark Logic Tests
- (void)testTheTester
{
[_runner runTest:_cmd module:@"IntegrationTestHarnessTest"];
}
- (void)testTheTester_waitOneFrame - (void)testTheTester_waitOneFrame
{ {
[_runner runTest:_cmd [_runner runTest:_cmd
@ -57,38 +58,12 @@
expectErrorRegex:@"because shouldThrow"]; expectErrorRegex:@"because shouldThrow"];
} }
- (void)testTimers RCT_TEST(TimersTest)
{ RCT_TEST(IntegrationTestHarnessTest)
[_runner runTest:_cmd module:@"TimersTest"]; RCT_TEST(AsyncStorageTest)
} // RCT_TEST(LayoutEventsTest) -- Disabled: #8153468
RCT_TEST(AppEventsTest)
- (void)testAsyncStorage RCT_TEST(PromiseTest)
{ // RCT_TEST(SimpleSnapshotTest) -- Disabled: #8153475
[_runner runTest:_cmd module:@"AsyncStorageTest"];
}
- (void)DISABLED_testLayoutEvents // #7149037
{
[_runner runTest:_cmd module:@"LayoutEventsTest"];
}
- (void)testAppEvents
{
[_runner runTest:_cmd module:@"AppEventsTest"];
}
- (void)testPromises
{
[_runner runTest:_cmd module:@"PromiseTest"];
}
#pragma mark Snapshot Tests
- (void)testSimpleSnapshot
{
// If tests have changes, set recordMode = YES below and re-run
_runner.recordMode = NO;
[_runner runTest:_cmd module:@"SimpleSnapshotTest"];
}
@end @end

View File

@ -19,7 +19,7 @@ var {
Text, Text,
View, View,
} = React; } = React;
var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager; var TestModule = NativeModules.TestModule;
var deepDiffer = require('deepDiffer'); var deepDiffer = require('deepDiffer');

View File

@ -20,7 +20,7 @@ var {
Text, Text,
View, View,
} = React; } = React;
var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager; var TestModule = NativeModules.TestModule;
var deepDiffer = require('deepDiffer'); var deepDiffer = require('deepDiffer');

View File

@ -60,7 +60,8 @@ var APIS = [
require('./ActionSheetIOSExample'), require('./ActionSheetIOSExample'),
require('./AdSupportIOSExample'), require('./AdSupportIOSExample'),
require('./AlertIOSExample'), require('./AlertIOSExample'),
require('./AnimationExample/AnExApp'), require('./AnimatedExample'),
require('./AnimatedGratuitousApp/AnExApp'),
require('./AppStateIOSExample'), require('./AppStateIOSExample'),
require('./AsyncStorageExample'), require('./AsyncStorageExample'),
require('./BorderExample'), require('./BorderExample'),

View File

@ -4,7 +4,7 @@ SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/$1.platform/Deve
SDK_VERSION = $(shell plutil -convert json -o - $(call SDK_PATH,iPhoneOS)/SDKSettings.plist | awk -f parseSDKVersion.awk) SDK_VERSION = $(shell plutil -convert json -o - $(call SDK_PATH,iPhoneOS)/SDKSettings.plist | awk -f parseSDKVersion.awk)
CERT ?= "iPhone Developer" CERT ?= iPhone Developer
ARCHS = x86_64 arm64 armv7 i386 ARCHS = x86_64 arm64 armv7 i386
@ -26,13 +26,12 @@ else
cp $^ cp $^
endif endif
/tmp/JSCProfiler: /tmp/RCTJSCProfiler:
mkdir -p $@ mkdir -p $@
.PRECIOUS: RCTJSCProfiler.ios8.dylib
RCTJSCProfiler.ios8.dylib: RCTJSCProfiler_unsigned.ios8.dylib RCTJSCProfiler.ios8.dylib: RCTJSCProfiler_unsigned.ios8.dylib
cp $< $@ cp $< $@
codesign -f -s ${CERT} $@ || rm $@ codesign -f -s "${CERT}" $@
.PRECIOUS: RCTJSCProfiler_unsigned.ios8.dylib .PRECIOUS: RCTJSCProfiler_unsigned.ios8.dylib
RCTJSCProfiler_unsigned.ios8.dylib: $(patsubst %,RCTJSCProfiler_%.ios8.dylib,$(ARCHS)) RCTJSCProfiler_unsigned.ios8.dylib: $(patsubst %,RCTJSCProfiler_%.ios8.dylib,$(ARCHS))
@ -77,7 +76,7 @@ libyajl_%.a: download/yajl-2.1.0
.PRECIOUS: download/yajl-2.1.0 .PRECIOUS: download/yajl-2.1.0
download/yajl-2.1.0: download/yajl-2.1.0.tar.gz download/yajl-2.1.0: download/yajl-2.1.0.tar.gz
tar -zxvf $< -C download tar -zxvf $< -C download > /dev/null
mkdir -p download/yajl-2.1.0/build && cd download/yajl-2.1.0/build && cmake .. mkdir -p download/yajl-2.1.0/build && cd download/yajl-2.1.0/build && cmake ..
.PRECIOUS: download/yajl-2.1.0.tar.gz .PRECIOUS: download/yajl-2.1.0.tar.gz
@ -87,7 +86,7 @@ download/yajl-2.1.0.tar.gz:
.PRECIOUS: download/% .PRECIOUS: download/%
download/%: download/%.tar.gz download/%: download/%.tar.gz
tar -zxvf $< -C `dirname $@` tar -zxvf $< -C `dirname $@` > /dev/null
.PRECIOUS: %.tar.gz .PRECIOUS: %.tar.gz
%.tar.gz: %.tar.gz:

View File

@ -503,6 +503,12 @@ type ValueListenerCallback = (state: {value: number}) => void;
var _uniqueId = 1; var _uniqueId = 1;
/**
* Standard value for driving animations. One `Animated.Value` can drive
* multiple properties in a synchronized fashion, but can only be driven by one
* mechanism at a time. Using a new mechanism (e.g. starting a new animation,
* or calling `setValue`) will stop any previous ones.
*/
class AnimatedValue extends AnimatedWithChildren { class AnimatedValue extends AnimatedWithChildren {
_value: number; _value: number;
_offset: number; _offset: number;
@ -526,6 +532,10 @@ class AnimatedValue extends AnimatedWithChildren {
return this._value + this._offset; return this._value + this._offset;
} }
/**
* Directly set the value. This will stop any animations running on the value
* and udpate all the bound properties.
*/
setValue(value: number): void { setValue(value: number): void {
if (this._animation) { if (this._animation) {
this._animation.stop(); this._animation.stop();
@ -534,15 +544,29 @@ class AnimatedValue extends AnimatedWithChildren {
this._updateValue(value); this._updateValue(value);
} }
/**
* Sets an offset that is applied on top of whatever value is set, whether via
* `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*/
setOffset(offset: number): void { setOffset(offset: number): void {
this._offset = offset; this._offset = offset;
} }
/**
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*/
flattenOffset(): void { flattenOffset(): void {
this._value += this._offset; this._value += this._offset;
this._offset = 0; this._offset = 0;
} }
/**
* Adds an asynchronous listener to the value so you can observe updates from
* animations or whathaveyou. This is useful because there is no way to
* syncronously read the value because it might be driven natively.
*/
addListener(callback: ValueListenerCallback): string { addListener(callback: ValueListenerCallback): string {
var id = String(_uniqueId++); var id = String(_uniqueId++);
this._listeners[id] = callback; this._listeners[id] = callback;
@ -557,6 +581,30 @@ class AnimatedValue extends AnimatedWithChildren {
this._listeners = {}; this._listeners = {};
} }
/**
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*/
stopAnimation(callback?: ?(value: number) => void): void {
this.stopTracking();
this._animation && this._animation.stop();
this._animation = null;
callback && callback(this.__getValue());
}
/**
* Interpolates the value before updating the property, e.g. mapping 0-1 to
* 0-10.
*/
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, Interpolation.create(config));
}
/**
* Typically only used internally, but could be used by a custom Animation
* class.
*/
animate(animation: Animation, callback: ?EndCallback): void { animate(animation: Animation, callback: ?EndCallback): void {
var handle = InteractionManager.createInteractionHandle(); var handle = InteractionManager.createInteractionHandle();
var previousAnimation = this._animation; var previousAnimation = this._animation;
@ -576,27 +624,22 @@ class AnimatedValue extends AnimatedWithChildren {
); );
} }
stopAnimation(callback?: ?(value: number) => void): void { /**
this.stopTracking(); * Typically only used internally.
this._animation && this._animation.stop(); */
this._animation = null;
callback && callback(this.__getValue());
}
stopTracking(): void { stopTracking(): void {
this._tracking && this._tracking.__detach(); this._tracking && this._tracking.__detach();
this._tracking = null; this._tracking = null;
} }
/**
* Typically only used internally.
*/
track(tracking: Animated): void { track(tracking: Animated): void {
this.stopTracking(); this.stopTracking();
this._tracking = tracking; this._tracking = tracking;
} }
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, Interpolation.create(config));
}
_updateValue(value: number): void { _updateValue(value: number): void {
this._value = value; this._value = value;
_flush(this); _flush(this);
@ -607,6 +650,45 @@ class AnimatedValue extends AnimatedWithChildren {
} }
type ValueXYListenerCallback = (value: {x: number; y: number}) => void; type ValueXYListenerCallback = (value: {x: number; y: number}) => void;
/**
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
* API to normal `Animated.Value`, but multiplexed. Contains two regular
* `Animated.Value`s under the hood. Example:
*
*```javascript
* class DraggableView extends React.Component {
* constructor(props) {
* super(props);
* this.state = {
* pan: new Animated.ValueXY(), // inits to zero
* };
* this.state.panResponder = PanResponder.create({
* onStartShouldSetPanResponder: () => true,
* onPanResponderMove: Animated.event([null, {
* dx: this.state.pan.x, // x,y are Animated.Value
* dy: this.state.pan.y,
* }]),
* onPanResponderRelease: () => {
* Animated.spring(
* this.state.pan, // Auto-multiplexed
* {toValue: {x: 0, y: 0}} // Back to zero
* ).start();
* },
* });
* }
* render() {
* return (
* <Animated.View
* {...this.state.panResponder.panHandlers}
* style={this.state.pan.getLayout()}>
* {this.props.children}
* </Animated.View>
* );
* }
* }
*```
*/
class AnimatedValueXY extends AnimatedWithChildren { class AnimatedValueXY extends AnimatedWithChildren {
x: AnimatedValue; x: AnimatedValue;
y: AnimatedValue; y: AnimatedValue;
@ -677,6 +759,13 @@ class AnimatedValueXY extends AnimatedWithChildren {
delete this._listeners[id]; delete this._listeners[id];
} }
/**
* Converts `{x, y}` into `{left, top}` for use in style, e.g.
*
*```javascript
* style={this.state.anim.getLayout()}
*```
*/
getLayout(): {[key: string]: AnimatedValue} { getLayout(): {[key: string]: AnimatedValue} {
return { return {
left: this.x, left: this.x,
@ -684,6 +773,15 @@ class AnimatedValueXY extends AnimatedWithChildren {
}; };
} }
/**
* Converts `{x, y}` into a useable translation transform, e.g.
*
*```javascript
* style={{
* transform: this.state.anim.getTranslateTransform()
* }}
*```
*/
getTranslateTransform(): Array<{[key: string]: AnimatedValue}> { getTranslateTransform(): Array<{[key: string]: AnimatedValue}> {
return [ return [
{translateX: this.x}, {translateX: this.x},
@ -1235,21 +1333,6 @@ var stagger = function(
type Mapping = {[key: string]: Mapping} | AnimatedValue; type Mapping = {[key: string]: Mapping} | AnimatedValue;
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls setValue on the mapped outputs. e.g.
*
* onScroll={this.AnimatedEvent(
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
* {listener} // optional listener invoked asynchronously
* )
* ...
* onPanResponderMove: this.AnimatedEvent([
* null, // raw event arg
* {dx: this._panX}, // gestureState arg
* ]),
*
*/
type EventConfig = {listener?: ?Function}; type EventConfig = {listener?: ?Function};
var event = function( var event = function(
argMapping: Array<?Mapping>, argMapping: Array<?Mapping>,
@ -1287,23 +1370,179 @@ var event = function(
}; };
}; };
/**
* Animations are an important part of modern UX, and the `Animated`
* library is designed to make them fluid, powerful, and easy to build and
* maintain.
*
* The simplest workflow is to create an `Animated.Value`, hook it up to one or
* more style attributes of an animated component, and then drive updates either
* via animations, such as `Animated.timing`, or by hooking into gestures like
* panning or scolling via `Animated.event`. `Animated.Value` can also bind to
* props other than style, and can be interpolated as well. Here is a basic
* example of a container view that will fade in when it's mounted:
*
*```javascript
* class FadeInView extends React.Component {
* constructor(props) {
* super(props);
* this.state = {
* fadeAnim: new Animated.Value(0), // init opacity 0
* };
* }
* componentDidMount() {
* Animated.timing( // Uses easing functions
* this.state.fadeAnim, // The value to drive
* {toValue: 1}, // Configuration
* ).start(); // Don't forget start!
* }
* render() {
* return (
* <Animated.View // Special animatable View
* style={{opacity: this.state.fadeAnim}}> // Binds
* {this.props.children}
* </Animated.View>
* );
* }
* }
*```
*
* Note that only animatable components can be animated. `View`, `Text`, and
* `Image` are already provided, and you can create custom ones with
* `createAnimatedComponent`. These special components do the magic of binding
* the animated values to the properties, and do targetted native updates to
* avoid the cost of the react render and reconciliation process on every frame.
* They also handle cleanup on unmount so they are safe by default.
*
* Animations are heavily configurable. Custom and pre-defined easing
* functions, delays, durations, decay factors, spring constants, and more can
* all be tweaked depending on the type of animation.
*
* A single `Animated.Value` can drive any number of properties, and each
* property can be run through an interpolation first. An interpolation maps
* input ranges to output ranges, typically using a linear interpolation but
* also supports easing functions. By default, it will extrapolate the curve
* beyond the ranges given, but you can also have it clamp the output value.
*
* For example, you may want to think about your `Animated.Value` as going from
* 0 to 1, but animate the position from 150px to 0px and the opacity from 0 to
* 1. This can easily be done by modifying `style` in the example above like so:
*
*```javascript
* style={{
* opacity: this.state.fadeAnim, // Binds directly
* transform: [{
* translateY: this.state.fadeAnim.interpolate({
* inputRange: [0, 1],
* outputRange: [150, 0] // 0 : 150, 0.5 : 75, 1 : 0
* }),
* }],
* }}>
*```
*
* Animations can also be combined in complex ways using composition functions
* such as `sequence` and `parallel`, and can also be chained together simply
* by setting the `toValue` of one animation to be another `Animated.Value`.
*
* `Animated.ValueXY` is handy for 2D animations, like panning, and there are
* other helpful additions like `setOffset` and `getLayout` to aid with typical
* interaction patterns, like drag-and-drop.
*
* You can see more example usage in `AnimationExample.js`, the Gratuitous
* Animation App, and [Animations documentation guide](http://facebook.github.io/react-native/docs/animations.html).
*
* Note that `Animated` is designed to be fully serializable so that animations
* can be run in a high performace way, independent of the normal JavaScript
* event loop. This does influence the API, so keep that in mind when it seems a
* little trickier to do something compared to a fully synchronous system.
* Checkout `Animated.Value.addListener` as a way to work around some of these
* limitations, but use it sparingly since it might have performance
* implications in the future.
*/
module.exports = { module.exports = {
delay, /**
sequence, * Standard value class for driving animations. Typically initialized with
parallel, * `new Animated.Value(0);`
stagger, */
Value: AnimatedValue,
/**
* 2D value class for driving 2D animations, such as pan gestures.
*/
ValueXY: AnimatedValueXY,
/**
* An animatable View component.
*/
View: createAnimatedComponent(View),
/**
* An animatable Text component.
*/
Text: createAnimatedComponent(Text),
/**
* An animatable Image component.
*/
Image: createAnimatedComponent(Image),
/**
* Animates a value from an initial velocity to zero based on a decay
* coefficient.
*/
decay, decay,
/**
* Animates a value along a timed easing curve. The `Easing` module has tons
* of pre-defined curves, or you can use your own function.
*/
timing, timing,
/**
* Spring animation based on Rebound and Origami. Tracks velocity state to
* create fluid motions as the `toValue` updates, and can be chained together.
*/
spring, spring,
/**
* Starts an animation after the given delay.
*/
delay,
/**
* Starts an array of animations in order, waiting for each to complete
* before starting the next. If the current running animation is stopped, no
* following animations will be started.
*/
sequence,
/**
* Starts an array of animations all at the same time. By default, if one
* of the animations is stopped, they will all be stopped. You can override
* this with the `stopTogether` flag.
*/
parallel,
/**
* Array of animations may run in parallel (overlap), but are started in
* sequence with successive delays. Nice for doing trailing effects.
*/
stagger,
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls `setValue` on the mapped outputs. e.g.
*
*```javascript
* onScroll={this.AnimatedEvent(
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
* {listener}, // Optional async listener
* )
* ...
* onPanResponderMove: this.AnimatedEvent([
* null, // raw event arg ignored
* {dx: this._panX}, // gestureState arg
* ]),
*```
*/
event, event,
Value: AnimatedValue, /**
ValueXY: AnimatedValueXY, * Make any React component Animatable. Used to create `Animated.View`, etc.
__PropsOnlyForTests: AnimatedProps, */
View: createAnimatedComponent(View),
Text: createAnimatedComponent(Text),
Image: createAnimatedComponent(Image),
createAnimatedComponent, createAnimatedComponent,
__PropsOnlyForTests: AnimatedProps,
}; };

View File

@ -123,12 +123,18 @@ class Easing {
return easing; return easing;
} }
/**
* Runs an easing function backwards.
*/
static out( static out(
easing: (t: number) => number, easing: (t: number) => number,
): (t: number) => number { ): (t: number) => number {
return (t) => 1 - easing(1 - t); return (t) => 1 - easing(1 - t);
} }
/**
* Makes any easing function symmetrical.
*/
static inOut( static inOut(
easing: (t: number) => number, easing: (t: number) => number,
): (t: number) => number { ): (t: number) => number {

View File

@ -0,0 +1,296 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B5115B1A9E6B3D00147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; };
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTCameraRoll.a; sourceTree = BUILT_PRODUCTS_DIR; };
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B5115A1A9E6B3D00147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */,
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */,
58B5115E1A9E6B3D00147676 /* Products */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
58B5115E1A9E6B3D00147676 /* Products */ = {
isa = PBXGroup;
children = (
58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B5115C1A9E6B3D00147676 /* RCTCameraRoll */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTCameraRoll" */;
buildPhases = (
58B511591A9E6B3D00147676 /* Sources */,
58B5115A1A9E6B3D00147676 /* Frameworks */,
58B5115B1A9E6B3D00147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RCTCameraRoll;
productName = RCTNetworkImage;
productReference = 58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511551A9E6B3D00147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B5115C1A9E6B3D00147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTCameraRoll" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511541A9E6B3D00147676;
productRefGroup = 58B5115E1A9E6B3D00147676 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
58B5115C1A9E6B3D00147676 /* RCTCameraRoll */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511591A9E6B3D00147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B5116F1A9E6B3D00147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
WARNING_CFLAGS = (
"-Werror",
"-Wall",
);
};
name = Debug;
};
58B511701A9E6B3D00147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
WARNING_CFLAGS = (
"-Werror",
"-Wall",
);
};
name = Release;
};
58B511721A9E6B3D00147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
"$(SRCROOT)/../Image/**",
"$(SRCROOT)/../Network/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/UIExplorer-gjaibsjtheitasdxdtcvxxqavkvy/Build/Products/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTCameraRoll;
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511731A9E6B3D00147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
"$(SRCROOT)/../Image/**",
"$(SRCROOT)/../Network/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/UIExplorer-gjaibsjtheitasdxdtcvxxqavkvy/Build/Products/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTCameraRoll;
RUN_CLANG_STATIC_ANALYZER = NO;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTCameraRoll" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B5116F1A9E6B3D00147676 /* Debug */,
58B511701A9E6B3D00147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTCameraRoll" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511721A9E6B3D00147676 /* Debug */,
58B511731A9E6B3D00147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511551A9E6B3D00147676 /* Project object */;
}

View File

@ -49,6 +49,7 @@ var AndroidTextInputAttributes = {
keyboardType: true, keyboardType: true,
mostRecentEventCount: true, mostRecentEventCount: true,
multiline: true, multiline: true,
numberOfLines: true,
password: true, password: true,
placeholder: true, placeholder: true,
placeholderTextColor: true, placeholderTextColor: true,
@ -193,6 +194,12 @@ var TextInput = React.createClass({
* @platform ios * @platform ios
*/ */
maxLength: PropTypes.number, maxLength: PropTypes.number,
/**
* Sets the number of lines for a TextInput. Use it with multiline set to
* true to be able to fill the lines.
* @platform android
*/
numberOfLines: PropTypes.number,
/** /**
* If true, the keyboard disables the return key when there is no text and * If true, the keyboard disables the return key when there is no text and
* automatically enables it when there is text. The default value is false. * automatically enables it when there is text. The default value is false.
@ -484,6 +491,7 @@ var TextInput = React.createClass({
keyboardType={this.props.keyboardType} keyboardType={this.props.keyboardType}
mostRecentEventCount={this.state.mostRecentEventCount} mostRecentEventCount={this.state.mostRecentEventCount}
multiline={this.props.multiline} multiline={this.props.multiline}
numberOfLines={this.props.numberOfLines}
onFocus={this._onFocus} onFocus={this._onFocus}
onBlur={this._onBlur} onBlur={this._onBlur}
onChange={this._onChange} onChange={this._onChange}

View File

@ -209,6 +209,8 @@ var TouchableHighlight = React.createClass({
return ( return (
<View <View
accessible={true} accessible={true}
accessibilityComponentType={this.props.accessibilityComponentType}
accessibilityTraits={this.props.accessibilityTraits}
ref={UNDERLAY_REF} ref={UNDERLAY_REF}
style={this.state.underlayStyle} style={this.state.underlayStyle}
onLayout={this.props.onLayout} onLayout={this.props.onLayout}

View File

@ -156,6 +156,8 @@ var TouchableOpacity = React.createClass({
return ( return (
<Animated.View <Animated.View
accessible={true} accessible={true}
accessibilityComponentType={this.props.accessibilityComponentType}
accessibilityTraits={this.props.accessibilityTraits}
style={[this.props.style, {opacity: this.state.anim}]} style={[this.props.style, {opacity: this.state.anim}]}
testID={this.props.testID} testID={this.props.testID}
onLayout={this.props.onLayout} onLayout={this.props.onLayout}

View File

@ -14,6 +14,7 @@
var React = require('React'); var React = require('React');
var TimerMixin = require('react-timer-mixin'); var TimerMixin = require('react-timer-mixin');
var Touchable = require('Touchable'); var Touchable = require('Touchable');
var View = require('View');
var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var onlyChild = require('onlyChild'); var onlyChild = require('onlyChild');
@ -36,11 +37,16 @@ var TouchableWithoutFeedback = React.createClass({
mixins: [TimerMixin, Touchable.Mixin], mixins: [TimerMixin, Touchable.Mixin],
propTypes: { propTypes: {
accessible: React.PropTypes.bool,
accessibilityComponentType: React.PropTypes.oneOf(View.AccessibilityComponentType),
accessibilityTraits: React.PropTypes.oneOfType([
React.PropTypes.oneOf(View.AccessibilityTraits),
React.PropTypes.arrayOf(React.PropTypes.oneOf(View.AccessibilityTraits)),
]),
/** /**
* Called when the touch is released, but not if cancelled (e.g. by a scroll * Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock). * that steals the responder lock).
*/ */
accessible: React.PropTypes.bool,
onPress: React.PropTypes.func, onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func, onPressIn: React.PropTypes.func,
onPressOut: React.PropTypes.func, onPressOut: React.PropTypes.func,
@ -120,6 +126,8 @@ var TouchableWithoutFeedback = React.createClass({
// Note(avik): remove dynamic typecast once Flow has been upgraded // Note(avik): remove dynamic typecast once Flow has been upgraded
return (React: any).cloneElement(onlyChild(this.props.children), { return (React: any).cloneElement(onlyChild(this.props.children), {
accessible: this.props.accessible !== false, accessible: this.props.accessible !== false,
accessibilityComponentType: this.props.accessibilityComponentType,
accessibilityTraits: this.props.accessibilityTraits,
testID: this.props.testID, testID: this.props.testID,
onLayout: this.props.onLayout, onLayout: this.props.onLayout,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,

View File

@ -44,6 +44,13 @@ var AccessibilityTraits = [
'pageTurn', 'pageTurn',
]; ];
var AccessibilityComponentType = [
'none',
'button',
'radiobutton_checked',
'radiobutton_unchecked',
];
/** /**
* The most fundamental component for building UI, `View` is a * The most fundamental component for building UI, `View` is a
* container that supports layout with flexbox, style, some touch handling, and * container that supports layout with flexbox, style, some touch handling, and
@ -76,6 +83,11 @@ var View = React.createClass({
validAttributes: ReactNativeViewAttributes.RCTView validAttributes: ReactNativeViewAttributes.RCTView
}, },
statics: {
AccessibilityTraits,
AccessibilityComponentType,
},
propTypes: { propTypes: {
/** /**
* When true, indicates that the view is an accessibility element. By default, * When true, indicates that the view is an accessibility element. By default,
@ -95,12 +107,7 @@ var View = React.createClass({
* native one. Works for Android only. * native one. Works for Android only.
* @platform android * @platform android
*/ */
accessibilityComponentType: PropTypes.oneOf([ accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentType),
'none',
'button',
'radiobutton_checked',
'radiobutton_unchecked',
]),
/** /**
* Indicates to accessibility services whether the user should be notified * Indicates to accessibility services whether the user should be notified
@ -152,7 +159,7 @@ var View = React.createClass({
* When `accessible` is true, the system will try to invoke this function * When `accessible` is true, the system will try to invoke this function
* when the user performs accessibility tap gesture. * when the user performs accessibility tap gesture.
*/ */
onAcccessibilityTap: PropTypes.func, onAccessibilityTap: PropTypes.func,
/** /**
* When `accessible` is true, the system will invoke this function when the * When `accessible` is true, the system will invoke this function when the

View File

@ -72,7 +72,9 @@ function setupDevtools() {
} }
function handleMessage(evt) { function handleMessage(evt) {
var data; // It's hard to handle JSON in a safe manner without inspecting it at
// runtime, hence the any
var data: any;
try { try {
data = JSON.parse(evt.data); data = JSON.parse(evt.data);
} catch (e) { } catch (e) {

View File

@ -27,60 +27,85 @@ RCT_EXPORT_MODULE()
return !strcmp(header, "GIF87a") || !strcmp(header, "GIF89a"); return !strcmp(header, "GIF87a") || !strcmp(header, "GIF89a");
} }
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{ {
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL); CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL); NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue]; NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
UIImage *image = nil;
size_t imageCount = CGImageSourceGetCount(imageSource); size_t imageCount = CGImageSourceGetCount(imageSource);
NSTimeInterval duration = 0; if (imageCount > 1) {
NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL);
NSDictionary *frameGIFProperties = frameProperties[(id)kCGImagePropertyGIFDictionary];
const NSTimeInterval kDelayTimeIntervalDefault = 0.1; NSTimeInterval duration = 0;
NSNumber *delayTime = frameGIFProperties[(id)kCGImagePropertyGIFUnclampedDelayTime] ?: frameGIFProperties[(id)kCGImagePropertyGIFDelayTime]; NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
if (delayTime == nil) { NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
if (i == 0) { for (size_t i = 0; i < imageCount; i++) {
delayTime = @(kDelayTimeIntervalDefault);
} else { CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
delayTime = delays[i - 1]; if (!image) {
image = [UIImage imageWithCGImage:imageRef];
} }
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL);
NSDictionary *frameGIFProperties = frameProperties[(id)kCGImagePropertyGIFDictionary];
const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
NSNumber *delayTime = frameGIFProperties[(id)kCGImagePropertyGIFUnclampedDelayTime] ?: frameGIFProperties[(id)kCGImagePropertyGIFDelayTime];
if (delayTime == nil) {
if (i == 0) {
delayTime = @(kDelayTimeIntervalDefault);
} else {
delayTime = delays[i - 1];
}
}
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
delayTime = @(kDelayTimeIntervalDefault);
}
duration += delayTime.doubleValue;
delays[i] = delayTime;
images[i] = (__bridge_transfer id)imageRef;
}
CFRelease(imageSource);
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
for (NSNumber *delayNumber in delays) {
[keyTimes addObject:@(runningDuration / duration)];
runningDuration += delayNumber.doubleValue;
} }
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02; [keyTimes addObject:@1.0];
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
delayTime = @(kDelayTimeIntervalDefault); // Create animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.calculationMode = kCAAnimationDiscrete;
animation.repeatCount = loopCount == 0 ? HUGE_VALF : loopCount;
animation.keyTimes = keyTimes;
animation.values = images;
animation.duration = duration;
image.reactKeyframeAnimation = animation;
} else {
// Don't bother creating an animation
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (imageRef) {
image = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
} }
CFRelease(imageSource);
duration += delayTime.doubleValue;
delays[i] = delayTime;
images[i] = (__bridge_transfer id)image;
}
CFRelease(imageSource);
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
for (NSNumber *delayNumber in delays) {
[keyTimes addObject:@(runningDuration / duration)];
runningDuration += delayNumber.doubleValue;
} }
[keyTimes addObject:@1.0]; completionHandler(nil, image);
return ^{};
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.calculationMode = kCAAnimationDiscrete;
animation.repeatCount = loopCount == 0 ? HUGE_VALF : loopCount;
animation.keyTimes = keyTimes;
animation.values = images;
animation.duration = duration;
completionHandler(nil, animation);
return nil;
} }
@end @end

View File

@ -11,14 +11,10 @@
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; }; 1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */; }; 1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */; };
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; }; 134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; }; 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; }; 35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; }; 354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; }; 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
83DDA1571B8DCA5800892A1C /* RCTAssetBundleImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */; }; 83DDA1571B8DCA5800892A1C /* RCTAssetBundleImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -43,10 +39,6 @@
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImageDecoder.m; sourceTree = "<group>"; }; 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImageDecoder.m; sourceTree = "<group>"; };
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; }; 134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; };
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; }; 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; };
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; }; 143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; }; 143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; };
35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; }; 35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; };
@ -56,10 +48,6 @@
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; }; 58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; }; 58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; }; 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetBundleImageLoader.h; sourceTree = "<group>"; }; 83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetBundleImageLoader.h; sourceTree = "<group>"; };
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetBundleImageLoader.m; sourceTree = "<group>"; }; 83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetBundleImageLoader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -80,10 +68,6 @@
children = ( children = (
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */, 83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */,
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */, 83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */,
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */, 1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */, 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
58B511891A9E6BD600147676 /* RCTImageDownloader.h */, 58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
@ -92,8 +76,6 @@
354631671B69857700AA0B86 /* RCTImageEditingManager.m */, 354631671B69857700AA0B86 /* RCTImageEditingManager.m */,
143879361AAD32A300F088A5 /* RCTImageLoader.h */, 143879361AAD32A300F088A5 /* RCTImageLoader.h */,
143879371AAD32A300F088A5 /* RCTImageLoader.m */, 143879371AAD32A300F088A5 /* RCTImageLoader.m */,
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */, 1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */, 1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */, 1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
@ -102,8 +84,6 @@
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */, 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */, 134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */, 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */,
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */,
58B5115E1A9E6B3D00147676 /* Products */, 58B5115E1A9E6B3D00147676 /* Products */,
); );
indentWidth = 2; indentWidth = 2;
@ -174,14 +154,10 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */, 35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */, 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */, 1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */, 1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */, 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */, 354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */, 1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,

View File

@ -50,7 +50,7 @@ RCT_EXPORT_MODULE()
*/ */
- (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url - (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
progressHandler:(RCTImageLoaderProgressBlock)progressBlock progressHandler:(RCTImageLoaderProgressBlock)progressBlock
completionHandler:(RCTImageLoaderCompletionBlock)completionBlock completionHandler:(void (^)(NSError *error, NSData *data))completionBlock
{ {
if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) { if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) {
RCTLogError(@"You need to import the RCTNetworking library in order to download remote images."); RCTLogError(@"You need to import the RCTNetworking library in order to download remote images.");

View File

@ -15,10 +15,15 @@
@class ALAssetsLibrary; @class ALAssetsLibrary;
typedef void (^RCTImageLoaderProgressBlock)(int64_t progress, int64_t total); typedef void (^RCTImageLoaderProgressBlock)(int64_t progress, int64_t total);
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* UIImage or CAAnimation */); typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, UIImage *image);
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* NSData, UIImage, CAAnimation */);
typedef void (^RCTImageLoaderCancellationBlock)(void); typedef void (^RCTImageLoaderCancellationBlock)(void);
@interface UIImage (React)
@property (nonatomic, copy) CAKeyframeAnimation *reactKeyframeAnimation;
@end
@interface RCTImageLoader : NSObject <RCTBridgeModule, RCTURLRequestHandler> @interface RCTImageLoader : NSObject <RCTBridgeModule, RCTURLRequestHandler>
/** /**

View File

@ -28,6 +28,20 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
} }
} }
@implementation UIImage (React)
- (CAKeyframeAnimation *)reactKeyframeAnimation
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setReactKeyframeAnimation:(CAKeyframeAnimation *)reactKeyframeAnimation
{
objc_setAssociatedObject(self, @selector(reactKeyframeAnimation), reactKeyframeAnimation, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
@implementation RCTImageLoader @implementation RCTImageLoader
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
@ -99,7 +113,7 @@ RCT_EXPORT_MODULE()
progressBlock(progress, total); progressBlock(progress, total);
}); });
} }
} completionHandler:^(NSError *error, id image) { } completionHandler:^(NSError *error, UIImage *image) {
RCTDispatchCallbackOnMainQueue(completionBlock, error, image); RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}] ?: ^{}; }] ?: ^{};
} }
@ -142,7 +156,7 @@ RCT_EXPORT_MODULE()
{ {
id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data]; id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data];
if (imageDecoder) { if (imageDecoder) {
return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, id image) { return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, UIImage *image) {
RCTDispatchCallbackOnMainQueue(completionBlock, error, image); RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}]; }];
} else { } else {

View File

@ -31,6 +31,7 @@
@implementation RCTImageView @implementation RCTImageView
{ {
RCTBridge *_bridge; RCTBridge *_bridge;
CGSize _targetSize;
} }
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
@ -142,10 +143,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
scale:RCTScreenScale() scale:RCTScreenScale()
resizeMode:self.contentMode resizeMode:self.contentMode
progressBlock:progressHandler progressBlock:progressHandler
completionBlock:^(NSError *error, id image) { completionBlock:^(NSError *error, UIImage *image) {
if ([image isKindOfClass:[CAAnimation class]]) { if (image.reactKeyframeAnimation) {
[self.layer addAnimation:image forKey:@"contents"]; [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else { } else {
[self.layer removeAnimationForKey:@"contents"]; [self.layer removeAnimationForKey:@"contents"];
self.image = image; self.image = image;
@ -173,19 +174,17 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
{ {
[super reactSetFrame:frame]; [super reactSetFrame:frame];
if (self.image == nil) { if (self.image == nil) {
_targetSize = frame.size;
[self reloadImage]; [self reloadImage];
} else if ([RCTImageView srcNeedsReload:_src]) { } else if ([RCTImageView srcNeedsReload:_src]) {
// Get optimal image size
CGSize currentSize = self.image.size;
CGSize idealSize = RCTTargetSize(self.image.size, self.image.scale, frame.size, CGSize idealSize = RCTTargetSize(self.image.size, self.image.scale, frame.size,
RCTScreenScale(), self.contentMode, YES); RCTScreenScale(), self.contentMode, YES);
CGFloat widthChangeFraction = ABS(_targetSize.width - idealSize.width) / _targetSize.width;
CGFloat widthChangeFraction = ABS(currentSize.width - idealSize.width) / currentSize.width; CGFloat heightChangeFraction = ABS(_targetSize.height - idealSize.height) / _targetSize.height;
CGFloat heightChangeFraction = ABS(currentSize.height - idealSize.height) / currentSize.height;
// If the combined change is more than 20%, reload the asset in case there is a better size. // If the combined change is more than 20%, reload the asset in case there is a better size.
if (widthChangeFraction + heightChangeFraction > 0.2) { if (widthChangeFraction + heightChangeFraction > 0.2) {
_targetSize = idealSize;
[self reloadImage]; [self reloadImage];
} }
} }

View File

@ -33,6 +33,7 @@ class Modal extends React.Component {
<RCTModalHostView <RCTModalHostView
animated={this.props.animated} animated={this.props.animated}
transparent={this.props.transparent} transparent={this.props.transparent}
onDismiss={this.props.onDismiss}
style={styles.modal}> style={styles.modal}>
<View style={[styles.container, containerBackgroundColor]}> <View style={[styles.container, containerBackgroundColor]}>
{this.props.children} {this.props.children}
@ -45,6 +46,7 @@ class Modal extends React.Component {
Modal.propTypes = { Modal.propTypes = {
animated: PropTypes.bool, animated: PropTypes.bool,
transparent: PropTypes.bool, transparent: PropTypes.bool,
onDismiss: PropTypes.func,
}; };
var styles = StyleSheet.create({ var styles = StyleSheet.create({

View File

@ -138,6 +138,23 @@ type ConnectivityStateAndroid = $Enum<{
var _subscriptions = new Map(); var _subscriptions = new Map();
if (Platform.OS === 'ios') {
var _isConnected = function(
reachability: ReachabilityStateIOS
): bool {
return reachability !== 'none' &&
reachability !== 'unknown';
};
} else if (Platform.OS === 'android') {
var _isConnected = function(
connectionType: ConnectivityStateAndroid
): bool {
return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
};
}
var _isConnectedSubscriptions = new Map();
var NetInfo = { var NetInfo = {
addEventListener: function ( addEventListener: function (
eventName: ChangeEventName, eventName: ChangeEventName,
@ -175,60 +192,41 @@ var NetInfo = {
}); });
}, },
isConnected: {}, isConnected: {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
var listener = (connection) => {
handler(_isConnected(connection));
};
_isConnectedSubscriptions.set(handler, listener);
NetInfo.addEventListener(
eventName,
listener
);
},
isConnectionMetered: {}, removeEventListener: function(
}; eventName: ChangeEventName,
handler: Function
): void {
var listener = _isConnectedSubscriptions.get(handler);
NetInfo.removeEventListener(
eventName,
listener
);
_isConnectedSubscriptions.delete(handler);
},
if (Platform.OS === 'ios') { fetch: function(): Promise {
var _isConnected = function( return NetInfo.fetch().then(
reachability: ReachabilityStateIOS (connection) => _isConnected(connection)
): bool { );
return reachability !== 'none' && },
reachability !== 'unknown';
};
} else if (Platform.OS === 'android') {
var _isConnected = function(
connectionType: ConnectivityStateAndroid
): bool {
return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
};
}
var _isConnectedSubscriptions = new Map();
NetInfo.isConnected = {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
var listener = (connection) => {
handler(_isConnected(connection));
};
_isConnectedSubscriptions.set(handler, listener);
NetInfo.addEventListener(
eventName,
listener
);
}, },
removeEventListener: function( isConnectionMetered: ({}: {} | (callback:Function) => void),
eventName: ChangeEventName,
handler: Function
): void {
var listener = _isConnectedSubscriptions.get(handler);
NetInfo.removeEventListener(
eventName,
listener
);
_isConnectedSubscriptions.delete(handler);
},
fetch: function(): Promise {
return NetInfo.fetch().then(
(connection) => _isConnected(connection)
);
},
}; };
if (Platform.OS === 'android') { if (Platform.OS === 'android') {

View File

@ -56,6 +56,7 @@ RCT_EXPORT_MODULE()
// Lazy setup // Lazy setup
if (!_session && [self isValid]) { if (!_session && [self isValid]) {
NSOperationQueue *callbackQueue = [NSOperationQueue new]; NSOperationQueue *callbackQueue = [NSOperationQueue new];
callbackQueue.maxConcurrentOperationCount = 1;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:configuration _session = [NSURLSession sessionWithConfiguration:configuration
delegate:self delegate:self

View File

@ -111,6 +111,12 @@ var styles = StyleSheet.create({
}, },
}); });
var RCTPickerIOS = requireNativeComponent('RCTPicker', null); var RCTPickerIOS = requireNativeComponent('RCTPicker', PickerIOS, {
nativeOnly: {
items: true,
onChange: true,
selectedIndex: true,
},
});
module.exports = PickerIOS; module.exports = PickerIOS;

View File

@ -16,13 +16,8 @@
#import "RCTTestModule.h" #import "RCTTestModule.h"
#import "RCTUtils.h" #import "RCTUtils.h"
#define TIMEOUT_SECONDS 60 static const NSTimeInterval kTestTimeoutSeconds = 60;
static const NSTimeInterval kTestTeardownTimeoutSeconds = 30;
@interface RCTBridge (RCTTestRunner)
@property (nonatomic, weak) RCTBridge *batchedBridge;
@end
@implementation RCTTestRunner @implementation RCTTestRunner
{ {
@ -49,7 +44,7 @@
_scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
RCTAssert(_scriptURL != nil, @"Could not locate main.jsBundle"); RCTAssert(_scriptURL != nil, @"Could not locate main.jsBundle");
#else #else
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?dev=true&platform=ios", app]]; _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]];
#endif #endif
} }
return self; return self;
@ -83,52 +78,69 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)runTest:(SEL)test module:(NSString *)moduleName - (void)runTest:(SEL)test module:(NSString *)moduleName
initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
{ {
__block NSString *error = nil; __weak id weakJSContext;
RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) { @autoreleasepool {
error = message; __block NSString *error = nil;
RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
error = message;
}
});
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:_moduleProvider
launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps];
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
RCTAssert(_testController != nil, @"_testController should not be nil");
testModule.controller = _testController;
testModule.testSelector = test;
testModule.view = rootView;
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
vc.view = [UIView new];
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:kTestTimeoutSeconds];
while (date.timeIntervalSinceNow > 0 && testModule.status == RCTTestStatusPending && error == nil) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} }
});
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL // Take a weak reference to the JS context, so we track its deallocation later
moduleProvider:_moduleProvider // (we can only do this now, since it's been lazily initialized)
launchOptions:nil]; weakJSContext = [[[bridge valueForKey:@"batchedBridge"] valueForKey:@"javaScriptExecutor"] valueForKey:@"context"];
[rootView removeFromSuperview];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps]; RCTSetLogFunction(RCTDefaultLogFunction);
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]); NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
RCTTestModule *testModule = rootView.bridge.batchedBridge.modules[testModuleName]; return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
RCTAssert(_testController != nil, @"_testController should not be nil"); }]];
testModule.controller = _testController; RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
testModule.testSelector = test;
testModule.view = rootView;
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; if (expectErrorBlock) {
vc.view = [UIView new]; RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched.");
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized } else {
RCTAssert(error == nil, @"RedBox error: %@", error);
RCTAssert(testModule.status != RCTTestStatusPending, @"Test didn't finish within %0.f seconds", kTestTimeoutSeconds);
RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed");
}
[bridge invalidate];
}
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; // Wait for the executor to have shut down completely before returning
while (date.timeIntervalSinceNow > 0 && testModule.status == RCTTestStatusPending && error == nil) { NSDate *teardownTimeout = [NSDate dateWithTimeIntervalSinceNow:kTestTeardownTimeoutSeconds];
while (teardownTimeout.timeIntervalSinceNow > 0 && weakJSContext) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} }
[rootView removeFromSuperview]; RCTAssert(!weakJSContext, @"JS context was not deallocated after being invalidated");
RCTSetLogFunction(RCTDefaultLogFunction);
NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
}]];
RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
if (expectErrorBlock) {
RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched.");
} else {
RCTAssert(error == nil, @"RedBox error: %@", error);
RCTAssert(testModule.status != RCTTestStatusPending, @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed");
}
} }
@end @end

View File

@ -0,0 +1,15 @@
/**
* 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 StyleSheetTypes
* @flow
*/
'use strict';
type Atom = number | bool | Object | Array<?Atom>;
export type StyleObj = Atom | Array<?StyleObj>;

View File

@ -14,8 +14,7 @@
var StyleSheetRegistry = require('StyleSheetRegistry'); var StyleSheetRegistry = require('StyleSheetRegistry');
var invariant = require('invariant'); var invariant = require('invariant');
type Atom = number | bool | Object | Array<?Atom> import type { StyleObj } from 'StyleSheetTypes';
type StyleObj = Atom | Array<?StyleObj>
function getStyle(style) { function getStyle(style) {
if (typeof style === 'number') { if (typeof style === 'number') {

View File

@ -117,8 +117,15 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
dispatch_group_t initModulesAndLoadSource = dispatch_group_create(); dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
dispatch_group_enter(initModulesAndLoadSource); dispatch_group_enter(initModulesAndLoadSource);
__weak RCTBatchedBridge *weakSelf = self;
__block NSString *sourceCode; __block NSString *sourceCode;
[self loadSource:^(__unused NSError *error, NSString *source) { [self loadSource:^(NSError *error, NSString *source) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
sourceCode = source; sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource); dispatch_group_leave(initModulesAndLoadSource);
}]; }];
@ -131,7 +138,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
RCTProfileHookModules(self); RCTProfileHookModules(self);
} }
__weak RCTBatchedBridge *weakSelf = self;
__block NSString *config; __block NSString *config;
dispatch_group_enter(initModulesAndLoadSource); dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{ dispatch_async(bridgeQueue, ^{
@ -150,16 +156,24 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
// We're not waiting for this complete to leave the dispatch group, since // We're not waiting for this complete to leave the dispatch group, since
// injectJSONConfiguration and executeSourceCode will schedule operations on the // injectJSONConfiguration and executeSourceCode will schedule operations on the
// same queue anyway. // same queue anyway.
[weakSelf injectJSONConfiguration:config onComplete:^(__unused NSError *error) { [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
}]; }];
dispatch_group_leave(initModulesAndLoadSource); dispatch_group_leave(initModulesAndLoadSource);
}); });
}); });
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{ dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
if (sourceCode) { RCTBatchedBridge *strongSelf = weakSelf;
[weakSelf executeSourceCode:sourceCode]; if (sourceCode && strongSelf.loading) {
dispatch_async(bridgeQueue, ^{
[weakSelf executeSourceCode:sourceCode];
});
} }
}); });
} }
@ -172,23 +186,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSString *source) { RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSString *source) {
RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil); RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil);
RCTPerformanceLoggerEnd(RCTPLScriptDownload); RCTPerformanceLoggerEnd(RCTPLScriptDownload);
if (error) {
NSArray *stack = error.userInfo[@"stack"];
if (stack) {
[self.redBox showErrorMessage:error.localizedDescription
withStack:stack];
} else {
[self.redBox showErrorMessage:error.localizedDescription
withDetails:error.localizedFailureReason];
}
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge
userInfo:userInfo];
}
_onSourceLoad(error, source); _onSourceLoad(error, source);
}; };
@ -283,7 +280,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
object:self]; object:self];
} }
- (void)setupExecutor - (void)setupExecutor
{ {
[_javaScriptExecutor setUp]; [_javaScriptExecutor setUp];
@ -313,12 +309,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
[_javaScriptExecutor injectJSONText:configJSON [_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig" asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:^(NSError *error) { callback:onComplete];
if (error) {
[self.redBox showError:error];
}
onComplete(error);
}];
} }
- (void)executeSourceCode:(NSString *)sourceCode - (void)executeSourceCode:(NSString *)sourceCode
@ -333,7 +324,9 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) { [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
if (loadError) { if (loadError) {
[self.redBox showError:loadError]; dispatch_async(dispatch_get_main_queue(), ^{
[self stopLoadingWithError:loadError];
});
return; return;
} }
@ -352,6 +345,28 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
}]; }];
} }
- (void)stopLoadingWithError:(NSError *)error
{
RCTAssertMainThread();
if (!self.isValid || !self.loading) {
return;
}
_loading = NO;
NSArray *stack = error.userInfo[@"stack"];
if (stack) {
[self.redBox showErrorMessage:error.localizedDescription withStack:stack];
} else {
[self.redBox showError:error];
}
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge
userInfo:userInfo];
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleURL RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleURL
moduleProvider:(__unused RCTBridgeModuleProviderBlock)block moduleProvider:(__unused RCTBridgeModuleProviderBlock)block

View File

@ -13,7 +13,6 @@
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h" #import "RCTKeyCommands.h"
#import "RCTLog.h" #import "RCTLog.h"
@ -246,6 +245,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return self; return self;
} }
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder) RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder)
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex - (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex

View File

@ -23,7 +23,7 @@
#import "RCTUtils.h" #import "RCTUtils.h"
#ifndef RCT_JSC_PROFILER #ifndef RCT_JSC_PROFILER
#if RCT_DEV && RCT_DEBUG #if RCT_DEV
#define RCT_JSC_PROFILER 1 #define RCT_JSC_PROFILER 1
#else #else
#define RCT_JSC_PROFILER 0 #define RCT_JSC_PROFILER 0
@ -34,7 +34,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#ifndef RCT_JSC_PROFILER_DYLIB #ifndef RCT_JSC_PROFILER_DYLIB
#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"Frameworks"] UTF8String] #define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String]
#endif #endif
#endif #endif

View File

@ -11,4 +11,6 @@
@interface RCTDevLoadingView : NSObject <RCTBridgeModule> @interface RCTDevLoadingView : NSObject <RCTBridgeModule>
+ (void)setEnabled:(BOOL)enabled;
@end @end

View File

@ -16,6 +16,8 @@
#if RCT_DEV #if RCT_DEV
static BOOL isEnabled = YES;
@implementation RCTDevLoadingView @implementation RCTDevLoadingView
{ {
UIWindow *_window; UIWindow *_window;
@ -27,6 +29,11 @@
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
+ (void)setEnabled:(BOOL)enabled
{
isEnabled = enabled;
}
- (instancetype)init - (instancetype)init
{ {
if ((self = [super init])) { if ((self = [super init])) {
@ -57,6 +64,10 @@ RCT_EXPORT_MODULE()
- (void)showWithURL:(NSURL *)URL - (void)showWithURL:(NSURL *)URL
{ {
if (!isEnabled) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
_showDate = [NSDate date]; _showDate = [NSDate date];
@ -90,6 +101,10 @@ RCT_EXPORT_MODULE()
- (void)hide - (void)hide
{ {
if (!isEnabled) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
const NSTimeInterval MIN_PRESENTED_TIME = 0.6; const NSTimeInterval MIN_PRESENTED_TIME = 0.6;
@ -117,6 +132,7 @@ RCT_EXPORT_MODULE()
@implementation RCTDevLoadingView @implementation RCTDevLoadingView
+ (NSString *)moduleName { return nil; } + (NSString *)moduleName { return nil; }
+ (void)setEnabled:(BOOL)enabled { }
@end @end

View File

@ -488,6 +488,11 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
// Perform layout (possibly animated) // Perform layout (possibly animated)
return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTResponseSenderBlock callback = self->_layoutAnimation.callback; RCTResponseSenderBlock callback = self->_layoutAnimation.callback;
// It's unsafe to call this callback more than once, so we nil it out here
// to make sure that doesn't happen.
_layoutAnimation.callback = nil;
__block NSUInteger completionsCalled = 0; __block NSUInteger completionsCalled = 0;
for (NSUInteger ii = 0; ii < frames.count; ii++) { for (NSUInteger ii = 0; ii < frames.count; ii++) {
NSNumber *reactTag = frameReactTags[ii]; NSNumber *reactTag = frameReactTags[ii];

View File

@ -589,7 +589,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "if [[ \"$CONFIGURATION\" == \"Debug\" ]] && [[ -d \"/tmp/RCTJSCProfiler\" ]]; then\n find \"${CONFIGURATION_BUILD_DIR}\" -name '*.app' | xargs -I{} sh -c 'mkdir -p \"$1/Frameworks\" && cp -r /tmp/RCTJSCProfiler/* \"$1/Frameworks\"' -- {}\nfi"; shellScript = "if [[ \"$CONFIGURATION\" == \"Debug\" ]] && [[ -d \"/tmp/RCTJSCProfiler\" ]]; then\n find \"${CONFIGURATION_BUILD_DIR}\" -name '*.app' | xargs -I{} sh -c 'cp -r /tmp/RCTJSCProfiler \"$1\"' -- {}\nfi";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */

View File

@ -17,4 +17,13 @@
@property (nonatomic, assign, readwrite) UIEdgeInsets contentInset; @property (nonatomic, assign, readwrite) UIEdgeInsets contentInset;
@property (nonatomic, assign, readwrite) BOOL automaticallyAdjustContentInsets; @property (nonatomic, assign, readwrite) BOOL automaticallyAdjustContentInsets;
/**
* Automatically adjusted content inset depends on view controller's top and bottom
* layout guides so if you've changed one of them (e.g. after rotation or manually) you should call this method
* to recalculate and refresh content inset.
* To handle case with changing navigation bar height call this method from viewDidLayoutSubviews:
* of your view controller.
*/
- (void)refreshContentInset;
@end @end

View File

@ -24,6 +24,7 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray) RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger) RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
- (NSDictionary *)constantsToExport - (NSDictionary *)constantsToExport
{ {

View File

@ -460,10 +460,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
_scrollView.frame = self.bounds; _scrollView.frame = self.bounds;
_scrollView.contentOffset = originalOffset; _scrollView.contentOffset = originalOffset;
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:YES];
[self updateClippedSubviews]; [self updateClippedSubviews];
} }
@ -523,6 +519,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[_scrollView zoomToRect:rect animated:animated]; [_scrollView zoomToRect:rect animated:animated];
} }
- (void)refreshContentInset
{
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:YES];
}
#pragma mark - ScrollView delegate #pragma mark - ScrollView delegate
#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \ #define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \

View File

@ -95,9 +95,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
{ {
[super layoutSubviews]; [super layoutSubviews];
_webView.frame = self.bounds; _webView.frame = self.bounds;
[RCTView autoAdjustInsetsForView:self
withScrollView:_webView.scrollView
updateOffset:YES];
} }
- (void)setContentInset:(UIEdgeInsets)contentInset - (void)setContentInset:(UIEdgeInsets)contentInset
@ -133,6 +130,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return event; return event;
} }
- (void)refreshContentInset
{
[RCTView autoAdjustInsetsForView:self
withScrollView:_webView.scrollView
updateOffset:YES];
}
#pragma mark - UIWebViewDelegate methods #pragma mark - UIWebViewDelegate methods
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request

View File

@ -16,13 +16,15 @@
#import "RCTUtils.h" #import "RCTUtils.h"
#import "RCTViewControllerProtocol.h" #import "RCTViewControllerProtocol.h"
#import "UIView+React.h" #import "UIView+React.h"
#import "RCTAutoInsetsProtocol.h"
@implementation RCTWrapperViewController @implementation RCTWrapperViewController
{ {
UIView *_wrapperView; UIView *_wrapperView;
UIView *_contentView; UIView *_contentView;
CGFloat _previousTopLayout; RCTEventDispatcher *_eventDispatcher;
CGFloat _previousBottomLayout; CGFloat _previousTopLayoutLength;
CGFloat _previousBottomLayoutLength;
} }
@synthesize currentTopLayoutGuide = _currentTopLayoutGuide; @synthesize currentTopLayoutGuide = _currentTopLayoutGuide;
@ -58,6 +60,32 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
_currentBottomLayoutGuide = self.bottomLayoutGuide; _currentBottomLayoutGuide = self.bottomLayoutGuide;
} }
static BOOL RCTFindScrollViewAndRefreshContentInsetInView(UIView *view)
{
if ([view conformsToProtocol:@protocol(RCTAutoInsetsProtocol)]) {
[(id <RCTAutoInsetsProtocol>) view refreshContentInset];
return YES;
}
for (UIView *subview in view.subviews) {
if (RCTFindScrollViewAndRefreshContentInsetInView(subview)) {
return YES;
}
}
return NO;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (_previousTopLayoutLength != _currentTopLayoutGuide.length ||
_previousBottomLayoutLength != _currentBottomLayoutGuide.length) {
RCTFindScrollViewAndRefreshContentInsetInView(_contentView);
_previousTopLayoutLength = _currentTopLayoutGuide.length;
_previousBottomLayoutLength = _currentBottomLayoutGuide.length;
}
}
static UIView *RCTFindNavBarShadowViewInView(UIView *view) static UIView *RCTFindNavBarShadowViewInView(UIView *view)
{ {
if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1) { if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1) {

2428
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,42 +47,42 @@
}, },
"dependencies": { "dependencies": {
"absolute-path": "0.0.0", "absolute-path": "0.0.0",
"babel": "5.8.21", "babel": "5.8.23",
"babel-core": "5.8.21", "babel-core": "5.8.23",
"bser": "1.0.0", "bser": "1.0.2",
"chalk": "1.0.0", "chalk": "1.1.1",
"connect": "2.8.3", "connect": "2.8.3",
"debug": "2.1.0", "debug": "2.2.0",
"graceful-fs": "4.1.2", "graceful-fs": "4.1.2",
"image-size": "0.3.5", "image-size": "0.3.5",
"immutable": "^3.7.4", "immutable": "3.7.5",
"joi": "5.1.0", "joi": "6.6.1",
"jstransform": "11.0.1", "jstransform": "11.0.3",
"module-deps": "3.5.6", "module-deps": "3.9.1",
"optimist": "0.6.1", "optimist": "0.6.1",
"progress": "^1.1.8", "progress": "1.1.8",
"promise": "^7.0.3", "promise": "7.0.4",
"react-timer-mixin": "^0.13.1", "react-timer-mixin": "0.13.2",
"react-tools": "git://github.com/facebook/react#b4e74e38e43ac53af8acd62c78c9213be0194245", "react-tools": "git://github.com/facebook/react#b4e74e38e43ac53af8acd62c78c9213be0194245",
"rebound": "^0.0.12", "rebound": "0.0.13",
"regenerator": "0.8.36", "regenerator": "0.8.36",
"sane": "^1.1.2", "sane": "^1.2.0",
"semver": "^4.3.6", "semver": "5.0.1",
"source-map": "0.1.31", "source-map": "0.4.4",
"stacktrace-parser": "0.1.3", "stacktrace-parser": "0.1.3",
"uglify-js": "2.4.16", "uglify-js": "2.4.24",
"underscore": "1.7.0", "underscore": "1.8.3",
"wordwrap": "^1.0.0", "wordwrap": "1.0.0",
"worker-farm": "^1.3.1", "worker-farm": "1.3.1",
"ws": "0.8.0", "ws": "0.8.0",
"yargs": "1.3.2", "yargs": "3.24.0",
"yeoman-environment": "^1.2.7", "yeoman-environment": "1.2.7",
"yeoman-generator": "^0.20.2" "yeoman-generator": "0.20.3"
}, },
"devDependencies": { "devDependencies": {
"jest-cli": "0.5.0", "jest-cli": "0.5.1",
"babel-eslint": "3.1.5", "babel-eslint": "4.1.1",
"eslint": "0.21.2", "eslint": "1.3.1",
"eslint-plugin-react": "2.3.0" "eslint-plugin-react": "3.3.1"
} }
} }

View File

@ -59,6 +59,14 @@ var options = parseCommandLine([{
type: 'string', type: 'string',
default: require.resolve('./transformer.js'), default: require.resolve('./transformer.js'),
description: 'Specify a custom transformer to be used (absolute path)' description: 'Specify a custom transformer to be used (absolute path)'
}, {
command: 'resetCache',
description: 'Removes cached files',
default: false,
}, {
command: 'reset-cache',
description: 'Removes cached files',
default: false,
}]); }]);
if (options.projectRoots) { if (options.projectRoots) {
@ -229,6 +237,7 @@ function getAppMiddleware(options) {
transformModulePath: transformerPath, transformModulePath: transformerPath,
assetRoots: options.assetRoots, assetRoots: options.assetRoots,
assetExts: ['png', 'jpeg', 'jpg'], assetExts: ['png', 'jpeg', 'jpg'],
resetCache: options.resetCache || options['reset-cache'],
polyfillModuleNames: [ polyfillModuleNames: [
require.resolve( require.resolve(
'../Libraries/JavaScriptAppEngine/polyfills/document.js' '../Libraries/JavaScriptAppEngine/polyfills/document.js'

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
jest jest
.dontMock('../../lib/getPlatformExtension')
.dontMock('../../lib/getAssetDataFromName') .dontMock('../../lib/getAssetDataFromName')
.dontMock('../'); .dontMock('../');
@ -47,6 +48,43 @@ describe('AssetServer', () => {
); );
}); });
pit('should work for the simple case with platform ext', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.ios.png': 'b ios image',
'b.android.png': 'b android image',
'c.png': 'c general image',
'c.android.png': 'c android image',
}
}
});
return Promise.all([
server.get('imgs/b.png', 'ios').then(
data => expect(data).toBe('b ios image')
),
server.get('imgs/b.png', 'android').then(
data => expect(data).toBe('b android image')
),
server.get('imgs/c.png', 'android').then(
data => expect(data).toBe('c android image')
),
server.get('imgs/c.png', 'ios').then(
data => expect(data).toBe('c general image')
),
server.get('imgs/c.png').then(
data => expect(data).toBe('c general image')
),
]);
});
pit('should work for the simple case with jpg', () => { pit('should work for the simple case with jpg', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
@ -95,6 +133,37 @@ describe('AssetServer', () => {
); );
}); });
pit('should pick the bigger one with platform ext', () => {
const 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',
'b@1x.ios.png': 'b1 ios image',
'b@2x.ios.png': 'b2 ios image',
'b@4x.ios.png': 'b4 ios image',
'b@4.5x.ios.png': 'b4.5 ios image',
}
}
});
return Promise.all([
server.get('imgs/b@3x.png').then(data =>
expect(data).toBe('b4 image')
),
server.get('imgs/b@3x.png', 'ios').then(data =>
expect(data).toBe('b4 ios image')
),
]);
});
pit('should support multiple project roots', () => { pit('should support multiple project roots', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root', '/root2'], projectRoots: ['/root', '/root2'],

View File

@ -20,7 +20,6 @@ const stat = Promise.denodeify(fs.stat);
const readDir = Promise.denodeify(fs.readdir); const readDir = Promise.denodeify(fs.readdir);
const readFile = Promise.denodeify(fs.readFile); const readFile = Promise.denodeify(fs.readFile);
const validateOpts = declareOpts({ const validateOpts = declareOpts({
projectRoots: { projectRoots: {
type: 'array', type: 'array',
@ -39,9 +38,9 @@ class AssetServer {
this._assetExts = opts.assetExts; this._assetExts = opts.assetExts;
} }
get(assetPath) { get(assetPath, platform = null) {
const assetData = getAssetDataFromName(assetPath); const assetData = getAssetDataFromName(assetPath);
return this._getAssetRecord(assetPath).then(record => { return this._getAssetRecord(assetPath, platform).then(record => {
for (let i = 0; i < record.scales.length; i++) { for (let i = 0; i < record.scales.length; i++) {
if (record.scales[i] >= assetData.resolution) { if (record.scales[i] >= assetData.resolution) {
return readFile(record.files[i]); return readFile(record.files[i]);
@ -52,14 +51,14 @@ class AssetServer {
}); });
} }
getAssetData(assetPath) { getAssetData(assetPath, platform = null) {
const nameData = getAssetDataFromName(assetPath); const nameData = getAssetDataFromName(assetPath);
const data = { const data = {
name: nameData.name, name: nameData.name,
type: nameData.type, type: nameData.type,
}; };
return this._getAssetRecord(assetPath).then(record => { return this._getAssetRecord(assetPath, platform).then(record => {
data.scales = record.scales; data.scales = record.scales;
return Promise.all( return Promise.all(
@ -85,9 +84,10 @@ class AssetServer {
* 1. We first parse the directory of the asset * 1. We first parse the directory of the asset
* 2. We check to find a matching directory in one of the project roots * 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 scales in this directory * 3. We then build a map of all assets and their scales in this directory
* 4. Then pick the closest resolution (rounding up) to the requested one * 4. Then try to pick platform-specific asset records
* 5. Then pick the closest resolution (rounding up) to the requested one
*/ */
_getAssetRecord(assetPath) { _getAssetRecord(assetPath, platform = null) {
const filename = path.basename(assetPath); const filename = path.basename(assetPath);
return ( return (
@ -104,11 +104,20 @@ class AssetServer {
const files = res[1]; const files = res[1];
const assetData = getAssetDataFromName(filename); const assetData = getAssetDataFromName(filename);
const map = this._buildAssetMap(dir, files); const map = this._buildAssetMap(dir, files, platform);
const record = map[assetData.assetName];
let record;
if (platform != null){
record = map[getAssetKey(assetData.assetName, platform)] ||
map[assetData.assetName];
} else {
record = map[assetData.assetName];
}
if (!record) { if (!record) {
throw new Error('Asset not found'); throw new Error(
`Asset not found: ${assetPath} for platform: ${platform}`
);
} }
return record; return record;
@ -141,9 +150,10 @@ class AssetServer {
const map = Object.create(null); const map = Object.create(null);
assets.forEach(function(asset, i) { assets.forEach(function(asset, i) {
const file = files[i]; const file = files[i];
let record = map[asset.assetName]; const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map[assetKey];
if (!record) { if (!record) {
record = map[asset.assetName] = { record = map[assetKey] = {
scales: [], scales: [],
files: [], files: [],
}; };
@ -151,6 +161,7 @@ class AssetServer {
let insertIndex; let insertIndex;
const length = record.scales.length; const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) { for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) { if (asset.resolution < record.scales[insertIndex]) {
break; break;
@ -164,4 +175,12 @@ class AssetServer {
} }
} }
function getAssetKey(assetName, platform) {
if (platform != null) {
return `${assetName} : ${platform}`;
} else {
return assetName;
}
}
module.exports = AssetServer; module.exports = AssetServer;

View File

@ -115,13 +115,18 @@ class Bundle {
getMinifiedSourceAndMap() { getMinifiedSourceAndMap() {
this._assertFinalized(); this._assertFinalized();
if (this._minifiedSourceAndMap) {
return this._minifiedSourceAndMap;
}
const source = this._getSource(); const source = this._getSource();
try { try {
return UglifyJS.minify(source, { this._minifiedSourceAndMap = UglifyJS.minify(source, {
fromString: true, fromString: true,
outSourceMap: 'bundle.js', outSourceMap: 'bundle.js',
inSourceMap: this.getSourceMap(), inSourceMap: this.getSourceMap(),
}); });
return this._minifiedSourceAndMap;
} catch(e) { } catch(e) {
// Sometimes, when somebody is using a new syntax feature that we // Sometimes, when somebody is using a new syntax feature that we
// don't yet have transform for, the untransformed line is sent to // don't yet have transform for, the untransformed line is sent to
@ -186,6 +191,10 @@ class Bundle {
options = options || {}; options = options || {};
if (options.minify) {
return this.getMinifiedSourceAndMap().map;
}
if (this._shouldCombineSourceMaps) { if (this._shouldCombineSourceMaps) {
return this._getCombinedSourceMaps(options); return this._getCombinedSourceMaps(options);
} }

View File

@ -154,7 +154,7 @@ class Bundler {
bundle.setMainModuleId(result.mainModuleId); bundle.setMainModuleId(result.mainModuleId);
return Promise.all( return Promise.all(
result.dependencies.map( result.dependencies.map(
module => this._transformModule(bundle, module).then(transformed => { module => this._transformModule(bundle, module, platform).then(transformed => {
if (bar) { if (bar) {
bar.tick(); bar.tick();
} }
@ -182,13 +182,13 @@ class Bundler {
return this._resolver.getDependencies(main, { dev: isDev, platform }); return this._resolver.getDependencies(main, { dev: isDev, platform });
} }
_transformModule(bundle, module) { _transformModule(bundle, module, platform = null) {
let transform; let transform;
if (module.isAsset_DEPRECATED()) { if (module.isAsset_DEPRECATED()) {
transform = this.generateAssetModule_DEPRECATED(bundle, module); transform = this.generateAssetModule_DEPRECATED(bundle, module);
} else if (module.isAsset()) { } else if (module.isAsset()) {
transform = this.generateAssetModule(bundle, module); transform = this.generateAssetModule(bundle, module, platform);
} else if (module.isJSON()) { } else if (module.isJSON()) {
transform = generateJSONModule(module); transform = generateJSONModule(module);
} else { } else {
@ -243,12 +243,12 @@ class Bundler {
}); });
} }
generateAssetModule(bundle, module) { generateAssetModule(bundle, module, platform = null) {
const relPath = getPathRelativeToRoot(this._projectRoots, module.path); const relPath = getPathRelativeToRoot(this._projectRoots, module.path);
return Promise.all([ return Promise.all([
sizeOf(module.path), sizeOf(module.path),
this._assetServer.getAssetData(relPath), this._assetServer.getAssetData(relPath, platform),
]).then(function(res) { ]).then(function(res) {
const dimensions = res[0]; const dimensions = res[0];
const assetData = res[1]; const assetData = res[1];

View File

@ -16,6 +16,7 @@ jest
.dontMock('../../crawlers') .dontMock('../../crawlers')
.dontMock('../../crawlers/node') .dontMock('../../crawlers/node')
.dontMock('../../replacePatterns') .dontMock('../../replacePatterns')
.dontMock('../../../lib/getPlatformExtension')
.dontMock('../../../lib/getAssetDataFromName') .dontMock('../../../lib/getAssetDataFromName')
.dontMock('../../fastfs') .dontMock('../../fastfs')
.dontMock('../../AssetModule_DEPRECATED') .dontMock('../../AssetModule_DEPRECATED')
@ -424,6 +425,90 @@ describe('DependencyGraph', function() {
}); });
}); });
pit('should respect platform extension in assets', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("./imgs/a.png");',
'require("./imgs/b.png");',
'require("./imgs/c.png");',
].join('\n'),
'imgs': {
'a@1.5x.ios.png': '',
'b@.7x.ios.png': '',
'c.ios.png': '',
'c@2x.ios.png': '',
},
'package.json': JSON.stringify({
name: 'rootPackage'
}),
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
dgraph.setup({ platform: 'ios' });
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.js',
dependencies: [
'./imgs/a.png',
'./imgs/b.png',
'./imgs/c.png',
],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'rootPackage/imgs/a.png',
path: '/root/imgs/a@1.5x.ios.png',
resolution: 1.5,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
{
id: 'rootPackage/imgs/b.png',
path: '/root/imgs/b@.7x.ios.png',
resolution: 0.7,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
{
id: 'rootPackage/imgs/c.png',
path: '/root/imgs/c.ios.png',
resolution: 1,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
]);
});
});
pit('Deprecated and relative assets can live together', function() { pit('Deprecated and relative assets can live together', function() {
var root = '/root'; var root = '/root';
fs.__setMockFilesystem({ fs.__setMockFilesystem({

View File

@ -1,4 +1,4 @@
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved. * All rights reserved.
* *
@ -17,6 +17,7 @@ const crawl = require('../crawlers');
const debug = require('debug')('DependencyGraph'); const debug = require('debug')('DependencyGraph');
const declareOpts = require('../../lib/declareOpts'); const declareOpts = require('../../lib/declareOpts');
const getAssetDataFromName = require('../../lib/getAssetDataFromName'); const getAssetDataFromName = require('../../lib/getAssetDataFromName');
const getPontentialPlatformExt = require('../../lib/getPlatformExtension');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const path = require('path'); const path = require('path');
const util = require('util'); const util = require('util');
@ -274,7 +275,7 @@ class DependencyGraph {
// `platformExt` could be set in the `setup` method. // `platformExt` could be set in the `setup` method.
if (!this._platformExt) { if (!this._platformExt) {
const platformExt = getPlatformExt(entryPath); const platformExt = getPontentialPlatformExt(entryPath);
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
this._platformExt = platformExt; this._platformExt = platformExt;
} else { } else {
@ -390,12 +391,18 @@ class DependencyGraph {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (this._isAssetFile(potentialModulePath)) { if (this._isAssetFile(potentialModulePath)) {
const {name, type} = getAssetDataFromName(potentialModulePath); const {name, type} = getAssetDataFromName(potentialModulePath);
const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type);
let pattern = '^' + name + '(@[\\d\\.]+x)?';
if (this._platformExt != null) {
pattern += '(\\.' + this._platformExt + ')?';
}
pattern += '\\.' + type;
// We arbitrarly grab the first one, because scale selection // We arbitrarly grab the first one, because scale selection
// will happen somewhere // will happen somewhere
const [assetFile] = this._fastfs.matches( const [assetFile] = this._fastfs.matches(
path.dirname(potentialModulePath), path.dirname(potentialModulePath),
pattern new RegExp(pattern)
); );
if (assetFile) { if (assetFile) {
@ -496,7 +503,7 @@ class DependencyGraph {
const modules = this._hasteMap[name]; const modules = this._hasteMap[name];
if (this._platformExt != null) { if (this._platformExt != null) {
for (let i = 0; i < modules.length; i++) { for (let i = 0; i < modules.length; i++) {
if (getPlatformExt(modules[i].path) === this._platformExt) { if (getPontentialPlatformExt(modules[i].path) === this._platformExt) {
return modules[i]; return modules[i];
} }
} }
@ -662,15 +669,6 @@ function normalizePath(modulePath) {
return modulePath.replace(/\/$/, ''); return modulePath.replace(/\/$/, '');
} }
// Extract platform extension: index.ios.js -> ios
function getPlatformExt(file) {
const parts = path.basename(file).split('.');
if (parts.length < 3) {
return null;
}
return parts[parts.length - 2];
}
util.inherits(NotFoundError, Error); util.inherits(NotFoundError, Error);
module.exports = DependencyGraph; module.exports = DependencyGraph;

View File

@ -245,8 +245,16 @@ describe('processRequest', () => {
expect(res.end).toBeCalledWith('i am image'); expect(res.end).toBeCalledWith('i am image');
}); });
it('should return 404', () => { it('should parse the platform option', () => {
const req = {url: '/assets/imgs/a.png?platform=ios'};
const res = {end: jest.genMockFn()};
AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image'));
server.processRequest(req, res);
jest.runAllTimers();
expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios');
expect(res.end).toBeCalledWith('i am image');
}); });
}); });

View File

@ -278,7 +278,8 @@ class Server {
_processAssetsRequest(req, res) { _processAssetsRequest(req, res) {
const urlObj = url.parse(req.url, true); const urlObj = url.parse(req.url, true);
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
this._assetServer.get(assetPath[1]) const assetEvent = Activity.startEvent(`processing asset request ${assetPath[1]}`);
this._assetServer.get(assetPath[1], urlObj.query.platform)
.then( .then(
data => res.end(data), data => res.end(data),
error => { error => {
@ -286,7 +287,7 @@ class Server {
res.writeHead('404'); res.writeHead('404');
res.end('Asset not found'); res.end('Asset not found');
} }
).done(); ).done(() => Activity.endEvent(assetEvent));
} }
_processProfile(req, res) { _processProfile(req, res) {
@ -370,7 +371,9 @@ class Server {
res.end(bundleSource); res.end(bundleSource);
Activity.endEvent(startReqEventId); Activity.endEvent(startReqEventId);
} else if (requestType === 'map') { } else if (requestType === 'map') {
var sourceMap = JSON.stringify(p.getSourceMap()); var sourceMap = JSON.stringify(p.getSourceMap({
minify: options.minify,
}));
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
res.end(sourceMap); res.end(sourceMap);
Activity.endEvent(startReqEventId); Activity.endEvent(startReqEventId);

View File

@ -12,6 +12,7 @@ const Bundle = require('../Bundler/Bundle');
const Promise = require('promise'); const Promise = require('promise');
const bser = require('bser'); const bser = require('bser');
const debug = require('debug')('ReactPackager:SocketClient'); const debug = require('debug')('ReactPackager:SocketClient');
const fs = require('fs');
const net = require('net'); const net = require('net');
const path = require('path'); const path = require('path');
const tmpdir = require('os').tmpdir(); const tmpdir = require('os').tmpdir();
@ -29,7 +30,16 @@ class SocketClient {
this._sock = net.connect(sockPath); this._sock = net.connect(sockPath);
this._ready = new Promise((resolve, reject) => { this._ready = new Promise((resolve, reject) => {
this._sock.on('connect', () => resolve(this)); this._sock.on('connect', () => resolve(this));
this._sock.on('error', (e) => reject(e)); this._sock.on('error', (e) => {
e.message = `Error connecting to server on ${sockPath} ` +
`with error: ${e.message}`;
if (fs.existsSync(LOG_PATH)) {
e.message += '\nServer logs:\n' + fs.readFileSync(LOG_PATH, 'utf8');
}
reject(e);
});
}); });
this._resolvers = Object.create(null); this._resolvers = Object.create(null);

View File

@ -16,6 +16,7 @@ const fs = require('fs');
const net = require('net'); const net = require('net');
const MAX_IDLE_TIME = 30 * 1000; const MAX_IDLE_TIME = 30 * 1000;
const MAX_STARTUP_TIME = 5 * 60 * 1000;
class SocketServer { class SocketServer {
constructor(sockPath, options) { constructor(sockPath, options) {
@ -35,13 +36,15 @@ class SocketServer {
process.on('exit', () => fs.unlinkSync(sockPath)); process.on('exit', () => fs.unlinkSync(sockPath));
}); });
}); });
this._numConnections = 0;
this._server.on('connection', (sock) => this._handleConnection(sock)); this._server.on('connection', (sock) => this._handleConnection(sock));
// Disable the file watcher. // Disable the file watcher.
options.nonPersistent = true; options.nonPersistent = true;
this._packagerServer = new Server(options); this._packagerServer = new Server(options);
this._jobs = 0; this._jobs = 0;
this._dieEventually(); this._dieEventually(MAX_STARTUP_TIME);
} }
onReady() { onReady() {
@ -50,10 +53,13 @@ class SocketServer {
_handleConnection(sock) { _handleConnection(sock) {
debug('connection to server', process.pid); debug('connection to server', process.pid);
this._numConnections++;
sock.on('close', () => this._numConnections--);
const bunser = new bser.BunserBuf(); const bunser = new bser.BunserBuf();
sock.on('data', (buf) => bunser.append(buf)); sock.on('data', (buf) => bunser.append(buf));
bunser.on('value', (m) => this._handleMessage(sock, m)); bunser.on('value', (m) => this._handleMessage(sock, m));
bunser.on('error', (e) => console.error(e));
} }
_handleMessage(sock, m) { _handleMessage(sock, m) {
@ -113,15 +119,15 @@ class SocketServer {
})); }));
} }
_dieEventually() { _dieEventually(delay = MAX_IDLE_TIME) {
clearTimeout(this._deathTimer); clearTimeout(this._deathTimer);
this._deathTimer = setTimeout(() => { this._deathTimer = setTimeout(() => {
if (this._jobs <= 0) { if (this._jobs <= 0 && this._numConnections <= 0) {
debug('server dying', process.pid); debug('server dying', process.pid);
process.exit(); process.exit();
} }
this._dieEventually(); this._dieEventually();
}, MAX_IDLE_TIME); }, delay);
} }
static listenOnServerIPCMessages() { static listenOnServerIPCMessages() {

View File

@ -20,7 +20,7 @@ const path = require('path');
const tmpdir = require('os').tmpdir(); const tmpdir = require('os').tmpdir();
const {spawn} = require('child_process'); const {spawn} = require('child_process');
const CREATE_SERVER_TIMEOUT = 60000; const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000;
const SocketInterface = { const SocketInterface = {
getOrCreateSocketFor(options) { getOrCreateSocketFor(options) {
@ -42,8 +42,16 @@ const SocketInterface = {
if (fs.existsSync(sockPath)) { if (fs.existsSync(sockPath)) {
var sock = net.connect(sockPath); var sock = net.connect(sockPath);
sock.on('connect', () => { sock.on('connect', () => {
sock.end(); SocketClient.create(sockPath).then(
resolve(SocketClient.create(sockPath)); client => {
sock.end();
resolve(client);
},
error => {
sock.end();
reject(error);
}
);
}); });
sock.on('error', (e) => { sock.on('error', (e) => {
try { try {

View File

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

View File

@ -0,0 +1,123 @@
/**
* 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';
jest.dontMock('../getPlatformExtension')
.dontMock('../getAssetDataFromName');
describe('getAssetDataFromName', () => {
let getAssetDataFromName;
beforeEach(() => {
getAssetDataFromName = require('../getAssetDataFromName');
});
it('should get data from name', () => {
expect(getAssetDataFromName('a/b/c.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c@1x.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c@2.5x.png')).toEqual({
resolution: 2.5,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c.ios.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
expect(getAssetDataFromName('a/b/c@1x.ios.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
expect(getAssetDataFromName('a/b/c@2.5x.ios.png')).toEqual({
resolution: 2.5,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
});
describe('resolution extraction', () => {
it('should extract resolution simple case', () => {
var data = getAssetDataFromName('test@2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 2,
type: 'png',
name: 'test',
platform: null,
});
});
it('should default resolution to 1', () => {
var data = getAssetDataFromName('test.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1,
type: 'png',
name: 'test',
platform: null,
});
});
it('should support float', () => {
var data = getAssetDataFromName('test@1.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1.1,
type: 'png',
name: 'test',
platform: null,
});
data = getAssetDataFromName('test@.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.1,
type: 'png',
name: 'test',
platform: null,
});
data = getAssetDataFromName('test@0.2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.2,
type: 'png',
name: 'test',
platform: null,
});
});
});
});

View File

@ -0,0 +1,24 @@
/**
* 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';
jest.dontMock('../getPlatformExtension');
describe('getPlatformExtension', function() {
it('should get platform ext', function() {
var getPlatformExtension = require('../getPlatformExtension');
expect(getPlatformExtension('a.ios.js')).toBe('ios');
expect(getPlatformExtension('a.android.js')).toBe('android');
expect(getPlatformExtension('/b/c/a.ios.js')).toBe('ios');
expect(getPlatformExtension('/b/c.android/a.ios.js')).toBe('ios');
expect(getPlatformExtension('/b/c/a@1.5x.ios.png')).toBe('ios');
expect(getPlatformExtension('/b/c/a@1.5x.lol.png')).toBe(null);
expect(getPlatformExtension('/b/c/a.lol.png')).toBe(null);
});
});

View File

@ -1,14 +1,29 @@
/**
* 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'; 'use strict';
var path = require('path'); const path = require('path');
const getPlatformExtension = require('./getPlatformExtension');
function getAssetDataFromName(filename) { function getAssetDataFromName(filename) {
var ext = path.extname(filename); const ext = path.extname(filename);
const platformExt = getPlatformExtension(filename);
var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$'); let pattern = '@([\\d\\.]+)x';
if (platformExt != null) {
pattern += '(\\.' + platformExt + ')?';
}
pattern += '\\' + ext + '$';
const re = new RegExp(pattern);
var match = filename.match(re); const match = filename.match(re);
var resolution; let resolution;
if (!(match && match[1])) { if (!(match && match[1])) {
resolution = 1; resolution = 1;
@ -19,12 +34,21 @@ function getAssetDataFromName(filename) {
} }
} }
var assetName = match ? filename.replace(re, ext) : filename; let assetName;
if (match) {
assetName = filename.replace(re, ext);
} else if (platformExt != null) {
assetName = filename.replace(new RegExp(`\\.${platformExt}\\${ext}`), ext);
} else {
assetName = filename;
}
return { return {
resolution: resolution, resolution: resolution,
assetName: assetName, assetName: assetName,
type: ext.slice(1), type: ext.slice(1),
name: path.basename(assetName, ext) name: path.basename(assetName, ext),
platform: platformExt,
}; };
} }

View File

@ -0,0 +1,28 @@
/**
* 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';
const path = require('path');
const SUPPORTED_PLATFORM_EXTS = ['android', 'ios'];
const re = new RegExp(
'[^\\.]+\\.(' + SUPPORTED_PLATFORM_EXTS.join('|') + ')\\.\\w+$'
);
// Extract platform extension: index.ios.js -> ios
function getPlatformExtension(file) {
const match = file.match(re);
if (match && match[1]) {
return match[1];
}
return null;
}
module.exports = getPlatformExtension;

View File

@ -39,6 +39,7 @@ function transform(srcTxt, filename, options) {
'es7.objectRestSpread', 'es7.objectRestSpread',
'flow', 'flow',
'react', 'react',
'react.displayName',
'regenerator', 'regenerator',
], ],
plugins: plugins, plugins: plugins,