diff options
Diffstat (limited to 'webui')
-rw-r--r-- | webui/src/pages/bug/EditHistoryMenu.tsx | 67 | ||||
-rw-r--r-- | webui/src/pages/bug/Message.tsx | 36 | ||||
-rw-r--r-- | webui/src/pages/bug/MessageHistoryDialog.tsx | 215 |
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; |