]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/pages/Schedule.js
221fa987b38953416686eb616e8663f3de3b959c
[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 Filter from '../episodes/Filter';
13 import List from '../episodes/List';
14 import RestreamDialog from '../episodes/RestreamDialog';
15 import { withUser } from '../../helpers/UserContext';
16
17 const Schedule = ({ user }) => {
18         const [ahead] = React.useState(14);
19         const [behind] = React.useState(0);
20         const [episodes, setEpisodes] = React.useState([]);
21         const [filter, setFilter] = React.useState({});
22         const [restreamChannel, setRestreamChannel] = React.useState(null);
23         const [restreamEpisode, setRestreamEpisode] = React.useState(null);
24         const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
25
26         const { t } = useTranslation();
27
28         React.useEffect(() => {
29                 const savedFilter = localStorage.getItem('episodes.filter.schedule');
30                 if (savedFilter) {
31                         setFilter(JSON.parse(savedFilter));
32                 } else {
33                         setFilter(filter => filter ? {} : filter);
34                 }
35         }, []);
36
37         const updateFilter = React.useCallback(newFilter => {
38                 localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
39                 setFilter(newFilter);
40         }, []);
41
42         const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
43                 axios.get(`/api/episodes`, {
44                         signal: controller.signal,
45                         params: {
46                                 after: moment().startOf('day').subtract(behind, 'days').toISOString(),
47                                 before: moment().startOf('day').add(ahead + 1, 'days').toISOString(),
48                                 ...filter,
49                         },
50                 }).then(response => {
51                         setEpisodes(response.data || []);
52                 }).catch(e => {
53                         if (!axios.isCancel(e)) {
54                                 console.error(e);
55                         }
56                 });
57         }, []);
58
59         const onAddRestream = React.useCallback(episode => {
60                 setRestreamEpisode(episode);
61                 setShowRestreamDialog(true);
62         }, []);
63
64         const onAddRestreamSubmit = React.useCallback(async values => {
65                 try {
66                         const response = await axios.post(
67                                 `/api/episodes/${values.episode_id}/add-restream`, values);
68                         const newEpisode = response.data;
69                         setEpisodes(episodes => episodes.map(episode =>
70                                 episode.id === newEpisode.id ? {
71                                         ...episode,
72                                         ...newEpisode,
73                                 } : episode
74                         ));
75                         toastr.success(t('episodes.restreamDialog.addSuccess'));
76                 } catch (e) {
77                         toastr.error(t('episodes.restreamDialog.addError'));
78                         throw e;
79                 }
80                 setRestreamEpisode(null);
81                 setShowRestreamDialog(false);
82         }, []);
83
84         const onRemoveRestream = React.useCallback(async (episode, channel) => {
85                 try {
86                         const response = await axios.post(
87                                 `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
88                         const newEpisode = response.data;
89                         setEpisodes(episodes => episodes.map(episode =>
90                                 episode.id === newEpisode.id ? {
91                                         ...episode,
92                                         ...newEpisode,
93                                 } : episode
94                         ));
95                         toastr.success(t('episodes.restreamDialog.removeSuccess'));
96                         setRestreamChannel(null);
97                         setRestreamEpisode(null);
98                         setShowRestreamDialog(false);
99                 } catch (e) {
100                         toastr.error(t('episodes.restreamDialog.removeError'));
101                 }
102         }, []);
103
104         const onEditRestream = React.useCallback((episode, channel) => {
105                 setRestreamChannel(channel);
106                 setRestreamEpisode(episode);
107                 setShowRestreamDialog(true);
108         }, []);
109
110         const onHideRestreamDialog = React.useCallback(() => {
111                 setShowRestreamDialog(false);
112                 setRestreamChannel(null);
113                 setRestreamEpisode(null);
114         }, []);
115
116         React.useEffect(() => {
117                 const controller = new AbortController();
118                 fetchEpisodes(controller, ahead, behind, filter);
119                 const timer = setInterval(() => {
120                         fetchEpisodes(controller, ahead, behind, filter);
121                 }, 1.5 * 60 * 1000);
122                 return () => {
123                         controller.abort();
124                         clearInterval(timer);
125                 };
126         }, [ahead, behind, fetchEpisodes, filter]);
127
128         return <Container>
129                 <Helmet>
130                         <title>{t('schedule.heading')}</title>
131                         <meta name="description" content={t('schedule.description')} />
132                 </Helmet>
133                 <CanonicalLinks base="/schedule" />
134                 <div className="d-flex align-items-start justify-content-between">
135                         <h1>{t('schedule.heading')}</h1>
136                         <div className="ms-3 mt-5">
137                                 <Filter filter={filter} setFilter={updateFilter} />
138                         </div>
139                 </div>
140                 <ErrorBoundary>
141                         {episodes.length ?
142                                 <List
143                                         episodes={episodes}
144                                         onAddRestream={onAddRestream}
145                                         onEditRestream={onEditRestream}
146                                 />
147                         :
148                                 <Alert variant="info">
149                                         {t('episodes.empty')}
150                                 </Alert>
151                         }
152                 </ErrorBoundary>
153                 <RestreamDialog
154                         channel={restreamChannel}
155                         episode={restreamEpisode}
156                         onRemoveRestream={onRemoveRestream}
157                         onHide={onHideRestreamDialog}
158                         onSubmit={onAddRestreamSubmit}
159                         show={showRestreamDialog}
160                 />
161         </Container>;
162 };
163
164 Schedule.propTypes = {
165         user: PropTypes.shape({
166         }),
167 };
168
169 export default withUser(Schedule);