Skip to content

Commit

Permalink
Merge pull request #1062 from RodriSanchez1/feature/rotateCropImage
Browse files Browse the repository at this point in the history
Feature/rotate crop image
  • Loading branch information
martinbedouret authored Nov 21, 2021
2 parents c474bec + faebdaa commit aaff6b6
Show file tree
Hide file tree
Showing 13 changed files with 497 additions and 116 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"ramda": "^0.27.1",
"react": "^17.0.2",
"react-autosuggest": "^9.4.2",
"react-cropper": "^2.1.8",
"react-dnd": "^11.1.3",
"react-dnd-touch-backend": "^11.1.3",
"react-dom": "^17.0.2",
Expand Down
1 change: 1 addition & 0 deletions src/components/Board/Board.container.js
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,7 @@ export class BoardContainer extends Component {
onEditSubmit={this.handleEditTileEditorSubmit}
onAddSubmit={this.handleAddTileEditorSubmit}
boards={this.props.boards}
userData={this.props.userData}
/>
</Fragment>
);
Expand Down
198 changes: 198 additions & 0 deletions src/components/Board/ImageEditor/ImageEditor.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions';
import IconButton from '../../UI/IconButton';
import CloseIcon from '@material-ui/icons/Close';
import withMobileDialog from '@material-ui/core/withMobileDialog';
import messages from './ImageEditor.messages';
import RotateRightIcon from '@material-ui/icons/RotateRight';
import DoneIcon from '@material-ui/icons/Done';
import CropIcon from '@material-ui/icons/Crop';
import BlockIcon from '@material-ui/icons/Block';
import ZoomInIcon from '@material-ui/icons/ZoomIn';
import ZoomOutIcon from '@material-ui/icons/ZoomOut';

import './ImageEditor.css';
import Cropper from 'react-cropper';
import 'cropperjs/dist/cropper.css';

