MessageHistoryDialog.tsx

  1import moment from 'moment';
  2import React from 'react';
  3import Moment from 'react-moment';
  4
  5import MuiAccordion from '@material-ui/core/Accordion';
  6import MuiAccordionDetails from '@material-ui/core/AccordionDetails';
  7import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
  8import CircularProgress from '@material-ui/core/CircularProgress';
  9import Dialog from '@material-ui/core/Dialog';
 10import MuiDialogContent from '@material-ui/core/DialogContent';
 11import MuiDialogTitle from '@material-ui/core/DialogTitle';
 12import Grid from '@material-ui/core/Grid';
 13import IconButton from '@material-ui/core/IconButton';
 14import Tooltip from '@material-ui/core/Tooltip/Tooltip';
 15import Typography from '@material-ui/core/Typography';
 16import {
 17  createStyles,
 18  Theme,
 19  withStyles,
 20  WithStyles,
 21} from '@material-ui/core/styles';
 22import CloseIcon from '@material-ui/icons/Close';
 23
 24import { AddCommentFragment } from './MessageCommentFragment.generated';
 25import { CreateFragment } from './MessageCreateFragment.generated';
 26import { useMessageHistoryQuery } from './MessageHistory.generated';
 27
 28const styles = (theme: Theme) =>
 29  createStyles({
 30    root: {
 31      margin: 0,
 32      padding: theme.spacing(2),
 33    },
 34    closeButton: {
 35      position: 'absolute',
 36      right: theme.spacing(1),
 37      top: theme.spacing(1),
 38    },
 39  });
 40
 41export interface DialogTitleProps extends WithStyles<typeof styles> {
 42  id: string;
 43  children: React.ReactNode;
 44  onClose: () => void;
 45}
 46
 47const DialogTitle = withStyles(styles)((props: DialogTitleProps) => {
 48  const { children, classes, onClose, ...other } = props;
 49  return (
 50    <MuiDialogTitle disableTypography className={classes.root} {...other}>
 51      <Typography variant="h6">{children}</Typography>
 52      {onClose ? (
 53        <IconButton
 54          aria-label="close"
 55          className={classes.closeButton}
 56          onClick={onClose}
 57        >
 58          <CloseIcon />
 59        </IconButton>
 60      ) : null}
 61    </MuiDialogTitle>
 62  );
 63});
 64
 65const DialogContent = withStyles((theme: Theme) => ({
 66  root: {
 67    padding: theme.spacing(2),
 68  },
 69}))(MuiDialogContent);
 70
 71const Accordion = withStyles({
 72  root: {
 73    border: '1px solid rgba(0, 0, 0, .125)',
 74    boxShadow: 'none',
 75    '&:not(:last-child)': {
 76      borderBottom: 0,
 77    },
 78    '&:before': {
 79      display: 'none',
 80    },
 81    '&$expanded': {
 82      margin: 'auto',
 83    },
 84  },
 85  expanded: {},
 86})(MuiAccordion);
 87
 88const AccordionSummary = withStyles((theme) => ({
 89  root: {
 90    backgroundColor: theme.palette.primary.light,
 91    borderBottomWidth: '1px',
 92    borderBottomStyle: 'solid',
 93    borderBottomColor: theme.palette.divider,
 94    marginBottom: -1,
 95    minHeight: 56,
 96    '&$expanded': {
 97      minHeight: 56,
 98    },
 99  },
100  content: {
101    '&$expanded': {
102      margin: '12px 0',
103    },
104  },
105  expanded: {},
106}))(MuiAccordionSummary);
107
108const AccordionDetails = withStyles((theme) => ({
109  root: {
110    padding: theme.spacing(2),
111  },
112}))(MuiAccordionDetails);
113
114type Props = {
115  bugId: string;
116  commentId: string;
117  open: boolean;
118  onClose: () => void;
119};
120function MessageHistoryDialog({ bugId, commentId, open, onClose }: Props) {
121  const [expanded, setExpanded] = React.useState<string | false>('panel0');
122
123  const { loading, error, data } = useMessageHistoryQuery({
124    variables: { bugIdPrefix: bugId },
125  });
126  if (loading) {
127    return (
128      <Dialog
129        onClose={onClose}
130        aria-labelledby="customized-dialog-title"
131        open={open}
132        fullWidth
133        maxWidth="sm"
134      >
135        <DialogTitle id="customized-dialog-title" onClose={onClose}>
136          Loading...
137        </DialogTitle>
138        <DialogContent dividers>
139          <Grid container justify="center">
140            <CircularProgress />
141          </Grid>
142        </DialogContent>
143      </Dialog>
144    );
145  }
146  if (error) {
147    return (
148      <Dialog
149        onClose={onClose}
150        aria-labelledby="customized-dialog-title"
151        open={open}
152        fullWidth
153        maxWidth="sm"
154      >
155        <DialogTitle id="customized-dialog-title" onClose={onClose}>
156          Something went wrong...
157        </DialogTitle>
158        <DialogContent dividers>
159          <p>Error: {error}</p>
160        </DialogContent>
161      </Dialog>
162    );
163  }
164
165  const comments = data?.repository?.bug?.timeline.comments as (
166    | AddCommentFragment
167    | CreateFragment
168  )[];
169  // NOTE Searching for the changed comment could be dropped if GraphQL get
170  // filter by id argument for timelineitems
171  const comment = comments.find((elem) => elem.id === commentId);
172  // Sort by most recent edit. Must create a copy of constant history as
173  // reverse() modifies inplace.
174  const history = comment?.history.slice().reverse();
175
176  const handleChange = (panel: string) => (
177    event: React.ChangeEvent<{}>,
178    newExpanded: boolean
179  ) => {
180    setExpanded(newExpanded ? panel : false);
181  };
182
183  return (
184    <Dialog
185      onClose={onClose}
186      aria-labelledby="customized-dialog-title"
187      open={open}
188      fullWidth
189      maxWidth="md"
190    >
191      <DialogTitle id="customized-dialog-title" onClose={onClose}>
192        Edited {history?.length} times.
193      </DialogTitle>
194      <DialogContent dividers>
195        {history?.map((edit, index) => (
196          <Accordion
197            square
198            expanded={expanded === 'panel' + index}
199            onChange={handleChange('panel' + index)}
200          >
201            <AccordionSummary
202              aria-controls="panel1d-content"
203              id="panel1d-header"
204            >
205              <Tooltip title={moment(edit.date).format('LLLL')}>
206                <Moment date={edit.date} format="on ll" />
207              </Tooltip>
208              {index === 0 && '• (most recent edit)'}
209            </AccordionSummary>
210            <AccordionDetails>{edit.message}</AccordionDetails>
211          </Accordion>
212        ))}
213      </DialogContent>
214    </Dialog>
215  );
216}
217
218export default MessageHistoryDialog;