Skip to content

Instantly share code, notes, and snippets.

@jermsam
Last active May 2, 2019 12:15
Show Gist options
  • Save jermsam/9a06ca376d870301b9e463003bd0d34b to your computer and use it in GitHub Desktop.
Save jermsam/9a06ca376d870301b9e463003bd0d34b to your computer and use it in GitHub Desktop.
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
});
/* 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