Skip to content

Commit

Permalink
#120 added scribble possibility for report forms (added react-canvas-…
Browse files Browse the repository at this point in the history
…draw), try to get only valid email and from username in form, added resizable option for modal

#163 changed "Title" to "Subject"
  • Loading branch information
matej-vavrek committed Mar 18, 2020
1 parent 8372d24 commit 8cdc943
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 70 deletions.
13 changes: 11 additions & 2 deletions js/components/common/Modal/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CircularProgress, makeStyles, Modal as MaterialModal } from '@material-ui/core';
import React, { memo } from 'react';
import classNames from 'classnames';

const useStyles = makeStyles(theme => ({
paper: {
Expand All @@ -14,10 +15,14 @@ const useStyles = makeStyles(theme => ({
},
withPadding: {
padding: theme.spacing(2, 4, 3)
},
resizable: {
resize: 'both',
overflow: 'auto'
}
}));

export const Modal = memo(({ children, open, loading, onClose, noPadding, ...rest }) => {
export const Modal = memo(({ children, open, loading, onClose, noPadding, resizable, ...rest }) => {
const classes = useStyles();
const content = loading ? <CircularProgress /> : children;
return (
Expand All @@ -28,7 +33,11 @@ export const Modal = memo(({ children, open, loading, onClose, noPadding, ...res
onClose={onClose}
{...rest}
>
<div className={classes.paper}>
<div
className={classNames(classes.paper, {
[classes.resizable]: resizable
})}
>
<div className={noPadding ? undefined : classes.withPadding}>{content}</div>
</div>
</MaterialModal>
Expand Down
2 changes: 1 addition & 1 deletion js/components/userFeedback/redux/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DJANGO_CONTEXT } from '../../../utils/djangoContext';

export const INITIAL_STATE = {
name: DJANGO_CONTEXT['name'] || '',
email: DJANGO_CONTEXT['email'] || '',
email: DJANGO_CONTEXT['username'] && DJANGO_CONTEXT['username'].includes('@') ? DJANGO_CONTEXT['username'] : '',
title: '',
description: '',
response: '',
Expand Down
259 changes: 193 additions & 66 deletions js/components/userFeedback/reportForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
* This component creates a button for reporting new issues and handling them.
*/

import React, { memo, useState, useContext } from 'react';
import { ButtonBase, Grid, makeStyles, Typography } from '@material-ui/core';
import React, { memo, useState, useContext, useRef } from 'react';
import { Grid, makeStyles, Typography, Popover, Box, Slider, Tooltip } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { ReportProblem, EmojiObjects } from '@material-ui/icons';
import { ReportProblem, EmojiObjects, ClearAll } from '@material-ui/icons';
import { Button } from '../common/Inputs/Button';
import Modal from '../common/Modal';
import { HeaderContext } from '../header/headerContext';
import { Formik, Form } from 'formik';
import { TextField } from 'formik-material-ui';
import { createIssue } from './githubApi';
import { canCaptureScreen, captureScreen, isFirefox, isChrome } from './browserApi';
import { resetForm, setName, setEmail, setTitle, setDescription } from './redux/actions';
import { resetForm, setName, setEmail, setTitle, setDescription, setImageSource } from './redux/actions';
import { useDispatch, useSelector } from 'react-redux';
import { snackbarColors } from '../header/constants';
import CanvasDraw from 'react-canvas-draw';
import { SketchPicker } from 'react-color';

const IMAGE_WIDTH = 620;
const IMAGE_HEIGHT = 350;

const useStyles = makeStyles(theme => ({
buttonGreen: {
Expand Down Expand Up @@ -51,16 +56,14 @@ const useStyles = makeStyles(theme => ({
color: theme.palette.primary.contrastText
}
},
// https://material-ui.com/components/grid/
image: {
width: 312,
height: 312
canvasDrawOptions: {
marginTop: '6px'
},
clearAllButton: {
minWidth: '44px'
},
img: {
margin: 'auto',
display: 'block',
maxWidth: '100%',
maxHeight: '100%'
formMinWidth: {
minWidth: '900px'
}
}));

Expand Down Expand Up @@ -154,6 +157,44 @@ export const ReportForm = memo(({ formType }) => {
/* Modal handlers */
const [openForm, setOpenForm] = useState(false);
const [openDialog, setOpenDialog] = useState(false);
const canvasDraw = useRef();
const colorPicker = useRef();
const [openBrushColor, setOpenBrushColor] = useState(false);
const [brushRadius, setBrushRadius] = useState(2);
const [brushColor, setBrushColor] = useState('#444');

const handleColorChange = color => {
setBrushColor(color.hex);
};

/**
* Clear drawing, i.e. second canvas of CanvasDraw
*/
const handleClearDrawing = () => {
if (canvasDraw && canvasDraw.current.canvasContainer.children.length > 0) {
// cursor, drawing, interface, backgroundImage
let rootCanvas = canvasDraw.current.canvasContainer.children[1];
let rootCanvasContex = rootCanvas.getContext('2d');
rootCanvasContex.clearRect(0, 0, rootCanvas.width, rootCanvas.height);
}
};

/**
* Merge canvas layers into one and then get its data URL
* @param ref - ref of CanvasDraw
* @return string - toDataURL|''
*/
const getCanvasDrawDataUrl = canvasDraw => {
if (canvasDraw && canvasDraw.current.canvasContainer.children.length > 0) {
// cursor, drawing, interface, backgroundImage
let rootCanvas = canvasDraw.current.canvasContainer.children[3];
let rootCanvasContex = rootCanvas.getContext('2d');
rootCanvasContex.drawImage(canvasDraw.current.canvasContainer.children[1], 0, 0);
return rootCanvas.toDataURL();
} else {
return '';
}
};

const isResponse = () => {
return openForm && formState.response.length > 0;
Expand Down Expand Up @@ -239,6 +280,8 @@ export const ReportForm = memo(({ formType }) => {
const errors = {};
if (!values.title) {
errors.title = 'Required field.';
} else if (values.title.length > 256) {
errors.title = 'Title has limit of 256 characters: ' + Math.abs(256 - values.title.length) + ' exceeded.';
}
if (!values.description) {
errors.description = 'Required field.';
Expand All @@ -249,73 +292,157 @@ export const ReportForm = memo(({ formType }) => {
return errors;
}}
onSubmit={async (values, { setSubmitting }) => {
// set new image from drawing before creating issue
dispatch(setImageSource(getCanvasDrawDataUrl(canvasDraw)));
await dispatch(createIssue(formState, formType.toLowerCase(), getLabels(), afterCreateIssueCallback));
setSubmitting(false);
}}
>
{({ submitForm, isSubmitting }) => (
<Form>
<Form className={classes.formMinWidth}>
<Grid container direction="column" className={classes.body}>
{isResponse() && <Alert severity="error">{formState.response}</Alert>}

<Grid container direction="row" spacing={2}>
<Grid item xs={12} sm container direction="column">
<Grid item xs>
<Typography variant="h3">{getTitle()}</Typography>
<TextField
name="name"
label="Name"
value={formState.name}
onInput={e => dispatch(setName(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
</Grid>
<Grid item xs>
<TextField
name="email"
label="Email"
value={formState.email}
onInput={e => dispatch(setEmail(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
</Grid>
<Grid item xs>
<TextField
required
name="title"
label="Title"
value={formState.title}
onInput={e => dispatch(setTitle(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
</Grid>
<Grid item xs>
<TextField
required
name="description"
label="Description"
placeholder="Describe your problem in a detail."
multiline
rows="4"
value={formState.description}
onInput={e => dispatch(setDescription(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
<Grid item xs={12} container direction="row" spacing={2}>
<Grid item xs={4}>
<Grid container direction="column">
<Grid item xs>
<Typography variant="h3">{getTitle()}</Typography>
<TextField
name="name"
label="Name"
value={formState.name}
onInput={e => dispatch(setName(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
</Grid>
<Grid item xs>
<TextField
name="email"
label="Email"
value={formState.email}
onInput={e => dispatch(setEmail(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
</Grid>
<Grid item xs>
<TextField
required
name="title"
label="Subject"
multiline
rows="2"
value={formState.title}
onInput={e => dispatch(setTitle(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
</Grid>
<Grid item xs>
<TextField
required
name="description"
label="Description"
placeholder="Describe your problem in a detail."
multiline
rows="6"
value={formState.description}
onInput={e => dispatch(setDescription(e.target.value))}
disabled={isSubmitting}
className={classes.input}
/>
</Grid>
</Grid>
</Grid>

<Grid item>
<ButtonBase className={classes.image}>
<img className={classes.img} alt="{no image}" src={formState.imageSource} />
</ButtonBase>
<Grid item xs={8}>
<Grid item container justify="flex-end" direction="column">
<Grid
item
xs
container
justify="center"
alignItems="center"
direction="row"
spacing={2}
className={classes.canvasDrawOptions}
>
<Grid item xs={2} align="center">
<Tooltip title="Select colour">
<Box
ref={colorPicker}
border="1px solid black"
bgcolor={brushColor}
width={24}
height={24}
spacing={1}
onClick={() => setOpenBrushColor(true)}
/>
</Tooltip>
<Popover
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right'
}}
anchorEl={colorPicker.current}
open={openBrushColor}
onClose={() => setOpenBrushColor(false)}
>
<SketchPicker color={brushColor} disableAlpha={true} onChangeComplete={handleColorChange} />
</Popover>
</Grid>
<Grid item xs={8}>
<Tooltip title="Select brush radius">
<Slider
value={brushRadius}
step={1}
marks
min={1}
max={6}
valueLabelDisplay="auto"
onChange={(event, newValue) => setBrushRadius(newValue)}
disabled={isSubmitting}
/>
</Tooltip>
</Grid>
<Grid item item xs={2} align="center">
<Tooltip title="Clear drawing">
<Button
size="small"
onClick={handleClearDrawing}
className={classes.clearAllButton}
disabled={isSubmitting}
>
<ClearAll />
</Button>
</Tooltip>
</Grid>
</Grid>
<Grid item xs>
{/* lazyRadius - how far is cursor from drawing point */}
<CanvasDraw
ref={canvasDraw}
imgSrc={formState.imageSource}
canvasWidth={IMAGE_WIDTH}
canvasHeight={IMAGE_HEIGHT}
hideGrid={true}
lazyRadius={0}
brushRadius={brushRadius}
brushColor={brushColor}
disabled={isSubmitting}
/>
</Grid>
</Grid>
</Grid>
</Grid>

<Grid item container justify="flex-end" direction="row">
<Grid item xs={12} container justify="flex-end" direction="row">
<Grid item>
<Button disabled={isSubmitting} onClick={handleCloseForm}>
Close
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fragalysis-frontend",
"version": "0.2.3",
"version": "0.3.0",
"description": "Frontend for fragalysis",
"main": "webpack.config.js",
"scripts": {
Expand Down Expand Up @@ -55,6 +55,7 @@
"logrocket": "^1.0.3",
"ngl": "2.0.0-dev.37",
"react": "^16.11.0",
"react-canvas-draw": "^1.1.0",
"react-color": "^2.17.3",
"react-dom": "^16.12.0",
"react-hot-loader": "^4.12.18",
Expand Down
Loading

0 comments on commit 8cdc943

Please sign in to comment.