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 { useMessageEditHistoryQuery } from './MessageEditHistory.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 } = useMessageEditHistoryQuery({
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 const history = comment?.history;
173
174 const handleChange = (panel: string) => (
175 event: React.ChangeEvent<{}>,
176 newExpanded: boolean
177 ) => {
178 setExpanded(newExpanded ? panel : false);
179 };
180
181 return (
182 <Dialog
183 onClose={onClose}
184 aria-labelledby="customized-dialog-title"
185 open={open}
186 fullWidth
187 maxWidth="md"
188 >
189 <DialogTitle id="customized-dialog-title" onClose={onClose}>
190 Edited {history?.length} times.
191 </DialogTitle>
192 <DialogContent dividers>
193 {history?.map((edit, index) => (
194 <Accordion
195 square
196 expanded={expanded === 'panel' + index}
197 onChange={handleChange('panel' + index)}
198 >
199 <AccordionSummary
200 aria-controls="panel1d-content"
201 id="panel1d-header"
202 >
203 <Tooltip title={moment(edit.date).format('LLLL')}>
204 <Moment date={edit.date} format="on ll" />
205 </Tooltip>
206 </AccordionSummary>
207 <AccordionDetails>{edit.message}</AccordionDetails>
208 </Accordion>
209 ))}
210 </DialogContent>
211 </Dialog>
212 );
213}
214
215export default MessageHistoryDialog;