Use dialog with accordion for message history menu

This commit is contained in:
Sascha 2021-03-19 14:20:54 +01:00
parent d453abdeed
commit 155b37e81f
3 changed files with 233 additions and 85 deletions

View File

@ -1,67 +0,0 @@
import React from 'react';
import CircularProgress from '@material-ui/core/CircularProgress';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import Date from 'src/components/Date';
import { AddCommentFragment } from './MessageCommentFragment.generated';
import { CreateFragment } from './MessageCreateFragment.generated';
import { useMessageEditHistoryQuery } from './MessageEditHistory.generated';
const ITEM_HEIGHT = 48;
type Props = {
anchor: null | HTMLElement;
bugId: string;
commentId: string;
onClose: () => void;
};
function EditHistoryMenu({ anchor, bugId, commentId, onClose }: Props) {
const open = Boolean(anchor);
const { loading, error, data } = useMessageEditHistoryQuery({
variables: { bugIdPrefix: bugId },
});
if (loading) return <CircularProgress />;
if (error) return <p>Error: {error}</p>;
const comments = data?.repository?.bug?.timeline.comments as (
| AddCommentFragment
| CreateFragment
)[];
// NOTE Searching for the changed comment could be dropped if GraphQL get
// filter by id argument for timelineitems
const comment = comments.find((elem) => elem.id === commentId);
const history = comment?.history;
return (
<div>
<Menu
id="long-menu"
anchorEl={anchor}
keepMounted
open={open}
onClose={onClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch',
},
}}
>
<MenuItem key={0} disabled>
Edited {history?.length} times.
</MenuItem>
{history?.map((edit, index) => (
<MenuItem key={index} onClick={onClose}>
<Date date={edit.date} />
</MenuItem>
))}
</Menu>
</div>
);
}
export default EditHistoryMenu;

View File

