aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webui/src/pages/bug/EditHistoryMenu.tsx67
-rw-r--r--webui/src/pages/bug/Message.tsx36
-rw-r--r--webui/src/pages/bug/MessageHistoryDialog.tsx215
3 files changed, 233 insertions, 85 deletions
diff --git a/webui/src/pages/bug/EditHistoryMenu.tsx b/webui/src/pages/bug/EditHistoryMenu.tsx
deleted file mode 100644
index da2ed0cd..00000000
--- a/webui/src/pages/bug/EditHistoryMenu.tsx
+++ /dev/null
@@ -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;
diff --git a/webui/src/pages/bug/Message.tsx b/webui/src/pages/bug/Message.tsx
index bf3fb6da..51f45a4b 100644
--- a/webui/src/pages/bug/Message.tsx
+++ b/webui/src/pages/bug/Message.tsx
@@ -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>
);
}
diff --git a/webui/src/pages/bug/MessageHistoryDialog.tsx b/webui/src/pages/bug/MessageHistoryDialog.tsx
new file mode 100644
index 00000000..c49ac661
--- /dev/null
+++ b/webui/src/pages/bug/MessageHistoryDialog.tsx
@@ -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;