Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion app/javascript/packs/components/EditPopup/EditPopup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { isNil } from 'ramda';
import { isNil, has } from 'ramda';

import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
Expand All @@ -14,6 +14,8 @@ import PropTypes from 'prop-types';

import Form from './components/Form';
import TaskPresenter from 'presenters/TaskPresenter';
import ImageUpload from 'packs/components/ImageUpload';
import TasksRepository from 'repositories/TasksRepository';

import useStyles from './useStyles';

Expand All @@ -27,6 +29,14 @@ function EditPopup({ cardId, onClose, onCardDestroy, onCardLoad, onCardUpdate })
onCardLoad(cardId).then(setTask);
}, []);

const onRemoveImage = () => {
TasksRepository.removeImage(task.id);
};

const onAttachImage = (attachment) => {
TasksRepository.attachImage(attachment, task.id);
};

const handleCardUpdate = () => {
setSaving(true);

Expand All @@ -49,6 +59,7 @@ function EditPopup({ cardId, onClose, onCardDestroy, onCardLoad, onCardUpdate })
alert(`Destrucion Failed! Error: ${error.message}`);
});
};

const isLoading = isNil(task);

return (
Expand Down Expand Up @@ -84,6 +95,20 @@ function EditPopup({ cardId, onClose, onCardDestroy, onCardLoad, onCardUpdate })
>
Destroy
</Button>
{isNil(TaskPresenter.imageUrl(task)) ? (
<div className={styles.imageUploadContainer}>
<ImageUpload onUpload={onAttachImage} />
</div>
) : (
<div className={styles.previewContainer}>
<a href={TaskPresenter.imageUrl(task)}>
<img className={styles.preview} src={TaskPresenter.imageUrl(task)} alt="Attachment" />
</a>
<Button variant="contained" size="small" color="primary" onClick={onRemoveImage}>
Remove image
</Button>
</div>
)}
</CardActions>
</Card>
</Modal>
Expand Down
8 changes: 8 additions & 0 deletions app/javascript/packs/components/EditPopup/useStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ const useStyles = makeStyles(() => ({
display: 'flex',
justifyContent: 'flex-end',
},

imageUploadContainer: {
display: 'flex',
},

previewContainer: {
display: 'flex',
},
}));

export default useStyles;
96 changes: 96 additions & 0 deletions app/javascript/packs/components/ImageUpload/ImageUpload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { isNil, path } from 'ramda';
import Button from '@material-ui/core/Button';
import ReactCrop, { makeAspectCrop } from 'react-image-crop';
import 'react-image-crop/dist/ReactCrop.css';

import useStyles from './useStyles';

function ImageUpload({ onUpload }) {
const styles = useStyles();

const DEFAULT_CROP_PARAMS = {
units: 'px',
x: 0,
y: 0,
width: 100,
height: 100,
};

const [fileAsBase64, changeFileAsBase64] = useState(null);
const [cropParams, changeCropParams] = useState(DEFAULT_CROP_PARAMS);
const [file, changeFile] = useState(null);
const [image, changeImage] = useState(null);

const handleCropComplete = (newCrop, newPercentageCrop) => {
changeCropParams(newPercentageCrop);
};

const onImageLoaded = (loadedImage) => {
const newCropParams = makeAspectCrop(DEFAULT_CROP_PARAMS, loadedImage.width, loadedImage.height);
changeCropParams(newCropParams);
changeImage(loadedImage);
};

const getActualCropParameters = (width, height, params) => ({
cropX: (params.x * width) / 100,
cropY: (params.y * height) / 100,
cropWidth: (params.width * width) / 100,
cropHeight: (params.height * height) / 100,
});

const handleCropChange = (_, newCropParams) => {
changeCropParams(newCropParams);
};

const handleSave = () => {
const { naturalWidth: width, naturalHeight: height } = image;
const actualCropParams = getActualCropParameters(width, height, cropParams);
onUpload({ attachment: { ...actualCropParams, image: file } });
};

const handleImageRead = (newImage) => changeFileAsBase64(path(['target', 'result'], newImage));

const handleLoadFile = (e) => {
e.preventDefault();

const [acceptedFile] = e.target.files;

const fileReader = new FileReader();
fileReader.onload = handleImageRead;
fileReader.readAsDataURL(acceptedFile);
changeFile(acceptedFile);
};

return fileAsBase64 ? (
<>
<div className={styles.crop}>
<ReactCrop
src={fileAsBase64}
crop={cropParams}
onImageLoaded={onImageLoaded}
onComplete={handleCropComplete}
onChange={handleCropChange}
keepSelection
/>
</div>
<Button variant="contained" size="small" color="primary" disabled={isNil(image)} onClick={handleSave}>
Save
</Button>
</>
) : (
<label htmlFor="imageUpload">
<Button variant="contained" size="small" color="primary" component="span">
Add Image
</Button>
<input accept="image/*" id="imageUpload" type="file" onChange={handleLoadFile} hidden />
</label>
);
}

ImageUpload.propTypes = {
onUpload: PropTypes.func.isRequired,
};

export default ImageUpload;
3 changes: 3 additions & 0 deletions app/javascript/packs/components/ImageUpload/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ImageUpload from './ImageUpload';

export default ImageUpload;
13 changes: 13 additions & 0 deletions app/javascript/packs/components/ImageUpload/useStyles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { makeStyles } from '@material-ui/core';

const useStyles = makeStyles(() => ({
crop: {
maxHeight: 500,
maxWidth: 500,
position: 'absolute',
top: '35%',
left: '33%',
},
}));

export default useStyles;
1 change: 1 addition & 0 deletions app/javascript/presenters/TaskPresenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default new PropTypesPresenter(
assignee: PropTypes.object,
transitions: PropTypes.array,
state: PropTypes.string,
imageUrl: PropTypes.string,
},
{
title(task) {
Expand Down
10 changes: 10 additions & 0 deletions app/javascript/repositories/TasksRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,14 @@ export default {
const path = routes.apiV1TaskPath(id);
return FetchHelper.delete(path);
},

attachImage(attachment, id) {
const path = routes.attachImageApiV1TaskPath(id);
return FetchHelper.putFormData(path, attachment);
},

removeImage(id) {
const path = routes.removeImageApiV1TaskPath(id);
return FetchHelper.delete(path);
},
};
Loading