unified library options (#168)

* Unified the way options are being handled. now everything uses the LibraryOptionsContext, removed the no-longer used useLibraryOptions hook

* removed query from library options. since it doesn't make sense for this to be cached

* fixed a bug in the useLocalStorage implementation
This commit is contained in:
amr
2022-04-07 12:53:14 +02:00
committed by GitHub
parent ee9c3d8c69
commit 13e6456190
7 changed files with 92 additions and 90 deletions

View File

@@ -8,16 +8,37 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
type ContextType = { type ContextType = {
// display options options: LibraryOptions;
options: LibraryDisplayOptions;
setOptions: React.Dispatch<React.SetStateAction<LibraryDisplayOptions>>; setOption: <Name extends keyof LibraryOptions>(
name: Name,
value: React.SetStateAction<LibraryOptions[Name]>
) => void;
setOptions: React.Dispatch<React.SetStateAction<LibraryOptions>>;
active: boolean
activeSort: boolean
};
export const DefaultLibraryOptions: LibraryOptions = {
showDownloadBadge: false,
showUnreadBadge: false,
gridLayout: 0,
SourcegridLayout: 0,
downloaded: undefined,
sortDesc: undefined,
sorts: undefined,
unread: undefined,
}; };
const LibraryOptionsContext = React.createContext<ContextType>({ const LibraryOptionsContext = React.createContext<ContextType>({
options: { options: DefaultLibraryOptions,
showDownloadBadge: false, showUnreadBadge: false, gridLayout: 0, SourcegridLayout: 0, setOption: () => {},
},
setOptions: () => {}, setOptions: () => {},
active: false,
activeSort: false,
}); });
export default LibraryOptionsContext; export default LibraryOptionsContext;

View File

@@ -8,8 +8,8 @@
import React from 'react'; import React from 'react';
import MangaGrid, { IMangaGridProps } from 'components/MangaGrid'; import MangaGrid, { IMangaGridProps } from 'components/MangaGrid';
import useLibraryOptions from 'util/useLibraryOptions';
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext'; import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
import { StringParam, useQueryParam } from 'use-query-params';
const FILTERED_OUT_MESSAGE = 'There are no Manga matching this filter'; const FILTERED_OUT_MESSAGE = 'There are no Manga matching this filter';
@@ -42,7 +42,9 @@ function queryFilter(query: NullAndUndefined<string>, { title }: IMangaCard): bo
} }
function filterManga(mangas: IMangaCard[]): IMangaCard[] { function filterManga(mangas: IMangaCard[]): IMangaCard[] {
const { downloaded, unread, query } = useLibraryOptions(); const [query] = useQueryParam('query', StringParam);
const { options: { downloaded, unread } } = useLibraryOptionsContext();
return mangas return mangas
.filter((manga) => downloadedFilter(downloaded, manga) .filter((manga) => downloadedFilter(downloaded, manga)
&& unreadFilter(unread, manga) && unreadFilter(unread, manga)
@@ -64,7 +66,7 @@ function toSortID(a: IMangaCard, b: IMangaCard): number {
} }
function sortManga(mangas: IMangaCard[]): IMangaCard[] { function sortManga(mangas: IMangaCard[]): IMangaCard[] {
const { sorts, sortDesc } = useLibraryOptions(); const { options: { sorts, sortDesc } } = useLibraryOptionsContext();
return (sorts === 'sortID' || sorts === undefined) && !sortDesc ? mangas : mangas.sort((a, b) => { return (sorts === 'sortID' || sorts === undefined) && !sortDesc ? mangas : mangas.sort((a, b) => {
const c = sortDesc === true ? b : a; const c = sortDesc === true ? b : a;
const d = sortDesc === true ? a : b; const d = sortDesc === true ? a : b;
@@ -80,8 +82,8 @@ export default function LibraryMangaGrid(props: IMangaGridProps) {
mangas, isLoading, hasNextPage, lastPageNum, setLastPageNum, message, mangas, isLoading, hasNextPage, lastPageNum, setLastPageNum, message,
} = props; } = props;
const { options } = useLibraryOptionsContext(); const [query] = useQueryParam('query', StringParam);
const { active, query } = useLibraryOptions(); const { options, active } = useLibraryOptionsContext();
const filteredManga = filterManga(mangas); const filteredManga = filterManga(mangas);
const sortedManga = sortManga(filteredManga); const sortedManga = sortManga(filteredManga);
const DoneManga = sortedManga.map((ele) => { const DoneManga = sortedManga.map((ele) => {

View File

@@ -23,7 +23,6 @@ import {
ListItemText, ListItemText,
Radio, Radio,
} from '@mui/material'; } from '@mui/material';
import useLibraryOptions from 'util/useLibraryOptions';
import ThreeStateCheckbox from 'components/util/ThreeStateCheckbox'; import ThreeStateCheckbox from 'components/util/ThreeStateCheckbox';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
@@ -31,9 +30,8 @@ import TabPanel from 'components/util/TabPanel';
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext'; import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
function filtersTab(currentTab: number) { function filtersTab(currentTab: number) {
const { const { options: { unread, downloaded }, setOption } = useLibraryOptionsContext();
downloaded, setDownloaded, unread, setUnread,
} = useLibraryOptions();
return ( return (
<TabPanel index={0} currentIndex={currentTab}> <TabPanel index={0} currentIndex={currentTab}>
<Stack direction="column"> <Stack direction="column">
@@ -42,7 +40,7 @@ function filtersTab(currentTab: number) {
<ThreeStateCheckbox <ThreeStateCheckbox
name="Unread" name="Unread"
checked={unread} checked={unread}
onChange={setUnread} onChange={(change) => setOption('unread', change)}
/> />
)} )}
label="Unread" label="Unread"
@@ -52,7 +50,7 @@ function filtersTab(currentTab: number) {
<ThreeStateCheckbox <ThreeStateCheckbox
name="Downloaded" name="Downloaded"
checked={downloaded} checked={downloaded}
onChange={setDownloaded} onChange={(change) => setOption('downloaded', change)}
/> />
)} )}
label="Downloaded" label="Downloaded"
@@ -63,16 +61,15 @@ function filtersTab(currentTab: number) {
} }
function sortsTab(currentTab: number) { function sortsTab(currentTab: number) {
const { const { options: { sorts, sortDesc }, setOption } = useLibraryOptionsContext();
sorts, setSorts, sortDesc, setSortDesc,
} = useLibraryOptions();
const handleChange = (event: const handleChange = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, index: string) => {
React.MouseEvent<HTMLDivElement, MouseEvent>, index: string) => {
if (sorts === index) { if (sorts === index) {
setSortDesc(!sortDesc); setOption('sortDesc', (sortDes) => !sortDes);
} else { setSortDesc(false); } } else {
setSorts(index); setOption('sortDesc', false);
}
setOption('sorts', index);
}; };
return ( return (
@@ -208,7 +205,7 @@ function Options() {
export default function LibraryOptions() { export default function LibraryOptions() {
const [filtersOpen, setFiltersOpen] = React.useState(false); const [filtersOpen, setFiltersOpen] = React.useState(false);
const { active } = useLibraryOptions(); const { active } = useLibraryOptionsContext();
return ( return (
<> <>
<IconButton <IconButton

View File

@@ -6,7 +6,7 @@
* 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'; import React from 'react';
import LibraryOptionsContext from 'components/context/LibraryOptionsContext'; import LibraryOptionsContext, { DefaultLibraryOptions } from 'components/context/LibraryOptionsContext';
import useLocalStorage from 'util/useLocalStorage'; import useLocalStorage from 'util/useLocalStorage';
interface IProps { interface IProps {
@@ -14,13 +14,34 @@ interface IProps {
} }
export default function LibraryOptionsContextProvider({ children }: IProps) { export default function LibraryOptionsContextProvider({ children }: IProps) {
const [options, setOptions] = useLocalStorage<LibraryDisplayOptions>('libraryOptions', const [options, setOptions] = useLocalStorage<LibraryOptions>('libraryOptions', DefaultLibraryOptions);
{
showDownloadBadge: false, showUnreadBadge: false, gridLayout: 0, SourcegridLayout: 0, function setOption<Name extends keyof LibraryOptions>(
}); option: Name,
value: React.SetStateAction<LibraryOptions[Name]>,
) {
setOptions((opts) => ({
...opts,
[option]: typeof value === 'function' ? value(opts[option]) : value,
}));
}
// TODO remove these fields when we have a better way to handle them
// eslint-disable-next-line eqeqeq
const active = !(options.unread == undefined) || !(options.downloaded == undefined);
// eslint-disable-next-line eqeqeq
const activeSort = (options.sortDesc != undefined) || (options.sorts != undefined);
return ( return (
<LibraryOptionsContext.Provider value={{ options, setOptions }}> <LibraryOptionsContext.Provider
value={{
options,
setOption,
setOptions,
active,
activeSort,
}}
>
{children} {children}
</LibraryOptionsContext.Provider> </LibraryOptionsContext.Provider>
); );

9
src/typings.d.ts vendored
View File

@@ -262,9 +262,16 @@ type ChapterOptionsReducerAction =
| { type: 'sortReverse' } | { type: 'sortReverse' }
| { type: 'showChapterNumber' }; | { type: 'showChapterNumber' };
interface LibraryDisplayOptions { interface LibraryOptions {
// display options
showDownloadBadge: boolean showDownloadBadge: boolean
showUnreadBadge: boolean showUnreadBadge: boolean
gridLayout: number gridLayout: number
SourcegridLayout:number SourcegridLayout:number
// filter options
downloaded: NullAndUndefined<boolean>
unread: NullAndUndefined<boolean>
sorts: NullAndUndefined<string>
sortDesc: NullAndUndefined<boolean>
} }

View File

@@ -1,51 +0,0 @@
/*
* 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 { BooleanParam, useQueryParam, StringParam } from 'use-query-params';
interface IUseLibraryOptions {
downloaded: NullAndUndefined<boolean>
setDownloaded: (downloaded: NullAndUndefined<boolean>) => void
unread: NullAndUndefined<boolean>
setUnread: (unread: NullAndUndefined<boolean>) => void
query: NullAndUndefined<string>
setQuery: (query: NullAndUndefined<string>) => void
active: boolean
activeSort: boolean
sorts: NullAndUndefined<string>
setSorts: (sorts: NullAndUndefined<string>) => void
sortDesc: NullAndUndefined<boolean>
setSortDesc: (sortDesc: NullAndUndefined<boolean>) => void
}
export default function useLibraryOptions(): IUseLibraryOptions {
const [downloaded, setDownloaded] = useQueryParam('downloaded', BooleanParam);
const [unread, setUnread] = useQueryParam('unread', BooleanParam);
const [query, setQuery] = useQueryParam('query', StringParam);
const [sorts, setSorts] = useQueryParam('sorts', StringParam);
const [sortDesc, setSortDesc] = useQueryParam('sortDesc', BooleanParam);
// eslint-disable-next-line eqeqeq
const active = !(unread == undefined) || !(downloaded == undefined);
// eslint-disable-next-line eqeqeq
const activeSort = (sortDesc != undefined) || (sorts != undefined);
return {
downloaded,
setDownloaded,
unread,
setUnread,
query,
setQuery,
active,
activeSort,
sorts,
setSorts,
sortDesc,
setSortDesc,
};
}

View File

@@ -11,6 +11,7 @@ import React, {
SetStateAction, SetStateAction,
useReducer, useReducer,
Reducer, Reducer,
useCallback,
} from 'react'; } from 'react';
import storage from './localStorage'; import storage from './localStorage';
@@ -24,12 +25,16 @@ export default function useLocalStorage<T>(
storage.getItem(key, initialState), storage.getItem(key, initialState),
); );
const setValue = ((value: T | ((prevState: T) => T)) => { const setValue = useCallback<React.Dispatch<React.SetStateAction<T>>>(
// Allow value to be a function so we have same API as useState ((value) => {
const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue((prevValue) => {
setStoredValue(valueToStore); // Allow value to be a function so we have same API as useState
storage.setItem(key, valueToStore); const valueToStore = value instanceof Function ? value(prevValue) : value;
}) as React.Dispatch<React.SetStateAction<T>>; storage.setItem(key, valueToStore);
return valueToStore;
});
}), [key],
);
return [storedValue, setValue]; return [storedValue, setValue];
} }