From f1ec97b0e689aef23bc459c0cc9a294f3548bd2d Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sun, 28 Apr 2024 01:00:43 +0200 Subject: [PATCH] Extract fetch logic into parent setting components --- .../settings/CategoriesInclusionSetting.tsx | 53 ++++++++-------- .../downloads/DownloadAheadSetting.tsx | 14 ++--- .../globalUpdate/GlobalUpdateSettings.tsx | 21 +++++-- .../GlobalUpdateSettingsEntries.tsx | 18 ++---- .../GlobalUpdateSettingsInterval.tsx | 20 +++--- .../webUI/WebUIUpdateIntervalSetting.tsx | 11 +++- src/lib/data/Categories.ts | 3 + src/screens/settings/DownloadSettings.tsx | 63 +++++++++++++------ src/screens/settings/LibrarySettings.tsx | 48 +++++++++++++- 9 files changed, 164 insertions(+), 87 deletions(-) diff --git a/src/components/settings/CategoriesInclusionSetting.tsx b/src/components/settings/CategoriesInclusionSetting.tsx index 5e507d8c..1bf10456 100644 --- a/src/components/settings/CategoriesInclusionSetting.tsx +++ b/src/components/settings/CategoriesInclusionSetting.tsx @@ -23,6 +23,14 @@ import { IncludeOrExclude } from '@/lib/graphql/generated/graphql.ts'; import { TCategory } from '@/typings.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { CheckboxContainer } from '@/components/settings/globalUpdate/CheckboxContainer.ts'; +import { + CategoryDownloadInclusionInfo, + CategoryIdInfo, + CategoryNameInfo, + CategoryUpdateInclusionInfo, +} from '@/lib/data/Categories.ts'; + +type CategoryType = CategoryIdInfo & CategoryNameInfo & CategoryUpdateInclusionInfo & CategoryDownloadInclusionInfo; const booleanToIncludeOrExcludeStatus = (status: boolean | null | undefined): IncludeOrExclude => { switch (status) { @@ -52,19 +60,11 @@ const includeInUpdateStatusToBoolean = (status: IncludeOrExclude): boolean | nul }; const getCategoryUpdateInfo = ( - categories: TCategory[], + categories: CategoryType[], areIncluded: boolean, unsetCategories: number, allCategories: number, - error: any, ) => { - if (error) { - return translate('global.error.label.failed_to_load_data'); - } - if (allCategories === -1) { - return translate('global.label.loading'); - } - const noSpecificallyIncludedCategories = areIncluded && !categories.length && unsetCategories; const includesAllCategories = categories.length === allCategories; if (noSpecificallyIncludedCategories || includesAllCategories) { @@ -80,18 +80,20 @@ const getCategoryUpdateInfo = ( type CategoryIncludeField = keyof Pick; -type CategoriesInclusionSettingProps = { +export type CategoriesInclusionSettingProps = { + categories: CategoryType[]; includeField: CategoryIncludeField; dialogText?: string; }; -export const CategoriesInclusionSetting = (props: CategoriesInclusionSettingProps) => { +export const CategoriesInclusionSetting = ({ + categories, + includeField, + dialogText, +}: CategoriesInclusionSettingProps) => { const { t } = useTranslation(); - const { includeField, dialogText } = props; - const { data, error: requestError } = requestManager.useGetCategories(); - const categories = data?.categories.nodes; - const [dialogCategories, setDialogCategories] = useState(categories ?? []); + const [dialogCategories, setDialogCategories] = useState(categories); const [isDialogOpen, setIsDialogOpen] = useState(false); useEffect(() => { @@ -102,28 +104,23 @@ export const CategoriesInclusionSetting = (props: CategoriesInclusionSettingProp setDialogCategories(categories); }, [categories]); - const unsetCategories: TCategory[] = - categories?.filter((category) => category[includeField] === IncludeOrExclude.Unset) ?? []; - const excludedCategories: TCategory[] = - categories?.filter((category) => category[includeField] === IncludeOrExclude.Exclude) ?? []; - const includedCategories: TCategory[] = - categories?.filter((category) => category[includeField] === IncludeOrExclude.Include) ?? []; + const unsetCategories = categories.filter((category) => category[includeField] === IncludeOrExclude.Unset); + const excludedCategories = categories.filter((category) => category[includeField] === IncludeOrExclude.Exclude); + const includedCategories = categories.filter((category) => category[includeField] === IncludeOrExclude.Include); const excludedCategoriesText = getCategoryUpdateInfo( excludedCategories, false, unsetCategories.length, - categories?.length ?? -1, - requestError, + categories.length, ); const includedCategoriesText = getCategoryUpdateInfo( includedCategories, true, unsetCategories.length, - categories?.length ?? -1, - requestError, + categories.length, ); - const updateCategory = (category: TCategory) => + const updateCategory = (category: CategoryType) => requestManager.updateCategory(category.id, { [includeField]: category[includeField] }).response; const updateCategories = async () => { @@ -150,7 +147,7 @@ export const CategoriesInclusionSetting = (props: CategoriesInclusionSettingProp }; const closeDialog = () => { - setDialogCategories(categories ?? []); + setDialogCategories(categories); setIsDialogOpen(false); }; @@ -193,7 +190,7 @@ export const CategoriesInclusionSetting = (props: CategoriesInclusionSettingProp const categoryIndex = dialogCategories.findIndex( (category_) => category_ === category, ); - const updatedDialogCategories: TCategory[] = [ + const updatedDialogCategories = [ ...dialogCategories.slice(0, categoryIndex), { ...category, diff --git a/src/components/settings/downloads/DownloadAheadSetting.tsx b/src/components/settings/downloads/DownloadAheadSetting.tsx index 00d2f07b..50ea50d2 100644 --- a/src/components/settings/downloads/DownloadAheadSetting.tsx +++ b/src/components/settings/downloads/DownloadAheadSetting.tsx @@ -13,21 +13,21 @@ import ListItemText from '@mui/material/ListItemText'; import Switch from '@mui/material/Switch'; import { NumberSetting } from '@/components/settings/NumberSetting.tsx'; import { getPersistedServerSetting, usePersistedValue } from '@/util/usePersistedValue.tsx'; -import { updateMetadataServerSettings, useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts'; -import { MetadataDownloadSettings } from '@/typings.ts'; +import { updateMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts'; +import { MetadataDownloadSettings, MetadataServerSettings } from '@/typings.ts'; import { makeToast } from '@/components/util/Toast.tsx'; const MIN_LIMIT = 2; const MAX_LIMIT = 10; const DEFAULT_LIMIT = MIN_LIMIT; -export const DownloadAheadSetting = () => { +export const DownloadAheadSetting = ({ + downloadAheadLimit, +}: { + downloadAheadLimit: MetadataServerSettings['downloadAheadLimit']; +}) => { const { t } = useTranslation(); - const { - settings: { downloadAheadLimit }, - } = useMetadataServerSettings(); - const shouldDownloadAhead = !!downloadAheadLimit; const [currentDownloadAheadLimit, persistDownloadAheadLimit] = usePersistedValue( 'lastDownloadAheadLimit', diff --git a/src/components/settings/globalUpdate/GlobalUpdateSettings.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettings.tsx index 910b259d..535b313d 100644 --- a/src/components/settings/globalUpdate/GlobalUpdateSettings.tsx +++ b/src/components/settings/globalUpdate/GlobalUpdateSettings.tsx @@ -12,7 +12,10 @@ import { useTranslation } from 'react-i18next'; import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; import Switch from '@mui/material/Switch'; -import { CategoriesInclusionSetting } from '@/components/settings/CategoriesInclusionSetting.tsx'; +import { + CategoriesInclusionSetting, + CategoriesInclusionSettingProps, +} from '@/components/settings/CategoriesInclusionSetting.tsx'; import { GlobalUpdateSettingsEntries } from '@/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx'; import { GlobalUpdateSettingsInterval } from '@/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx'; import { requestManager } from '@/lib/requests/RequestManager.ts'; @@ -21,11 +24,16 @@ import { ServerSettings } from '@/typings.ts'; type LibrarySettingsType = Pick; -export const GlobalUpdateSettings = () => { +export const GlobalUpdateSettings = ({ + serverSettings, + categories, +}: { + serverSettings: ServerSettings; + categories: CategoriesInclusionSettingProps['categories']; +}) => { const { t } = useTranslation(); - const { data } = requestManager.useGetServerSettings(); - const updateMangas = !!data?.settings.updateMangas; + const { updateMangas } = serverSettings; const [mutateSettings] = requestManager.useUpdateServerSettings(); const updateSetting = async ( @@ -47,9 +55,10 @@ export const GlobalUpdateSettings = () => { } > - - + + diff --git a/src/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx index 5e76dc26..eb529eeb 100644 --- a/src/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx +++ b/src/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx @@ -33,15 +33,7 @@ const settingToTextMap: { [setting in keyof GlobalUpdateSkipEntriesSettings]: Tr excludeCompleted: 'library.settings.global_update.entries.label.completed', }; -const getSkipMangasText = (settings: GlobalUpdateSkipEntriesSettings | undefined, isLoading: boolean, error: any) => { - if (error) { - return translate('global.error.label.failed_to_load_data'); - } - - if (!settings || isLoading) { - return translate('global.label.loading'); - } - +const getSkipMangasText = (settings: GlobalUpdateSkipEntriesSettings) => { const skipSettings: string[] = []; if (settings.excludeUnreadChapters) { @@ -70,10 +62,10 @@ const extractSkipEntriesSettings = (serverSettings: ServerSettings): GlobalUpdat excludeUnreadChapters: serverSettings.excludeUnreadChapters, }); -export const GlobalUpdateSettingsEntries = () => { +export const GlobalUpdateSettingsEntries = ({ serverSettings }: { serverSettings: ServerSettings }) => { const { t } = useTranslation(); - const { data, loading, error: requestError } = requestManager.useGetServerSettings(); - const globalUpdateSettings = data ? extractSkipEntriesSettings(data.settings) : undefined; + + const globalUpdateSettings = extractSkipEntriesSettings(serverSettings); const [mutateSettings] = requestManager.useUpdateServerSettings(); const [dialogSettings, setDialogSettings] = useState( @@ -81,7 +73,7 @@ export const GlobalUpdateSettingsEntries = () => { ); const [isDialogOpen, setIsDialogOpen] = useState(false); - const skipEntriesText = getSkipMangasText(globalUpdateSettings, loading, requestError); + const skipEntriesText = getSkipMangasText(globalUpdateSettings); const updateSettings = async () => { const didSettingsChange = diff --git a/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx index eb0af32a..b6361347 100644 --- a/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx +++ b/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx @@ -15,16 +15,20 @@ import { useCallback } from 'react'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { NumberSetting } from '@/components/settings/NumberSetting.tsx'; import { getPersistedServerSetting, usePersistedValue } from '@/util/usePersistedValue.tsx'; +import { ServerSettings } from '@/typings.ts'; const DEFAULT_INTERVAL_HOURS = 12; const MIN_INTERVAL_HOURS = 6; const MAX_INTERVAL_HOURS = 24 * 7 * 4; // 1 month -export const GlobalUpdateSettingsInterval = () => { +export const GlobalUpdateSettingsInterval = ({ + globalUpdateInterval, +}: { + globalUpdateInterval: ServerSettings['globalUpdateInterval']; +}) => { const { t } = useTranslation(); - const { data } = requestManager.useGetServerSettings(); - const autoUpdateIntervalHours = data?.settings.globalUpdateInterval; + const autoUpdateIntervalHours = globalUpdateInterval; const doAutoUpdates = !!autoUpdateIntervalHours; const [mutateSettings] = requestManager.useUpdateServerSettings(); const [currentAutoUpdateIntervalHours, persistAutoUpdateIntervalHours] = usePersistedValue( @@ -35,18 +39,18 @@ export const GlobalUpdateSettingsInterval = () => { ); const updateSetting = useCallback( - (globalUpdateInterval: number) => { + (newGlobalUpdateInterval: number) => { persistAutoUpdateIntervalHours( - globalUpdateInterval === 0 ? currentAutoUpdateIntervalHours : globalUpdateInterval, + newGlobalUpdateInterval === 0 ? currentAutoUpdateIntervalHours : newGlobalUpdateInterval, ); - mutateSettings({ variables: { input: { settings: { globalUpdateInterval } } } }); + mutateSettings({ variables: { input: { settings: { globalUpdateInterval: newGlobalUpdateInterval } } } }); }, [currentAutoUpdateIntervalHours], ); const setDoAutoUpdates = (enable: boolean) => { - const globalUpdateInterval = enable ? currentAutoUpdateIntervalHours : 0; - updateSetting(globalUpdateInterval); + const newGlobalUpdateInterval = enable ? currentAutoUpdateIntervalHours : 0; + updateSetting(newGlobalUpdateInterval); }; return ( diff --git a/src/components/settings/webUI/WebUIUpdateIntervalSetting.tsx b/src/components/settings/webUI/WebUIUpdateIntervalSetting.tsx index c2beb4e8..43230aa1 100644 --- a/src/components/settings/webUI/WebUIUpdateIntervalSetting.tsx +++ b/src/components/settings/webUI/WebUIUpdateIntervalSetting.tsx @@ -15,16 +15,21 @@ import { useCallback } from 'react'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { NumberSetting } from '@/components/settings/NumberSetting.tsx'; import { getPersistedServerSetting, usePersistedValue } from '@/util/usePersistedValue.tsx'; +import { ServerSettings } from '@/typings.ts'; const DEFAULT_VALUE = 23; const MIN_VALUE = 1; const MAX_VALUE = 23; // 1 month -export const WebUIUpdateIntervalSetting = ({ disabled = false }: { disabled?: boolean }) => { +export const WebUIUpdateIntervalSetting = ({ + disabled = false, + updateCheckInterval, +}: { + disabled?: boolean; + updateCheckInterval: ServerSettings['webUIUpdateCheckInterval']; +}) => { const { t } = useTranslation(); - const { data } = requestManager.useGetServerSettings(); - const updateCheckInterval = data?.settings.webUIUpdateCheckInterval; const shouldAutoUpdate = !!updateCheckInterval; const [mutateSettings] = requestManager.useUpdateServerSettings(); const [currentUpdateCheckInterval, persistUpdateCheckInterval] = usePersistedValue( diff --git a/src/lib/data/Categories.ts b/src/lib/data/Categories.ts index e9c338e6..9fda73e6 100644 --- a/src/lib/data/Categories.ts +++ b/src/lib/data/Categories.ts @@ -11,7 +11,10 @@ import { TCategory } from '@/typings.ts'; export const DEFAULT_CATEGORY_ID = 0; export type CategoryIdInfo = Pick; +export type CategoryNameInfo = Pick; export type CategoryDefaultInfo = Pick; +export type CategoryUpdateInclusionInfo = Pick; +export type CategoryDownloadInclusionInfo = Pick; export class Categories { static getIds(categories: CategoryIdInfo[]): number[] { diff --git a/src/screens/settings/DownloadSettings.tsx b/src/screens/settings/DownloadSettings.tsx index ab37fba0..8b659a08 100644 --- a/src/screens/settings/DownloadSettings.tsx +++ b/src/screens/settings/DownloadSettings.tsx @@ -65,12 +65,48 @@ export const DownloadSettings = () => { }; }, [t]); - const { data, loading, error, refetch } = requestManager.useGetServerSettings({ - notifyOnNetworkStatusChange: true, - }); - const downloadSettings = data ? extractDownloadSettings(data.settings) : undefined; + const categories = requestManager.useGetCategories(); + const serverSettings = requestManager.useGetServerSettings({ notifyOnNetworkStatusChange: true }); const [mutateSettings] = requestManager.useUpdateServerSettings(); - const { settings: metadataSettings } = useMetadataServerSettings(); + const { + settings: metadataSettings, + loading: areMetadataServerSettingsLoading, + request: { error: metadataServerSettingsError, refetch: refetchMetadataServerSettings }, + } = useMetadataServerSettings(); + + const loading = serverSettings.loading || areMetadataServerSettingsLoading || categories.loading; + if (loading) { + return ; + } + + const error = serverSettings.error ?? metadataServerSettingsError ?? categories.error; + if (error) { + return ( + { + if (serverSettings.error) { + serverSettings + .refetch() + .catch(defaultPromiseErrorHandler('DownloadSettings::refetchServerSettings')); + } + + if (metadataServerSettingsError) { + refetchMetadataServerSettings().catch( + defaultPromiseErrorHandler('refetchMetadataServerSettings::'), + ); + } + + if (categories.error) { + categories.refetch().catch(defaultPromiseErrorHandler('LibrarySettings::refetchCategories')); + } + }} + /> + ); + } + + const downloadSettings = extractDownloadSettings(serverSettings.data!.settings); const updateSetting = ( setting: Setting, @@ -85,20 +121,6 @@ export const DownloadSettings = () => { makeToast(t('global.error.label.failed_to_save_changes'), 'error'), ); - if (loading) { - return ; - } - - if (error) { - return ( - refetch().catch(defaultPromiseErrorHandler('DownloadSettings::refetch'))} - /> - ); - } - return ( { /> @@ -215,7 +238,7 @@ export const DownloadSettings = () => { } > - + ); diff --git a/src/screens/settings/LibrarySettings.tsx b/src/screens/settings/LibrarySettings.tsx index 6687667c..9befbc7c 100644 --- a/src/screens/settings/LibrarySettings.tsx +++ b/src/screens/settings/LibrarySettings.tsx @@ -25,6 +25,9 @@ import { } from '@/lib/metadata/metadataServerSettings.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { Mangas } from '@/lib/data/Mangas.ts'; +import { EmptyView } from '@/components/util/EmptyView'; +import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; +import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx'; const removeNonLibraryMangasFromCategories = async (): Promise => { try { @@ -61,12 +64,50 @@ export function LibrarySettings() { useSetDefaultBackTo('settings'); - const { settings } = useMetadataServerSettings(); + const categories = requestManager.useGetCategories(); + const serverSettings = requestManager.useGetServerSettings({ notifyOnNetworkStatusChange: true }); + const { + settings, + loading: areMetadataServerSettingsLoading, + request: { error: metadataServerSettingsError, refetch: refetchMetadataServerSettings }, + } = useMetadataServerSettings(); const setSettingValue = createUpdateMetadataServerSettings(() => makeToast(t('search.error.label.failed_to_save_settings'), 'warning'), ); + const loading = serverSettings.loading || areMetadataServerSettingsLoading || categories.loading; + if (loading) { + return ; + } + + const error = serverSettings.error ?? metadataServerSettingsError ?? categories.error; + if (error) { + return ( + { + if (serverSettings.error) { + serverSettings + ?.refetch() + .catch(defaultPromiseErrorHandler('LibrarySettings::refetchServerSettings')); + } + + if (metadataServerSettingsError) { + refetchMetadataServerSettings().catch( + defaultPromiseErrorHandler('LibrarySettings::refetchMetadataServerSettings'), + ); + } + + if (categories.error) { + categories.refetch().catch(defaultPromiseErrorHandler('LibrarySettings::refetchCategories')); + } + }} + /> + ); + } + return ( - +