[React Native] open source ImageEditingManager native module
This commit is contained in:
parent
809a2dc1d6
commit
37636fc59a
|
@ -0,0 +1,305 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
CameraRoll,
|
||||||
|
Image,
|
||||||
|
NativeModules,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TouchableHighlight,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
var ImageEditingManager = NativeModules.ImageEditingManager;
|
||||||
|
var RCTScrollViewConsts = NativeModules.UIManager.RCTScrollView.Constants;
|
||||||
|
|
||||||
|
var PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
type ImageOffset = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ImageSize = {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TransformData = {
|
||||||
|
offset: ImageOffset;
|
||||||
|
size: ImageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SquareImageCropper extends React.Component {
|
||||||
|
_isMounted: boolean;
|
||||||
|
_transformData: TransformData;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this._isMounted = true;
|
||||||
|
this.state = {
|
||||||
|
randomPhoto: null,
|
||||||
|
measuredSize: null,
|
||||||
|
croppedImageURI: null,
|
||||||
|
cropError: null,
|
||||||
|
};
|
||||||
|
this._fetchRandomPhoto();
|
||||||
|
}
|
||||||
|
|
||||||
|
_fetchRandomPhoto() {
|
||||||
|
CameraRoll.getPhotos(
|
||||||
|
{first: PAGE_SIZE},
|
||||||
|
(data) => {
|
||||||
|
if (!this._isMounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var edges = data.edges;
|
||||||
|
var edge = edges[Math.floor(Math.random() * edges.length)];
|
||||||
|
var randomPhoto = edge && edge.node && edge.node.image;
|
||||||
|
if (randomPhoto) {
|
||||||
|
this.setState({randomPhoto});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._isMounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.measuredSize) {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={styles.container}
|
||||||
|
onLayout={(event) => {
|
||||||
|
var measuredWidth = event.nativeEvent.layout.width;
|
||||||
|
if (!measuredWidth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
measuredSize: {width: measuredWidth, height: measuredWidth},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.croppedImageURI) {
|
||||||
|
return this._renderImageCropper();
|
||||||
|
}
|
||||||
|
return this._renderCroppedImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderImageCropper() {
|
||||||
|
if (!this.state.randomPhoto) {
|
||||||
|
return (
|
||||||
|
<View style={styles.container} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var error = null;
|
||||||
|
if (this.state.cropError) {
|
||||||
|
error = (
|
||||||
|
<Text>{this.state.cropError.message}</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text>Drag the image within the square to crop:</Text>
|
||||||
|
<ImageCropper
|
||||||
|
image={this.state.randomPhoto}
|
||||||
|
size={this.state.measuredSize}
|
||||||
|
style={[styles.imageCropper, this.state.measuredSize]}
|
||||||
|
onTransformDataChange={(data) => this._transformData = data}
|
||||||
|
/>
|
||||||
|
<TouchableHighlight
|
||||||
|
style={styles.cropButtonTouchable}
|
||||||
|
onPress={this._crop.bind(this)}>
|
||||||
|
<View style={styles.cropButton}>
|
||||||
|
<Text style={styles.cropButtonLabel}>
|
||||||
|
Crop
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
|
{error}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderCroppedImage() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text>Here is the cropped image:</Text>
|
||||||
|
<Image
|
||||||
|
source={{uri: this.state.croppedImageURI}}
|
||||||
|
style={[styles.imageCropper, this.state.measuredSize]}
|
||||||
|
/>
|
||||||
|
<TouchableHighlight
|
||||||
|
style={styles.cropButtonTouchable}
|
||||||
|
onPress={this._reset.bind(this)}>
|
||||||
|
<View style={styles.cropButton}>
|
||||||
|
<Text style={styles.cropButtonLabel}>
|
||||||
|
Try again
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_crop() {
|
||||||
|
ImageEditingManager.cropImage(
|
||||||
|
this.state.randomPhoto.uri,
|
||||||
|
this._transformData,
|
||||||
|
(croppedImageURI) => this.setState({croppedImageURI}),
|
||||||
|
(cropError) => this.setState({cropError})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_reset() {
|
||||||
|
this.setState({
|
||||||
|
randomPhoto: null,
|
||||||
|
croppedImageURI: null,
|
||||||
|
cropError: null,
|
||||||
|
});
|
||||||
|
this._fetchRandomPhoto();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageCropper extends React.Component {
|
||||||
|
_scaledImageSize: ImageSize;
|
||||||
|
_contentOffset: ImageOffset;
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
// Scale an image to the minimum size that is large enough to completely
|
||||||
|
// fill the crop box.
|
||||||
|
var widthRatio = this.props.image.width / this.props.size.width;
|
||||||
|
var heightRatio = this.props.image.height / this.props.size.height;
|
||||||
|
if (widthRatio < heightRatio) {
|
||||||
|
this._scaledImageSize = {
|
||||||
|
width: this.props.size.width,
|
||||||
|
height: this.props.image.height / widthRatio,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this._scaledImageSize = {
|
||||||
|
width: this.props.image.width / heightRatio,
|
||||||
|
height: this.props.size.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this._contentOffset = {
|
||||||
|
x: (this._scaledImageSize.width - this.props.size.width) / 2,
|
||||||
|
y: (this._scaledImageSize.height - this.props.size.height) / 2,
|
||||||
|
};
|
||||||
|
this._updateTransformData(
|
||||||
|
this._contentOffset,
|
||||||
|
this._scaledImageSize,
|
||||||
|
this.props.size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onScroll(event) {
|
||||||
|
this._updateTransformData(
|
||||||
|
event.nativeEvent.contentOffset,
|
||||||
|
event.nativeEvent.contentSize,
|
||||||
|
event.nativeEvent.layoutMeasurement
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTransformData(offset, scaledImageSize, croppedImageSize) {
|
||||||
|
var offsetRatioX = offset.x / scaledImageSize.width;
|
||||||
|
var offsetRatioY = offset.y / scaledImageSize.height;
|
||||||
|
var sizeRatioX = croppedImageSize.width / scaledImageSize.width;
|
||||||
|
var sizeRatioY = croppedImageSize.height / scaledImageSize.height;
|
||||||
|
|
||||||
|
this.props.onTransformDataChange && this.props.onTransformDataChange({
|
||||||
|
offset: {
|
||||||
|
x: this.props.image.width * offsetRatioX,
|
||||||
|
y: this.props.image.height * offsetRatioY,
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
width: this.props.image.width * sizeRatioX,
|
||||||
|
height: this.props.image.height * sizeRatioY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var decelerationRate =
|
||||||
|
RCTScrollViewConsts && RCTScrollViewConsts.DecelerationRate ?
|
||||||
|
RCTScrollViewConsts.DecelerationRate.Fast :
|
||||||
|
0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView
|
||||||
|
alwaysBounceVertical={true}
|
||||||
|
automaticallyAdjustContentInsets={false}
|
||||||
|
contentOffset={this._contentOffset}
|
||||||
|
decelerationRate={decelerationRate}
|
||||||
|
horizontal={true}
|
||||||
|
maximumZoomScale={3.0}
|
||||||
|
onMomentumScrollEnd={this._onScroll.bind(this)}
|
||||||
|
onScrollEndDrag={this._onScroll.bind(this)}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
style={this.props.style}
|
||||||
|
scrollEventThrottle={16}>
|
||||||
|
<Image source={this.props.image} style={this._scaledImageSize} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.framework = 'React';
|
||||||
|
exports.title = 'ImageEditingManager';
|
||||||
|
exports.description = 'Cropping and scaling with ImageEditingManager';
|
||||||
|
exports.examples = [{
|
||||||
|
title: 'Image Cropping',
|
||||||
|
render() {
|
||||||
|
return <SquareImageCropper/>;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
},
|
||||||
|
imageCropper: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
cropButtonTouchable: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
cropButton: {
|
||||||
|
padding: 12,
|
||||||
|
backgroundColor: 'blue',
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
cropButtonLabel: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
});
|
|
@ -75,6 +75,7 @@ var APIS = [
|
||||||
require('./TimerExample'),
|
require('./TimerExample'),
|
||||||
require('./VibrationIOSExample'),
|
require('./VibrationIOSExample'),
|
||||||
require('./XHRExample'),
|
require('./XHRExample'),
|
||||||
|
require('./ImageEditingExample'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Register suitable examples for snapshot tests
|
// Register suitable examples for snapshot tests
|
||||||
|
|
|
@ -1,344 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
AppRegistry,
|
|
||||||
ListView,
|
|
||||||
PixelRatio,
|
|
||||||
Platform,
|
|
||||||
Settings,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
TouchableHighlight,
|
|
||||||
View,
|
|
||||||
} = React;
|
|
||||||
|
|
||||||
var { TestModule } = React.addons;
|
|
||||||
|
|
||||||
import type { ExampleModule } from 'ExampleTypes';
|
|
||||||
import type { NavigationContext } from 'NavigationContext';
|
|
||||||
|
|
||||||
var createExamplePage = require('./createExamplePage');
|
|
||||||
|
|
||||||
var COMMON_COMPONENTS = [
|
|
||||||
require('./ImageExample'),
|
|
||||||
require('./LayoutEventsExample'),
|
|
||||||
require('./ListViewExample'),
|
|
||||||
require('./ListViewGridLayoutExample'),
|
|
||||||
require('./ListViewPagingExample'),
|
|
||||||
require('./MapViewExample'),
|
|
||||||
require('./Navigator/NavigatorExample'),
|
|
||||||
require('./ScrollViewExample'),
|
|
||||||
require('./TextInputExample'),
|
|
||||||
require('./TouchableExample'),
|
|
||||||
require('./ViewExample'),
|
|
||||||
require('./WebViewExample'),
|
|
||||||
];
|
|
||||||
|
|
||||||
var COMMON_APIS = [
|
|
||||||
require('./AnimationExample/AnExApp'),
|
|
||||||
require('./GeolocationExample'),
|
|
||||||
require('./LayoutExample'),
|
|
||||||
require('./PanResponderExample'),
|
|
||||||
require('./PointerEventsExample'),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (Platform.OS === 'ios') {
|
|
||||||
var COMPONENTS = COMMON_COMPONENTS.concat([
|
|
||||||
require('./ActivityIndicatorIOSExample'),
|
|
||||||
require('./DatePickerIOSExample'),
|
|
||||||
require('./NavigatorIOSColorsExample'),
|
|
||||||
require('./NavigatorIOSExample'),
|
|
||||||
require('./PickerIOSExample'),
|
|
||||||
require('./ProgressViewIOSExample'),
|
|
||||||
require('./SegmentedControlIOSExample'),
|
|
||||||
require('./SliderIOSExample'),
|
|
||||||
require('./SwitchIOSExample'),
|
|
||||||
require('./TabBarIOSExample'),
|
|
||||||
require('./TextExample.ios'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
var APIS = COMMON_APIS.concat([
|
|
||||||
require('./AccessibilityIOSExample'),
|
|
||||||
require('./ActionSheetIOSExample'),
|
|
||||||
require('./AdSupportIOSExample'),
|
|
||||||
require('./AlertIOSExample'),
|
|
||||||
require('./AppStateIOSExample'),
|
|
||||||
require('./AsyncStorageExample'),
|
|
||||||
require('./TransformExample'),
|
|
||||||
require('./BorderExample'),
|
|
||||||
require('./CameraRollExample.ios'),
|
|
||||||
require('./NetInfoExample'),
|
|
||||||
require('./PushNotificationIOSExample'),
|
|
||||||
require('./StatusBarIOSExample'),
|
|
||||||
require('./TimerExample'),
|
|
||||||
require('./VibrationIOSExample'),
|
|
||||||
require('./XHRExample'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
} else if (Platform.OS === 'android') {
|
|
||||||
var COMPONENTS = COMMON_COMPONENTS.concat([
|
|
||||||
]);
|
|
||||||
|
|
||||||
var APIS = COMMON_APIS.concat([
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ds = new ListView.DataSource({
|
|
||||||
rowHasChanged: (r1, r2) => r1 !== r2,
|
|
||||||
sectionHeaderHasChanged: (h1, h2) => h1 !== h2,
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeRenderable(example: any): ReactClass<any, any, any> {
|
|
||||||
return example.examples ?
|
|
||||||
createExamplePage(null, example) :
|
|
||||||
example;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register suitable examples for snapshot tests
|
|
||||||
COMPONENTS.concat(APIS).forEach((Example) => {
|
|
||||||
if (Example.displayName) {
|
|
||||||
var Snapshotter = React.createClass({
|
|
||||||
componentDidMount: function() {
|
|
||||||
// View is still blank after first RAF :\
|
|
||||||
global.requestAnimationFrame(() =>
|
|
||||||
global.requestAnimationFrame(() => TestModule.verifySnapshot(
|
|
||||||
TestModule.markTestPassed
|
|
||||||
)
|
|
||||||
));
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var Renderable = makeRenderable(Example);
|
|
||||||
return <Renderable />;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
navigator: {
|
|
||||||
navigationContext: NavigationContext,
|
|
||||||
push: (route: {title: string, component: ReactClass<any,any,any>}) => void,
|
|
||||||
},
|
|
||||||
onExternalExampleRequested: Function,
|
|
||||||
onSelectExample: Function,
|
|
||||||
isInDrawer: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
class UIExplorerList extends React.Component {
|
|
||||||
props: Props;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
dataSource: ds.cloneWithRowsAndSections({
|
|
||||||
components: COMPONENTS,
|
|
||||||
apis: APIS,
|
|
||||||
}),
|
|
||||||
searchText: Platform.OS === 'ios' ? Settings.get('searchText') : '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.props.navigator.navigationContext.addListener('didfocus', function(event) {
|
|
||||||
if (event.data.route.title === 'UIExplorer') {
|
|
||||||
Settings.set({visibleExample: null});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._search(this.state.searchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (Platform.OS === 'ios' ||
|
|
||||||
(Platform.OS === 'android' && !this.props.isInDrawer)) {
|
|
||||||
var platformTextInputStyle =
|
|
||||||
Platform.OS === 'ios' ? styles.searchTextInputIOS :
|
|
||||||
Platform.OS === 'android' ? styles.searchTextInputAndroid : {};
|
|
||||||
var textInput = (
|
|
||||||
<View style={styles.searchRow}>
|
|
||||||
<TextInput
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect={false}
|
|
||||||
clearButtonMode="always"
|
|
||||||
onChangeText={this._search.bind(this)}
|
|
||||||
placeholder="Search..."
|
|
||||||
style={[styles.searchTextInput, platformTextInputStyle]}
|
|
||||||
testID="explorer_search"
|
|
||||||
value={this.state.searchText}
|
|
||||||
/>
|
|
||||||
</View>);
|
|
||||||
}
|
|
||||||
|
|
||||||
var homePage;
|
|
||||||
if (Platform.OS === 'android' && this.props.isInDrawer) {
|
|
||||||
homePage = this._renderRow({
|
|
||||||
title: 'UIExplorer',
|
|
||||||
description: 'List of examples',
|
|
||||||
}, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.listContainer}>
|
|
||||||
{textInput}
|
|
||||||
{homePage}
|
|
||||||
<ListView
|
|
||||||
style={styles.list}
|
|
||||||
dataSource={this.state.dataSource}
|
|
||||||
renderRow={this._renderRow.bind(this)}
|
|
||||||
renderSectionHeader={this._renderSectionHeader}
|
|
||||||
keyboardShouldPersistTaps={true}
|
|
||||||
automaticallyAdjustContentInsets={false}
|
|
||||||
keyboardDismissMode="on-drag"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderSectionHeader(data: any, section: string) {
|
|
||||||
return (
|
|
||||||
<View style={styles.sectionHeader}>
|
|
||||||
<Text style={styles.sectionHeaderTitle}>
|
|
||||||
{section.toUpperCase()}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderRow(example: any, i: number) {
|
|
||||||
return (
|
|
||||||
<View key={i}>
|
|
||||||
<TouchableHighlight onPress={() => this._onPressRow(example)}>
|
|
||||||
<View style={styles.row}>
|
|
||||||
<Text style={styles.rowTitleText}>
|
|
||||||
{example.title}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.rowDetailText}>
|
|
||||||
{example.description}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableHighlight>
|
|
||||||
<View style={styles.separator} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_search(text: mixed) {
|
|
||||||
var regex = new RegExp(text, 'i');
|
|
||||||
var filter = (component) => regex.test(component.title);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
dataSource: ds.cloneWithRowsAndSections({
|
|
||||||
components: COMPONENTS.filter(filter),
|
|
||||||
apis: APIS.filter(filter),
|
|
||||||
}),
|
|
||||||
searchText: text,
|
|
||||||
});
|
|
||||||
Settings.set({searchText: text});
|
|
||||||
}
|
|
||||||
|
|
||||||
_openExample(example: any) {
|
|
||||||
if (example.external) {
|
|
||||||
this.props.onExternalExampleRequested(example);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var Component = makeRenderable(example);
|
|
||||||
if (Platform.OS === 'ios') {
|
|
||||||
this.props.navigator.push({
|
|
||||||
title: Component.title,
|
|
||||||
component: Component,
|
|
||||||
});
|
|
||||||
} else if (Platform.OS === 'android') {
|
|
||||||
this.props.onSelectExample({
|
|
||||||
title: Component.title,
|
|
||||||
component: Component,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onPressRow(example: any) {
|
|
||||||
Settings.set({visibleExample: example.title});
|
|
||||||
this._openExample(example);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var styles = StyleSheet.create({
|
|
||||||
listContainer: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
backgroundColor: '#eeeeee',
|
|
||||||
},
|
|
||||||
sectionHeader: {
|
|
||||||
padding: 5,
|
|
||||||
},
|
|
||||||
group: {
|
|
||||||
backgroundColor: 'white',
|
|
||||||
},
|
|
||||||
sectionHeaderTitle: {
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: 11,
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
backgroundColor: 'white',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingHorizontal: 15,
|
|
||||||
paddingVertical: 8,
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
height: 1 / PixelRatio.get(),
|
|
||||||
backgroundColor: '#bbbbbb',
|
|
||||||
marginLeft: 15,
|
|
||||||
},
|
|
||||||
rowTitleText: {
|
|
||||||
fontSize: 17,
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
rowDetailText: {
|
|
||||||
fontSize: 15,
|
|
||||||
color: '#888888',
|
|
||||||
lineHeight: 20,
|
|
||||||
},
|
|
||||||
searchRow: {
|
|
||||||
backgroundColor: '#eeeeee',
|
|
||||||
paddingTop: 75,
|
|
||||||
paddingLeft: 10,
|
|
||||||
paddingRight: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
},
|
|
||||||
searchTextInput: {
|
|
||||||
backgroundColor: 'white',
|
|
||||||
borderColor: '#cccccc',
|
|
||||||
borderRadius: 3,
|
|
||||||
borderWidth: 1,
|
|
||||||
paddingLeft: 8,
|
|
||||||
},
|
|
||||||
searchTextInputIOS: {
|
|
||||||
height: 30,
|
|
||||||
},
|
|
||||||
searchTextInputAndroid: {
|
|
||||||
padding: 2,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = UIExplorerList;
|
|
|
@ -144,7 +144,6 @@ class FormUploader extends React.Component {
|
||||||
CameraRoll.getPhotos(
|
CameraRoll.getPhotos(
|
||||||
{first: PAGE_SIZE},
|
{first: PAGE_SIZE},
|
||||||
(data) => {
|
(data) => {
|
||||||
console.log('isMounted', this._isMounted);
|
|
||||||
if (!this._isMounted) {
|
if (!this._isMounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
|
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
|
||||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
|
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
|
||||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
|
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
|
||||||
|
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
|
||||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
@ -50,6 +51,8 @@
|
||||||
143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; };
|
143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; };
|
||||||
35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; };
|
35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; };
|
||||||
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageStoreManager.m; sourceTree = "<group>"; };
|
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageStoreManager.m; sourceTree = "<group>"; };
|
||||||
|
354631661B69857700AA0B86 /* RCTImageEditingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageEditingManager.h; sourceTree = "<group>"; };
|
||||||
|
354631671B69857700AA0B86 /* RCTImageEditingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageEditingManager.m; sourceTree = "<group>"; };
|
||||||
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
|
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
|
||||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
|
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
|
||||||
|
@ -75,6 +78,8 @@
|
||||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
|
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
|
||||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
||||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
|
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
|
||||||
|
354631661B69857700AA0B86 /* RCTImageEditingManager.h */,
|
||||||
|
354631671B69857700AA0B86 /* RCTImageEditingManager.m */,
|
||||||
143879361AAD32A300F088A5 /* RCTImageLoader.h */,
|
143879361AAD32A300F088A5 /* RCTImageLoader.h */,
|
||||||
143879371AAD32A300F088A5 /* RCTImageLoader.m */,
|
143879371AAD32A300F088A5 /* RCTImageLoader.m */,
|
||||||
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
|
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
|
||||||
|
@ -167,6 +172,7 @@
|
||||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
||||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
||||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
||||||
|
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
|
||||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
|
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
|
||||||
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
|
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "RCTBridgeModule.h"
|
||||||
|
|
||||||
|
@interface RCTImageEditingManager : NSObject <RCTBridgeModule>
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,133 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "RCTImageEditingManager.h"
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import "RCTConvert.h"
|
||||||
|
#import "RCTLog.h"
|
||||||
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
|
#import "RCTImageStoreManager.h"
|
||||||
|
#import "RCTImageLoader.h"
|
||||||
|
|
||||||
|
@implementation RCTImageEditingManager
|
||||||
|
|
||||||
|
RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
|
@synthesize bridge = _bridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crops an image and adds the result to the image store.
|
||||||
|
*
|
||||||
|
* @param imageTag A URL, a string identifying an asset etc.
|
||||||
|
* @param cropData Dictionary with `offset`, `size` and `displaySize`.
|
||||||
|
* `offset` and `size` are relative to the full-resolution image size.
|
||||||
|
* `displaySize` is an optimization - if specified, the image will
|
||||||
|
* be scaled down to `displaySize` rather than `size`.
|
||||||
|
* All units are in px (not points).
|
||||||
|
*/
|
||||||
|
RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag
|
||||||
|
cropData:(NSDictionary *)cropData
|
||||||
|
successCallback:(RCTResponseSenderBlock)successCallback
|
||||||
|
errorCallback:(RCTResponseErrorBlock)errorCallback)
|
||||||
|
{
|
||||||
|
NSDictionary *offset = cropData[@"offset"];
|
||||||
|
NSDictionary *size = cropData[@"size"];
|
||||||
|
NSDictionary *displaySize = cropData[@"displaySize"];
|
||||||
|
NSString *resizeMode = cropData[@"resizeMode"] ?: @"contain";
|
||||||
|
|
||||||
|
if (!offset[@"x"] || !offset[@"y"] || !size[@"width"] || !size[@"height"]) {
|
||||||
|
NSString *errorMessage = [NSString stringWithFormat:@"Invalid cropData: %@", cropData];
|
||||||
|
RCTLogError(@"%@", errorMessage);
|
||||||
|
errorCallback(RCTErrorWithMessage(errorMessage));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *error, UIImage *image) {
|
||||||
|
if (error) {
|
||||||
|
errorCallback(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CGRect rect = (CGRect){
|
||||||
|
[RCTConvert CGPoint:offset],
|
||||||
|
[RCTConvert CGSize:size]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Crop image
|
||||||
|
CGRect rectToDrawIn = {{-rect.origin.x, -rect.origin.y}, image.size};
|
||||||
|
UIGraphicsBeginImageContextWithOptions(rect.size, !RCTImageHasAlpha(image.CGImage), image.scale);
|
||||||
|
[image drawInRect:rectToDrawIn];
|
||||||
|
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
|
UIGraphicsEndImageContext();
|
||||||
|
|
||||||
|
if (displaySize && displaySize[@"width"] && displaySize[@"height"]) {
|
||||||
|
CGSize targetSize = [RCTConvert CGSize:displaySize];
|
||||||
|
croppedImage = [self scaleImage:croppedImage targetSize:targetSize resizeMode:resizeMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
[_bridge.imageStoreManager storeImage:croppedImage withBlock:^(NSString *croppedImageTag) {
|
||||||
|
if (!croppedImageTag) {
|
||||||
|
NSString *errorMessage = @"Error storing cropped image in RCTImageStoreManager";
|
||||||
|
RCTLogWarn(@"%@", errorMessage);
|
||||||
|
errorCallback(RCTErrorWithMessage(errorMessage));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successCallback(@[croppedImageTag]);
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIImage *)scaleImage:(UIImage *)image targetSize:(CGSize)targetSize resizeMode:(NSString *)resizeMode
|
||||||
|
{
|
||||||
|
if (CGSizeEqualToSize(image.size, targetSize)) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGFloat imageRatio = image.size.width / image.size.height;
|
||||||
|
CGFloat targetRatio = targetSize.width / targetSize.height;
|
||||||
|
|
||||||
|
CGFloat newWidth = targetSize.width;
|
||||||
|
CGFloat newHeight = targetSize.height;
|
||||||
|
|
||||||
|
// contain vs cover
|
||||||
|
// http://blog.vjeux.com/2013/image/css-container-and-cover.html
|
||||||
|
if ([resizeMode isEqualToString:@"contain"]) {
|
||||||
|
if (imageRatio <= targetRatio) {
|
||||||
|
newWidth = targetSize.height * imageRatio;
|
||||||
|
newHeight = targetSize.height;
|
||||||
|
} else {
|
||||||
|
newWidth = targetSize.width;
|
||||||
|
newHeight = targetSize.width / imageRatio;
|
||||||
|
}
|
||||||
|
} else if ([resizeMode isEqualToString:@"cover"]) {
|
||||||
|
if (imageRatio <= targetRatio) {
|
||||||
|
newWidth = targetSize.width;
|
||||||
|
newHeight = targetSize.width / imageRatio;
|
||||||
|
} else {
|
||||||
|
newWidth = targetSize.height * imageRatio;
|
||||||
|
newHeight = targetSize.height;
|
||||||
|
}
|
||||||
|
} // else assume we're stretching the image
|
||||||
|
|
||||||
|
// prevent upscaling
|
||||||
|
newWidth = MIN(newWidth, image.size.width);
|
||||||
|
newHeight = MIN(newHeight, image.size.height);
|
||||||
|
|
||||||
|
// perform the scaling @1x because targetSize is in actual pixel width/height
|
||||||
|
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 1.0f);
|
||||||
|
[image drawInRect:CGRectMake(0.f, 0.f, newWidth, newHeight)];
|
||||||
|
UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
|
UIGraphicsEndImageContext();
|
||||||
|
|
||||||
|
return scaledImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -61,7 +61,7 @@ RCT_EXPORT_MODULE()
|
||||||
{
|
{
|
||||||
return [self loadImageWithTag:imageTag
|
return [self loadImageWithTag:imageTag
|
||||||
size:CGSizeZero
|
size:CGSizeZero
|
||||||
scale:0
|
scale:1
|
||||||
resizeMode:UIViewContentModeScaleToFill
|
resizeMode:UIViewContentModeScaleToFill
|
||||||
progressBlock:nil
|
progressBlock:nil
|
||||||
completionBlock:callback];
|
completionBlock:callback];
|
||||||
|
|
Loading…
Reference in New Issue