diff --git a/web/src/external/api.ts b/web/src/external/api.ts index 71c096e3..81f847d5 100644 --- a/web/src/external/api.ts +++ b/web/src/external/api.ts @@ -32,7 +32,10 @@ import { getPlexStreamSettings, getXmlTvSettings, updateFfmpegSettings, + updateHdhrSettings, updatePlexServerEndpoint, + updatePlexStreamSettings, + updateXmlTvSettings, } from './settingsApi.ts'; export const api = makeApi([ @@ -320,8 +323,11 @@ export const api = makeApi([ deletePlexServerEndpoint, getPlexBackendStatus, getXmlTvSettings, + updateXmlTvSettings, getHdhrSettings, + updateHdhrSettings, getPlexStreamSettings, + updatePlexStreamSettings, getFffmpegSettings, updateFfmpegSettings, { diff --git a/web/src/external/settingsApi.ts b/web/src/external/settingsApi.ts index f19268d7..fcdb570c 100644 --- a/web/src/external/settingsApi.ts +++ b/web/src/external/settingsApi.ts @@ -78,6 +78,14 @@ export const getXmlTvSettings = makeEndpoint({ alias: 'getXmlTvSettings', }); +export const updateXmlTvSettings = makeEndpoint({ + method: 'put', + path: '/api/xmltv-settings', + response: XmlTvSettingsSchema, + parameters: parametersBuilder().addBody(XmlTvSettingsSchema).build(), + alias: 'updateXmlTvSettings', +}); + export const getFffmpegSettings = makeEndpoint({ method: 'get', path: '/api/ffmpeg-settings', @@ -100,9 +108,25 @@ export const getHdhrSettings = makeEndpoint({ alias: 'getHdhrSettings', }); +export const updateHdhrSettings = makeEndpoint({ + method: 'put', + path: '/api/hdhr-settings', + response: HdhrSettingsSchema, + parameters: parametersBuilder().addBody(HdhrSettingsSchema).build(), + alias: 'updateHdhrSettings', +}); + export const getPlexStreamSettings = makeEndpoint({ method: 'get', path: '/api/plex-settings', response: PlexStreamSettingsSchema, alias: 'getPlexStreamSettings', }); + +export const updatePlexStreamSettings = makeEndpoint({ + method: 'put', + path: '/api/plex-settings', + response: PlexStreamSettingsSchema, + parameters: parametersBuilder().addBody(PlexStreamSettingsSchema).build(), + alias: 'updatePlexStreamSettings', +}); diff --git a/web/src/pages/settings/FfmpegSettingsPage.tsx b/web/src/pages/settings/FfmpegSettingsPage.tsx index 718fd5f4..6d963292 100644 --- a/web/src/pages/settings/FfmpegSettingsPage.tsx +++ b/web/src/pages/settings/FfmpegSettingsPage.tsx @@ -22,6 +22,7 @@ import { } from '@mui/material'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { FfmpegSettings, defaultFfmpegSettings } from '@tunarr/types'; +import _ from 'lodash-es'; import React, { useEffect } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { @@ -134,7 +135,7 @@ export default function FfmpegSettingsPage() { const { reset, control, - formState: { isDirty, isValid }, + formState: { isDirty, isValid, isSubmitting, defaultValues }, watch, handleSubmit, } = useForm>({ @@ -145,18 +146,23 @@ export default function FfmpegSettingsPage() { const enableTranscoding = watch('enableTranscoding'); useEffect(() => { - if (data && !isDirty) { + if (data) { reset(data); } - }, [data, isDirty, reset]); + }, [data, reset]); const [snackStatus, setSnackStatus] = React.useState(false); + const [restoreTunarrDefaults, setRestoreTunarrDefaults] = + React.useState(false); + const queryClient = useQueryClient(); const updateFfmpegSettingsMutation = useMutation({ mutationFn: apiClient.updateFfmpegSettings, - onSuccess: () => { + onSuccess: (data) => { + setRestoreTunarrDefaults(false); setSnackStatus(true); + reset(data, { keepValues: true }); return queryClient.invalidateQueries({ queryKey: ['settings', 'ffmpeg-settings'], }); @@ -777,14 +783,47 @@ export default function FfmpegSettingsPage() { )} - - - - + + + {!_.isEqual(defaultValues, defaultFfmpegSettings) && ( + + )} + + + {(isDirty || (isDirty && !isSubmitting) || restoreTunarrDefaults) && ( + + )} + + ); diff --git a/web/src/pages/settings/GeneralSettingsPage.tsx b/web/src/pages/settings/GeneralSettingsPage.tsx index 57084858..7551a14f 100644 --- a/web/src/pages/settings/GeneralSettingsPage.tsx +++ b/web/src/pages/settings/GeneralSettingsPage.tsx @@ -1,14 +1,14 @@ -import { Button, Stack } from '@mui/material'; import DarkModeButton from '../../components/settings/DarkModeButton.tsx'; export default function GeneralSettingsPage() { return ( <> - + {/* This is currently not needed for this page as Dark Mode saves automatically */} + {/* - + */} ); } diff --git a/web/src/pages/settings/HdhrSettingsPage.tsx b/web/src/pages/settings/HdhrSettingsPage.tsx index 51ee8300..9d578571 100644 --- a/web/src/pages/settings/HdhrSettingsPage.tsx +++ b/web/src/pages/settings/HdhrSettingsPage.tsx @@ -10,21 +10,26 @@ import { } from '@mui/material'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { HdhrSettings, defaultHdhrSettings } from '@tunarr/types'; +import _ from 'lodash-es'; import React, { useEffect } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { CheckboxFormController, NumericFormControllerText, } from '../../components/util/TypedController.tsx'; +import { apiClient } from '../../external/api.ts'; import { useHdhrSettings } from '../../hooks/settingsHooks.ts'; export default function HdhrSettingsPage() { + const [restoreTunarrDefaults, setRestoreTunarrDefaults] = + React.useState(false); + const { data, isPending, error } = useHdhrSettings(); const { reset, control, - formState: { isDirty, isValid }, + formState: { isDirty, isValid, isSubmitting, defaultValues }, handleSubmit, } = useForm({ defaultValues: defaultHdhrSettings, @@ -32,33 +37,29 @@ export default function HdhrSettingsPage() { }); useEffect(() => { - if (data && !isDirty) { + if (data) { reset(data); } - }, [data, isDirty, reset]); + }, [data, reset]); const [snackStatus, setSnackStatus] = React.useState(false); const queryClient = useQueryClient(); const updateHdhrSettingsMutation = useMutation({ - mutationFn: (updateSettings: HdhrSettings) => { - return fetch('http://localhost:8000/api/hdhr-settings', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(updateSettings), - }); - }, - onSuccess: () => { + mutationFn: apiClient.updateHdhrSettings, + onSuccess: (data) => { setSnackStatus(true); + setRestoreTunarrDefaults(false); + reset(data, { keepValues: true }); return queryClient.invalidateQueries({ queryKey: ['settings', 'hdhr-settings'], }); }, }); - const updateHdhrSettings: SubmitHandler = (data) => { + const updateHdhrSettings: SubmitHandler = ( + data: HdhrSettings, + ) => { updateHdhrSettingsMutation.mutate({ ...data, }); @@ -109,13 +110,47 @@ export default function HdhrSettingsPage() { }} /> - - - + + + {!_.isEqual(defaultValues, defaultHdhrSettings) && ( + + )} + + + {isDirty && ( + + )} + + ); diff --git a/web/src/pages/settings/PlexSettingsPage.tsx b/web/src/pages/settings/PlexSettingsPage.tsx index c55cf43c..704875a5 100644 --- a/web/src/pages/settings/PlexSettingsPage.tsx +++ b/web/src/pages/settings/PlexSettingsPage.tsx @@ -53,7 +53,7 @@ import { PlexStreamSettings, defaultPlexStreamSettings, } from '@tunarr/types'; -import { fill, isNil, isNull, isUndefined, map } from 'lodash-es'; +import _, { fill, isNil, isNull, isUndefined, map } from 'lodash-es'; import React, { useCallback, useEffect, useState } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { RotatingLoopIcon } from '../../components/base/LoadingIcon.tsx'; @@ -413,6 +413,9 @@ function PlexServerRow({ server }: PlexServerRowProps) { } export default function PlexSettingsPage() { + const [restoreTunarrDefaults, setRestoreTunarrDefaults] = + React.useState(false); + const { data: servers, isPending: serversPending, @@ -428,7 +431,7 @@ export default function PlexSettingsPage() { const { reset, control, - formState: { isDirty, isValid }, + formState: { isDirty, isValid, isSubmitting, defaultValues }, watch, handleSubmit, } = useForm({ @@ -440,26 +443,20 @@ export default function PlexSettingsPage() { const showSubtitles = watch('enableSubtitles'); useEffect(() => { - if (streamSettings && !isDirty) { + if (streamSettings) { reset(streamSettings); } - }, [streamSettings, isDirty, reset]); + }, [streamSettings, reset]); const [snackStatus, setSnackStatus] = React.useState(false); const queryClient = useQueryClient(); const updatePlexStreamingSettingsMutation = useMutation({ - mutationFn: (updateSettings: PlexStreamSettings) => { - return fetch('http://localhost:8000/api/plex-settings', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(updateSettings), - }); - }, - onSuccess: () => { + mutationFn: apiClient.updatePlexStreamSettings, + onSuccess: (data) => { setSnackStatus(true); + setRestoreTunarrDefaults(false); + reset(data, { keepValues: true }); return queryClient.invalidateQueries({ queryKey: ['settings', 'plex-settings'], }); @@ -1167,18 +1164,52 @@ export default function PlexSettingsPage() { renderPathReplacements() )} - - - + + + {!_.isEqual(defaultValues, defaultPlexStreamSettings) && ( + + )} + + + {isDirty && ( + + )} + + diff --git a/web/src/pages/settings/XmlTvSettingsPage.tsx b/web/src/pages/settings/XmlTvSettingsPage.tsx index d2af4df9..099e3350 100644 --- a/web/src/pages/settings/XmlTvSettingsPage.tsx +++ b/web/src/pages/settings/XmlTvSettingsPage.tsx @@ -10,21 +10,25 @@ import { } from '@mui/material'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { XmlTvSettings, defaultXmlTvSettings } from '@tunarr/types'; +import _ from 'lodash-es'; import React, { useEffect } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { CheckboxFormController, NumericFormControllerText, } from '../../components/util/TypedController.tsx'; +import { apiClient } from '../../external/api.ts'; import { useXmlTvSettings } from '../../hooks/settingsHooks.ts'; export default function XmlTvSettingsPage() { + const [restoreTunarrDefaults, setRestoreTunarrDefaults] = + React.useState(false); const { data, isPending, error } = useXmlTvSettings(); const { reset, control, - formState: { isDirty, isValid }, + formState: { isDirty, isValid, isSubmitting, defaultValues }, handleSubmit, } = useForm({ defaultValues: defaultXmlTvSettings, @@ -32,26 +36,20 @@ export default function XmlTvSettingsPage() { }); useEffect(() => { - if (data && !isDirty) { + if (data) { reset(data); } - }, [data, isDirty, reset]); + }, [data, reset]); const [snackStatus, setSnackStatus] = React.useState(false); const queryClient = useQueryClient(); const updateXmlTvSettingsMutation = useMutation({ - mutationFn: (updateSettings: XmlTvSettings) => { - return fetch('http://localhost:8000/api/xmltv-settings', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(updateSettings), - }); - }, - onSuccess: () => { + mutationFn: apiClient.updateXmlTvSettings, + onSuccess: (data) => { setSnackStatus(true); + setRestoreTunarrDefaults(false); + reset(data, { keepValues: true }); return queryClient.invalidateQueries({ queryKey: ['settings', 'xmltv-settings'], }); @@ -91,6 +89,10 @@ export default function XmlTvSettingsPage() { - + - - - + + + {!_.isEqual(defaultValues, { + ...defaultXmlTvSettings, + outputPath: data.outputPath, + }) && ( + + )} + + + {isDirty && ( + + )} + + );