mirror of
https://github.com/zhigang1992/examples.git
synced 2026-01-12 17:13:21 +08:00
Add with-formdata-image-upload
This commit is contained in:
18
with-formdata-image-upload/README.md
Normal file
18
with-formdata-image-upload/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Image Upload Example
|
||||
|
||||
Try it at https://expo.io/@community/image-upload-example
|
||||
|
||||
## How to use
|
||||
|
||||
### Running the app
|
||||
|
||||
- `cd` into the `app` directory and run `yarn` or `npm install`
|
||||
- Open `app` with `exp` or XDE, try it out.
|
||||
|
||||
### Running the server
|
||||
|
||||
- By default, the app will use a server that is already deployed in order to upload the image to S3. If you want to deploy your own, follow the steps in the [backend directory](https://github.com/expo/examples/tree/master/with-formdata-image-upload/backend).
|
||||
|
||||
## The idea behind the example
|
||||
|
||||
A common requirement for apps is to be able to upload an image to a server. This example shows how you can use `ImagePicker` to snap a photo or grab it from your camera roll, then use `FormData` with `fetch` to upload it to a server. The `/backend` demsontrates a simple Node app that uploads an image to S3. The `/app` directory contains the Expo app that sends the image to that backend.
|
||||
8
with-formdata-image-upload/app/.babelrc
Normal file
8
with-formdata-image-upload/app/.babelrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"presets": ["babel-preset-expo"],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["transform-react-jsx-source"]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
with-formdata-image-upload/app/.gitignore
vendored
Normal file
3
with-formdata-image-upload/app/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
192
with-formdata-image-upload/app/App.js
Normal file
192
with-formdata-image-upload/app/App.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Clipboard,
|
||||
Image,
|
||||
Share,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Exponent, { Constants, ImagePicker, registerRootComponent } from 'expo';
|
||||
|
||||
export default class App extends React.Component {
|
||||
state = {
|
||||
image: null,
|
||||
uploading: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
let { image } = this.state;
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 20,
|
||||
marginBottom: 20,
|
||||
textAlign: 'center',
|
||||
marginHorizontal: 15,
|
||||
}}>
|
||||
Example: Upload ImagePicker result
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
onPress={this._pickImage}
|
||||
title="Pick an image from camera roll"
|
||||
/>
|
||||
|
||||
<Button onPress={this._takePhoto} title="Take a photo" />
|
||||
|
||||
{this._maybeRenderImage()}
|
||||
{this._maybeRenderUploadingOverlay()}
|
||||
|
||||
<StatusBar barStyle="default" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_maybeRenderUploadingOverlay = () => {
|
||||
if (this.state.uploading) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
]}>
|
||||
<ActivityIndicator color="#fff" animating size="large" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_maybeRenderImage = () => {
|
||||
let { image } = this.state;
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
marginTop: 30,
|
||||
width: 250,
|
||||
borderRadius: 3,
|
||||
elevation: 2,
|
||||
shadowColor: 'rgba(0,0,0,1)',
|
||||
shadowOpacity: 0.2,
|
||||
shadowOffset: { width: 4, height: 4 },
|
||||
shadowRadius: 5,
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
borderTopRightRadius: 3,
|
||||
borderTopLeftRadius: 3,
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
|
||||
</View>
|
||||
|
||||
<Text
|
||||
onPress={this._copyToClipboard}
|
||||
onLongPress={this._share}
|
||||
style={{ paddingVertical: 10, paddingHorizontal: 10 }}>
|
||||
{image}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
_share = () => {
|
||||
Share.share({
|
||||
message: this.state.image,
|
||||
title: 'Check out this photo',
|
||||
url: this.state.image,
|
||||
});
|
||||
};
|
||||
|
||||
_copyToClipboard = () => {
|
||||
Clipboard.setString(this.state.image);
|
||||
alert('Copied image URL to clipboard');
|
||||
};
|
||||
|
||||
_takePhoto = async () => {
|
||||
let pickerResult = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
});
|
||||
|
||||
this._handleImagePicked(pickerResult);
|
||||
};
|
||||
|
||||
_pickImage = async () => {
|
||||
let pickerResult = await ImagePicker.launchImageLibraryAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
});
|
||||
|
||||
this._handleImagePicked(pickerResult);
|
||||
};
|
||||
|
||||
_handleImagePicked = async pickerResult => {
|
||||
let uploadResponse, uploadResult;
|
||||
|
||||
try {
|
||||
this.setState({ uploading: true });
|
||||
|
||||
if (!pickerResult.cancelled) {
|
||||
uploadResponse = await uploadImageAsync(pickerResult.uri);
|
||||
uploadResult = await uploadResponse.json();
|
||||
this.setState({ image: uploadResult.location });
|
||||
}
|
||||
} catch (e) {
|
||||
console.log({ uploadResponse });
|
||||
console.log({ uploadResult });
|
||||
console.log({ e });
|
||||
alert('Upload failed, sorry :(');
|
||||
} finally {
|
||||
this.setState({ uploading: false });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function uploadImageAsync(uri) {
|
||||
let apiUrl = 'https://file-upload-example-backend-dkhqoilqqn.now.sh/upload';
|
||||
|
||||
// Note:
|
||||
// Uncomment this if you want to experiment with local server
|
||||
//
|
||||
// if (Constants.isDevice) {
|
||||
// apiUrl = `https://your-ngrok-subdomain.ngrok.io/upload`;
|
||||
// } else {
|
||||
// apiUrl = `http://localhost:3000/upload`
|
||||
// }
|
||||
|
||||
let uriParts = uri.split('.');
|
||||
let fileType = uri[uri.length - 1];
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append('photo', {
|
||||
uri,
|
||||
name: `photo.${fileType}`,
|
||||
type: `image/${fileType}`,
|
||||
});
|
||||
|
||||
let options = {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
};
|
||||
|
||||
return fetch(apiUrl, options);
|
||||
}
|
||||
17
with-formdata-image-upload/app/app.json
Normal file
17
with-formdata-image-upload/app/app.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "image-upload-example",
|
||||
"description": "Demonstration of how to upload images from the ImagePicker, using a Node backend to upload to S3. https://github.com/expo/examples/tree/master/with-formdata-image-upload",
|
||||
"slug": "image-upload-example",
|
||||
"sdkVersion": "20.0.0",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"primaryColor": "#cccccc",
|
||||
"packagerOpts": {
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
}
|
||||
}
|
||||
}
|
||||
13
with-formdata-image-upload/app/package.json
Normal file
13
with-formdata-image-upload/app/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "image-upload-example-frontend",
|
||||
"version": "0.0.0",
|
||||
"description": "Hello Exponent!",
|
||||
"author": "exponent.team@gmail.com",
|
||||
"private": true,
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"dependencies": {
|
||||
"expo": "^20.0.0",
|
||||
"react": "16.0.0-alpha.12",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-20.0.0.tar.gz"
|
||||
}
|
||||
}
|
||||
3948
with-formdata-image-upload/app/yarn.lock
Normal file
3948
with-formdata-image-upload/app/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
3
with-formdata-image-upload/backend/.env.example
Normal file
3
with-formdata-image-upload/backend/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
|
||||
AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
|
||||
AWS_BUCKET=<YOUR_BUCKET_NAME>
|
||||
2
with-formdata-image-upload/backend/.gitignore
vendored
Normal file
2
with-formdata-image-upload/backend/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/**/*
|
||||
.env
|
||||
31
with-formdata-image-upload/backend/README.md
Normal file
31
with-formdata-image-upload/backend/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
## Run this locally
|
||||
|
||||
1. Rename `.env.example` to `.env`
|
||||
|
||||
2. Fill in your AWS credentials in `.env`
|
||||
|
||||
3. `npm start`
|
||||
|
||||
4. Update the `apiUrl` in `frontend` to point to the server.
|
||||
|
||||
## Deploy this with [now.sh](https://zeit.co/now/):
|
||||
|
||||
1. Add your AWS credentials to now
|
||||
|
||||
```
|
||||
now secret add aws-access-key-id your-access-key-id-here
|
||||
now secret add aws-secret-access-key your-secret-access-key
|
||||
now secret add aws-bucket your-bucket-name-here
|
||||
```
|
||||
|
||||
2. Deploy
|
||||
|
||||
```
|
||||
now . \
|
||||
-e AWS_ACCESS_KEY_ID=@aws-access-key-id \
|
||||
-e AWS_SECRET_ACCESS_KEY=@aws-secret-access-key \
|
||||
-e AWS_BUCKET=@aws-bucket \
|
||||
-e NODE_ENV=production
|
||||
```
|
||||
|
||||
3. Update the `apiUrl` in `frontend` to point to your deploy.
|
||||
42
with-formdata-image-upload/backend/index.js
Normal file
42
with-formdata-image-upload/backend/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// Use local .env file for env vars when not deployed
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
require('dotenv').config();
|
||||
}
|
||||
|
||||
const aws = require('aws-sdk')
|
||||
const multer = require('multer')
|
||||
const multerS3 = require('multer-s3')
|
||||
|
||||
const s3 = new aws.S3({
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
region: "us-east-1",
|
||||
});
|
||||
|
||||
// Initialize multers3 with our s3 config and other options
|
||||
const upload = multer({
|
||||
storage: multerS3({
|
||||
s3,
|
||||
bucket: process.env.AWS_BUCKET,
|
||||
acl: 'public-read',
|
||||
metadata(req, file, cb) {
|
||||
cb(null, {fieldName: file.fieldname});
|
||||
},
|
||||
key(req, file, cb) {
|
||||
cb(null, Date.now().toString() + '.png');
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Expose the /upload endpoint
|
||||
const app = require('express')();
|
||||
const http = require('http').Server(app);
|
||||
|
||||
app.post('/upload', upload.single('photo'), (req, res, next) => {
|
||||
res.json(req.file)
|
||||
})
|
||||
|
||||
let port = process.env.PORT || 3000;
|
||||
http.listen(port, () => {
|
||||
console.log(`Listening on port ${port}`);
|
||||
});
|
||||
18
with-formdata-image-upload/backend/package.json
Normal file
18
with-formdata-image-upload/backend/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "file-upload-example-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.7.13",
|
||||
"dotenv": "^2.0.0",
|
||||
"express": "^4.14.0",
|
||||
"multer": "^1.2.0",
|
||||
"multer-s3": "^2.5.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user