class ImageEditor extends PureComponent {
static defaultProps = {
open: false
};
static propTypes = {
open: PropTypes.bool,
onImageEditorClose: PropTypes.func,
onImageEditorDone: PropTypes.func,
image: PropTypes.string,
intl: PropTypes.object.isRequired
};

constructor(props) {
super(props);
const setImageSize = () => {
if (window.innerWidth < 576) {
return { width: 248, height: 182 };
} else {
return { width: 492, height: 369 };
}
};
this.state = {
isCropActive: false,
imgCropped: null,
style: setImageSize()
};
}

handleOnClickCrop = () => {
this.setState({ isCropActive: true });
this.state.cropper.setDragMode('crop');
this.state.cropper.crop();
};
handleOnClickDoneCrop = () => {
const { cropper } = this.state;
this.setState({
isCropActive: false,
imgCropped: cropper.getCroppedCanvas().toDataURL()
});
this.state.cropper.setDragMode('move');
};
handleOnClickClose = () => {
this.setState({ isCropActive: false, imgCropped: null });
this.state.cropper.destroy();
this.props.onImageEditorClose();
};

handleOnClickDone = async () => {
const { cropper } = this.state;
cropper.setDragMode('move');
this.setState({ imgCropped: null });
this.props.onImageEditorClose();
cropper
.getCroppedCanvas({
maxWidth: 200,
maxHeight: 200,
fillColor: '#fff',
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high'
})
.toBlob(
blob => {
this.props.onImageEditorDone(blob);
},
'image/png',
1
);
cropper.destroy();
};
handleOnClickCancelCrop = () => {
this.setState({ isCropActive: false });
this.state.cropper.clear();
this.state.cropper.setDragMode('move');
};

render() {
const { intl, open, onImageEditorClose, image } = this.props;
const srcImage = this.state.imgCropped ? this.state.imgCropped : image;
return (
<React.Fragment>
<Dialog
open={open}
onClose={onImageEditorClose}
fullScreen={false}
className="ImageEditor__container"
>
<DialogTitle className="ImageEditor__title">
<div className="ImageEditor__Container">
<FormattedMessage {...messages.title} />
</div>
</DialogTitle>
<DialogContent>
<Cropper
style={this.state.style}
zoomTo={0}
src={srcImage}
viewMode={0}
background={true}
responsive={true}
checkOrientation={false}
guides={true}
dragMode="move"
autoCrop={false}
onInitialized={instance => {
this.setState({ cropper: instance });
}}
/>
<div className="ImageEditor__actionBar">
<IconButton
label={intl.formatMessage(messages.rotateRight)}
onClick={() => {
this.state.cropper.rotate(90);
}}
>
<RotateRightIcon />
</IconButton>
{this.state.isCropActive ? (
<React.Fragment>
<IconButton
label={intl.formatMessage(messages.cropImage)}
onClick={this.handleOnClickDoneCrop}
>
<DoneIcon />
</IconButton>
<IconButton
label={intl.formatMessage(messages.cancelCrop)}
onClick={this.handleOnClickCancelCrop}
>
<BlockIcon />
</IconButton>
</React.Fragment>
) : (
<IconButton
label={intl.formatMessage(messages.cropImage)}
onClick={this.handleOnClickCrop}
>
<CropIcon />
</IconButton>
)}
<IconButton
label={intl.formatMessage(messages.zoomIn)}
onClick={() => this.state.cropper.zoom(0.1)}
>
<ZoomInIcon />
</IconButton>
<IconButton
label={intl.formatMessage(messages.zoomOut)}
onClick={() => this.state.cropper.zoom(-0.1)}
>
<ZoomOutIcon />
</IconButton>
</div>
</DialogContent>
<DialogActions>
<IconButton
label={intl.formatMessage(messages.done)}
onClick={this.handleOnClickDone}
disabled={this.state.isCropActive}
>
<DoneIcon />
</IconButton>

<IconButton
label={intl.formatMessage(messages.close)}
onClick={this.handleOnClickClose}
>
<CloseIcon />
</IconButton>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
}

export default withMobileDialog()(ImageEditor);
18 changes: 18 additions & 0 deletions src/components/Board/ImageEditor/ImageEditor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.ImageEditor__container {
display: flex;
justify-content: space-between;
align-items: center;
}

.ImageEditor__actionBar {
display: flex;
justify-content: center;
margin-bottom: 2px;
}

.ImageEditor__title {
border-bottom: 1px solid #ccc;
margin-bottom: 18px;
padding: 20px 24px 18px;
position: relative;
}
36 changes: 36 additions & 0 deletions src/components/Board/ImageEditor/ImageEditor.messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { defineMessages } from 'react-intl';

export default defineMessages({
title: {
id: 'cboard.components.Board.ImageEditor.title',
defaultMessage: 'Image editor'
},
rotateRight: {
id: 'cboard.components.Board.ImageEditor.rotateRight',
defaultMessage: 'Rotate right'
},
cropImage: {
id: 'cboard.components.Board.ImageEditor.cropImage',
defaultMessage: 'Crop image'
},
close: {
id: 'cboard.components.Board.ImageEditor.close',
defaultMessage: 'Close'
},
done: {
id: 'cboard.components.Board.ImageEditor.done',
defaultMessage: 'Done'
},
cancelCrop: {
id: 'cboard.components.Board.ImageEditor.cancelCrop',
defaultMessage: 'Cancel crop'
},
zoomIn: {
id: 'cboard.components.Board.ImageEditor.zoomIn',
defaultMessage: 'Zoom in'
},
zoomOut: {
id: 'cboard.components.Board.ImageEditor.zoomOut',
defaultMessage: 'Zoom out'
}
});
1 change: 1 addition & 0 deletions src/components/Board/ImageEditor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ImageEditor.component';
3 changes: 1 addition & 2 deletions src/components/Board/SymbolSearch/SymbolSearch.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,7 @@ export class SymbolSearch extends PureComponent {
image: suggestion.src,
label: suggestion.translatedId,
labelKey: undefined
});
onClose();
}).then(() => onClose());
};

handleChange = (event, { newValue }) => {
Expand Down
Loading

0 comments on commit aaff6b6

Please sign in to comment.