@ -14,9 +14,9 @@ import IfLoggedIn from 'src/components/IfLoggedIn/IfLoggedIn';
import { BugFragment } from './Bug.generated';
import EditCommentForm from './EditCommentForm';
import EditHistoryMenu from './EditHistoryMenu';
import { AddCommentFragment } from './MessageCommentFragment.generated';
import { CreateFragment } from './MessageCreateFragment.generated';
import MessageHistoryDialog from './MessageHistoryDialog';
const useStyles = makeStyles((theme) => ({
author: {
@ -70,10 +70,6 @@ const useStyles = makeStyles((theme) => ({
},
}));
//TODO move button out of this component and let only menu as component with
//query. Then the query won't execute unless button click renders menu with
//query.
//TODO Fix display of load button spinner.
//TODO Move this button and menu in separate component directory
//TODO fix failing pipeline due to eslint error
type HistBtnProps = {
@ -82,14 +78,14 @@ type HistBtnProps = {
};
function HistoryMenuToggleButton({ bugId, commentId }: HistBtnProps) {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [open, setOpen] = React.useState(false);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setAnchorEl(null);
setOpen(false);
};
return (
@ -98,19 +94,23 @@ function HistoryMenuToggleButton({ bugId, commentId }: HistBtnProps) {
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
onClick={handleClickOpen}
className={classes.headerActions}
>
<HistoryIcon />
</IconButton>
{anchorEl && (
<EditHistoryMenu
bugId={bugId}
commentId={commentId}
anchor={anchorEl}
onClose={handleClose}
/>
)}
{
// Render CustomizedDialogs on open to prevent fetching the history
// before opening the history menu.
open && (
<MessageHistoryDialog
bugId={bugId}
commentId={commentId}
open={open}
onClose={handleClose}
/>
)
}
</div>
);
}

View File

@ -0,0 +1,215 @@
import moment from 'moment';
import React from 'react';
import Moment from 'react-moment';
import MuiAccordion from '@material-ui/core/Accordion';
import MuiAccordionDetails from '@material-ui/core/AccordionDetails';
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
import CircularProgress from '@material-ui/core/CircularProgress';
import Dialog from '@material-ui/core/Dialog';
import MuiDialogContent from '@material-ui/core/DialogContent';
import MuiDialogTitle from '@material-ui/core/DialogTitle';
import Grid from '@material-ui/core/Grid';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip/Tooltip';
import Typography from '@material-ui/core/Typography';
import {
createStyles,
Theme,
withStyles,
WithStyles,
} from '@material-ui/core/styles';
import CloseIcon from '@material-ui/icons/Close';
import { AddCommentFragment } from './MessageCommentFragment.generated';
import { CreateFragment } from './MessageCreateFragment.generated';
import { useMessageEditHistoryQuery } from './MessageEditHistory.generated';
const styles = (theme: Theme) =>
createStyles({
root: {
margin: 0,
padding: theme.spacing(2),
},
closeButton: {
position: 'absolute',
right: theme.spacing(1),
top: theme.spacing(1),
},
});
export interface DialogTitleProps extends WithStyles<typeof styles> {
id: string;
children: React.ReactNode;
onClose: () => void;
}
const DialogTitle = withStyles(styles)((props: DialogTitleProps) => {
const { children, classes, onClose, ...other } = props;
return (
<MuiDialogTitle disableTypography className={classes.root} {...other}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton
aria-label="close"
className={classes.closeButton}
onClick={onClose}
>
<CloseIcon />
</IconButton>
) : null}
</MuiDialogTitle>
);
});
const DialogContent = withStyles((theme: Theme) => ({
root: {
padding: theme.spacing(2),
},
}))(MuiDialogContent);
const Accordion = withStyles({
root: {
border: '1px solid rgba(0, 0, 0, .125)',
boxShadow: 'none',
'&:not(:last-child)': {
borderBottom: 0,
},
'&:before': {
display: 'none',
},
'&$expanded': {
margin: 'auto',
},
},
expanded: {},
})(MuiAccordion);
const AccordionSummary = withStyles((theme) => ({
root: {
backgroundColor: theme.palette.primary.light,
borderBottomWidth: '1px',
borderBottomStyle: 'solid',
borderBottomColor: theme.palette.divider,
marginBottom: -1,
minHeight: 56,
'&$expanded': {
minHeight: 56,
},
},
content: {
'&$expanded': {
margin: '12px 0',
},
},
expanded: {},
}))(MuiAccordionSummary);
const AccordionDetails = withStyles((theme) => ({
root: {
padding: theme.spacing(2),
},
}))(MuiAccordionDetails);
type Props = {
bugId: string;
commentId: string;
open: boolean;
onClose: () => void;
};
function MessageHistoryDialog({ bugId, commentId, open, onClose }: Props) {
const [expanded, setExpanded] = React.useState<string | false>('panel0');
const { loading, error, data } = useMessageEditHistoryQuery({
variables: { bugIdPrefix: bugId },
});
if (loading) {
return (
<Dialog
onClose={onClose}
aria-labelledby="customized-dialog-title"
open={open}
fullWidth
maxWidth="sm"
>
<DialogTitle id="customized-dialog-title" onClose={onClose}>
Loading...
</DialogTitle>
<DialogContent dividers>
<Grid container justify="center">
<CircularProgress />
</Grid>
</DialogContent>
</Dialog>
);
}
if (error) {
return (
<Dialog
onClose={onClose}
aria-labelledby="customized-dialog-title"
open={open}
fullWidth
maxWidth="sm"
>
<DialogTitle id="customized-dialog-title" onClose={onClose}>
Something went wrong...
</DialogTitle>
<DialogContent dividers>
<p>Error: {error}</p>
</DialogContent>
</Dialog>
);
}
const comments = data?.repository?.bug?.timeline.comments as (
| AddCommentFragment
| CreateFragment
)[];
// NOTE Searching for the changed comment could be dropped if GraphQL get
// filter by id argument for timelineitems
const comment = comments.find((elem) => elem.id === commentId);
const history = comment?.history;
const handleChange = (panel: string) => (
event: React.ChangeEvent<{}>,
newExpanded: boolean
) => {
setExpanded(newExpanded ? panel : false);
};
return (
<Dialog
onClose={onClose}
aria-labelledby="customized-dialog-title"
open={open}
fullWidth
maxWidth="md"
>
<DialogTitle id="customized-dialog-title" onClose={onClose}>
Edited {history?.length} times.
</DialogTitle>
<DialogContent dividers>
{history?.map((edit, index) => (
<Accordion
square
expanded={expanded === 'panel' + index}
onChange={handleChange('panel' + index)}
>
<AccordionSummary
aria-controls="panel1d-content"
id="panel1d-header"
>
<Tooltip title={moment(edit.date).format('LLLL')}>
<Moment date={edit.date} format="on ll" />
</Tooltip>
</AccordionSummary>
<AccordionDetails>{edit.message}</AccordionDetails>
</Accordion>
))}
</DialogContent>
</Dialog>
);
}
export default MessageHistoryDialog;