Last active
May 2, 2019 12:15
-
-
Save jermsam/9a06ca376d870301b9e463003bd0d34b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, createRef } from 'react'; | |
import { | |
Menu, | |
Modal, | |
Header, | |
Button, | |
Icon, | |
Loader, | |
Container | |
} from 'semantic-ui-react'; | |
import ProcessImage from 'react-imgpro'; | |
import moment from 'moment'; | |
import Webcam from 'react-webcam'; | |
import ImageDropzone from './ImageDropzone'; | |
import ImageCropper from './ImageCropper'; | |
import { | |
// downloadBase64File, | |
base64StringtoFile, | |
extractImageFileExtensionFromBase64, | |
image64toCanvasRef | |
} from '../../methods'; | |
const INITIAL_STATE = { | |
isPreviewed: false | |
}; | |
export default class ImageEditor extends Component { | |
state = INITIAL_STATE; | |
_isMounted = false; | |
_previewCanvasRef = createRef(); | |
componentDidMount() { | |
this._isMounted = true; | |
// this.updateCanvas(); | |
} | |
componentWillUnmount() { | |
this._isMounted = false; | |
} | |
setRef = webcam => { | |
this.webcam = webcam; | |
}; | |
onFilePreview = async () => { | |
const { image, pixelCrop, goToPreview } = this.props; | |
await goToPreview(); | |
const canvasRef = this._previewCanvasRef.current; | |
// console.log(pixelCrop); | |
image64toCanvasRef(canvasRef, image, pixelCrop); | |
}; | |
onFileSubmitted = async () => { | |
const { image, uploadToServer, onClose } = this.props; | |
const canvasRef = this._previewCanvasRef.current; | |
const fileExtension = extractImageFileExtensionFromBase64(image); | |
const fileName = `${moment().unix()}-photo.${fileExtension}`; | |
// get new manipulated image | |
const processedImage = canvasRef.toDataURL(`image/${fileExtension}`, 1.0); | |
// file to be uploaded | |
const fileToUpload = base64StringtoFile(processedImage, fileName); | |
await uploadToServer(fileToUpload); | |
onClose(); | |
}; | |
capture = () => { | |
const imageSrc = this.webcam.getScreenshot(); | |
const { capture } = this.props; | |
capture(imageSrc); | |
}; | |
render() { | |
const { | |
image, | |
isAdded, | |
open, | |
onClose, | |
addedfile, | |
removedfile, | |
crop, | |
onCrop, | |
onCompleteCrop, | |
onFileUploaded, | |
isRotated, | |
startFileRotation, | |
stopFileRotation, | |
isPreviewed, | |
backToEdit, | |
isFlipped, | |
startFileFlip, | |
stopFileFlip, | |
isGreyed, | |
startFileGrey, | |
stopFileGrey, | |
isWebcam, | |
selectWebcam, | |
isCaptured, | |
backToDropzone, | |
canvas: { width, height }, | |
isCropped | |
} = this.props; | |
return ( | |
<> | |
<Modal | |
{...{ open }} | |
{...{ onClose }} | |
size="small" | |
closeOnDimmerClick={false} | |
centered | |
dimmer="inverted" | |
// wow ... finally modal close is clean | |
closeIcon={{ | |
style: { top: '1.335rem', right: '1rem' }, | |
inverted: true, | |
color: 'black', | |
name: 'close', | |
circular: true, | |
size: 'mini' | |
}} | |
> | |
<Menu attached="top" borderless> | |
<Menu.Item> | |
<Header | |
style={{ fontSize: '11px' }} | |
icon={ | |
isGreyed | |
? 'image outline' | |
: `${ | |
isFlipped | |
? 'exchange' | |
: `${ | |
isRotated | |
? 'undo' | |
: `${ | |
!(isAdded || isCaptured) | |
? `${!isWebcam ? 'dropbox' : 'camera'}` | |
: `${isPreviewed ? 'eye' : 'cut'}` | |
}` | |
}` | |
}` | |
} | |
content={ | |
isGreyed | |
? 'Image greying ...' | |
: `${ | |
isFlipped | |
? 'Image flipping ...' | |
: `${ | |
isRotated | |
? 'Image rotating ...' | |
: `${ | |
!(isAdded || isCaptured) | |
? ` | |
${ | |
!isWebcam | |
? 'Upload a photo' | |
: 'Use the webcam' | |
} | |
` | |
: `${ | |
isPreviewed | |
? 'Preview photo' | |
: 'Crop your photo to submit' | |
}` | |
}` | |
}` | |
}` | |
} | |
/> | |
</Menu.Item> | |
<Menu.Menu position="right"> | |
{!(isAdded || isCaptured) && ( | |
<> | |
{!isWebcam ? ( | |
<Menu.Item onClick={selectWebcam}> | |
<Icon name="camera" size="large" /> use webcam | |
</Menu.Item> | |
) : ( | |
<Menu.Item onClick={backToDropzone}> | |
<Icon name="dropbox" size="large" /> use dropbox | |
</Menu.Item> | |
)} | |
<Menu.Item /> | |
<Menu.Item /> | |
</> | |
)} | |
</Menu.Menu> | |
</Menu> | |
<Modal.Content | |
style={{ background: `url(/static/imgs/placeholder.jpg)` }} | |
> | |
<> | |
{isAdded || isCaptured ? ( | |
<> | |
{isGreyed ? ( | |
<> | |
<Loader>greying</Loader> | |
<div style={{ visibility: 'hidden', width, height }}> | |
<ProcessImage | |
image={image} | |
greyscale | |
processedImage={stopFileGrey} | |
storage={false} | |
/> | |
</div> | |
</> | |
) : ( | |
<> | |
{isFlipped ? ( | |
<> | |
<Loader>flipping</Loader> | |
<div style={{ visibility: 'hidden', width, height }}> | |
<ProcessImage | |
image={image} | |
flip={{ horizontal: true }} | |
processedImage={stopFileFlip} | |
storage={false} | |
/> | |
</div> | |
</> | |
) : ( | |
<> | |
{isRotated ? ( | |
<> | |
<Loader>rotating</Loader> | |
<div | |
style={{ visibility: 'hidden', width, height }} | |
> | |
<ProcessImage | |
image={image} | |
rotate={{ degree: 90, mode: 'bilinear' }} | |
processedImage={stopFileRotation} | |
storage={false} | |
/> | |
</div> | |
</> | |
) : ( | |
<> | |
{isPreviewed ? ( | |
<Container textAlign="center"> | |
<canvas | |
ref={this._previewCanvasRef} | |
{...{ width }} | |
{...{ height }} | |
/> | |
</Container> | |
) : ( | |
// Image cropper in itself gives us a preview | |
<ImageCropper | |
{...{ image }} | |
{...{ crop }} | |
{...{ onCrop }} | |
{...{ onCompleteCrop }} | |
/> | |
)} | |
</> | |
)} | |
</> | |
)} | |
</> | |
)} | |
</> | |
) : ( | |
<> | |
{isWebcam ? ( | |
<> | |
<Webcam | |
audio={false} | |
ref={this.setRef} | |
screenshotFormat="image/png" | |
/> | |
</> | |
) : ( | |
<ImageDropzone | |
{...{ image }} | |
prompt="drag & drop a photo or click to upload one" | |
handleFileUploaded={onFileUploaded} | |
{...{ addedfile }} | |
{...{ removedfile }} | |
/> | |
)} | |
</> | |
)} | |
</> | |
</Modal.Content> | |
<Modal.Actions> | |
{isWebcam && ( | |
<Button | |
color="black" | |
onClick={this.capture} | |
basic | |
circular | |
size="tiny" | |
> | |
<Icon name="camera" /> Take Photo | |
</Button> | |
)} | |
{(isAdded || isCaptured) && ( | |
<> | |
{isCropped ? ( | |
<> | |
{isPreviewed ? ( | |
<> | |
<Button | |
color="teal" | |
onClick={backToEdit} | |
basic | |
circular | |
> | |
<Icon name="long arrow alternate left" /> back | |
</Button> | |
<Button | |
color="blue" | |
onClick={this.onFileSubmitted} | |
basic | |
circular | |
size="tiny" | |
> | |
<Icon name="checkmark" /> submit | |
</Button> | |
</> | |
) : ( | |
<Button | |
color="teal" | |
onClick={this.onFilePreview} | |
basic | |
circular | |
size="tiny" | |
> | |
<Icon name="cut" /> crop | |
</Button> | |
)} | |
</> | |
) : ( | |
<> | |
<Button | |
color="grey" | |
onClick={startFileGrey} | |
basic | |
circular | |
size="tiny" | |
> | |
<Icon name="image outline" /> grey | |
</Button> | |
<Button | |
color="purple" | |
onClick={startFileFlip} | |
basic | |
circular | |
size="tiny" | |
> | |
<Icon name="exchange" /> flip | |
</Button> | |
<Button | |
color="green" | |
onClick={startFileRotation} | |
basic | |
circular | |
size="tiny" | |
> | |
<Icon name="undo" /> rotate | |
</Button> | |
</> | |
)} | |
</> | |
)} | |
</Modal.Actions> | |
</Modal> | |
</> | |
); | |
} | |
} | |
ImageEditor.getInnitialProps = ({ | |
image, | |
isAdded, | |
isSubmitted, | |
open, | |
onClose, | |
addedfile, | |
removedfile, | |
crop, | |
pixelCrop, | |
onCrop, | |
onCompleteCrop, | |
onFileUploaded, | |
isRotated, | |
startFileRotation, | |
stopFileRotation, | |
isPreviewed, | |
goToPreview, | |
backToEdit, | |
isFlipped, | |
startFileFlip, | |
stopFileFlip, | |
isGreyed, | |
startFileGrey, | |
stopFileGrey, | |
uploadToServer, | |
isWebcam, | |
selectWebcam, | |
isCaptured, | |
capture, | |
backToDropzone, | |
canvas, | |
isCropped | |
}) => ({ | |
image, | |
isAdded, | |
isSubmitted, | |
open, | |
onClose, | |
addedfile, | |
removedfile, | |
crop, | |
pixelCrop, | |
onCrop, | |
onCompleteCrop, | |
onFileUploaded, | |
isRotated, | |
startFileRotation, | |
stopFileRotation, | |
isPreviewed, | |
goToPreview, | |
backToEdit, | |
isFlipped, | |
startFileFlip, | |
stopFileFlip, | |
isGreyed, | |
startFileGrey, | |
stopFileGrey, | |
uploadToServer, | |
isWebcam, | |
selectWebcam, | |
isCaptured, | |
capture, | |
backToDropzone, | |
canvas, | |
isCropped | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-console */ | |
import React from 'react'; | |
import { app } from '../../feathers'; | |
import ImageEditor from './ImageEditor'; | |
import { realImgDimension } from '../../methods'; | |
const INNITIAL_STATE = { | |
image: '', | |
isAdded: false, | |
crop: { | |
aspect: 1 / 1 | |
}, | |
pixelCrop: '', | |
isRotated: false, | |
isPreviewed: false, | |
isFlipped: false, | |
isGreyed: false, | |
isWebcam: false, | |
isCaptured: false, | |
isCropped: false | |
}; | |
export default class PhotoEditor extends React.Component { | |
state = INNITIAL_STATE; | |
_isMounted = false; | |
componentDidMount() { | |
this._isMounted = true; | |
this.prepopulate(); | |
} | |
componentDidUpdate(prevProps) { | |
const { setPhoto, canvas, crop, open, closeEditor, upload } = this.props; | |
if ( | |
prevProps.open !== open || | |
prevProps.setPhoto !== setPhoto || | |
prevProps.canvas !== canvas || | |
prevProps.crop !== crop || | |
prevProps.closeEditor !== closeEditor || | |
prevProps.upload !== upload | |
) { | |
this.prepopulate(); | |
} | |
} | |
componentWillUnmount() { | |
this._isMounted = false; | |
} | |
toggleConcealled = () => { | |
if (this._isMounted) { | |
this.setState(prevState => ({ | |
...prevState, | |
concealed: !prevState.concealed | |
})); | |
} | |
}; | |
handleOnChange = (e, { name, value }) => { | |
if (this._isMounted) { | |
this.setState(prevState => ({ | |
...prevState, | |
data: { | |
...prevState.data, | |
[name]: value | |
} | |
})); | |
} | |
}; | |
closeImageEditor = () => { | |
const { closeEditor } = this.props; | |
this.onFileRemoved(); | |
this.setState(prevState => ({ ...prevState, isPreviewed: false })); | |
closeEditor(); | |
}; | |
onFileAdded = file => { | |
const reader = new FileReader(); | |
reader.onload = e => { | |
const image = e.target.result; | |
// do whatever you want with the file content | |
if (this._isMounted) { | |
const img = realImgDimension(image); | |
img.onload = () => { | |
const { width, height, x, y } = img; | |
const pixelCrop = { | |
width, | |
height, | |
x, | |
y | |
}; | |
// console.log(pixelCrop); | |
this.setState(prevState => ({ | |
...prevState, | |
isAdded: true, | |
image, | |
pixelCrop, | |
isCropped: false | |
})); | |
}; | |
} | |
}; | |
reader.onabort = () => console.log('file reading was aborted'); | |
reader.onerror = () => console.log('file reading has failed'); | |
if (file) { | |
reader.readAsDataURL(file); | |
} | |
}; | |
onFileRemoved = () => { | |
this.setState(prevState => ({ | |
...prevState, | |
isAdded: false, | |
isCaptured: false, | |
image: null, | |
isCropped: false | |
})); | |
}; | |
onFileCropped = crop => { | |
if (this._isMounted) { | |
this.setState(prevState => ({ ...prevState, crop })); | |
} | |
}; | |
onFileCropCompleted = (crop, pixelCrop) => { | |
console.log(crop, pixelCrop); | |
this.setState(prevState => ({ | |
...prevState, | |
crop, | |
pixelCrop, | |
isCropped: true | |
})); | |
}; | |
onFileRotationStarted = () => | |
this.setState(prevState => ({ | |
...prevState, | |
isRotated: true, | |
isCropped: false | |
})); | |
onFileFlipStarted = () => | |
this.setState(prevState => ({ | |
...prevState, | |
isFlipped: true, | |
isCropped: false | |
})); | |
onFileGreyStarted = () => | |
this.setState(prevState => ({ | |
...prevState, | |
isGreyed: true, | |
isCropped: false | |
})); | |
onFileRotationStopped = image => { | |
if (this._isMounted) { | |
const img = realImgDimension(image); | |
try { | |
img.onload = () => { | |
const { width, height, x, y } = img; | |
const pixelCrop = { | |
width, | |
height, | |
x, | |
y | |
}; | |
// console.log(pixelCrop); | |
this.setState(prevState => ({ | |
...prevState, | |
isRotated: false, | |
isPreviewed: false, | |
image, | |
pixelCrop, | |
isCropped: false | |
})); | |
}; | |
} catch ({ message }) { | |
console.warn('Error: ', message); | |
} | |
} | |
}; | |
onFileFlipStopped = image => { | |
if (this._isMounted) { | |
const img = realImgDimension(image); | |
try { | |
img.onload = () => { | |
const { width, height, x, y } = img; | |
const pixelCrop = { | |
width, | |
height, | |
x, | |
y | |
}; | |
// console.log(pixelCrop); | |
this.setState(prevState => ({ | |
...prevState, | |
isFlipped: false, | |
isPreviewed: false, | |
image, | |
pixelCrop, | |
isCropped: false | |
})); | |
}; | |
} catch ({ message }) { | |
// console.warn('Error: ', message); | |
this.setState(prevState => ({ | |
...prevState, | |
isFlipped: false, | |
isPreviewed: false, | |
image, | |
isCropped: false | |
})); | |
} | |
} | |
}; | |
onFileGreyStopped = image => { | |
if (this._isMounted) { | |
const img = realImgDimension(image); | |
try { | |
img.onload = () => { | |
const { width, height, x, y } = img; | |
const pixelCrop = { | |
width, | |
height, | |
x, | |
y | |
}; | |
// console.log(pixelCrop); | |
this.setState(prevState => ({ | |
...prevState, | |
isGreyed: false, | |
isPreviewed: false, | |
image, | |
pixelCrop, | |
isCropped: false | |
})); | |
}; | |
} catch ({ message }) { | |
// console.log('Error: ', message); | |
this.setState(prevState => ({ | |
...prevState, | |
isGreyed: false, | |
isPreviewed: false, | |
image, | |
isCropped: false | |
})); | |
} | |
} | |
}; | |
onGoToPreview = () => | |
this.setState(prevState => ({ ...prevState, isPreviewed: true })); | |
onBackToEdit = () => | |
this.setState(prevState => ({ | |
...prevState, | |
isPreviewed: false, | |
isCropped: false | |
})); | |
onUploadToServer = file => { | |
const { setPhoto, upload } = this.props; | |
const reader = new FileReader(); | |
try { | |
reader.onload = () => { | |
const uri = reader.result; | |
// do whatever you want with the file content | |
const service = app.service('uploads'); | |
upload(true); | |
service | |
.create({ uri }) | |
.then(({ id }) => setPhoto(id)) | |
.catch(err => console.log(err)); | |
}; | |
reader.onabort = () => console.log('file reading was aborted'); | |
reader.onerror = () => console.log('file reading has failed'); | |
if (file) { | |
reader.readAsDataURL(file); | |
} | |
} catch ({ message }) { | |
console.warn('Error: ', message); | |
} | |
}; | |
// webcam | |
onWebcamSelected = () => | |
this.setState(prevState => ({ ...prevState, isWebcam: true })); | |
onCapture = image => { | |
if (this._isMounted) { | |
const img = realImgDimension(image); | |
img.onload = () => { | |
const { width, height, x, y } = img; | |
const pixelCrop = { | |
width, | |
height, | |
x, | |
y | |
}; | |
// console.log(pixelCrop); | |
this.setState(prevState => ({ | |
...prevState, | |
isCaptured: true, | |
isWebcam: false, | |
image, | |
pixelCrop | |
})); | |
}; | |
} | |
}; | |
onBackToDropzone = () => | |
this.setState(prevState => ({ ...prevState, isWebcam: false })); | |
prepopulate = () => { | |
const { crop } = this.props; | |
this.setState(prevState => ({ ...prevState, crop })); | |
}; | |
render() { | |
const { | |
image, | |
isAdded, | |
crop, | |
pixelCrop, | |
isRotated, | |
isPreviewed, | |
isFlipped, | |
isGreyed, | |
isWebcam, | |
isCaptured, | |
isCropped | |
} = this.state; | |
const { canvas, open } = this.props; | |
return ( | |
<> | |
<ImageEditor | |
{...{ image }} | |
{...{ canvas }} | |
{...{ open }} | |
{...{ isAdded }} | |
onClose={this.closeImageEditor} | |
addedfile={this.onFileAdded} | |
onFileUploaded={this.onFileUploaded} | |
removedfile={this.onFileRemoved} | |
{...{ crop }} | |
{...{ pixelCrop }} | |
onCrop={this.onFileCropped} | |
onCompleteCrop={this.onFileCropCompleted} | |
previewCanvasRef={this.previewCanvasRef} | |
{...{ isRotated }} | |
startFileRotation={this.onFileRotationStarted} | |
stopFileRotation={this.onFileRotationStopped} | |
{...{ isPreviewed }} | |
goToPreview={this.onGoToPreview} | |
backToEdit={this.onBackToEdit} | |
{...{ isFlipped }} | |
startFileFlip={this.onFileFlipStarted} | |
stopFileFlip={this.onFileFlipStopped} | |
{...{ isGreyed }} | |
startFileGrey={this.onFileGreyStarted} | |
stopFileGrey={this.onFileGreyStopped} | |
uploadToServer={this.onUploadToServer} | |
{...{ isWebcam }} | |
selectWebcam={this.onWebcamSelected} | |
{...{ isCaptured }} | |
capture={this.onCapture} | |
backToDropzone={this.onBackToDropzone} | |
{...{ isCropped }} | |
/> | |
</> | |
); | |
} | |
} | |
PhotoEditor.getInitialProps = ({ | |
setPhoto, | |
canvas, | |
crop, | |
open, | |
closeEditor, | |
upload | |
}) => ({ setPhoto, canvas, crop, open, closeEditor, upload }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment