From: Daniel Karbach Date: Sat, 26 Jul 2025 09:31:54 +0000 (+0200) Subject: allow restream management from event page X-Git-Url: http://git.localhorst.tv/?a=commitdiff_plain;h=b0132836c43e3218f21a6ffc21cfdee1b31739f1;p=alttp.git allow restream management from event page --- diff --git a/resources/js/components/episodes/ApplyForm.jsx b/resources/js/components/episodes/ApplyForm.jsx index ce5f88e..65e6d2a 100644 --- a/resources/js/components/episodes/ApplyForm.jsx +++ b/resources/js/components/episodes/ApplyForm.jsx @@ -68,7 +68,7 @@ const ApplyForm = ({ : null} ; diff --git a/resources/js/components/episodes/Crew.jsx b/resources/js/components/episodes/Crew.jsx index d069841..d89acae 100644 --- a/resources/js/components/episodes/Crew.jsx +++ b/resources/js/components/episodes/Crew.jsx @@ -13,9 +13,11 @@ import { hasSGRestream, } from '../../helpers/Episode'; import { canApplyForEpisode } from '../../helpers/permissions'; +import { useEpisodes } from '../../hooks/episodes'; import { useUser } from '../../hooks/user'; -const Crew = ({ episode, onApply }) => { +const Crew = ({ episode }) => { + const { onApplyRestream } = useEpisodes(); const { t } = useTranslation(); const { user } = useUser(); @@ -57,10 +59,10 @@ const Crew = ({ episode, onApply }) => { {commentators.map(c => )} - {onApply && canApplyForEpisode(user, episode, 'commentary') ? + {onApplyRestream && canApplyForEpisode(user, episode, 'commentary') ?
)} ; @@ -59,9 +50,6 @@ List.propTypes = { episodes: PropTypes.arrayOf(PropTypes.shape({ start: PropTypes.string, })), - onAddRestream: PropTypes.func, - onApply: PropTypes.func, - onEditRestream: PropTypes.func, }; export default List; diff --git a/resources/js/hooks/episodes.jsx b/resources/js/hooks/episodes.jsx new file mode 100644 index 0000000..4748dcd --- /dev/null +++ b/resources/js/hooks/episodes.jsx @@ -0,0 +1,206 @@ +import axios from 'axios'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import toastr from 'toastr'; + +import { useUser } from './user'; +import ApplyDialog from '../components/episodes/ApplyDialog'; +import RestreamDialog from '../components/episodes/RestreamDialog'; + +const context = React.createContext({ + onAddRestream: null, + onApplyRestream: null, + onEditRestream: null, +}); + +export const useEpisodes = () => React.useContext(context); + +export const EpisodesProvider = ({ children, setEpisodes }) => { + const [applyAs, setApplyAs] = React.useState('commentary'); + 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 { t } = useTranslation(); + const { user } = useUser(); + + const onAddRestream = React.useCallback(episode => { + setRestreamEpisode(episode); + setShowRestreamDialog(true); + }, []); + + const onAddRestreamSubmit = React.useCallback(async values => { + try { + const response = await axios.post( + `/api/episodes/${values.episode_id}/add-restream`, values); + const newEpisode = response.data; + setEpisodes(episodes => episodes.map(episode => + episode.id === newEpisode.id ? { + ...episode, + ...newEpisode, + } : episode + )); + toastr.success(t('episodes.restreamDialog.addSuccess')); + } catch (e) { + toastr.error(t('episodes.restreamDialog.addError')); + throw e; + } + setRestreamEpisode(null); + setShowRestreamDialog(false); + }, []); + + const onRemoveRestream = React.useCallback(async (episode, channel) => { + try { + const response = await axios.post( + `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id }); + const newEpisode = response.data; + setEpisodes(episodes => episodes.map(episode => + episode.id === newEpisode.id ? { + ...episode, + ...newEpisode, + } : episode + )); + toastr.success(t('episodes.restreamDialog.removeSuccess')); + setRestreamChannel(null); + setRestreamEpisode(null); + setShowRestreamDialog(false); + } catch (error) { + toastr.error(t('episodes.restreamDialog.removeError', { error })); + } + }, []); + + const onEditRestream = React.useCallback((episode, channel) => { + setRestreamChannel(channel); + setRestreamEpisode(episode); + setShowRestreamDialog(true); + }, []); + + const editRestream = React.useCallback(async values => { + try { + const response = await axios.post( + `/api/episodes/${values.episode_id}/edit-restream`, values); + const newEpisode = response.data; + setEpisodes(episodes => episodes.map(episode => + episode.id === newEpisode.id ? { + ...episode, + ...newEpisode, + } : episode + )); + setRestreamEpisode(episode => ({ + ...episode, + ...newEpisode, + })); + const newChannel = newEpisode.channels.find(c => c.id === values.channel_id); + setRestreamChannel(channel => ({ + ...channel, + ...newChannel, + })); + toastr.success(t('episodes.restreamDialog.editSuccess')); + } catch (error) { + toastr.error(t('episodes.restreamDialog.editError', { error })); + } + }, []); + + const manageCrew = React.useCallback(async values => { + try { + const response = await axios.post( + `/api/episodes/${values.episode_id}/crew-manage`, values); + const newEpisode = response.data; + setEpisodes(episodes => episodes.map(episode => + episode.id === newEpisode.id ? { + ...episode, + ...newEpisode, + } : episode + )); + setRestreamEpisode(episode => ({ + ...episode, + ...newEpisode, + })); + const newChannel = newEpisode.channels.find(c => c.id === values.channel_id); + setRestreamChannel(channel => ({ + ...channel, + ...newChannel, + })); + toastr.success(t('episodes.restreamDialog.crewSuccess')); + } catch (error) { + toastr.error(t('episodes.restreamDialog.crewError', { error })); + } + }, []); + + const onHideRestreamDialog = React.useCallback(() => { + setShowRestreamDialog(false); + setRestreamChannel(null); + setRestreamEpisode(null); + }, []); + + const onApplyRestream = React.useCallback((episode, as) => { + setShowApplyDialog(true); + setRestreamEpisode(episode); + setApplyAs(as); + }, []); + + const onSubmitApplyDialog = React.useCallback(async values => { + try { + const response = await axios.post( + `/api/episodes/${values.episode_id}/crew-signup`, values); + const newEpisode = response.data; + setEpisodes(episodes => episodes.map(episode => + episode.id === newEpisode.id ? { + ...episode, + ...newEpisode, + } : episode + )); + toastr.success(t('episodes.applyDialog.applySuccess')); + } catch (e) { + toastr.error(t('episodes.applyDialog.applyError')); + throw e; + } + setRestreamEpisode(null); + setShowApplyDialog(false); + }, []); + + const onHideApplyDialog = React.useCallback(() => { + setShowApplyDialog(false); + setRestreamEpisode(null); + }, []); + + const value = React.useMemo(() => ({ + onAddRestream, + onApplyRestream, + onEditRestream, + }), [ + onAddRestream, + onApplyRestream, + onEditRestream, + ]); + + return + {children} + {user ? <> + + + : null} + ; +}; + +EpisodesProvider.propTypes = { + children: PropTypes.node, + setEpisodes: PropTypes.func, +}; diff --git a/resources/js/pages/Event.jsx b/resources/js/pages/Event.jsx index ad2dd61..3fda20d 100644 --- a/resources/js/pages/Event.jsx +++ b/resources/js/pages/Event.jsx @@ -21,6 +21,7 @@ import { mayEditContent, } from '../helpers/permissions'; import { getTranslation } from '../helpers/Technique'; +import { EpisodesProvider } from '../hooks/episodes'; import { useUser } from '../hooks/user'; import i18n from '../i18n'; @@ -90,7 +91,7 @@ export const Component = () => { })); setShowContentDialog(false); } catch (e) { - toastr.error(t('content.saveError')); + toastr.error(t('content.saveError', { message: e.message })); } }, [event && event.description_id]); @@ -179,7 +180,9 @@ export const Component = () => { {episodes.length ? - + + + : {t(pastMode ? 'events.noPastEpisodes' : 'events.noUpcomingEpisodes')} diff --git a/resources/js/pages/Schedule.jsx b/resources/js/pages/Schedule.jsx index fde4c96..1738cb1 100644 --- a/resources/js/pages/Schedule.jsx +++ b/resources/js/pages/Schedule.jsx @@ -4,35 +4,26 @@ import React from 'react'; import { Alert, Button, Container } from 'react-bootstrap'; import { Helmet } from 'react-helmet'; import { useTranslation } from 'react-i18next'; -import toastr from 'toastr'; import CanonicalLinks from '../components/common/CanonicalLinks'; import ErrorBoundary from '../components/common/ErrorBoundary'; import Icon from '../components/common/Icon'; -import ApplyDialog from '../components/episodes/ApplyDialog'; import Filter from '../components/episodes/Filter'; import List from '../components/episodes/List'; -import RestreamDialog from '../components/episodes/RestreamDialog'; import { getFilterParams, isFilterActive } from '../helpers/Episode'; -import { useUser } from '../hooks/user'; +import { EpisodesProvider } from '../hooks/episodes'; const GAMES = [ 'alttp', 'alttpr', 'sm', 'smr', 'smz3', ]; export const Component = () => { - const [applyAs, setApplyAs] = React.useState('commentary'); 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(); - const { user } = useUser(); React.useEffect(() => { const savedFilter = localStorage.getItem('episodes.filter.schedule'); @@ -92,146 +83,6 @@ export const Component = () => { }); }, []); - const onAddRestream = React.useCallback(episode => { - setRestreamEpisode(episode); - setShowRestreamDialog(true); - }, []); - - const onAddRestreamSubmit = React.useCallback(async values => { - try { - const response = await axios.post( - `/api/episodes/${values.episode_id}/add-restream`, values); - const newEpisode = response.data; - setEpisodes(episodes => episodes.map(episode => - episode.id === newEpisode.id ? { - ...episode, - ...newEpisode, - } : episode - )); - toastr.success(t('episodes.restreamDialog.addSuccess')); - } catch (e) { - toastr.error(t('episodes.restreamDialog.addError')); - throw e; - } - setRestreamEpisode(null); - setShowRestreamDialog(false); - }, []); - - const onRemoveRestream = React.useCallback(async (episode, channel) => { - try { - const response = await axios.post( - `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id }); - const newEpisode = response.data; - setEpisodes(episodes => episodes.map(episode => - episode.id === newEpisode.id ? { - ...episode, - ...newEpisode, - } : episode - )); - toastr.success(t('episodes.restreamDialog.removeSuccess')); - setRestreamChannel(null); - setRestreamEpisode(null); - setShowRestreamDialog(false); - } catch (error) { - toastr.error(t('episodes.restreamDialog.removeError', { error })); - } - }, []); - - const onEditRestream = React.useCallback((episode, channel) => { - setRestreamChannel(channel); - setRestreamEpisode(episode); - setShowRestreamDialog(true); - }, []); - - const editRestream = React.useCallback(async values => { - try { - const response = await axios.post( - `/api/episodes/${values.episode_id}/edit-restream`, values); - const newEpisode = response.data; - setEpisodes(episodes => episodes.map(episode => - episode.id === newEpisode.id ? { - ...episode, - ...newEpisode, - } : episode - )); - setRestreamEpisode(episode => ({ - ...episode, - ...newEpisode, - })); - const newChannel = newEpisode.channels.find(c => c.id === values.channel_id); - setRestreamChannel(channel => ({ - ...channel, - ...newChannel, - })); - toastr.success(t('episodes.restreamDialog.editSuccess')); - } catch (error) { - toastr.error(t('episodes.restreamDialog.editError', { error })); - } - }, []); - - const manageCrew = React.useCallback(async values => { - try { - const response = await axios.post( - `/api/episodes/${values.episode_id}/crew-manage`, values); - const newEpisode = response.data; - setEpisodes(episodes => episodes.map(episode => - episode.id === newEpisode.id ? { - ...episode, - ...newEpisode, - } : episode - )); - setRestreamEpisode(episode => ({ - ...episode, - ...newEpisode, - })); - const newChannel = newEpisode.channels.find(c => c.id === values.channel_id); - setRestreamChannel(channel => ({ - ...channel, - ...newChannel, - })); - toastr.success(t('episodes.restreamDialog.crewSuccess')); - } catch (error) { - toastr.error(t('episodes.restreamDialog.crewError', { error })); - } - }, []); - - const onHideRestreamDialog = React.useCallback(() => { - setShowRestreamDialog(false); - setRestreamChannel(null); - setRestreamEpisode(null); - }, []); - - const onApply = React.useCallback((episode, as) => { - setShowApplyDialog(true); - setRestreamEpisode(episode); - setApplyAs(as); - }, []); - - const onSubmitApplyDialog = React.useCallback(async values => { - try { - const response = await axios.post( - `/api/episodes/${values.episode_id}/crew-signup`, values); - const newEpisode = response.data; - setEpisodes(episodes => episodes.map(episode => - episode.id === newEpisode.id ? { - ...episode, - ...newEpisode, - } : episode - )); - toastr.success(t('episodes.applyDialog.applySuccess')); - } catch (e) { - toastr.error(t('episodes.applyDialog.applyError')); - throw e; - } - setRestreamEpisode(null); - setShowApplyDialog(false); - }, []); - - const onHideApplyDialog = React.useCallback(() => { - setShowApplyDialog(false); - setRestreamEpisode(null); - }, []); - React.useEffect(() => { const controller = new AbortController(); fetchEpisodes(controller, filter); @@ -283,36 +134,14 @@ export const Component = () => { {episodes.length ? - + + + : {t('episodes.empty')} } - {user ? <> - - - : null} ; };