diff --git a/web/src/external/api.ts b/web/src/external/api.ts index e7de38789..82872daac 100644 --- a/web/src/external/api.ts +++ b/web/src/external/api.ts @@ -27,7 +27,9 @@ import { makeErrors, parametersBuilder, } from '@zodios/core'; +import { isEmpty } from 'lodash-es'; import { z } from 'zod'; +import { getFfmpegInfoEndpoint } from './ffmpegApi.ts'; import { createPlexServerEndpoint, deletePlexServerEndpoint, @@ -45,8 +47,6 @@ import { updateSystemSettings, updateXmlTvSettings, } from './settingsApi.ts'; -import { isEmpty } from 'lodash-es'; -import { getFfmpegInfoEndpoint } from './ffmpegApi.ts'; export const api = makeApi([ { diff --git a/web/src/helpers/constants.ts b/web/src/helpers/constants.ts index 618176b5a..07bc824fb 100644 --- a/web/src/helpers/constants.ts +++ b/web/src/helpers/constants.ts @@ -1 +1,4 @@ export const OneDayMillis = 1000 * 60 * 60 * 24; + +// Special ID to use for in-progress entity operations +export const UnsavedId = 'unsaved'; diff --git a/web/src/hooks/useCustomShows.ts b/web/src/hooks/useCustomShows.ts index 3d526155e..3d9857065 100644 --- a/web/src/hooks/useCustomShows.ts +++ b/web/src/hooks/useCustomShows.ts @@ -1,10 +1,12 @@ import { DataTag, DefinedInitialDataOptions, + QueriesResults, + UseQueryResult, useQueries, useQuery, } from '@tanstack/react-query'; -import { CustomShow } from '@tunarr/types'; +import { CustomProgram, CustomShow } from '@tunarr/types'; import { ApiClient } from '../external/api.ts'; import { ZodiosAliasReturnType } from '../types/index.ts'; import { makeQueryOptionsInitialData } from './useQueryHelpers.ts'; @@ -56,19 +58,51 @@ export const customShowProgramsQuery = (apiClient: ApiClient, id: string) => ({ queryFn: () => apiClient.getCustomShowPrograms({ params: { id } }), }); -export const useCustomShow = ( +// Tried to do a clever overload here but it's easier to just blow out the method +// and get the rid type inference... +export function useCustomShowWithInitialData( + apiClient: ApiClient, + id: string, + enabled: boolean, + initialData: { customShow: CustomShow; programs: CustomProgram[] }, +) { + return useQueries({ + queries: [ + { + ...customShowQuery(apiClient, id), + enabled, + initialData: initialData?.customShow, + }, + { + ...customShowProgramsQuery(apiClient, id), + enabled: enabled, + initialData: initialData?.programs, + }, + ], + }); +} + +export function useCustomShow( apiClient: ApiClient, id: string, enabled: boolean, includePrograms: boolean, -) => { + initialData: { customShow?: CustomShow; programs?: CustomProgram[] } = {}, +): QueriesResults< + [UseQueryResult, UseQueryResult] +> { return useQueries({ queries: [ - { ...customShowQuery(apiClient, id), enabled }, + { + ...customShowQuery(apiClient, id), + enabled, + initialData: initialData?.customShow, + }, { ...customShowProgramsQuery(apiClient, id), enabled: enabled && includePrograms, + initialData: initialData?.programs, }, ], }); -}; +} diff --git a/web/src/hooks/useFillerLists.ts b/web/src/hooks/useFillerLists.ts index 1fa616bec..6e453b14c 100644 --- a/web/src/hooks/useFillerLists.ts +++ b/web/src/hooks/useFillerLists.ts @@ -1,7 +1,8 @@ -import { useQuery } from '@tanstack/react-query'; +import { useQueries, useQuery } from '@tanstack/react-query'; +import { FillerList, FillerListProgramming } from '@tunarr/types'; +import { ApiClient } from '../external/api.ts'; import useStore from '../store/index.ts'; import { makeQueryOptions } from './useQueryHelpers.ts'; -import { ApiClient } from '../external/api.ts'; import { useTunarrApi } from './useTunarrApi.ts'; export const fillerListsQuery = (apiClient: ApiClient) => @@ -23,3 +24,30 @@ export const fillerListProgramsQuery = (apiClient: ApiClient, id: string) => makeQueryOptions(['fillers', id, 'programs'], () => apiClient.getFillerListPrograms({ params: { id } }), ); + +// Tried to do a clever overload here but it's easier to just blow out the method +// and get the rid type inference... +export function useFillersWithInitialData( + apiClient: ApiClient, + id: string, + enabled: boolean, + initialData: { + filler: FillerList; + fillerListPrograms: FillerListProgramming; + }, +) { + return useQueries({ + queries: [ + { + ...fillerListQuery(apiClient, id), + enabled, + initialData: initialData?.filler, + }, + { + ...fillerListProgramsQuery(apiClient, id), + enabled: enabled, + initialData: initialData?.fillerListPrograms, + }, + ], + }); +} diff --git a/web/src/hooks/usePreloadedCustomShow.ts b/web/src/hooks/usePreloadedCustomShow.ts new file mode 100644 index 000000000..baf2a43d9 --- /dev/null +++ b/web/src/hooks/usePreloadedCustomShow.ts @@ -0,0 +1,31 @@ +import { useLoaderData } from 'react-router-dom'; +import { UnsavedId } from '../helpers/constants'; +import { CustomShowPreload } from '../preloaders/customShowLoaders'; +import { setCurrentCustomShow } from '../store/channelEditor/actions'; +import { useCustomShowEditor } from '../store/selectors'; +import { useCustomShowWithInitialData } from './useCustomShows'; +import { useTunarrApi } from './useTunarrApi'; + +export const usePreloadedCustomShow = () => { + const apiClient = useTunarrApi(); + const { show: preloadShow, programs: preloadPrograms } = + useLoaderData() as CustomShowPreload; + + const [customShow, customPrograms] = useCustomShowWithInitialData( + apiClient, + preloadShow.id, + preloadShow.id !== UnsavedId, + { + customShow: preloadShow, + programs: preloadPrograms, + }, + ); + + const customShowEditor = useCustomShowEditor(); + + if (customShowEditor.currentEntity?.id !== customShow.data.id) { + setCurrentCustomShow(customShow.data, customPrograms.data); + } + + return customShowEditor; +}; diff --git a/web/src/hooks/usePreloadedFiller.ts b/web/src/hooks/usePreloadedFiller.ts new file mode 100644 index 000000000..7dd8bd891 --- /dev/null +++ b/web/src/hooks/usePreloadedFiller.ts @@ -0,0 +1,31 @@ +import { FillerPreload } from '@/preloaders/fillerListLoader'; +import { setCurrentFillerList } from '@/store/channelEditor/actions'; +import { useLoaderData } from 'react-router-dom'; +import { UnsavedId } from '../helpers/constants'; +import { useFillerListEditor } from '../store/selectors'; +import { useFillersWithInitialData } from './useFillerLists'; +import { useTunarrApi } from './useTunarrApi'; + +export const usePreloadedFiller = () => { + const apiClient = useTunarrApi(); + const { filler: preloadFiller, programs: preloadPrograms } = + useLoaderData() as FillerPreload; + + const [filler, fillerPrograms] = useFillersWithInitialData( + apiClient, + preloadFiller.id, + preloadFiller.id !== UnsavedId, + { + filler: preloadFiller, + fillerListPrograms: preloadPrograms, + }, + ); + + const fillerListEditor = useFillerListEditor(); + + if (fillerListEditor.currentEntity?.id !== filler.data.id) { + setCurrentFillerList(filler.data, fillerPrograms.data); + } + + return fillerListEditor; +}; diff --git a/web/src/pages/channels/ProgrammingSelectorPage.tsx b/web/src/pages/channels/ProgrammingSelectorPage.tsx index 5ba736b7e..1468c0e14 100644 --- a/web/src/pages/channels/ProgrammingSelectorPage.tsx +++ b/web/src/pages/channels/ProgrammingSelectorPage.tsx @@ -1,22 +1,48 @@ import SelectedProgrammingList from '@/components/channel_config/SelectedProgrammingList.tsx'; +import { + addMediaToCurrentChannel, + addMediaToCurrentCustomShow, + addMediaToCurrentFillerList, +} from '@/store/channelEditor/actions.ts'; +import useStore from '@/store/index.ts'; +import { useLocation, useNavigate } from 'react-router-dom'; import Breadcrumbs from '../../components/Breadcrumbs.tsx'; import PaddedPaper from '../../components/base/PaddedPaper.tsx'; import ProgrammingSelector from '../../components/channel_config/ProgrammingSelector.tsx'; -import { useNavigate } from 'react-router-dom'; -import { addMediaToCurrentChannel } from '@/store/channelEditor/actions.ts'; -import useStore from '@/store/index.ts'; export default function ProgrammingSelectorPage() { const selectedLibrary = useStore((s) => s.currentLibrary); - console.log(selectedLibrary); const navigate = useNavigate(); + const location = useLocation(); + + const displayPaths = [ + { + path: 'fillers/programming/add', + onMediaAdd: addMediaToCurrentFillerList, + }, + { + path: 'custom-shows/programming/add', + onMediaAdd: addMediaToCurrentCustomShow, + }, + { + path: 'channels/:id/programming/add', + onMediaAdd: addMediaToCurrentChannel, + }, + ]; + const displaySelectedProgramming = displayPaths.find((pathObject) => { + const { path } = pathObject; // Destructure path from the object + return location.pathname.match(new RegExp(path)); + }); + return ( <> navigate(-1)} selectAllEnabled={selectedLibrary?.type === 'plex'} /> diff --git a/web/src/pages/library/EditCustomShowPage.tsx b/web/src/pages/library/EditCustomShowPage.tsx index 89caabd6d..9868b3dc4 100644 --- a/web/src/pages/library/EditCustomShowPage.tsx +++ b/web/src/pages/library/EditCustomShowPage.tsx @@ -1,56 +1,42 @@ +import { Tv } from '@mui/icons-material'; import DeleteIcon from '@mui/icons-material/Delete'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { Button, - Divider, IconButton, List, ListItem, ListItemText, Stack, TextField, + Tooltip, } from '@mui/material'; -import Accordion from '@mui/material/Accordion'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import AccordionSummary from '@mui/material/AccordionSummary'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import Breadcrumbs from '../../components/Breadcrumbs.tsx'; import PaddedPaper from '../../components/base/PaddedPaper.tsx'; -import AddSelectedMediaButton from '../../components/channel_config/AddSelectedMediaButton.tsx'; -import ProgrammingSelector from '../../components/channel_config/ProgrammingSelector.tsx'; -import { usePreloadedData } from '../../hooks/preloadedDataHook.ts'; +import { usePreloadedCustomShow } from '../../hooks/usePreloadedCustomShow.ts'; import { useTunarrApi } from '../../hooks/useTunarrApi.ts'; -import { - existingCustomShowLoader, - newCustomShowLoader, -} from '../../preloaders/customShowLoaders.ts'; -import { - addMediaToCurrentCustomShow, - removeCustomShowProgram, -} from '../../store/channelEditor/actions.ts'; -import useStore from '../../store/index.ts'; +import { removeCustomShowProgram } from '../../store/channelEditor/actions.ts'; import { UICustomShowProgram } from '../../types/index.ts'; type Props = { isNew: boolean }; type CustomShowForm = { + id?: string; name: string; }; export default function EditCustomShowPage({ isNew }: Props) { const apiClient = useTunarrApi(); - const { show: customShow } = usePreloadedData( - isNew ? existingCustomShowLoader : newCustomShowLoader, - ); - const customShowPrograms = useStore((s) => s.customShowEditor.programList); + const { currentEntity: customShow, programList: customShowPrograms } = + usePreloadedCustomShow(); + const queryClient = useQueryClient(); const navigate = useNavigate(); - const [addProgrammingOpen, setAddProgrammingOpen] = useState(false); const { control, @@ -59,25 +45,32 @@ export default function EditCustomShowPage({ isNew }: Props) { formState: { isValid }, } = useForm({ defaultValues: { - name: '', + name: customShow?.name ?? '', }, }); useEffect(() => { reset({ - name: customShow.name, + name: customShow?.name, }); }, [customShow, reset]); const saveShowMutation = useMutation({ - mutationKey: ['custom-shows', isNew ? 'new' : customShow.id], + mutationKey: ['custom-shows', isNew ? 'new' : customShow?.id], mutationFn: async ( data: CustomShowForm & { programs: UICustomShowProgram[] }, ) => { - return apiClient.createCustomShow({ - name: data.name, - programs: data.programs, - }); + if (isNew) { + return apiClient.createCustomShow({ + name: data.name, + programs: data.programs, + }); + } else { + return apiClient.updateCustomShow( + { name: data.name, programs: data.programs }, + { params: { id: customShow!.id } }, + ); + } }, onSuccess: async () => { await queryClient.invalidateQueries({ @@ -106,10 +99,17 @@ export default function EditCustomShowPage({ isNew }: Props) { return customShowPrograms.map((p, idx) => { let id: string; let title: string; + switch (p.type) { case 'custom': id = p.id; - title = 'Custom'; + + // Display the program title when available + if (p.program && p.program.title) { + title = p.program.title; + } else { + title = 'Custom'; + } break; case 'content': if (p.episodeTitle) { @@ -166,6 +166,22 @@ export default function EditCustomShowPage({ isNew }: Props) { )} /> + + + + + Programming @@ -195,32 +211,6 @@ export default function EditCustomShowPage({ isNew }: Props) { - setAddProgrammingOpen(expanded)} - > - }> - Add Programming - - - - - - {}} - variant="contained" - /> - - - ); } diff --git a/web/src/pages/library/EditFillerPage.tsx b/web/src/pages/library/EditFillerPage.tsx index e29d0721f..172ce858c 100644 --- a/web/src/pages/library/EditFillerPage.tsx +++ b/web/src/pages/library/EditFillerPage.tsx @@ -1,54 +1,45 @@ +import { usePreloadedFiller } from '@/hooks/usePreloadedFiller.ts'; +import { Tv } from '@mui/icons-material'; import DeleteIcon from '@mui/icons-material/Delete'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { Button, - Divider, IconButton, List, ListItem, ListItemText, Stack, TextField, + Tooltip, } from '@mui/material'; -import Accordion from '@mui/material/Accordion'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import AccordionSummary from '@mui/material/AccordionSummary'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import Breadcrumbs from '../../components/Breadcrumbs.tsx'; import PaddedPaper from '../../components/base/PaddedPaper.tsx'; -import AddSelectedMediaButton from '../../components/channel_config/AddSelectedMediaButton.tsx'; -import ProgrammingSelector from '../../components/channel_config/ProgrammingSelector.tsx'; -import { useCurrentFillerList } from '../../hooks/useFillerLists.ts'; import { useTunarrApi } from '../../hooks/useTunarrApi.ts'; -import { - addMediaToCurrentFillerList, - removeFillerListProgram, -} from '../../store/channelEditor/actions.ts'; -import useStore from '../../store/index.ts'; +import { removeFillerListProgram } from '../../store/channelEditor/actions.ts'; import { UIFillerListProgram } from '../../types/index.ts'; -type Props = { isNew: boolean }; +export type Props = { isNew: boolean }; -type FillerListMutationArgs = { +export type FillerListMutationArgs = { id?: string; name: string; programs: UIFillerListProgram[]; }; -type FillerListFormType = Omit; +export type FillerListFormType = Omit; export default function EditFillerPage({ isNew }: Props) { const apiClient = useTunarrApi(); - const fillerList = useCurrentFillerList()!; - const fillerListPrograms = useStore((s) => s.fillerListEditor.programList); + const { currentEntity: fillerList, programList: fillerListPrograms } = + usePreloadedFiller(); + const queryClient = useQueryClient(); const navigate = useNavigate(); - const [addProgrammingOpen, setAddProgrammingOpen] = useState(false); const { control, @@ -60,25 +51,25 @@ export default function EditFillerPage({ isNew }: Props) { } = useForm({ mode: 'onChange', defaultValues: { - name: '', - programs: [], + name: fillerList?.name ?? '', }, }); useEffect(() => { reset({ - name: fillerList.name, + name: fillerList?.name, }); - }, [fillerList.name, reset]); + }, [fillerList?.name, reset]); const saveShowMutation = useMutation({ - mutationFn: async ({ id, name, programs }: FillerListMutationArgs) => { + mutationKey: ['fillers', isNew ? 'new' : fillerList?.id], + mutationFn: async ({ name, programs }: FillerListMutationArgs) => { if (isNew) { return apiClient.createFillerList({ name, programs }); } else { return apiClient.updateFillerList( { name, programs }, - { params: { id: id! } }, + { params: { id: fillerList!.id } }, ); } }, @@ -98,7 +89,7 @@ export default function EditFillerPage({ isNew }: Props) { const saveFiller: SubmitHandler = (data) => { return saveShowMutation.mutateAsync({ - id: fillerList.id, + id: fillerList?.id, name: data.name, programs: data.programs, }); @@ -113,50 +104,55 @@ export default function EditFillerPage({ isNew }: Props) { }, [fillerListPrograms, setValue]); const renderPrograms = () => { - return fillerListPrograms.map((p, idx) => { - let id: string; - let title: string; - switch (p.type) { - case 'custom': - id = p.id; - title = 'Custom'; - break; - case 'content': - if (p.episodeTitle) { - title = `${p.title} - ${p.episodeTitle}`; - } else { - title = p.title; - } - id = p.persisted - ? p.id! - : `${p.externalSourceType}|${p.externalSourceName}|${ - p.originalProgram!.key - }`; - break; - } - - const key = `${p.type}|${id}`; - - return ( - deleteProgramAtIndex(idx)} - edge="end" - aria-label="delete" - > - - - } - > - - - ); - }); + return fillerListPrograms.length > 0 ? ( + fillerListPrograms.map((p, idx) => { + let id: string; + let title: string; + switch (p.type) { + case 'custom': + id = p.id; + title = 'Custom'; + break; + case 'content': + if (p.episodeTitle) { + title = `${p.title} - ${p.episodeTitle}`; + } else { + title = p.title; + } + + id = p.persisted + ? p.id! + : `${p.externalSourceType}|${p.externalSourceName}|${ + p.originalProgram!.key + }`; + break; + } + + const key = `${p.type}|${id}`; + + return ( + deleteProgramAtIndex(idx)} + edge="end" + aria-label="delete" + > + + + } + > + + + ); + }) + ) : ( + No media added yet. + ); }; return ( @@ -177,6 +173,22 @@ export default function EditFillerPage({ isNew }: Props) { )} /> + + + + + Programming @@ -205,32 +217,6 @@ export default function EditFillerPage({ isNew }: Props) { - setAddProgrammingOpen(expanded)} - > - }> - Add Programming - - - - - - {}} - variant="contained" - /> - - - ); diff --git a/web/src/pages/settings/FfmpegSettingsPage.tsx b/web/src/pages/settings/FfmpegSettingsPage.tsx index 7006291d0..db176df15 100644 --- a/web/src/pages/settings/FfmpegSettingsPage.tsx +++ b/web/src/pages/settings/FfmpegSettingsPage.tsx @@ -654,33 +654,6 @@ export default function FfmpegSettingsPage() { }, }} /> - - - - } - label="Normalize Audio Codec" - /> - - - - - } - label="Normalize Audio" - /> - - This will force the preferred number of audio channels and sample - rate, in addition it will align the lengths of the audio and video - channels. This will prevent audio-related episode transition issues - in many clients. Audio will always be transcoded. - - ); }; diff --git a/web/src/preloaders/customShowLoaders.ts b/web/src/preloaders/customShowLoaders.ts index 9c902390d..7730a67d0 100644 --- a/web/src/preloaders/customShowLoaders.ts +++ b/web/src/preloaders/customShowLoaders.ts @@ -1,36 +1,34 @@ import { QueryClient } from '@tanstack/react-query'; -import { CustomShow, CustomShowProgramming } from '@tunarr/types'; +import { CustomProgram, CustomShow } from '@tunarr/types'; import { LoaderFunctionArgs } from 'react-router-dom'; +import { getApiClient } from '../components/TunarrApiContext.tsx'; +import { UnsavedId } from '../helpers/constants.ts'; import { createPreloader } from '../helpers/preloaderUtil.ts'; import { customShowProgramsQuery, customShowQuery, customShowsQuery, } from '../hooks/useCustomShows.ts'; -import { setCurrentCustomShow } from '../store/channelEditor/actions.ts'; import { Preloader } from '../types/index.ts'; -import { getApiClient } from '../components/TunarrApiContext.tsx'; export type CustomShowPreload = { show: CustomShow; - programs: CustomShowProgramming; + programs: CustomProgram[]; }; export const customShowLoader = (isNew: boolean): Preloader => { if (!isNew) { - return createPreloader( - (apiClient, { params }) => customShowQuery(apiClient, params.id!), - (show) => setCurrentCustomShow(show, []), + return createPreloader((apiClient, { params }) => + customShowQuery(apiClient, params.id!), ); } else { return () => () => { const customShow = { - id: 'unsaved', - name: 'New', + id: UnsavedId, + name: 'New Custom Show', contentCount: 0, totalDuration: 0, }; - setCurrentCustomShow(customShow, []); return Promise.resolve(customShow); }; } @@ -60,7 +58,6 @@ export const existingCustomShowLoader: Preloader = ( return await Promise.all([showLoaderPromise, programsPromise]).then( ([show, programs]) => { - setCurrentCustomShow(show, programs); return { show, programs, diff --git a/web/src/preloaders/fillerListLoader.ts b/web/src/preloaders/fillerListLoader.ts index 51bfb0bf5..26f25df0e 100644 --- a/web/src/preloaders/fillerListLoader.ts +++ b/web/src/preloaders/fillerListLoader.ts @@ -1,3 +1,4 @@ +import { UnsavedId } from '@/helpers/constants.ts'; import { QueryClient } from '@tanstack/react-query'; import { CustomShowProgramming, @@ -12,27 +13,29 @@ import { fillerListQuery, fillerListsQuery, } from '../hooks/useFillerLists.ts'; -import { setCurrentFillerList } from '../store/channelEditor/actions.ts'; import { Preloader } from '../types/index.ts'; +export type FillerPreload = { + filler: FillerList; + programs: FillerListProgramming; +}; + export const fillerListsLoader = createPreloader((apiClient) => fillerListsQuery(apiClient), ); const fillerListLoader = (isNew: boolean) => { if (!isNew) { - return createPreloader( - (apiClient, { params }) => fillerListQuery(apiClient, params.id!), - (filler) => setCurrentFillerList(filler, []), + return createPreloader((apiClient, { params }) => + fillerListQuery(apiClient, params.id!), ); } else { return () => () => { const filler = { - id: 'unsaved', + id: UnsavedId, name: 'New Filler List', contentCount: 0, }; - setCurrentFillerList(filler, []); return Promise.resolve(filler); }; } @@ -69,7 +72,6 @@ export const existingFillerListLoader: Preloader<{ return await Promise.all([showLoaderPromise, programsPromise]).then( ([filler, programs]) => { - setCurrentFillerList(filler, programs); return { filler, programs, diff --git a/web/src/router.tsx b/web/src/router.tsx index c648caebf..50990c703 100644 --- a/web/src/router.tsx +++ b/web/src/router.tsx @@ -164,6 +164,10 @@ export const router = createBrowserRouter( element: , loader: customShowsLoader(queryClient), }, + { + path: '/library/custom-shows/programming/add', + element: , + }, { path: '/library/custom-shows/new', element: , @@ -179,6 +183,10 @@ export const router = createBrowserRouter( element: , loader: fillerListsLoader(queryClient), }, + { + path: '/library/fillers/programming/add', + element: , + }, { path: '/library/fillers/new', element: , diff --git a/web/src/store/channelEditor/actions.ts b/web/src/store/channelEditor/actions.ts index 9992d9816..4876f857e 100644 --- a/web/src/store/channelEditor/actions.ts +++ b/web/src/store/channelEditor/actions.ts @@ -421,6 +421,7 @@ export const addMediaToCurrentFillerList = (programs: AddedMedia[]) => 'custom-show': ({ program }) => program, }), ); + fillerListEditor.programList = fillerListEditor.programList.concat( zipWithIndex(convertedPrograms, fillerListEditor.programList.length), ); diff --git a/web/src/store/selectors.ts b/web/src/store/selectors.ts index f4f4a5b42..373306d65 100644 --- a/web/src/store/selectors.ts +++ b/web/src/store/selectors.ts @@ -70,3 +70,25 @@ export const useChannelEditor = () => { }; }); }; + +export const useCustomShowEditor = () => { + return useStore((s) => { + const editor = s.customShowEditor; + return { + ...editor, + programList: editor.programList, + originalProgramList: editor.originalProgramList, + }; + }); +}; + +export const useFillerListEditor = () => { + return useStore((s) => { + const editor = s.fillerListEditor; + return { + ...editor, + programList: editor.programList, + originalProgramList: editor.originalProgramList, + }; + }); +}; diff --git a/web/tsconfig.json b/web/tsconfig.json index 7171f80f6..7b9826bd6 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -3,11 +3,7 @@ "rootDir": ".", "target": "ES2020", "useDefineForClassFields": true, - "lib": [ - "ES2020", - "DOM", - "DOM.Iterable" - ], + "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "sourceMap": true, @@ -24,27 +20,15 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "paths": { - "@/*": [ - "./src/*" - ] + "@/*": ["./src/*"] } }, - "files": [ - "vite.config.ts", - "vitest.config.ts", - ], - "include": [ - "./src/**/*.ts", - "./src/**/*.tsx", - ], - "exclude": [ - "./build/**/*", - "./dist/**/*", - "./src/**/*.ignore.ts" - ], + "files": ["vite.config.ts", "vitest.config.ts"], + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["./build/**/*", "./dist/**/*", "./src/**/*.ignore.ts"], "references": [ { "path": "./tsconfig.node.json" } ] -} \ No newline at end of file +}