Fix/save active device only in local storage (#643)

* Save "activeDevice" in the local storage

* Set default back to for "DeviceSetting"
This commit is contained in:
schroda
2024-03-07 01:12:35 +01:00
committed by GitHub
parent c0c30163c3
commit 535cb1b359
7 changed files with 92 additions and 100 deletions

View File

@@ -6,9 +6,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
import { Box, Container } from '@mui/material'; import { Container } from '@mui/material';
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
import React, { useEffect, useLayoutEffect, useMemo } from 'react'; import React, { useLayoutEffect } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev'; import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev';
import { AppContext } from '@/components/context/AppContext'; import { AppContext } from '@/components/context/AppContext';
@@ -38,8 +38,6 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
import { BrowseSettings } from '@/screens/settings/BrowseSettings.tsx'; import { BrowseSettings } from '@/screens/settings/BrowseSettings.tsx';
import { WebUISettings } from '@/screens/settings/WebUISettings.tsx'; import { WebUISettings } from '@/screens/settings/WebUISettings.tsx';
import { Migrate } from '@/screens/Migrate.tsx'; import { Migrate } from '@/screens/Migrate.tsx';
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
import { getActiveDevice, setActiveDevice } from '@/util/device.ts';
import { DeviceSetting } from '@/components/settings/DeviceSetting.tsx'; import { DeviceSetting } from '@/components/settings/DeviceSetting.tsx';
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
@@ -72,88 +70,68 @@ const BackgroundSubscriptions = () => {
return null; return null;
}; };
const ActiveDeviceListener = ({ children }: { children?: React.ReactNode }) => {
const {
settings: { devices, activeDevice },
} = useMetadataServerSettings();
useEffect(() => {
if (activeDevice === getActiveDevice()) {
return;
}
setActiveDevice(activeDevice);
}, [devices, activeDevice]);
const memorizedChildren = useMemo(() => children, [activeDevice]);
return <Box key={activeDevice}>{memorizedChildren}</Box>;
};
export const App: React.FC = () => ( export const App: React.FC = () => (
<AppContext> <AppContext>
<ActiveDeviceListener> <ScrollToTop />
<ScrollToTop /> <ServerUpdateChecker />
<ServerUpdateChecker /> <BackgroundSubscriptions />
<BackgroundSubscriptions /> <CssBaseline />
<CssBaseline /> <DefaultNavBar />
<DefaultNavBar /> <Container
<Container id="appMainContainer"
id="appMainContainer" maxWidth={false}
maxWidth={false} disableGutters
disableGutters sx={{
sx={{ mt: 8,
mt: 8, ml: { sm: 8 },
ml: { sm: 8 }, mb: { xs: 8, sm: 0 },
mb: { xs: 8, sm: 0 }, width: 'auto',
width: 'auto', overflow: 'auto',
overflow: 'auto', }}
}} >
>
<Routes>
{/* General Routes */}
<Route path="/" element={<Navigate to="/library" replace />} />
<Route path="settings">
<Route index element={<Settings />} />
<Route path="about" element={<About />} />
<Route path="categories" element={<Categories />} />
<Route path="defaultReaderSettings" element={<DefaultReaderSettings />} />
<Route path="librarySettings" element={<LibrarySettings />} />
<Route path="downloadSettings" element={<DownloadSettings />} />
<Route path="backup" element={<Backup />} />
<Route path="server" element={<ServerSettings />} />
<Route path="webUI" element={<WebUISettings />} />
<Route path="browseSettings" element={<BrowseSettings />} />
<Route path="device" element={<DeviceSetting />} />
</Route>
{/* Manga Routes */}
<Route path="sources">
<Route index element={<Sources />} />
<Route path=":sourceId" element={<SourceMangas />} />
<Route path=":sourceId/configure/" element={<SourceConfigure />} />
<Route path="all/search/" element={<SearchAll />} />
</Route>
<Route path="downloads" element={<DownloadQueue />} />
<Route path="manga/:id">
<Route path="chapter/:chapterNum" element={null} />
<Route index element={<Manga />} />
</Route>
<Route path="library" element={<Library />} />
<Route path="updates" element={<Updates />} />
<Route path="extensions" element={<Extensions />} />
<Route path="browse" element={<Browse />} />
<Route path="migrate/source/:sourceId">
<Route index element={<Migrate />} />
<Route path="manga/:mangaId/search" element={<SearchAll />} />
</Route>
</Routes>
</Container>
<Routes> <Routes>
<Route path="manga/:mangaId/chapter/:chapterIndex" element={<Reader />} /> {/* General Routes */}
<Route path="*" element={null} /> <Route path="/" element={<Navigate to="/library" replace />} />
<Route path="settings">
<Route index element={<Settings />} />
<Route path="about" element={<About />} />
<Route path="categories" element={<Categories />} />
<Route path="defaultReaderSettings" element={<DefaultReaderSettings />} />
<Route path="librarySettings" element={<LibrarySettings />} />
<Route path="downloadSettings" element={<DownloadSettings />} />
<Route path="backup" element={<Backup />} />
<Route path="server" element={<ServerSettings />} />
<Route path="webUI" element={<WebUISettings />} />
<Route path="browseSettings" element={<BrowseSettings />} />
<Route path="device" element={<DeviceSetting />} />
</Route>
{/* Manga Routes */}
<Route path="sources">
<Route index element={<Sources />} />
<Route path=":sourceId" element={<SourceMangas />} />
<Route path=":sourceId/configure/" element={<SourceConfigure />} />
<Route path="all/search/" element={<SearchAll />} />
</Route>
<Route path="downloads" element={<DownloadQueue />} />
<Route path="manga/:id">
<Route path="chapter/:chapterNum" element={null} />
<Route index element={<Manga />} />
</Route>
<Route path="library" element={<Library />} />
<Route path="updates" element={<Updates />} />
<Route path="extensions" element={<Extensions />} />
<Route path="browse" element={<Browse />} />
<Route path="migrate/source/:sourceId">
<Route index element={<Migrate />} />
<Route path="manga/:mangaId/search" element={<SearchAll />} />
</Route>
</Routes> </Routes>
</ActiveDeviceListener> </Container>
<Routes>
<Route path="manga/:mangaId/chapter/:chapterIndex" element={<Reader />} />
<Route path="*" element={null} />
</Routes>
</AppContext> </AppContext>
); );

