Updates from Tue 8 Sep
|
@ -36,7 +36,7 @@
|
|||
* 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
|
||||
|
|
|
@ -216,7 +216,7 @@
|
|||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0610;
|
||||
LastUpgradeCheck = 0700;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Movies" */;
|
||||
|
@ -358,6 +358,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Movies;
|
||||
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**";
|
||||
};
|
||||
|
@ -376,6 +377,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Movies;
|
||||
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**";
|
||||
};
|
||||
|
@ -401,6 +403,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0610"
|
||||
LastUpgradeVersion = "0700"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -38,6 +38,8 @@
|
|||
ReferencedContainer = "container:Movies.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
|
@ -47,8 +49,10 @@
|
|||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
|
@ -66,7 +70,8 @@
|
|||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
* 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
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.facebook.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
|
@ -33,6 +33,11 @@
|
|||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
@ -47,11 +52,5 @@
|
|||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<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>
|
||||
</plist>
|
||||
|
|
|
@ -22,7 +22,9 @@ var {
|
|||
|
||||
var MAX_VALUE = 200;
|
||||
|
||||
function getStyleFromScore(score: number): {color: string} {
|
||||
import type { StyleObj } from 'StyleSheetTypes';
|
||||
|
||||
function getStyleFromScore(score: number): StyleObj {
|
||||
if (score < 0) {
|
||||
return styles.noScore;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
* 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
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
* 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
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
});
|
|
@ -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',
|
||||
description:
|
||||
|
@ -384,5 +396,9 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
}
|
||||
},
|
||||
gif: {
|
||||
flex: 1,
|
||||
height: 200,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -18,27 +18,12 @@
|
|||
var React = require('react-native');
|
||||
var {
|
||||
AlertIOS,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var Button = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
onPress={this.props.onPress}
|
||||
style={styles.button}
|
||||
underlayColor="#eeeeee">
|
||||
<Text>
|
||||
{this.props.children}
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
},
|
||||
});
|
||||
var UIExplorerButton = require('./UIExplorerButton');
|
||||
|
||||
var TimerTester = React.createClass({
|
||||
mixins: [TimerMixin],
|
||||
|
@ -52,9 +37,9 @@ var TimerTester = React.createClass({
|
|||
render: function() {
|
||||
var args = 'fn' + (this.props.dt !== undefined ? ', ' + this.props.dt : '');
|
||||
return (
|
||||
<Button onPress={this._run}>
|
||||
<UIExplorerButton onPress={this._run}>
|
||||
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.title = 'Timers, TimerMixin';
|
||||
exports.description = 'The TimerMixin provides timer functions for executing ' +
|
||||
|
@ -183,9 +156,9 @@ exports.examples = [
|
|||
if (this.state.showTimer) {
|
||||
var timer = [
|
||||
<TimerTester ref="interval" dt={25} type="setInterval" />,
|
||||
<Button onPress={() => this.refs.interval.clear() }>
|
||||
<UIExplorerButton onPress={() => this.refs.interval.clear() }>
|
||||
Clear interval
|
||||
</Button>
|
||||
</UIExplorerButton>
|
||||
];
|
||||
var toggleText = 'Unmount timer';
|
||||
} else {
|
||||
|
@ -195,9 +168,9 @@ exports.examples = [
|
|||
return (
|
||||
<View>
|
||||
{timer}
|
||||
<Button onPress={this._toggleTimer}>
|
||||
<UIExplorerButton onPress={this._toggleTimer}>
|
||||
{toggleText}
|
||||
</Button>
|
||||
</UIExplorerButton>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
|
||||
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.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 */; };
|
||||
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
|
||||
|
@ -96,6 +97,13 @@
|
|||
remoteGlobalIDString = 134814201AA4EA6300B7C361;
|
||||
remoteInfo = RCTGeolocation;
|
||||
};
|
||||
138DEE081B9EDDDB007F4EA5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
|
||||
remoteInfo = RCTImage;
|
||||
};
|
||||
139FDED81B0651EA00C62182 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -254,6 +263,7 @@
|
|||
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */,
|
||||
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */,
|
||||
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */,
|
||||
138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */,
|
||||
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */,
|
||||
13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */,
|
||||
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */,
|
||||
|
@ -281,13 +291,14 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
14AADEFF1AC3DB95002390C9 /* React.xcodeproj */,
|
||||
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */,
|
||||
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */,
|
||||
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */,
|
||||
138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */,
|
||||
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
|
||||
13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
|
||||
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */,
|
||||
134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
|
||||
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */,
|
||||
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */,
|
||||
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */,
|
||||
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
|
||||
|
@ -337,6 +348,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
138DEE031B9EDDDB007F4EA5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
139FDECB1B0651EA00C62182 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -624,6 +643,10 @@
|
|||
ProductGroup = 134454561AAFCAAE003F0779 /* Products */;
|
||||
ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 138DEE031B9EDDDB007F4EA5 /* Products */;
|
||||
ProjectRef = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 134A8A211AACED6A00945AAE /* Products */;
|
||||
ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
|
||||
|
@ -714,6 +737,13 @@
|
|||
remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */;
|
||||
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 */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
* 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
|
||||
|
|
|
@ -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;
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 267 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
@ -14,11 +14,17 @@
|
|||
|
||||
#import "RCTAssert.h"
|
||||
|
||||
@interface IntegrationTests : XCTestCase
|
||||
#define RCT_TEST(name) \
|
||||
- (void)test##name \
|
||||
{ \
|
||||
[_runner runTest:_cmd module:@#name]; \
|
||||
}
|
||||
|
||||
@interface UIExplorerIntegrationTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation IntegrationTests
|
||||
@implementation UIExplorerIntegrationTests
|
||||
{
|
||||
RCTTestRunner *_runner;
|
||||
}
|
||||
|
@ -36,11 +42,6 @@
|
|||
|
||||
#pragma mark Logic Tests
|
||||
|
||||
- (void)testTheTester
|
||||
{
|
||||
[_runner runTest:_cmd module:@"IntegrationTestHarnessTest"];
|
||||
}
|
||||
|
||||
- (void)testTheTester_waitOneFrame
|
||||
{
|
||||
[_runner runTest:_cmd
|
||||
|
@ -57,38 +58,12 @@
|
|||
expectErrorRegex:@"because shouldThrow"];
|
||||
}
|
||||
|
||||
- (void)testTimers
|
||||
{
|
||||
[_runner runTest:_cmd module:@"TimersTest"];
|
||||
}
|
||||
|
||||
- (void)testAsyncStorage
|
||||
{
|
||||
[_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"];
|
||||
}
|
||||
RCT_TEST(TimersTest)
|
||||
RCT_TEST(IntegrationTestHarnessTest)
|
||||
RCT_TEST(AsyncStorageTest)
|
||||
// RCT_TEST(LayoutEventsTest) -- Disabled: #8153468
|
||||
RCT_TEST(AppEventsTest)
|
||||
RCT_TEST(PromiseTest)
|
||||
// RCT_TEST(SimpleSnapshotTest) -- Disabled: #8153475
|
||||
|
||||
@end
|
|
@ -19,7 +19,7 @@ var {
|
|||
Text,
|
||||
View,
|
||||
} = React;
|
||||
var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager;
|
||||
var TestModule = NativeModules.TestModule;
|
||||
|
||||
var deepDiffer = require('deepDiffer');
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ var {
|
|||
Text,
|
||||
View,
|
||||
} = React;
|
||||
var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager;
|
||||
var TestModule = NativeModules.TestModule;
|
||||
|
||||
var deepDiffer = require('deepDiffer');
|
||||
|
||||
|
|
|
@ -60,7 +60,8 @@ var APIS = [
|
|||
require('./ActionSheetIOSExample'),
|
||||
require('./AdSupportIOSExample'),
|
||||
require('./AlertIOSExample'),
|
||||
require('./AnimationExample/AnExApp'),
|
||||
require('./AnimatedExample'),
|
||||
require('./AnimatedGratuitousApp/AnExApp'),
|
||||
require('./AppStateIOSExample'),
|
||||
require('./AsyncStorageExample'),
|
||||
require('./BorderExample'),
|
||||
|
|
|
@ -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)
|
||||
|
||||
CERT ?= "iPhone Developer"
|
||||
CERT ?= iPhone Developer
|
||||
|
||||
ARCHS = x86_64 arm64 armv7 i386
|
||||
|
||||
|
@ -26,13 +26,12 @@ else
|
|||
cp $^
|
||||
endif
|
||||
|
||||
/tmp/JSCProfiler:
|
||||
/tmp/RCTJSCProfiler:
|
||||
mkdir -p $@
|
||||
|
||||
.PRECIOUS: RCTJSCProfiler.ios8.dylib
|
||||
RCTJSCProfiler.ios8.dylib: RCTJSCProfiler_unsigned.ios8.dylib
|
||||
cp $< $@
|
||||
codesign -f -s ${CERT} $@ || rm $@
|
||||
codesign -f -s "${CERT}" $@
|
||||
|
||||
.PRECIOUS: RCTJSCProfiler_unsigned.ios8.dylib
|
||||
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
|
||||
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 ..
|
||||
|
||||
.PRECIOUS: download/yajl-2.1.0.tar.gz
|
||||
|
@ -87,7 +86,7 @@ download/yajl-2.1.0.tar.gz:
|
|||
|
||||
.PRECIOUS: download/%
|
||||
download/%: download/%.tar.gz
|
||||
tar -zxvf $< -C `dirname $@`
|
||||
tar -zxvf $< -C `dirname $@` > /dev/null
|
||||
|
||||
.PRECIOUS: %.tar.gz
|
||||
%.tar.gz:
|
||||
|
|
|
@ -503,6 +503,12 @@ type ValueListenerCallback = (state: {value: number}) => void;
|
|||
|
||||
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 {
|
||||
_value: number;
|
||||
_offset: number;
|
||||
|
@ -526,6 +532,10 @@ class AnimatedValue extends AnimatedWithChildren {
|
|||
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 {
|
||||
if (this._animation) {
|
||||
this._animation.stop();
|
||||
|
@ -534,15 +544,29 @@ class AnimatedValue extends AnimatedWithChildren {
|
|||
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 {
|
||||
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 {
|
||||
this._value += this._offset;
|
||||
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 {
|
||||
var id = String(_uniqueId++);
|
||||
this._listeners[id] = callback;
|
||||
|
@ -557,6 +581,30 @@ class AnimatedValue extends AnimatedWithChildren {
|
|||
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 {
|
||||
var handle = InteractionManager.createInteractionHandle();
|
||||
var previousAnimation = this._animation;
|
||||
|
@ -576,27 +624,22 @@ class AnimatedValue extends AnimatedWithChildren {
|
|||
);
|
||||
}
|
||||
|
||||
stopAnimation(callback?: ?(value: number) => void): void {
|
||||
this.stopTracking();
|
||||
this._animation && this._animation.stop();
|
||||
this._animation = null;
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally.
|
||||
*/
|
||||
stopTracking(): void {
|
||||
this._tracking && this._tracking.__detach();
|
||||
this._tracking = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally.
|
||||
*/
|
||||
track(tracking: Animated): void {
|
||||
this.stopTracking();
|
||||
this._tracking = tracking;
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, Interpolation.create(config));
|
||||
}
|
||||
|
||||
_updateValue(value: number): void {
|
||||
this._value = value;
|
||||
_flush(this);
|
||||
|
@ -607,6 +650,45 @@ class AnimatedValue extends AnimatedWithChildren {
|
|||
}
|
||||
|
||||
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 {
|
||||
x: AnimatedValue;
|
||||
y: AnimatedValue;
|
||||
|
@ -677,6 +759,13 @@ class AnimatedValueXY extends AnimatedWithChildren {
|
|||
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} {
|
||||
return {
|
||||
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}> {
|
||||
return [
|
||||
{translateX: this.x},
|
||||
|
@ -1235,21 +1333,6 @@ var stagger = function(
|
|||
|
||||
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};
|
||||
var event = function(
|
||||
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 = {
|
||||
delay,
|
||||
sequence,
|
||||
parallel,
|
||||
stagger,
|
||||
/**
|
||||
* Standard value class for driving animations. Typically initialized with
|
||||
* `new Animated.Value(0);`
|
||||
*/
|
||||
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,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Spring animation based on Rebound and Origami. Tracks velocity state to
|
||||
* create fluid motions as the `toValue` updates, and can be chained together.
|
||||
*/
|
||||
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,
|
||||
|
||||
Value: AnimatedValue,
|
||||
ValueXY: AnimatedValueXY,
|
||||
__PropsOnlyForTests: AnimatedProps,
|
||||
View: createAnimatedComponent(View),
|
||||
Text: createAnimatedComponent(Text),
|
||||
Image: createAnimatedComponent(Image),
|
||||
/**
|
||||
* Make any React component Animatable. Used to create `Animated.View`, etc.
|
||||
*/
|
||||
createAnimatedComponent,
|
||||
|
||||
__PropsOnlyForTests: AnimatedProps,
|
||||
};
|
||||
|
|
|
@ -123,12 +123,18 @@ class Easing {
|
|||
return easing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an easing function backwards.
|
||||
*/
|
||||
static out(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return (t) => 1 - easing(1 - t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes any easing function symmetrical.
|
||||
*/
|
||||
static inOut(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
|
|
|
@ -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 */;
|
||||
}
|
|
@ -49,6 +49,7 @@ var AndroidTextInputAttributes = {
|
|||
keyboardType: true,
|
||||
mostRecentEventCount: true,
|
||||
multiline: true,
|
||||
numberOfLines: true,
|
||||
password: true,
|
||||
placeholder: true,
|
||||
placeholderTextColor: true,
|
||||
|
@ -193,6 +194,12 @@ var TextInput = React.createClass({
|
|||
* @platform ios
|
||||
*/
|
||||
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
|
||||
* automatically enables it when there is text. The default value is false.
|
||||
|
@ -484,6 +491,7 @@ var TextInput = React.createClass({
|
|||
keyboardType={this.props.keyboardType}
|
||||
mostRecentEventCount={this.state.mostRecentEventCount}
|
||||
multiline={this.props.multiline}
|
||||
numberOfLines={this.props.numberOfLines}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
onChange={this._onChange}
|
||||
|
|
|
@ -209,6 +209,8 @@ var TouchableHighlight = React.createClass({
|
|||
return (
|
||||
<View
|
||||
accessible={true}
|
||||
accessibilityComponentType={this.props.accessibilityComponentType}
|
||||
accessibilityTraits={this.props.accessibilityTraits}
|
||||
ref={UNDERLAY_REF}
|
||||
style={this.state.underlayStyle}
|
||||
onLayout={this.props.onLayout}
|
||||
|
|
|
@ -156,6 +156,8 @@ var TouchableOpacity = React.createClass({
|
|||
return (
|
||||
<Animated.View
|
||||
accessible={true}
|
||||
accessibilityComponentType={this.props.accessibilityComponentType}
|
||||
accessibilityTraits={this.props.accessibilityTraits}
|
||||
style={[this.props.style, {opacity: this.state.anim}]}
|
||||
testID={this.props.testID}
|
||||
onLayout={this.props.onLayout}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
var React = require('React');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('Touchable');
|
||||
var View = require('View');
|
||||
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
|
||||
var onlyChild = require('onlyChild');
|
||||
|
||||
|
@ -36,11 +37,16 @@ var TouchableWithoutFeedback = React.createClass({
|
|||
mixins: [TimerMixin, Touchable.Mixin],
|
||||
|
||||
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
|
||||
* that steals the responder lock).
|
||||
*/
|
||||
accessible: React.PropTypes.bool,
|
||||
onPress: React.PropTypes.func,
|
||||
onPressIn: 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
|
||||
return (React: any).cloneElement(onlyChild(this.props.children), {
|
||||
accessible: this.props.accessible !== false,
|
||||
accessibilityComponentType: this.props.accessibilityComponentType,
|
||||
accessibilityTraits: this.props.accessibilityTraits,
|
||||
testID: this.props.testID,
|
||||
onLayout: this.props.onLayout,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
|
|
|
@ -44,6 +44,13 @@ var AccessibilityTraits = [
|
|||
'pageTurn',
|
||||
];
|
||||
|
||||
var AccessibilityComponentType = [
|
||||
'none',
|
||||
'button',
|
||||
'radiobutton_checked',
|
||||
'radiobutton_unchecked',
|
||||
];
|
||||
|
||||
/**
|
||||
* The most fundamental component for building UI, `View` is a
|
||||
* container that supports layout with flexbox, style, some touch handling, and
|
||||
|
@ -76,6 +83,11 @@ var View = React.createClass({
|
|||
validAttributes: ReactNativeViewAttributes.RCTView
|
||||
},
|
||||
|
||||
statics: {
|
||||
AccessibilityTraits,
|
||||
AccessibilityComponentType,
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* 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.
|
||||
* @platform android
|
||||
*/
|
||||
accessibilityComponentType: PropTypes.oneOf([
|
||||
'none',
|
||||
'button',
|
||||
'radiobutton_checked',
|
||||
'radiobutton_unchecked',
|
||||
]),
|
||||
accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentType),
|
||||
|
||||
/**
|
||||
* 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 the user performs accessibility tap gesture.
|
||||
*/
|
||||
onAcccessibilityTap: PropTypes.func,
|
||||
onAccessibilityTap: PropTypes.func,
|
||||
|
||||
/**
|
||||
* When `accessible` is true, the system will invoke this function when the
|
||||
|
|
|
@ -72,7 +72,9 @@ function setupDevtools() {
|
|||
}
|
||||
|
||||
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 {
|
||||
data = JSON.parse(evt.data);
|
||||
} catch (e) {
|
||||
|
|
|
@ -27,60 +27,85 @@ RCT_EXPORT_MODULE()
|
|||
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);
|
||||
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
|
||||
NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
|
||||
|
||||
UIImage *image = nil;
|
||||
size_t imageCount = CGImageSourceGetCount(imageSource);
|
||||
NSTimeInterval duration = 0;
|
||||
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];
|
||||
if (imageCount > 1) {
|
||||
|
||||
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];
|
||||
NSTimeInterval duration = 0;
|
||||
NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
|
||||
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
|
||||
for (size_t i = 0; i < imageCount; i++) {
|
||||
|
||||
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
|
||||
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;
|
||||
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
|
||||
delayTime = @(kDelayTimeIntervalDefault);
|
||||
[keyTimes addObject:@1.0];
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
CFRelease(imageSource);
|
||||
}
|
||||
|
||||
[keyTimes addObject:@1.0];
|
||||
|
||||
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;
|
||||
completionHandler(nil, image);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -11,14 +11,10 @@
|
|||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.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 */; };
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
|
||||
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -43,10 +39,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -56,10 +48,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetBundleImageLoader.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -80,10 +68,6 @@
|
|||
children = (
|
||||
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */,
|
||||
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */,
|
||||
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
|
||||
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
|
||||
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
|
||||
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
|
||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
|
||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
||||
|
@ -92,8 +76,6 @@
|
|||
354631671B69857700AA0B86 /* RCTImageEditingManager.m */,
|
||||
143879361AAD32A300F088A5 /* RCTImageLoader.h */,
|
||||
143879371AAD32A300F088A5 /* RCTImageLoader.m */,
|
||||
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
|
||||
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
|
||||
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
|
||||
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
|
||||
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
|
||||
|
@ -102,8 +84,6 @@
|
|||
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
|
||||
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
|
||||
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
|
||||
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */,
|
||||
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */,
|
||||
58B5115E1A9E6B3D00147676 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
|
@ -174,14 +154,10 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
|
||||
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
|
||||
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
|
||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
||||
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
|
||||
|
|
|
@ -50,7 +50,7 @@ RCT_EXPORT_MODULE()
|
|||
*/
|
||||
- (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressBlock
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
completionHandler:(void (^)(NSError *error, NSData *data))completionBlock
|
||||
{
|
||||
if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) {
|
||||
RCTLogError(@"You need to import the RCTNetworking library in order to download remote images.");
|
||||
|
|
|
@ -15,10 +15,15 @@
|
|||
@class ALAssetsLibrary;
|
||||
|
||||
typedef void (^RCTImageLoaderProgressBlock)(int64_t progress, int64_t total);
|
||||
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* UIImage or CAAnimation */);
|
||||
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* NSData, UIImage, CAAnimation */);
|
||||
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, UIImage *image);
|
||||
typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
|
||||
@interface UIImage (React)
|
||||
|
||||
@property (nonatomic, copy) CAKeyframeAnimation *reactKeyframeAnimation;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTImageLoader : NSObject <RCTBridgeModule, RCTURLRequestHandler>
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
@ -99,7 +113,7 @@ RCT_EXPORT_MODULE()
|
|||
progressBlock(progress, total);
|
||||
});
|
||||
}
|
||||
} completionHandler:^(NSError *error, id image) {
|
||||
} completionHandler:^(NSError *error, UIImage *image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
}] ?: ^{};
|
||||
}
|
||||
|
@ -142,7 +156,7 @@ RCT_EXPORT_MODULE()
|
|||
{
|
||||
id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data];
|
||||
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);
|
||||
}];
|
||||
} else {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
@implementation RCTImageView
|
||||
{
|
||||
RCTBridge *_bridge;
|
||||
CGSize _targetSize;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
|
@ -142,10 +143,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
scale:RCTScreenScale()
|
||||
resizeMode:self.contentMode
|
||||
progressBlock:progressHandler
|
||||
completionBlock:^(NSError *error, id image) {
|
||||
completionBlock:^(NSError *error, UIImage *image) {
|
||||
|
||||
if ([image isKindOfClass:[CAAnimation class]]) {
|
||||
[self.layer addAnimation:image forKey:@"contents"];
|
||||
if (image.reactKeyframeAnimation) {
|
||||
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
|
||||
} else {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
self.image = image;
|
||||
|
@ -173,19 +174,17 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
{
|
||||
[super reactSetFrame:frame];
|
||||
if (self.image == nil) {
|
||||
_targetSize = frame.size;
|
||||
[self reloadImage];
|
||||
} 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,
|
||||
RCTScreenScale(), self.contentMode, YES);
|
||||
|
||||
CGFloat widthChangeFraction = ABS(currentSize.width - idealSize.width) / currentSize.width;
|
||||
CGFloat heightChangeFraction = ABS(currentSize.height - idealSize.height) / currentSize.height;
|
||||
CGFloat widthChangeFraction = ABS(_targetSize.width - idealSize.width) / _targetSize.width;
|
||||
CGFloat heightChangeFraction = ABS(_targetSize.height - idealSize.height) / _targetSize.height;
|
||||
|
||||
// If the combined change is more than 20%, reload the asset in case there is a better size.
|
||||
if (widthChangeFraction + heightChangeFraction > 0.2) {
|
||||
_targetSize = idealSize;
|
||||
[self reloadImage];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ class Modal extends React.Component {
|
|||
<RCTModalHostView
|
||||
animated={this.props.animated}
|
||||
transparent={this.props.transparent}
|
||||
onDismiss={this.props.onDismiss}
|
||||
style={styles.modal}>
|
||||
<View style={[styles.container, containerBackgroundColor]}>
|
||||
{this.props.children}
|
||||
|
@ -45,6 +46,7 @@ class Modal extends React.Component {
|
|||
Modal.propTypes = {
|
||||
animated: PropTypes.bool,
|
||||
transparent: PropTypes.bool,
|
||||
onDismiss: PropTypes.func,
|
||||
};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
|
|
|
@ -138,6 +138,23 @@ type ConnectivityStateAndroid = $Enum<{
|
|||
|
||||
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 = {
|
||||
addEventListener: function (
|
||||
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') {
|
||||
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();
|
||||
|
||||
NetInfo.isConnected = {
|
||||
addEventListener: function (
|
||||
eventName: ChangeEventName,
|
||||
handler: Function
|
||||
): void {
|
||||
var listener = (connection) => {
|
||||
handler(_isConnected(connection));
|
||||
};
|
||||
_isConnectedSubscriptions.set(handler, listener);
|
||||
NetInfo.addEventListener(
|
||||
eventName,
|
||||
listener
|
||||
);
|
||||
fetch: function(): Promise {
|
||||
return NetInfo.fetch().then(
|
||||
(connection) => _isConnected(connection)
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
removeEventListener: function(
|
||||
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)
|
||||
);
|
||||
},
|
||||
isConnectionMetered: ({}: {} | (callback:Function) => void),
|
||||
};
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
|
|
|
@ -56,6 +56,7 @@ RCT_EXPORT_MODULE()
|
|||
// Lazy setup
|
||||
if (!_session && [self isValid]) {
|
||||
NSOperationQueue *callbackQueue = [NSOperationQueue new];
|
||||
callbackQueue.maxConcurrentOperationCount = 1;
|
||||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
_session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:self
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -16,13 +16,8 @@
|
|||
#import "RCTTestModule.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#define TIMEOUT_SECONDS 60
|
||||
|
||||
@interface RCTBridge (RCTTestRunner)
|
||||
|
||||
@property (nonatomic, weak) RCTBridge *batchedBridge;
|
||||
|
||||
@end
|
||||
static const NSTimeInterval kTestTimeoutSeconds = 60;
|
||||
static const NSTimeInterval kTestTeardownTimeoutSeconds = 30;
|
||||
|
||||
@implementation RCTTestRunner
|
||||
{
|
||||
|
@ -49,7 +44,7 @@
|
|||
_scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
RCTAssert(_scriptURL != nil, @"Could not locate main.jsBundle");
|
||||
#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
|
||||
}
|
||||
return self;
|
||||
|
@ -83,52 +78,69 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
- (void)runTest:(SEL)test module:(NSString *)moduleName
|
||||
initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||
{
|
||||
__block NSString *error = nil;
|
||||
RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||
if (level >= RCTLogLevelError) {
|
||||
error = message;
|
||||
__weak id weakJSContext;
|
||||
|
||||
@autoreleasepool {
|
||||
__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
|
||||
moduleProvider:_moduleProvider
|
||||
launchOptions:nil];
|
||||
// Take a weak reference to the JS context, so we track its deallocation later
|
||||
// (we can only do this now, since it's been lazily initialized)
|
||||
weakJSContext = [[[bridge valueForKey:@"batchedBridge"] valueForKey:@"javaScriptExecutor"] valueForKey:@"context"];
|
||||
[rootView removeFromSuperview];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps];
|
||||
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
|
||||
RCTSetLogFunction(RCTDefaultLogFunction);
|
||||
|
||||
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
|
||||
RCTTestModule *testModule = rootView.bridge.batchedBridge.modules[testModuleName];
|
||||
RCTAssert(_testController != nil, @"_testController should not be nil");
|
||||
testModule.controller = _testController;
|
||||
testModule.testSelector = test;
|
||||
testModule.view = rootView;
|
||||
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);
|
||||
|
||||
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||
vc.view = [UIView new];
|
||||
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
|
||||
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 %0.f seconds", kTestTimeoutSeconds);
|
||||
RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed");
|
||||
}
|
||||
[bridge invalidate];
|
||||
}
|
||||
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||
while (date.timeIntervalSinceNow > 0 && testModule.status == RCTTestStatusPending && error == nil) {
|
||||
// Wait for the executor to have shut down completely before returning
|
||||
NSDate *teardownTimeout = [NSDate dateWithTimeIntervalSinceNow:kTestTeardownTimeoutSeconds];
|
||||
while (teardownTimeout.timeIntervalSinceNow > 0 && weakJSContext) {
|
||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
}
|
||||
[rootView removeFromSuperview];
|
||||
|
||||
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");
|
||||
}
|
||||
RCTAssert(!weakJSContext, @"JS context was not deallocated after being invalidated");
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -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>;
|
|
@ -14,8 +14,7 @@
|
|||
var StyleSheetRegistry = require('StyleSheetRegistry');
|
||||
var invariant = require('invariant');
|
||||
|
||||
type Atom = number | bool | Object | Array<?Atom>
|
||||
type StyleObj = Atom | Array<?StyleObj>
|
||||
import type { StyleObj } from 'StyleSheetTypes';
|
||||
|
||||
function getStyle(style) {
|
||||
if (typeof style === 'number') {
|
||||
|
|
|
@ -117,8 +117,15 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
|
|||
|
||||
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
|
||||
dispatch_group_enter(initModulesAndLoadSource);
|
||||
__weak RCTBatchedBridge *weakSelf = self;
|
||||
__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;
|
||||
dispatch_group_leave(initModulesAndLoadSource);
|
||||
}];
|
||||
|
@ -131,7 +138,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
|
|||
RCTProfileHookModules(self);
|
||||
}
|
||||
|
||||
__weak RCTBatchedBridge *weakSelf = self;
|
||||
__block NSString *config;
|
||||
dispatch_group_enter(initModulesAndLoadSource);
|
||||
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
|
||||
// injectJSONConfiguration and executeSourceCode will schedule operations on the
|
||||
// same queue anyway.
|
||||
[weakSelf injectJSONConfiguration:config onComplete:^(__unused NSError *error) {
|
||||
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
|
||||
RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
|
||||
if (error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf stopLoadingWithError:error];
|
||||
});
|
||||
}
|
||||
}];
|
||||
dispatch_group_leave(initModulesAndLoadSource);
|
||||
});
|
||||
});
|
||||
|
||||
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
|
||||
if (sourceCode) {
|
||||
[weakSelf executeSourceCode:sourceCode];
|
||||
dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
|
||||
RCTBatchedBridge *strongSelf = weakSelf;
|
||||
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) {
|
||||
RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil);
|
||||
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);
|
||||
};
|
||||
|
||||
|
@ -283,7 +280,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
|
|||
object:self];
|
||||
}
|
||||
|
||||
|
||||
- (void)setupExecutor
|
||||
{
|
||||
[_javaScriptExecutor setUp];
|
||||
|
@ -313,12 +309,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
|
|||
|
||||
[_javaScriptExecutor injectJSONText:configJSON
|
||||
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
|
||||
callback:^(NSError *error) {
|
||||
if (error) {
|
||||
[self.redBox showError:error];
|
||||
}
|
||||
onComplete(error);
|
||||
}];
|
||||
callback:onComplete];
|
||||
}
|
||||
|
||||
- (void)executeSourceCode:(NSString *)sourceCode
|
||||
|
@ -333,7 +324,9 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
|
|||
|
||||
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
|
||||
if (loadError) {
|
||||
[self.redBox showError:loadError];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self stopLoadingWithError:loadError];
|
||||
});
|
||||
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
|
||||
moduleProvider:(__unused RCTBridgeModuleProviderBlock)block
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
#import "RCTLog.h"
|
||||
|
@ -246,6 +245,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame)
|
||||
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder)
|
||||
|
||||
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#import "RCTUtils.h"
|
||||
|
||||
#ifndef RCT_JSC_PROFILER
|
||||
#if RCT_DEV && RCT_DEBUG
|
||||
#if RCT_DEV
|
||||
#define RCT_JSC_PROFILER 1
|
||||
#else
|
||||
#define RCT_JSC_PROFILER 0
|
||||
|
@ -34,7 +34,7 @@
|
|||
#include <dlfcn.h>
|
||||
|
||||
#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
|
||||
|
||||
|
|
|
@ -11,4 +11,6 @@
|
|||
|
||||
@interface RCTDevLoadingView : NSObject <RCTBridgeModule>
|
||||
|
||||
+ (void)setEnabled:(BOOL)enabled;
|
||||
|
||||
@end
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#if RCT_DEV
|
||||
|
||||
static BOOL isEnabled = YES;
|
||||
|
||||
@implementation RCTDevLoadingView
|
||||
{
|
||||
UIWindow *_window;
|
||||
|
@ -27,6 +29,11 @@
|
|||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
+ (void)setEnabled:(BOOL)enabled
|
||||
{
|
||||
isEnabled = enabled;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
@ -57,6 +64,10 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (void)showWithURL:(NSURL *)URL
|
||||
{
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
_showDate = [NSDate date];
|
||||
|
@ -90,6 +101,10 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (void)hide
|
||||
{
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
const NSTimeInterval MIN_PRESENTED_TIME = 0.6;
|
||||
|
@ -117,6 +132,7 @@ RCT_EXPORT_MODULE()
|
|||
@implementation RCTDevLoadingView
|
||||
|
||||
+ (NSString *)moduleName { return nil; }
|
||||
+ (void)setEnabled:(BOOL)enabled { }
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -488,6 +488,11 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
|
|||
// Perform layout (possibly animated)
|
||||
return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
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;
|
||||
for (NSUInteger ii = 0; ii < frames.count; ii++) {
|
||||
NSNumber *reactTag = frameReactTags[ii];
|
||||
|
|
|
@ -589,7 +589,7 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
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;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
|
|
@ -17,4 +17,13 @@
|
|||
@property (nonatomic, assign, readwrite) UIEdgeInsets contentInset;
|
||||
@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
|
||||
|
|
|
@ -24,6 +24,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
|
|
|
@ -460,10 +460,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
_scrollView.frame = self.bounds;
|
||||
_scrollView.contentOffset = originalOffset;
|
||||
|
||||
[RCTView autoAdjustInsetsForView:self
|
||||
withScrollView:_scrollView
|
||||
updateOffset:YES];
|
||||
|
||||
[self updateClippedSubviews];
|
||||
}
|
||||
|
||||
|
@ -523,6 +519,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
[_scrollView zoomToRect:rect animated:animated];
|
||||
}
|
||||
|
||||
- (void)refreshContentInset
|
||||
{
|
||||
[RCTView autoAdjustInsetsForView:self
|
||||
withScrollView:_scrollView
|
||||
updateOffset:YES];
|
||||
}
|
||||
|
||||
#pragma mark - ScrollView delegate
|
||||
|
||||
#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \
|
||||
|
|
|
@ -95,9 +95,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
{
|
||||
[super layoutSubviews];
|
||||
_webView.frame = self.bounds;
|
||||
[RCTView autoAdjustInsetsForView:self
|
||||
withScrollView:_webView.scrollView
|
||||
updateOffset:YES];
|
||||
}
|
||||
|
||||
- (void)setContentInset:(UIEdgeInsets)contentInset
|
||||
|
@ -133,6 +130,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
return event;
|
||||
}
|
||||
|
||||
- (void)refreshContentInset
|
||||
{
|
||||
[RCTView autoAdjustInsetsForView:self
|
||||
withScrollView:_webView.scrollView
|
||||
updateOffset:YES];
|
||||
}
|
||||
|
||||
#pragma mark - UIWebViewDelegate methods
|
||||
|
||||
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
||||
|
|
|
@ -16,13 +16,15 @@
|
|||
#import "RCTUtils.h"
|
||||
#import "RCTViewControllerProtocol.h"
|
||||
#import "UIView+React.h"
|
||||
#import "RCTAutoInsetsProtocol.h"
|
||||
|
||||
@implementation RCTWrapperViewController
|
||||
{
|
||||
UIView *_wrapperView;
|
||||
UIView *_contentView;
|
||||
CGFloat _previousTopLayout;
|
||||
CGFloat _previousBottomLayout;
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
CGFloat _previousTopLayoutLength;
|
||||
CGFloat _previousBottomLayoutLength;
|
||||
}
|
||||
|
||||
@synthesize currentTopLayoutGuide = _currentTopLayoutGuide;
|
||||
|
@ -58,6 +60,32 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
_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)
|
||||
{
|
||||
if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1) {
|
||||
|
|
54
package.json
|
@ -47,42 +47,42 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"absolute-path": "0.0.0",
|
||||
"babel": "5.8.21",
|
||||
"babel-core": "5.8.21",
|
||||
"bser": "1.0.0",
|
||||
"chalk": "1.0.0",
|
||||
"babel": "5.8.23",
|
||||
"babel-core": "5.8.23",
|
||||
"bser": "1.0.2",
|
||||
"chalk": "1.1.1",
|
||||
"connect": "2.8.3",
|
||||
"debug": "2.1.0",
|
||||
"debug": "2.2.0",
|
||||
"graceful-fs": "4.1.2",
|
||||
"image-size": "0.3.5",
|
||||
"immutable": "^3.7.4",
|
||||
"joi": "5.1.0",
|
||||
"jstransform": "11.0.1",
|
||||
"module-deps": "3.5.6",
|
||||
"immutable": "3.7.5",
|
||||
"joi": "6.6.1",
|
||||
"jstransform": "11.0.3",
|
||||
"module-deps": "3.9.1",
|
||||
"optimist": "0.6.1",
|
||||
"progress": "^1.1.8",
|
||||
"promise": "^7.0.3",
|
||||
"react-timer-mixin": "^0.13.1",
|
||||
"progress": "1.1.8",
|
||||
"promise": "7.0.4",
|
||||
"react-timer-mixin": "0.13.2",
|
||||
"react-tools": "git://github.com/facebook/react#b4e74e38e43ac53af8acd62c78c9213be0194245",
|
||||
"rebound": "^0.0.12",
|
||||
"rebound": "0.0.13",
|
||||
"regenerator": "0.8.36",
|
||||
"sane": "^1.1.2",
|
||||
"semver": "^4.3.6",
|
||||
"source-map": "0.1.31",
|
||||
"sane": "^1.2.0",
|
||||
"semver": "5.0.1",
|
||||
"source-map": "0.4.4",
|
||||
"stacktrace-parser": "0.1.3",
|
||||
"uglify-js": "2.4.16",
|
||||
"underscore": "1.7.0",
|
||||
"wordwrap": "^1.0.0",
|
||||
"worker-farm": "^1.3.1",
|
||||
"uglify-js": "2.4.24",
|
||||
"underscore": "1.8.3",
|
||||
"wordwrap": "1.0.0",
|
||||
"worker-farm": "1.3.1",
|
||||
"ws": "0.8.0",
|
||||
"yargs": "1.3.2",
|
||||
"yeoman-environment": "^1.2.7",
|
||||
"yeoman-generator": "^0.20.2"
|
||||
"yargs": "3.24.0",
|
||||
"yeoman-environment": "1.2.7",
|
||||
"yeoman-generator": "0.20.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest-cli": "0.5.0",
|
||||
"babel-eslint": "3.1.5",
|
||||
"eslint": "0.21.2",
|
||||
"eslint-plugin-react": "2.3.0"
|
||||
"jest-cli": "0.5.1",
|
||||
"babel-eslint": "4.1.1",
|
||||
"eslint": "1.3.1",
|
||||
"eslint-plugin-react": "3.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,14 @@ var options = parseCommandLine([{
|
|||
type: 'string',
|
||||
default: require.resolve('./transformer.js'),
|
||||
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) {
|
||||
|
@ -229,6 +237,7 @@ function getAppMiddleware(options) {
|
|||
transformModulePath: transformerPath,
|
||||
assetRoots: options.assetRoots,
|
||||
assetExts: ['png', 'jpeg', 'jpg'],
|
||||
resetCache: options.resetCache || options['reset-cache'],
|
||||
polyfillModuleNames: [
|
||||
require.resolve(
|
||||
'../Libraries/JavaScriptAppEngine/polyfills/document.js'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('../../lib/getPlatformExtension')
|
||||
.dontMock('../../lib/getAssetDataFromName')
|
||||
.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', () => {
|
||||
const server = new AssetServer({
|
||||
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', () => {
|
||||
const server = new AssetServer({
|
||||
projectRoots: ['/root', '/root2'],
|
||||
|
|
|
@ -20,7 +20,6 @@ const stat = Promise.denodeify(fs.stat);
|
|||
const readDir = Promise.denodeify(fs.readdir);
|
||||
const readFile = Promise.denodeify(fs.readFile);
|
||||
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
|
@ -39,9 +38,9 @@ class AssetServer {
|
|||
this._assetExts = opts.assetExts;
|
||||
}
|
||||
|
||||
get(assetPath) {
|
||||
get(assetPath, platform = null) {
|
||||
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++) {
|
||||
if (record.scales[i] >= assetData.resolution) {
|
||||
return readFile(record.files[i]);
|
||||
|
@ -52,14 +51,14 @@ class AssetServer {
|
|||
});
|
||||
}
|
||||
|
||||
getAssetData(assetPath) {
|
||||
getAssetData(assetPath, platform = null) {
|
||||
const nameData = getAssetDataFromName(assetPath);
|
||||
const data = {
|
||||
name: nameData.name,
|
||||
type: nameData.type,
|
||||
};
|
||||
|
||||
return this._getAssetRecord(assetPath).then(record => {
|
||||
return this._getAssetRecord(assetPath, platform).then(record => {
|
||||
data.scales = record.scales;
|
||||
|
||||
return Promise.all(
|
||||
|
@ -85,9 +84,10 @@ class AssetServer {
|
|||
* 1. We first parse the directory of the asset
|
||||
* 2. We check to find a matching directory in one of the project roots
|
||||
* 3. We then build a map of all assets and their 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);
|
||||
|
||||
return (
|
||||
|
@ -104,11 +104,20 @@ class AssetServer {
|
|||
const files = res[1];
|
||||
const assetData = getAssetDataFromName(filename);
|
||||
|
||||
const map = this._buildAssetMap(dir, files);
|
||||
const record = map[assetData.assetName];
|
||||
const map = this._buildAssetMap(dir, files, platform);
|
||||
|
||||
let record;
|
||||
if (platform != null){
|
||||
record = map[getAssetKey(assetData.assetName, platform)] ||
|
||||
map[assetData.assetName];
|
||||
} else {
|
||||
record = map[assetData.assetName];
|
||||
}
|
||||
|
||||
if (!record) {
|
||||
throw new Error('Asset not found');
|
||||
throw new Error(
|
||||
`Asset not found: ${assetPath} for platform: ${platform}`
|
||||
);
|
||||
}
|
||||
|
||||
return record;
|
||||
|
@ -141,9 +150,10 @@ class AssetServer {
|
|||
const map = Object.create(null);
|
||||
assets.forEach(function(asset, i) {
|
||||
const file = files[i];
|
||||
let record = map[asset.assetName];
|
||||
const assetKey = getAssetKey(asset.assetName, asset.platform);
|
||||
let record = map[assetKey];
|
||||
if (!record) {
|
||||
record = map[asset.assetName] = {
|
||||
record = map[assetKey] = {
|
||||
scales: [],
|
||||
files: [],
|
||||
};
|
||||
|
@ -151,6 +161,7 @@ class AssetServer {
|
|||
|
||||
let insertIndex;
|
||||
const length = record.scales.length;
|
||||
|
||||
for (insertIndex = 0; insertIndex < length; insertIndex++) {
|
||||
if (asset.resolution < record.scales[insertIndex]) {
|
||||
break;
|
||||
|
@ -164,4 +175,12 @@ class AssetServer {
|
|||
}
|
||||
}
|
||||
|
||||
function getAssetKey(assetName, platform) {
|
||||
if (platform != null) {
|
||||
return `${assetName} : ${platform}`;
|
||||
} else {
|
||||
return assetName;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetServer;
|
||||
|
|
|
@ -115,13 +115,18 @@ class Bundle {
|
|||
getMinifiedSourceAndMap() {
|
||||
this._assertFinalized();
|
||||
|
||||
if (this._minifiedSourceAndMap) {
|
||||
return this._minifiedSourceAndMap;
|
||||
}
|
||||
|
||||
const source = this._getSource();
|
||||
try {
|
||||
return UglifyJS.minify(source, {
|
||||
this._minifiedSourceAndMap = UglifyJS.minify(source, {
|
||||
fromString: true,
|
||||
outSourceMap: 'bundle.js',
|
||||
inSourceMap: this.getSourceMap(),
|
||||
});
|
||||
return this._minifiedSourceAndMap;
|
||||
} catch(e) {
|
||||
// Sometimes, when somebody is using a new syntax feature that we
|
||||
// don't yet have transform for, the untransformed line is sent to
|
||||
|
@ -186,6 +191,10 @@ class Bundle {
|
|||
|
||||
options = options || {};
|
||||
|
||||
if (options.minify) {
|
||||
return this.getMinifiedSourceAndMap().map;
|
||||
}
|
||||
|
||||
if (this._shouldCombineSourceMaps) {
|
||||
return this._getCombinedSourceMaps(options);
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ class Bundler {
|
|||
bundle.setMainModuleId(result.mainModuleId);
|
||||
return Promise.all(
|
||||
result.dependencies.map(
|
||||
module => this._transformModule(bundle, module).then(transformed => {
|
||||
module => this._transformModule(bundle, module, platform).then(transformed => {
|
||||
if (bar) {
|
||||
bar.tick();
|
||||
}
|
||||
|
@ -182,13 +182,13 @@ class Bundler {
|
|||
return this._resolver.getDependencies(main, { dev: isDev, platform });
|
||||
}
|
||||
|
||||
_transformModule(bundle, module) {
|
||||
_transformModule(bundle, module, platform = null) {
|
||||
let transform;
|
||||
|
||||
if (module.isAsset_DEPRECATED()) {
|
||||
transform = this.generateAssetModule_DEPRECATED(bundle, module);
|
||||
} else if (module.isAsset()) {
|
||||
transform = this.generateAssetModule(bundle, module);
|
||||
transform = this.generateAssetModule(bundle, module, platform);
|
||||
} else if (module.isJSON()) {
|
||||
transform = generateJSONModule(module);
|
||||
} else {
|
||||
|
@ -243,12 +243,12 @@ class Bundler {
|
|||
});
|
||||
}
|
||||
|
||||
generateAssetModule(bundle, module) {
|
||||
generateAssetModule(bundle, module, platform = null) {
|
||||
const relPath = getPathRelativeToRoot(this._projectRoots, module.path);
|
||||
|
||||
return Promise.all([
|
||||
sizeOf(module.path),
|
||||
this._assetServer.getAssetData(relPath),
|
||||
this._assetServer.getAssetData(relPath, platform),
|
||||
]).then(function(res) {
|
||||
const dimensions = res[0];
|
||||
const assetData = res[1];
|
||||
|
|
|
@ -16,6 +16,7 @@ jest
|
|||
.dontMock('../../crawlers')
|
||||
.dontMock('../../crawlers/node')
|
||||
.dontMock('../../replacePatterns')
|
||||
.dontMock('../../../lib/getPlatformExtension')
|
||||
.dontMock('../../../lib/getAssetDataFromName')
|
||||
.dontMock('../../fastfs')
|
||||
.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() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
|
@ -17,6 +17,7 @@ const crawl = require('../crawlers');
|
|||
const debug = require('debug')('DependencyGraph');
|
||||
const declareOpts = require('../../lib/declareOpts');
|
||||
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
|
||||
const getPontentialPlatformExt = require('../../lib/getPlatformExtension');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
|
@ -274,7 +275,7 @@ class DependencyGraph {
|
|||
|
||||
// `platformExt` could be set in the `setup` method.
|
||||
if (!this._platformExt) {
|
||||
const platformExt = getPlatformExt(entryPath);
|
||||
const platformExt = getPontentialPlatformExt(entryPath);
|
||||
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
|
||||
this._platformExt = platformExt;
|
||||
} else {
|
||||
|
@ -390,12 +391,18 @@ class DependencyGraph {
|
|||
return Promise.resolve().then(() => {
|
||||
if (this._isAssetFile(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
|
||||
// will happen somewhere
|
||||
const [assetFile] = this._fastfs.matches(
|
||||
path.dirname(potentialModulePath),
|
||||
pattern
|
||||
new RegExp(pattern)
|
||||
);
|
||||
|
||||
if (assetFile) {
|
||||
|
@ -496,7 +503,7 @@ class DependencyGraph {
|
|||
const modules = this._hasteMap[name];
|
||||
if (this._platformExt != null) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
@ -662,15 +669,6 @@ function normalizePath(modulePath) {
|
|||
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);
|
||||
|
||||
module.exports = DependencyGraph;
|
||||
|
|
|
@ -245,8 +245,16 @@ describe('processRequest', () => {
|
|||
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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -278,7 +278,8 @@ class Server {
|
|||
_processAssetsRequest(req, res) {
|
||||
const urlObj = url.parse(req.url, true);
|
||||
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(
|
||||
data => res.end(data),
|
||||
error => {
|
||||
|
@ -286,7 +287,7 @@ class Server {
|
|||
res.writeHead('404');
|
||||
res.end('Asset not found');
|
||||
}
|
||||
).done();
|
||||
).done(() => Activity.endEvent(assetEvent));
|
||||
}
|
||||
|
||||
_processProfile(req, res) {
|
||||
|
@ -370,7 +371,9 @@ class Server {
|
|||
res.end(bundleSource);
|
||||
Activity.endEvent(startReqEventId);
|
||||
} 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.end(sourceMap);
|
||||
Activity.endEvent(startReqEventId);
|
||||
|
|
|
@ -12,6 +12,7 @@ const Bundle = require('../Bundler/Bundle');
|
|||
const Promise = require('promise');
|
||||
const bser = require('bser');
|
||||
const debug = require('debug')('ReactPackager:SocketClient');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
|
@ -29,7 +30,16 @@ class SocketClient {
|
|||
this._sock = net.connect(sockPath);
|
||||
this._ready = new Promise((resolve, reject) => {
|
||||
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);
|
||||
|
|
|
@ -16,6 +16,7 @@ const fs = require('fs');
|
|||
const net = require('net');
|
||||
|
||||
const MAX_IDLE_TIME = 30 * 1000;
|
||||
const MAX_STARTUP_TIME = 5 * 60 * 1000;
|
||||
|
||||
class SocketServer {
|
||||
constructor(sockPath, options) {
|
||||
|
@ -35,13 +36,15 @@ class SocketServer {
|
|||
process.on('exit', () => fs.unlinkSync(sockPath));
|
||||
});
|
||||
});
|
||||
|
||||
this._numConnections = 0;
|
||||
this._server.on('connection', (sock) => this._handleConnection(sock));
|
||||
|
||||
// Disable the file watcher.
|
||||
options.nonPersistent = true;
|
||||
this._packagerServer = new Server(options);
|
||||
this._jobs = 0;
|
||||
this._dieEventually();
|
||||
this._dieEventually(MAX_STARTUP_TIME);
|
||||
}
|
||||
|
||||
onReady() {
|
||||
|
@ -50,10 +53,13 @@ class SocketServer {
|
|||
|
||||
_handleConnection(sock) {
|
||||
debug('connection to server', process.pid);
|
||||
this._numConnections++;
|
||||
sock.on('close', () => this._numConnections--);
|
||||
|
||||
const bunser = new bser.BunserBuf();
|
||||
sock.on('data', (buf) => bunser.append(buf));
|
||||
bunser.on('value', (m) => this._handleMessage(sock, m));
|
||||
bunser.on('error', (e) => console.error(e));
|
||||
}
|
||||
|
||||
_handleMessage(sock, m) {
|
||||
|
@ -113,15 +119,15 @@ class SocketServer {
|
|||
}));
|
||||
}
|
||||
|
||||
_dieEventually() {
|
||||
_dieEventually(delay = MAX_IDLE_TIME) {
|
||||
clearTimeout(this._deathTimer);
|
||||
this._deathTimer = setTimeout(() => {
|
||||
if (this._jobs <= 0) {
|
||||
if (this._jobs <= 0 && this._numConnections <= 0) {
|
||||
debug('server dying', process.pid);
|
||||
process.exit();
|
||||
}
|
||||
this._dieEventually();
|
||||
}, MAX_IDLE_TIME);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
static listenOnServerIPCMessages() {
|
||||
|
|
|
@ -20,7 +20,7 @@ const path = require('path');
|
|||
const tmpdir = require('os').tmpdir();
|
||||
const {spawn} = require('child_process');
|
||||
|
||||
const CREATE_SERVER_TIMEOUT = 60000;
|
||||
const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000;
|
||||
|
||||
const SocketInterface = {
|
||||
getOrCreateSocketFor(options) {
|
||||
|
@ -42,8 +42,16 @@ const SocketInterface = {
|
|||
if (fs.existsSync(sockPath)) {
|
||||
var sock = net.connect(sockPath);
|
||||
sock.on('connect', () => {
|
||||
sock.end();
|
||||
resolve(SocketClient.create(sockPath));
|
||||
SocketClient.create(sockPath).then(
|
||||
client => {
|
||||
sock.end();
|
||||
resolve(client);
|
||||
},
|
||||
error => {
|
||||
sock.end();
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
sock.on('error', (e) => {
|
||||
try {
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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';
|
||||
|
||||
var path = require('path');
|
||||
const path = require('path');
|
||||
const getPlatformExtension = require('./getPlatformExtension');
|
||||
|
||||
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);
|
||||
var resolution;
|
||||
const match = filename.match(re);
|
||||
let resolution;
|
||||
|
||||
if (!(match && match[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 {
|
||||
resolution: resolution,
|
||||
assetName: assetName,
|
||||
type: ext.slice(1),
|
||||
name: path.basename(assetName, ext)
|
||||
name: path.basename(assetName, ext),
|
||||
platform: platformExt,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -39,6 +39,7 @@ function transform(srcTxt, filename, options) {
|
|||
'es7.objectRestSpread',
|
||||
'flow',
|
||||
'react',
|
||||
'react.displayName',
|
||||
'regenerator',
|
||||
],
|
||||
plugins: plugins,
|
||||
|
|