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