Icon.FIRST_PLACE = makePreset('FirstPlaceIcon', 'trophy');
Icon.FORBIDDEN = makePreset('ForbiddenIcon', 'square-xmark');
Icon.FORFEIT = makePreset('ForfeitIcon', 'square-xmark');
+Icon.INVERT = makePreset('InveretIcon', 'circle-half-stroke');
Icon.LANGUAGE = makePreset('LanguageIcon', 'language');
Icon.LOCKED = makePreset('LockedIcon', 'lock');
Icon.LOGOUT = makePreset('LogoutIcon', 'sign-out-alt');
-import axios from 'axios';
-import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
import { Button } from 'react-bootstrap';
-const Filter = ({ filter, setFilter }) => {
- const [events, setEvents] = React.useState([]);
-
- 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();
- };
- }, []);
+import { isEventSelected, toggleEventFilter } from '../../helpers/Episode';
+const Filter = ({ events, filter, setFilter }) => {
const toggleEvent = React.useCallback(event => {
- const eventFilter = filter.event || [];
- if (eventFilter.includes(event.id)) {
- setFilter({
- ...filter,
- event: eventFilter.filter(id => id !== event.id && events.find(e => e.id === id)),
- });
- } else {
- setFilter({
- ...filter,
- event: [...eventFilter, event.id],
- });
- }
+ setFilter(toggleEventFilter(events, filter, event));
}, [events, filter, setFilter]);
- const isEventSelected = React.useCallback(event => {
- const eventFilter = filter.event || [];
- return eventFilter.includes(event.id);
- }, [filter]);
-
if (!events || !events.length) return null;
return <div className="episode-filter button-bar text-end">
{events.map(event =>
<Button
- active={isEventSelected(event)}
+ active={isEventSelected(filter, event)}
key={event.id}
onClick={() => toggleEvent(event)}
title={event.short ? event.title : null}
};
Filter.propTypes = {
+ events: PropTypes.arrayOf(PropTypes.shape({
+ })),
filter: PropTypes.shape(),
setFilter: PropTypes.func,
};
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 }) => {
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);
}
}, []);
+ 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,
<CanonicalLinks base="/schedule" />
<div className="d-flex align-items-end justify-content-between">
<h1 className="mb-0">{t('schedule.heading')}</h1>
- <Button
- onClick={toggleFilter}
- title={t('button.filter')}
- variant={filterButtonVariant}
- >
- <Icon.FILTER title="" />
- </Button>
+ <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 filter={filter} setFilter={updateFilter} />
+ <Filter events={events} filter={filter} setFilter={updateFilter} />
</div>
: null}
<ErrorBoundary>
return !!episode.channels.find(c =>
c.pivot && (c.pivot.accept_comms || c.pivot.accept_tracker));
};
+
+export const isEventSelected = (filter, event) => {
+ return (filter.event || []).includes(event.id);
+};
+
+export const toggleEventFilter = (events, filter, event) => {
+ const eventFilter = filter.event || [];
+ if (eventFilter.includes(event.id)) {
+ return {
+ ...filter,
+ event: eventFilter.filter(id => id !== event.id && events.find(e => e.id === id)),
+ };
+ }
+ return {
+ ...filter,
+ event: [...eventFilter, event.id],
+ };
+};
filter: 'Filter',
generate: 'Generieren',
help: 'Hilfe',
+ invert: 'Umkehren',
login: 'Login',
logout: 'Logout',
new: 'Neu',
filter: 'Filter',
generate: 'Generate',
help: 'Help',
+ invert: 'Invert',
login: 'Login',
logout: 'Logout',
new: 'New',