]> git.localhorst.tv Git - alttp.git/blobdiff - resources/js/components/pages/Schedule.js
option to invert event filter
[alttp.git] / resources / js / components / pages / Schedule.js
index 3d0a14082b8d5262c8350d207b6186c6a04e6116..cac23e7e66d19e5cc39534db61a528ef6a50349f 100644 (file)
@@ -2,17 +2,19 @@ import axios from 'axios';
 import moment from 'moment';
 import PropTypes from 'prop-types';
 import React from 'react';
-import { Alert, Container } from 'react-bootstrap';
+import { Alert, Button, Container } from 'react-bootstrap';
 import { Helmet } from 'react-helmet';
 import { useTranslation } from 'react-i18next';
 import toastr from 'toastr';
 
 import CanonicalLinks from '../common/CanonicalLinks';
 import ErrorBoundary from '../common/ErrorBoundary';
+import Icon from '../common/Icon';
 import ApplyDialog from '../episodes/ApplyDialog';
 import Filter from '../episodes/Filter';
 import List from '../episodes/List';
 import RestreamDialog from '../episodes/RestreamDialog';
+import { toggleEventFilter } from '../../helpers/Episode';
 import { withUser } from '../../helpers/UserContext';
 
 const Schedule = ({ user }) => {
@@ -20,11 +22,13 @@ const Schedule = ({ user }) => {
        const [applyAs, setApplyAs] = React.useState('commentary');
        const [behind] = React.useState(0);
        const [episodes, setEpisodes] = React.useState([]);
+       const [events, setEvents] = React.useState([]);
        const [filter, setFilter] = React.useState({});
        const [restreamChannel, setRestreamChannel] = React.useState(null);
        const [restreamEpisode, setRestreamEpisode] = React.useState(null);
        const [showApplyDialog, setShowApplyDialog] = React.useState(false);
        const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
+       const [showFilter, setShowFilter] = React.useState(false);
 
        const { t } = useTranslation();
 
@@ -37,17 +41,46 @@ const Schedule = ({ user }) => {
                }
        }, []);
 
+       React.useEffect(() => {
+               const controller = new AbortController();
+               axios.get(`/api/events`, {
+                       signal: controller.signal,
+                       params: {
+                               after: moment().startOf('day').subtract(7, 'days').toISOString(),
+                               before: moment().startOf('day').add(8, 'days').toISOString(),
+                       },
+               }).then(response => {
+                       const newEvents = (response.data || []).sort(
+                               (a, b) => (a.short || a.title).localeCompare(b.short || b.title)
+                       );
+                       setEvents(newEvents);
+               }).catch(e => {
+                       if (!axios.isCancel(e)) {
+                               console.error(e);
+                       }
+               });
+               return () => {
+                       controller.abort();
+               };
+       }, []);
+
        const updateFilter = React.useCallback(newFilter => {
                localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
                setFilter(newFilter);
        }, []);
 
+       const invertFilter = React.useCallback(() => {
+               updateFilter(events.reduce((newFilter, event) => {
+                       return toggleEventFilter(events, newFilter, event);
+               }, filter));
+       }, [events, filter]);
+
        const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
                axios.get(`/api/episodes`, {
                        signal: controller.signal,
                        params: {
-                               after: moment().startOf('day').subtract(behind, 'days').toISOString(),
-                               before: moment().startOf('day').add(ahead + 1, 'days').toISOString(),
+                               after: moment().subtract(8, 'hours').subtract(behind, 'days').toISOString(),
+                               before: moment().add(16, 'hours').add(ahead, 'days').toISOString(),
                                ...filter,
                        },
                }).then(response => {
@@ -211,18 +244,48 @@ const Schedule = ({ user }) => {
                };
        }, [ahead, behind, fetchEpisodes, filter]);
 
+       const toggleFilter = React.useCallback(() => {
+               setShowFilter(show => !show);
+       }, []);
+
+       const filterButtonVariant = React.useMemo(() => {
+               const outline = showFilter ? '' : 'outline-';
+               const filterActive = filter && filter.event && filter.event.length;
+               return `${outline}${filterActive ? 'info' : 'secondary'}`;
+       }, [filter, showFilter]);
+
        return <Container>
                <Helmet>
                        <title>{t('schedule.heading')}</title>
                        <meta name="description" content={t('schedule.description')} />
                </Helmet>
                <CanonicalLinks base="/schedule" />
-               <div className="d-flex align-items-start justify-content-between">
-                       <h1>{t('schedule.heading')}</h1>
-                       <div className="ms-3 mt-5">
-                               <Filter filter={filter} setFilter={updateFilter} />
+               <div className="d-flex align-items-end justify-content-between">
+                       <h1 className="mb-0">{t('schedule.heading')}</h1>
+                       <div className="button-bar">
+                               {showFilter ?
+                                       <Button
+                                               onClick={invertFilter}
+                                               title={t('button.invert')}
+                                               variant="outline-secondary"
+                                       >
+                                               <Icon.INVERT title="" />
+                                       </Button>
+                               : null}
+                               <Button
+                                       onClick={toggleFilter}
+                                       title={t('button.filter')}
+                                       variant={filterButtonVariant}
+                               >
+                                       <Icon.FILTER title="" />
+                               </Button>
                        </div>
                </div>
+               {showFilter ?
+                       <div className="my-2">
+                               <Filter events={events} filter={filter} setFilter={updateFilter} />
+                       </div>
+               : null}
                <ErrorBoundary>
                        {episodes.length ?
                                <List