]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/pages/Schedule.js
3d0a14082b8d5262c8350d207b6186c6a04e6116
[alttp.git] / resources / js / components / pages / Schedule.js
1 import axios from 'axios';
2 import moment from 'moment';
3 import PropTypes from 'prop-types';
4 import React from 'react';
5 import { Alert, Container } from 'react-bootstrap';
6 import { Helmet } from 'react-helmet';
7 import { useTranslation } from 'react-i18next';
8 import toastr from 'toastr';
9
10 import CanonicalLinks from '../common/CanonicalLinks';
11 import ErrorBoundary from '../common/ErrorBoundary';
12 import ApplyDialog from '../episodes/ApplyDialog';
13 import Filter from '../episodes/Filter';
14 import List from '../episodes/List';
15 import RestreamDialog from '../episodes/RestreamDialog';
16 import { withUser } from '../../helpers/UserContext';
17
18 const Schedule = ({ user }) => {
19         const [ahead] = React.useState(14);
20         const [applyAs, setApplyAs] = React.useState('commentary');
21         const [behind] = React.useState(0);
22         const [episodes, setEpisodes] = React.useState([]);
23         const [filter, setFilter] = React.useState({});
24         const [restreamChannel, setRestreamChannel] = React.useState(null);
25         const [restreamEpisode, setRestreamEpisode] = React.useState(null);
26         const [showApplyDialog, setShowApplyDialog] = React.useState(false);
27         const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
28
29         const { t } = useTranslation();
30
31         React.useEffect(() => {
32                 const savedFilter = localStorage.getItem('episodes.filter.schedule');
33                 if (savedFilter) {
34                         setFilter(JSON.parse(savedFilter));
35                 } else {
36                         setFilter(filter => filter ? {} : filter);
37                 }
38         }, []);
39
40         const updateFilter = React.useCallback(newFilter => {
41                 localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
42                 setFilter(newFilter);
43         }, []);
44
45         const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
46                 axios.get(`/api/episodes`, {
47                         signal: controller.signal,
48                         params: {
49                                 after: moment().startOf('day').subtract(behind, 'days').toISOString(),
50                                 before: moment().startOf('day').add(ahead + 1, 'days').toISOString(),
51                                 ...filter,
52                         },
53                 }).then(response => {
54                         setEpisodes(response.data || []);
55                 }).catch(e => {
56                         if (!axios.isCancel(e)) {
57                                 console.error(e);
58                         }
59                 });
60         }, []);
61
62         const onAddRestream = React.useCallback(episode => {
63                 setRestreamEpisode(episode);
64                 setShowRestreamDialog(true);
65         }, []);
66
67         const onAddRestreamSubmit = React.useCallback(async values => {
68                 try {
69                         const response = await axios.post(
70                                 `/api/episodes/${values.episode_id}/add-restream`, values);
71                         const newEpisode = response.data;
72                         setEpisodes(episodes => episodes.map(episode =>
73                                 episode.id === newEpisode.id ? {
74                                         ...episode,
75                                         ...newEpisode,
76                                 } : episode
77                         ));
78                         toastr.success(t('episodes.restreamDialog.addSuccess'));
79                 } catch (e) {
80                         toastr.error(t('episodes.restreamDialog.addError'));
81                         throw e;
82                 }
83                 setRestreamEpisode(null);
84                 setShowRestreamDialog(false);
85         }, []);
86
87         const onRemoveRestream = React.useCallback(async (episode, channel) => {
88                 try {
89                         const response = await axios.post(
90                                 `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
91                         const newEpisode = response.data;
92                         setEpisodes(episodes => episodes.map(episode =>
93                                 episode.id === newEpisode.id ? {
94                                         ...episode,
95                                         ...newEpisode,
96                                 } : episode
97                         ));
98                         toastr.success(t('episodes.restreamDialog.removeSuccess'));
99                         setRestreamChannel(null);
100                         setRestreamEpisode(null);
101                         setShowRestreamDialog(false);
102                 } catch (e) {
103                         toastr.error(t('episodes.restreamDialog.removeError'));
104                 }
105         }, []);
106
107         const onEditRestream = React.useCallback((episode, channel) => {
108                 setRestreamChannel(channel);
109                 setRestreamEpisode(episode);
110                 setShowRestreamDialog(true);
111         }, []);
112
113         const editRestream = React.useCallback(async values => {
114                 try {
115                         const response = await axios.post(
116                                 `/api/episodes/${values.episode_id}/edit-restream`, values);
117                         const newEpisode = response.data;
118                         setEpisodes(episodes => episodes.map(episode =>
119                                 episode.id === newEpisode.id ? {
120                                         ...episode,
121                                         ...newEpisode,
122                                 } : episode
123                         ));
124                         setRestreamEpisode(episode => ({
125                                 ...episode,
126                                 ...newEpisode,
127                         }));
128                         const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
129                         setRestreamChannel(channel => ({
130                                 ...channel,
131                                 ...newChannel,
132                         }));
133                         toastr.success(t('episodes.restreamDialog.editSuccess'));
134                 } catch (e) {
135                         toastr.error(t('episodes.restreamDialog.editError'));
136                 }
137         }, []);
138
139         const manageCrew = React.useCallback(async values => {
140                 try {
141                         const response = await axios.post(
142                                 `/api/episodes/${values.episode_id}/crew-manage`, values);
143                         const newEpisode = response.data;
144                         setEpisodes(episodes => episodes.map(episode =>
145                                 episode.id === newEpisode.id ? {
146                                         ...episode,
147                                         ...newEpisode,
148                                 } : episode
149                         ));
150                         setRestreamEpisode(episode => ({
151                                 ...episode,
152                                 ...newEpisode,
153                         }));
154                         const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
155                         setRestreamChannel(channel => ({
156                                 ...channel,
157                                 ...newChannel,
158                         }));
159                         toastr.success(t('episodes.restreamDialog.crewSuccess'));
160                 } catch (e) {
161                         toastr.error(t('episodes.restreamDialog.crewError'));
162                 }
163         }, []);
164
165         const onHideRestreamDialog = React.useCallback(() => {
166                 setShowRestreamDialog(false);
167                 setRestreamChannel(null);
168                 setRestreamEpisode(null);
169         }, []);
170
171         const onApply = React.useCallback((episode, as) => {
172                 setShowApplyDialog(true);
173                 setRestreamEpisode(episode);
174                 setApplyAs(as);
175         }, []);
176
177         const onSubmitApplyDialog = React.useCallback(async values => {
178                 try {
179                         const response = await axios.post(
180                                 `/api/episodes/${values.episode_id}/crew-signup`, values);
181                         const newEpisode = response.data;
182                         setEpisodes(episodes => episodes.map(episode =>
183                                 episode.id === newEpisode.id ? {
184                                         ...episode,
185                                         ...newEpisode,
186                                 } : episode
187                         ));
188                         toastr.success(t('episodes.applyDialog.applySuccess'));
189                 } catch (e) {
190                         toastr.error(t('episodes.applyDialog.applyError'));
191                         throw e;
192                 }
193                 setRestreamEpisode(null);
194                 setShowApplyDialog(false);
195         }, []);
196
197         const onHideApplyDialog = React.useCallback(() => {
198                 setShowApplyDialog(false);
199                 setRestreamEpisode(null);
200         }, []);
201
202         React.useEffect(() => {
203                 const controller = new AbortController();
204                 fetchEpisodes(controller, ahead, behind, filter);
205                 const timer = setInterval(() => {
206                         fetchEpisodes(controller, ahead, behind, filter);
207                 }, 1.5 * 60 * 1000);
208                 return () => {
209                         controller.abort();
210                         clearInterval(timer);
211                 };
212         }, [ahead, behind, fetchEpisodes, filter]);
213
214         return <Container>
215                 <Helmet>
216                         <title>{t('schedule.heading')}</title>
217                         <meta name="description" content={t('schedule.description')} />
218                 </Helmet>
219                 <CanonicalLinks base="/schedule" />
220                 <div className="d-flex align-items-start justify-content-between">
221                         <h1>{t('schedule.heading')}</h1>
222                         <div className="ms-3 mt-5">
223                                 <Filter filter={filter} setFilter={updateFilter} />
224                         </div>
225                 </div>
226                 <ErrorBoundary>
227                         {episodes.length ?
228                                 <List
229                                         episodes={episodes}
230                                         onAddRestream={onAddRestream}
231                                         onApply={onApply}
232                                         onEditRestream={onEditRestream}
233                                 />
234                         :
235                                 <Alert variant="info">
236                                         {t('episodes.empty')}
237                                 </Alert>
238                         }
239                 </ErrorBoundary>
240                 {user ? <>
241                         <ApplyDialog
242                                 as={applyAs}
243                                 episode={restreamEpisode}
244                                 onHide={onHideApplyDialog}
245                                 onSubmit={onSubmitApplyDialog}
246                                 show={showApplyDialog}
247                         />
248                         <RestreamDialog
249                                 channel={restreamChannel}
250                                 editRestream={editRestream}
251                                 episode={restreamEpisode}
252                                 manageCrew={manageCrew}
253                                 onRemoveRestream={onRemoveRestream}
254                                 onHide={onHideRestreamDialog}
255                                 onSubmit={onAddRestreamSubmit}
256                                 show={showRestreamDialog}
257                         />
258                 </> : null}
259         </Container>;
260 };
261
262 Schedule.propTypes = {
263         user: PropTypes.shape({
264         }),
265 };
266
267 export default withUser(Schedule);