Refactor upload-image to image-upload component

This commit is contained in:
Barry Low committed 2018-09-28 17:31:19 -07:00
1 parent 2e21c13415
commit 5596f54864
4 files changed
+306 -22

No files matched your search

+8 -15
View File
@@ -13,18 +13,7 @@ const checkJwt = require('./auth/middleware/jwt')
const { getMember } = require('./auth/middleware/getMember')
const { validateIdToken } = require('./auth/validateIdToken')
const { directiveResolvers } = require('./auth/authDirectives')
const {
createStoragePipline,
createServiceURL,
listContainers,
createContainer,
createBlob,
uploadBlob,
downloadBlob,
deleteBlob,
deleteContainer
} = require('./storage/azure')
//var azure = require('azure-storage')
const Azure = require("@azure/storage-blob")
const azureAccount = process.env.AZURE_STORAGE_ACCOUNT
const azureKey = process.env.AZURE_STORAGE_ACCESS_KEY
@@ -160,6 +149,7 @@ const resolvers = {
const containerName = teamSlug
const containerURL = Azure.ContainerURL.fromServiceURL(serviceURL, containerName)
//list container and check if already exists.
let marker
let containerCheck = null
do {
@@ -167,25 +157,28 @@ const resolvers = {
Azure.Aborter.None,
marker,
)
console.log(`ContainerCheck marker: `, listContainersResponse)
// console.log(`ContainerCheck marker: `, listContainersResponse)
marker = listContainersResponse.marker;
for (const container of listContainersResponse.containerItems) {
console.log(`ContainerCheck: ${container.name} | ${containerName} | ${marker}`)
// console.log(`ContainerCheck: ${container.name} | ${containerName} | ${marker}`)
if(container.name === containerName){
containerCheck = container.name
}
}
} while (marker)
// if container doesn't exist, create one
let newContainer
if(containerCheck === null){
newContainer = await containerURL.create(Azure.Aborter.None, { access: 'blob' })
}
console.log('getAzureSAS container: ', containerURL, containerCheck, newContainer)
// console.log('getAzureSAS container: ', containerURL, containerCheck, newContainer)
// Set permissions for service, resource types and containers
const SASServicePerms = Azure.AccountSASServices.parse('b')
const SASResourceTypes = Azure.AccountSASResourceTypes.parse('co')
const SASContainerPerms = Azure.ContainerSASPermissions.parse('rwl')
// Generate SAS url
const SASQueryParameters = Azure.generateAccountSASQueryParameters(
{
+1 -1
View File
@@ -4,7 +4,7 @@ const Azure = require('@azure/storage-blob')
* @accountName: Azure Blob Storage account name (required) - Environment variable
* @accountKey: Azure Blob Storage storage key (required) - Environment variable
*/
const createStoragePipline = async (accountNAme, accountKey) => {
const createStoragePipline = async (accountName, accountKey) => {
return Azure.StorageURL.newPipeline(
new Azure.SharedKeyCredential(accountName, accountKey)
)
+264
View File
@@ -0,0 +1,264 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Mutation } from 'react-apollo'
import Dropzone from 'react-dropzone'
import { Cropper } from 'react-image-cropper'
import * as Azure from '@azure/storage-blob'
import readAndCompressImage from 'browser-image-resizer'
import { imgSrcToBlob } from 'blob-util'
import { withStyles } from '@material-ui/core/styles'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import Button from '@material-ui/core/Button'
import Typography from '@material-ui/core/Typography'
const styles = theme => ({
dropzoneContainer: {
display: 'block',
margin: '1em',
height: 200
},
dropzone: {
display: 'block',
padding: '3em',
height: 200,
borderWidth: 2,
borderColor: '#999',
backgroundColor: '#eee'
},
dropzoneText: {
textAlign: 'center'
},
existingImagePreview: {
width: 100,
height: 100
},
cropImageBox: {
width: 350,
height: 350
}
})
// placeholder image if no existing image
const placeHolder = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EAvatar%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'
// config for image resize and quality
const imageResizeConfig = {
quality: 0.6,
maxWidth: 200,
maxHeight: 200,
autoRotate: true,
debug: true,
mimeType: 'image/jpeg'
}
class UploadImage extends Component {
constructor (props) {
super(props)
this.onDrop = this.onDrop.bind(this)
this.handleCrop = this.handleCrop.bind(this)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleChangeImage = this.handleChangeImage.bind(this)
this.cancelChangeImage = this.cancelChangeImage.bind(this)
this.saveImage = this.saveImage.bind(this)
this.state = {
open: false,
change: false,
file: null,
src: null,
crop: {
x: 10,
y: 10,
width: 200,
height: 200
},
cropping: false,
cropped: null,
uploaded: false,
image: null,
sasqr: null
}
}
render () {
const { classes, cropContainer, saveImageConfig, containerName, fileName, existingImage, handleUrl, AZURE_SAS_QUERY } = this.props
let image = null
let resizeImage = null
if (existingImage !== undefined && existingImage !== null) {
image = existingImage
} else {
image = placeHolder
}
if (saveImageConfig !== undefined && saveImageConfig !== null) {
resizeImage = saveImageConfig
} else {
resizeImage = imageResizeConfig
}
return (
<div className={classes.dropzoneContainer}>
<Mutation mutation={AZURE_SAS_QUERY} >
{(getAzureSAS, { loading, error, data }) => {
if (this.state.cropping) {
return (
<Typography variant='title' color='primary' >Cropping...</Typography>
)
} else {
return (
<React.Fragment>
<div>
{(this.state.change || this.state.src === null) && (
<React.Fragment>
<Dropzone
accept='image/jpeg, image/png'
onDrop={(e) => this.onDrop(e, this.state.edit)}
className={classes.dropzone}
multiple={false}
>
<p>Drag-and-drop image here or click to select <br /> Only *.jpeg and *.png images</p>
</Dropzone>
<Button
onClick={(e) => this.cancelChangeImage()}
color='primary'
>
Cancel Image Change
</Button>
</React.Fragment>
)}
{!this.state.change && (
<React.Fragment>
<img src={image} className={classes.existingImagePreview} alt='Existing Image Preview' />
<Button
onClick={e => this.handleChangeImage(e, cropContainer)}
color='primary'
>
Change Image
</Button>
</React.Fragment>
)}
{this.state.image !== null && (
<Typography variant='title' color='primary' >Save profile to save image changes!</Typography>
)}
</div>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={classes.dialogContainer}>
<DialogContent>
<div className={classes.cropImageBox} >
{this.state.src !== null && (
<Cropper
src={this.state.src}
originX={100}
originY={100}
ref={ref => { this.cropper = ref }}
onImgLoad={this.onImageLoaded}
ratio={1}
/>
)}
</div>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button
onClick={(e) => this.handleCrop(e, getAzureSAS, resizeImage, containerName, fileName, handleUrl)}
color='primary'
>
Crop Image
</Button>
</DialogActions>
</div>
</Dialog>
</React.Fragment>
)
}
}}
</Mutation>
</div>
)
}
onDrop (file, editImageType) {
console.log('onDrop', file)
this.onSelectFile(file, editImageType)
this.setState({
file
})
}
async handleCrop (e, getAzureSAS, resizeImage, containerName, fileName, handleUrl) {
e.preventDefault()
const data = await getAzureSAS({ variables: { containerName } })
const cropped = this.cropper.crop()
const croppedBlob = await imgSrcToBlob(cropped, 'image/png', 'Anonymous')
let resizedBlob = await readAndCompressImage(croppedBlob, resizeImage)
return this.setState({ cropped: resizedBlob, cropping: true, open: false }, () => {
this.saveImage(data.getAzureSAS, containerName, fileName, handleUrl)
})
}
async saveImage (SASQueryParameters, containerName, fileName, handleUrl) {
const SASurl = `${process.env.AZURE_STORAGE_URL}?${this.state.sasqr}`
// create Azure BlockBlobURL to save image to
const pipeline = Azure.StorageURL.newPipeline(new Azure.AnonymousCredential())
const serviceURL = new Azure.ServiceURL(SASurl, pipeline)
const containerURL = Azure.ContainerURL.fromServiceURL(serviceURL, containerName)
const blockBlobURL = Azure.BlockBlobURL.fromContainerURL(containerURL, fileName)
await Azure.uploadBrowserDataToBlockBlob(Azure.Aborter.None, this.state.cropped, blockBlobURL)
const image = `${process.env.AZURE_STORAGE_URL}${containerName}/${fileName}?${Math.random()}` // random number keeps browser from using cached version of existing image
handleUrl(image) // pass image URL up to be save to DB
this.setState({ cropping: false, uploaded: true, change: false, image: image })
return image
}
handleChangeImage (e, cropContainer) {
e.preventDefault()
let crop = cropContainer // dimensions of cropContainer passed from above
this.setState({
change: true,
crop: crop
})
}
cancelChangeImage () {
this.setState({ change: false })
}
handleClickOpen (e) {
e.preventDefault()
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false, src: null, change: false })
}
}
UploadImage.propTypes = {
classes: PropTypes.object.isRequired,
cropContainer: PropTypes.object, // { x: 10, y: 10, width: 200, height: 200 } (default)
saveImageConfig: PropTypes.object, // see imageResizeConfig above for default (default)
containerName: PropTypes.string.isRequired, // name of Azure Container image will be saved in
fileName: PropTypes.string.isRequired, // filename to save image as
existingImage: PropTypes.string, // image url of existing image (optional)
handleUrl: PropTypes.func.isRequired, // function that handles uploaded image file url
AZURE_SAS_QUERY: PropTypes.any.isRequired // GraphQL for getting Azure SAS URL
}
export default withStyles(styles)(UploadImage)
@@ -20,7 +20,8 @@ import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import { checkGraphQLError } from '../../../utils/graphql-errors'
import { UploadImage } from '../../common'
import { ImageUpload } from '../../common'
import GET_AZURE_SAS from '../../graphql/teams/GetAzureSASMutation'
const styles = theme => ({
dialogContainer: {
@@ -111,11 +112,37 @@ export class ManageTeamEdit extends Component {
Edit Team Details
</DialogTitle>
<DialogContent>
<UploadImage
team={team}
authId={authId}
handleAvatar={this.handleAvatar}
handleBanner={this.handleBanner}
<ImageUpload
handleURL={this.handleBanner}
fileName={`${team.slug}-banner.jpg`}
containerName={team.slug}
existingImage={team.profile.banner}
cropContainer={{ x: 10, y: 10, width: 800, height: 200 }}
saveImageConfig={{
quality: 0.6,
maxWidth: 800,
maxHeight: 200,
autoRotate: true,
debug: true,
mimeType: 'image/jpeg'
}}
AZURE_SAS_QUERY={GET_AZURE_SAS}
/>
<ImageUpload
handleURL={this.handleAvatar}
fileName={`${team.slug}-avatar.jpg`}
containerName={team.slug}
existingImage={team.profile.avatar}
cropContainer={{ x: 10, y: 10, width: 200, height: 200 }}
saveImageConfig={{
quality: 0.6,
maxWidth: 200,
maxHeight: 200,
autoRotate: true,
debug: true,
mimeType: 'image/jpeg'
}}
AZURE_SAS_QUERY={GET_AZURE_SAS}
/>
<TextField
autoFocus