View File

@@ -16,6 +16,7 @@ import { useLocalStorage } from '@/util/useLocalStorage';
import { DarkTheme } from '@/components/context/DarkTheme'; import { DarkTheme } from '@/components/context/DarkTheme';
import { NavBarContextProvider } from '@/components/navbar/NavBarContextProvider'; import { NavBarContextProvider } from '@/components/navbar/NavBarContextProvider';
import { LibraryOptionsContextProvider } from '@/components/library/LibraryOptionsProvider'; import { LibraryOptionsContextProvider } from '@/components/library/LibraryOptionsProvider';
import { ActiveDevice, DEFAULT_DEVICE, setActiveDevice } from '@/util/device.ts';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
@@ -23,6 +24,7 @@ interface Props {
export const AppContext: React.FC<Props> = ({ children }) => { export const AppContext: React.FC<Props> = ({ children }) => {
const [darkTheme, setDarkTheme] = useLocalStorage<boolean>('darkTheme', true); const [darkTheme, setDarkTheme] = useLocalStorage<boolean>('darkTheme', true);
const [activeDevice, setActiveDeviceContext] = useLocalStorage('activeDevice', DEFAULT_DEVICE);
const darkThemeContext = useMemo( const darkThemeContext = useMemo(
() => ({ () => ({
@@ -32,8 +34,15 @@ export const AppContext: React.FC<Props> = ({ children }) => {
[darkTheme], [darkTheme],
); );
const activeDeviceContext = useMemo(
() => ({ activeDevice, setActiveDevice: setActiveDeviceContext }),
[activeDevice],
);
const theme = useMemo(() => createTheme(darkTheme), [darkTheme]); const theme = useMemo(() => createTheme(darkTheme), [darkTheme]);
setActiveDevice(activeDevice);
return ( return (
<Router> <Router>
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
@@ -41,7 +50,11 @@ export const AppContext: React.FC<Props> = ({ children }) => {
<DarkTheme.Provider value={darkThemeContext}> <DarkTheme.Provider value={darkThemeContext}>
<QueryParamProvider adapter={ReactRouter6Adapter}> <QueryParamProvider adapter={ReactRouter6Adapter}>
<LibraryOptionsContextProvider> <LibraryOptionsContextProvider>
<NavBarContextProvider>{children}</NavBarContextProvider> <NavBarContextProvider>
<ActiveDevice.Provider value={activeDeviceContext}>
{children}
</ActiveDevice.Provider>
</NavBarContextProvider>
</LibraryOptionsContextProvider> </LibraryOptionsContextProvider>
</QueryParamProvider> </QueryParamProvider>
</DarkTheme.Provider> </DarkTheme.Provider>

View File

@@ -14,13 +14,15 @@ import { MetadataServerSettingKeys, MetadataServerSettings } from '@/typings.ts'
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts'; import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts';
import { makeToast } from '@/components/util/Toast.tsx'; import { makeToast } from '@/components/util/Toast.tsx';
import { MutableListSetting } from '@/components/settings/MutableListSetting.tsx'; import { MutableListSetting } from '@/components/settings/MutableListSetting.tsx';
import { NavBarContext } from '@/components/context/NavbarContext.tsx'; import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
import { DEFAULT_DEVICE } from '@/util/device.ts'; import { ActiveDevice, DEFAULT_DEVICE } from '@/util/device.ts';
export const DeviceSetting = () => { export const DeviceSetting = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavBarContext); const { setTitle, setAction } = useContext(NavBarContext);
useSetDefaultBackTo('settings');
useEffect(() => { useEffect(() => {
setTitle(t('settings.device.title.settings')); setTitle(t('settings.device.title.settings'));
setAction(null); setAction(null);
@@ -33,10 +35,12 @@ export const DeviceSetting = () => {
const { const {
metadata, metadata,
settings: { devices, activeDevice }, settings: { devices },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const updateMetadataSetting = async <Setting extends MetadataServerSettingKeys>( const { activeDevice, setActiveDevice } = useContext(ActiveDevice);
const updateMetadataSetting = <Setting extends MetadataServerSettingKeys>(
setting: Setting, setting: Setting,
value: MetadataServerSettings[Setting], value: MetadataServerSettings[Setting],
) => { ) => {
@@ -46,14 +50,7 @@ export const DeviceSetting = () => {
const wasActiveDeviceDeleted = setting === 'devices' && !(value as string[]).includes(activeDevice); const wasActiveDeviceDeleted = setting === 'devices' && !(value as string[]).includes(activeDevice);
if (wasActiveDeviceDeleted) { if (wasActiveDeviceDeleted) {
try { setActiveDevice(DEFAULT_DEVICE);
await requestUpdateServerMetadata(convertToGqlMeta(metadata) ?? [], [
['activeDevice', convertSettingsToMetadata({ activeDevice: DEFAULT_DEVICE }).activeDevice],
]);
} catch (e) {
makeToast(t('global.error.label.failed_to_save_changes'), 'error');
return;
}
} }
requestUpdateServerMetadata(convertToGqlMeta(metadata) ?? [], [ requestUpdateServerMetadata(convertToGqlMeta(metadata) ?? [], [
@@ -82,7 +79,7 @@ export const DeviceSetting = () => {
<Select <Select
MenuProps={{ PaperProps: { style: { maxHeight: 150 } } }} MenuProps={{ PaperProps: { style: { maxHeight: 150 } } }}
value={activeDevice} value={activeDevice}
onChange={({ target: { value: device } }) => updateMetadataSetting('activeDevice', device)} onChange={({ target: { value: device } }) => setActiveDevice(device)}
> >
{devices.map((device) => ( {devices.map((device) => (
<MenuItem key={device} value={device}> <MenuItem key={device} value={device}>

View File

@@ -232,7 +232,6 @@ export type MetadataServerSettings = {
// client // client
devices: string[]; devices: string[];
activeDevice: string;
}; };
export interface ISearchSettings { export interface ISearchSettings {

View File

@@ -6,6 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
import React from 'react';
export const DEFAULT_DEVICE = 'default'; export const DEFAULT_DEVICE = 'default';
let activeDevice = DEFAULT_DEVICE; let activeDevice = DEFAULT_DEVICE;
@@ -13,3 +15,8 @@ export const getActiveDevice = (): string => activeDevice;
export const setActiveDevice = (device: string) => { export const setActiveDevice = (device: string) => {
activeDevice = device; activeDevice = device;
}; };
export const ActiveDevice = React.createContext<{ activeDevice: string; setActiveDevice: (device: string) => void }>({
activeDevice: DEFAULT_DEVICE,
setActiveDevice: () => {},
});

View File

@@ -37,7 +37,6 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
// client // client
'devices', 'devices',
'activeDevice',
]; ];
/** /**

View File

@@ -26,7 +26,6 @@ export const getDefaultSettings = (): MetadataServerSettings => ({
// client // client
devices: [DEFAULT_DEVICE], devices: [DEFAULT_DEVICE],
activeDevice: DEFAULT_DEVICE,
}); });
export const convertSettingsToMetadata = ( export const convertSettingsToMetadata = (