import Button from '@app/components/Common/Button'; import ConfirmButton from '@app/components/Common/ConfirmButton'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import PageTitle from '@app/components/Common/PageTitle'; import Tooltip from '@app/components/Common/Tooltip'; import { sliderTitles } from '@app/components/Discover/constants'; import CreateSlider from '@app/components/Discover/CreateSlider'; import DiscoverSliderEdit from '@app/components/Discover/DiscoverSliderEdit'; import MovieGenreSlider from '@app/components/Discover/MovieGenreSlider'; import NetworkSlider from '@app/components/Discover/NetworkSlider'; import PlexWatchlistSlider from '@app/components/Discover/PlexWatchlistSlider'; import RecentlyAddedSlider from '@app/components/Discover/RecentlyAddedSlider'; import RecentRequestsSlider from '@app/components/Discover/RecentRequestsSlider'; import StudioSlider from '@app/components/Discover/StudioSlider'; import TvGenreSlider from '@app/components/Discover/TvGenreSlider'; import MediaSlider from '@app/components/MediaSlider'; import { encodeURIExtraParams } from '@app/hooks/useDiscover'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import { Transition } from '@headlessui/react'; import { ArrowDownOnSquareIcon, ArrowPathIcon, ArrowUturnLeftIcon, PencilIcon, PlusIcon, } from '@heroicons/react/24/solid'; import { DiscoverSliderType } from '@server/constants/discover'; import type DiscoverSlider from '@server/entity/DiscoverSlider'; import axios from 'axios'; import { useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; const messages = defineMessages({ discover: 'Discover', emptywatchlist: 'Media added to your Plex Watchlist will appear here.', resettodefault: 'Reset to Default', resetwarning: 'Reset all sliders to default. This will also delete any custom sliders!', updatesuccess: 'Updated discover customization settings.', updatefailed: 'Something went wrong updating the discover customization settings.', resetsuccess: 'Sucessfully reset discover customization settings.', resetfailed: 'Something went wrong resetting the discover customization settings.', customizediscover: 'Customize Discover', stopediting: 'Stop Editing', createnewslider: 'Create New Slider', }); const Discover = () => { const intl = useIntl(); const { hasPermission } = useUser(); const { addToast } = useToasts(); const { data: discoverData, error: discoverError, mutate, } = useSWR('/api/v1/settings/discover'); const [sliders, setSliders] = useState[]>([]); const [isEditing, setIsEditing] = useState(false); // We need to sync the state here so that we can modify the changes locally without commiting // anything to the server until the user decides to save the changes useEffect(() => { if (discoverData && !isEditing) { setSliders(discoverData); } }, [discoverData, isEditing]); const hasChanged = () => !Object.is(discoverData, sliders); const updateSliders = async () => { try { await axios.post('/api/v1/settings/discover', sliders); addToast(intl.formatMessage(messages.updatesuccess), { appearance: 'success', autoDismiss: true, }); setIsEditing(false); mutate(); } catch (e) { addToast(intl.formatMessage(messages.updatefailed), { appearance: 'error', autoDismiss: true, }); } }; const resetSliders = async () => { try { await axios.get('/api/v1/settings/discover/reset'); addToast(intl.formatMessage(messages.resetsuccess), { appearance: 'success', autoDismiss: true, }); setIsEditing(false); mutate(); } catch (e) { addToast(intl.formatMessage(messages.resetfailed), { appearance: 'error', autoDismiss: true, }); } }; const now = new Date(); const offset = now.getTimezoneOffset(); const upcomingDate = new Date(now.getTime() - offset * 60 * 1000) .toISOString() .split('T')[0]; if (!discoverData && !discoverError) { return ; } return ( <> {hasPermission(Permission.ADMIN) && ( <> {isEditing && (
{intl.formatMessage(messages.createnewslider)}
{ const newSliders = await mutate(); if (newSliders) { setSliders(newSliders); } }} />
)} resetSliders()} confirmText={intl.formatMessage(globalMessages.areyousure)} className="w-full sm:w-auto" > {intl.formatMessage(messages.resettodefault)} )} {(isEditing ? sliders : discoverData)?.map((slider, index) => { let sliderComponent: React.ReactNode; switch (slider.type) { case DiscoverSliderType.RECENTLY_ADDED: sliderComponent = ; break; case DiscoverSliderType.RECENT_REQUESTS: sliderComponent = ; break; case DiscoverSliderType.PLEX_WATCHLIST: sliderComponent = ; break; case DiscoverSliderType.TRENDING: sliderComponent = ( ); break; case DiscoverSliderType.POPULAR_MOVIES: sliderComponent = ( ); break; case DiscoverSliderType.MOVIE_GENRES: sliderComponent = ; break; case DiscoverSliderType.UPCOMING_MOVIES: sliderComponent = ( ); break; case DiscoverSliderType.STUDIOS: sliderComponent = ; break; case DiscoverSliderType.POPULAR_TV: sliderComponent = ( ); break; case DiscoverSliderType.TV_GENRES: sliderComponent = ; break; case DiscoverSliderType.UPCOMING_TV: sliderComponent = ( ); break; case DiscoverSliderType.NETWORKS: sliderComponent = ; break; case DiscoverSliderType.TMDB_MOVIE_KEYWORD: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_TV_KEYWORD: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_MOVIE_GENRE: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_TV_GENRE: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_STUDIO: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_NETWORK: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_SEARCH: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_MOVIE_STREAMING_SERVICES: sliderComponent = ( ); break; case DiscoverSliderType.TMDB_TV_STREAMING_SERVICES: sliderComponent = ( ); break; } if (isEditing) { return ( { const newSliders = await mutate(); if (newSliders) { setSliders(newSliders); } }} onEnable={() => { const tempSliders = sliders.slice(); tempSliders[index].enabled = !tempSliders[index].enabled; setSliders(tempSliders); }} onPositionUpdate={(updatedItemId, position, hasClickedArrows) => { const originalPosition = sliders.findIndex( (item) => item.id === updatedItemId ); const originalItem = sliders[originalPosition]; const tempSliders = sliders.slice(); tempSliders.splice(originalPosition, 1); hasClickedArrows ? tempSliders.splice( position === 'Above' ? index - 1 : index + 1, 0, originalItem ) : tempSliders.splice( position === 'Above' && index > originalPosition ? Math.max(index - 1, 0) : index, 0, originalItem ); setSliders(tempSliders); }} disableUpButton={index === 0} disableDownButton={index === sliders.length - 1} > {sliderComponent} ); } if (!slider.enabled) { return null; } return (
{sliderComponent}
); })} ); }; export default Discover;