diff --git a/src/App.tsx b/src/App.tsx index c833a21b..3ee40643 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,6 +24,7 @@ import Settings from 'screens/Settings'; import About from 'screens/settings/About'; import Backup from 'screens/settings/Backup'; import Categories from 'screens/settings/Categories'; +import DefaultReaderSettings from 'screens/settings/DefaultReaderSettings'; import SourceConfigure from 'screens/SourceConfigure'; import SourceMangas from 'screens/SourceMangas'; import Sources from 'screens/Sources'; @@ -60,6 +61,9 @@ const App: React.FC = () => ( + + + diff --git a/src/components/navbar/ReaderNavBar.tsx b/src/components/navbar/ReaderNavBar.tsx index 20600754..71e59d4b 100644 --- a/src/components/navbar/ReaderNavBar.tsx +++ b/src/components/navbar/ReaderNavBar.tsx @@ -17,18 +17,15 @@ import { useHistory, Link, useLocation } from 'react-router-dom'; import Slide from '@mui/material/Slide'; import Fade from '@mui/material/Fade'; import Zoom from '@mui/material/Zoom'; -import { Divider, Switch } from '@mui/material'; -import List from '@mui/material/List'; +import { Divider } from '@mui/material'; import ListItem from '@mui/material/ListItem'; -import MenuItem from '@mui/material/MenuItem'; -import Select from '@mui/material/Select'; import ListItemText from '@mui/material/ListItemText'; import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; import Collapse from '@mui/material/Collapse'; import Button from '@mui/material/Button'; import { styled } from '@mui/system'; import useBackTo from 'util/useBackTo'; -import { getMetadataFrom } from 'util/metadata'; +import ReaderSettingsOptions from '../reader/ReaderSettingsOptions'; const Root = styled('div')(({ theme }) => ({ top: 0, @@ -106,27 +103,6 @@ const OpenDrawerButton = styled(IconButton)(({ theme }) => ({ }, })); -const defaultReaderSettings = () => ({ - staticNav: false, - showPageNumber: true, - continuesPageGap: false, - loadNextonEnding: false, - readerType: 'ContinuesVertical', -} as IReaderSettings); - -const getReaderSettingsFromMetadata = ( - meta?: IMetadata, -): IReaderSettings => ({ - ...getMetadataFrom( - { meta }, - Object.entries(defaultReaderSettings()) as MetadataKeyValuePair[], - ) as unknown as IReaderSettings, -}); - -export const getReaderSettingsFor = ( - { meta }: IMangaCard | IManga, -): IReaderSettings => getReaderSettingsFromMetadata(meta); - interface IProps { settings: IReaderSettings setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void @@ -276,75 +252,13 @@ export default function ReaderNavBar(props: IProps) { - - - - - updateSettingValue('staticNav', e.target.checked)} - /> - - - - - - updateSettingValue('showPageNumber', e.target.checked)} - /> - - - - - - updateSettingValue('loadNextonEnding', e.target.checked)} - /> - - - - - - - + diff --git a/src/components/reader/ReaderSettingsOptions.tsx b/src/components/reader/ReaderSettingsOptions.tsx new file mode 100644 index 00000000..12197b83 --- /dev/null +++ b/src/components/reader/ReaderSettingsOptions.tsx @@ -0,0 +1,97 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { + List, ListItem, ListItemText, Switch, +} from '@mui/material'; +import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import React from 'react'; + +interface IProps extends IReaderSettings { + setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void +} + +export default function ReaderSettingsOptions({ + staticNav, loadNextonEnding, readerType, showPageNumber, setSettingValue, +}: IProps) { + return ( + <> + + + + + setSettingValue('staticNav', e.target.checked)} + /> + + + + + + setSettingValue('showPageNumber', e.target.checked)} + /> + + + + + + setSettingValue('loadNextonEnding', e.target.checked)} + /> + + + + + + + + + ); +} diff --git a/src/screens/Reader.tsx b/src/screens/Reader.tsx index 33663c02..4c2cef0c 100644 --- a/src/screens/Reader.tsx +++ b/src/screens/Reader.tsx @@ -15,12 +15,17 @@ import PageNumber from 'components/reader/PageNumber'; import PagedPager from 'components/reader/pager/PagedPager'; import DoublePagedPager from 'components/reader/pager/DoublePagedPager'; import VerticalPager from 'components/reader/pager/VerticalPager'; -import ReaderNavBar, { getReaderSettingsFor } from 'components/navbar/ReaderNavBar'; +import ReaderNavBar from 'components/navbar/ReaderNavBar'; import NavbarContext from 'components/context/NavbarContext'; import client from 'util/client'; import useLocalStorage from 'util/useLocalStorage'; import { Box } from '@mui/system'; import { requestUpdateMangaMetadata } from 'util/metadata'; +import { + checkAndHandleMissingStoredReaderSettings, + getReaderSettingsFor, + useDefaultReaderSettings, +} from 'util/readerSettings'; import makeToast from '../components/util/Toast'; const getReaderComponent = (readerType: ReaderType) => { @@ -68,13 +73,25 @@ export default function Reader() { const [curPage, setCurPage] = useState(0); const { setOverride, setTitle } = useContext(NavbarContext); - const [settings, setSettings] = useState(getReaderSettingsFor(manga)); + const { + settings: defaultSettings, + loading: areDefaultSettingsLoading, + } = useDefaultReaderSettings(); + const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings)); + const [isMangaLoading, setIsMangaLoading] = useState(true); const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => { setSettings({ ...settings, [key]: value }); requestUpdateMangaMetadata(manga, [[key, value]]).catch(() => makeToast('Failed to save the reader settings to the server', 'warning')); }; + useEffect(() => { + if (!areDefaultSettingsLoading && !isMangaLoading) { + checkAndHandleMissingStoredReaderSettings(manga, 'manga', defaultSettings).catch(() => {}); + setSettings(getReaderSettingsFor(manga, defaultSettings)); + } + }, [areDefaultSettingsLoading, isMangaLoading]); + useEffect(() => { // set the custom navbar setOverride( @@ -97,13 +114,14 @@ export default function Reader() { }, [manga, chapter, settings, curPage, chapterIndex]); useEffect(() => { + setIsMangaLoading(true); setTitle('Reader'); client.get(`/api/v1/manga/${mangaId}/`) .then((response) => response.data) .then((data: IManga) => { setManga(data); setTitle(data.title); - setSettings(getReaderSettingsFor(data)); + setIsMangaLoading(false); }); }, [mangaId]); diff --git a/src/screens/Settings.tsx b/src/screens/Settings.tsx index 50dd2303..3f976e1f 100644 --- a/src/screens/Settings.tsx +++ b/src/screens/Settings.tsx @@ -6,6 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import React, { useContext, useEffect, useState } from 'react'; +import AutoStoriesIcon from '@mui/icons-material/AutoStories'; import List from '@mui/material/List'; import ListAltIcon from '@mui/icons-material/ListAlt'; import BackupIcon from '@mui/icons-material/Backup'; @@ -96,6 +97,12 @@ export default function Settings() { + + + + + + diff --git a/src/screens/settings/DefaultReaderSettings.tsx b/src/screens/settings/DefaultReaderSettings.tsx new file mode 100644 index 00000000..d8110a70 --- /dev/null +++ b/src/screens/settings/DefaultReaderSettings.tsx @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable react/destructuring-assignment */ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import React, { useContext, useEffect } from 'react'; +import NavbarContext from 'components/context/NavbarContext'; +import { Box } from '@mui/system'; +import CircularProgress from '@mui/material/CircularProgress'; +import { requestUpdateServerMetadata } from 'util/metadata'; +import makeToast from 'components/util/Toast'; +import { + checkAndHandleMissingStoredReaderSettings, + getDefaultSettings, + useDefaultReaderSettings, +} from 'util/readerSettings'; +import ReaderSettingsOptions from 'components/reader/ReaderSettingsOptions'; + +export default function DefaultReaderSettings() { + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { + setTitle('Default Reader Settings'); + setAction(<>); + }, []); + + const { metadata, settings, loading } = useDefaultReaderSettings(); + + const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => { + requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() => makeToast('Failed to save the default reader settings to the server', 'warning')); + }; + + if (loading) { + return ( + + + + ); + } + + checkAndHandleMissingStoredReaderSettings({ meta: metadata }, 'server', getDefaultSettings()) + .catch(() => {}); + + return ( + + ); +} diff --git a/src/util/readerSettings.ts b/src/util/readerSettings.ts new file mode 100644 index 00000000..37004b9d --- /dev/null +++ b/src/util/readerSettings.ts @@ -0,0 +1,89 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { getMetadataFrom, requestUpdateMangaMetadata, requestUpdateServerMetadata } from 'util/metadata'; +import { useQuery } from './client'; + +export const getDefaultSettings = (forceUndefined: boolean = false) => ({ + staticNav: forceUndefined ? undefined : false, + showPageNumber: forceUndefined ? undefined : true, + continuesPageGap: forceUndefined ? undefined : false, + loadNextonEnding: forceUndefined ? undefined : false, + readerType: forceUndefined ? undefined : 'ContinuesVertical', +} as IReaderSettings); + +const getReaderSettingsWithDefaultValueFallback = ( + meta?: IMetadata, + defaultSettings?: IReaderSettings, +): IReaderSettings => ({ + ...getMetadataFrom( + { meta }, + Object.entries(defaultSettings ?? getDefaultSettings()) as MetadataKeyValuePair[], + ) as unknown as IReaderSettings, +}); + +export const getReaderSettingsFromMetadata = ( + meta?: IMetadata, + defaultSettings?: IReaderSettings, +): IReaderSettings => ({ + ...getReaderSettingsWithDefaultValueFallback(meta, defaultSettings), +}); + +export const getReaderSettingsFor = ( + { meta }: IMetadataHolder, + defaultSettings?: IReaderSettings, +): IReaderSettings => getReaderSettingsFromMetadata(meta, defaultSettings); + +export const useDefaultReaderSettings = (): { + metadata?: IMetadata, + settings: IReaderSettings, + loading: boolean +} => { + const { data: meta, loading } = useQuery('/api/v1/meta'); + const settings = getReaderSettingsWithDefaultValueFallback(meta); + + return { metadata: meta, settings, loading }; +}; + +/** + * Saves all missing reader settings from the metadata to the server + * + * @param metadataHolder + * @param metadataHolderType + * @param defaultSettings + */ +export const checkAndHandleMissingStoredReaderSettings = async ( + metadataHolder: IManga | IMetadataHolder, + metadataHolderType: 'manga' | 'server', + defaultSettings: IReaderSettings, +): Promise => { + const meta = metadataHolder.meta ?? metadataHolder as IMetadata; + const settingsToCheck = getReaderSettingsFor({ meta }, getDefaultSettings(true)); + const newSettings = getReaderSettingsFor({ meta }, defaultSettings); + + const undefinedSettings = Object.entries(settingsToCheck) + .filter((setting) => setting[1] === undefined); + + const settingsToUpdate: MetadataKeyValuePair[] = []; + undefinedSettings.forEach((setting) => { + const key = setting[0] as keyof IReaderSettings; + + settingsToUpdate.push([key, newSettings[key]]); + }); + + if (!undefinedSettings.length) { + return; + } + + if (metadataHolderType === 'manga') { + await requestUpdateMangaMetadata(metadataHolder as IManga, settingsToUpdate); + return; + } + + await requestUpdateServerMetadata(meta, settingsToUpdate); +};