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';
type ContextType = {
// display options
options: LibraryDisplayOptions;
setOptions: React.Dispatch<React.SetStateAction<LibraryDisplayOptions>>;
options: LibraryOptions;
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>({
options: {
showDownloadBadge: false, showUnreadBadge: false, gridLayout: 0, SourcegridLayout: 0,
},
options: DefaultLibraryOptions,
setOption: () => {},
setOptions: () => {},
active: false,
activeSort: false,
});
export default LibraryOptionsContext;

View File

@@ -8,8 +8,8 @@
import React from 'react';
import MangaGrid, { IMangaGridProps } from 'components/MangaGrid';
import useLibraryOptions from 'util/useLibraryOptions';
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
import { StringParam, useQueryParam } from 'use-query-params';
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[] {
const { downloaded, unread, query } = useLibraryOptions();
const [query] = useQueryParam('query', StringParam);
const { options: { downloaded, unread } } = useLibraryOptionsContext();
return mangas
.filter((manga) => downloadedFilter(downloaded, manga)
&& unreadFilter(unread, manga)
@@ -64,7 +66,7 @@ function toSortID(a: IMangaCard, b: IMangaCard): number {
}
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) => {
const c = sortDesc === true ? b : a;
const d = sortDesc === true ? a : b;
@@ -80,8 +82,8 @@ export default function LibraryMangaGrid(props: IMangaGridProps) {
mangas, isLoading, hasNextPage, lastPageNum, setLastPageNum, message,
} = props;
const { options } = useLibraryOptionsContext();
const { active, query } = useLibraryOptions();
const [query] = useQueryParam('query', StringParam);
const { options, active } = useLibraryOptionsContext();
const filteredManga = filterManga(mangas);
const sortedManga = sortManga(filteredManga);
const DoneManga = sortedManga.map((ele) => {

View File

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

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import React from 'react';
import LibraryOptionsContext from 'components/context/LibraryOptionsContext';
import LibraryOptionsContext, { DefaultLibraryOptions } from 'components/context/LibraryOptionsContext';
import useLocalStorage from 'util/useLocalStorage';
interface IProps {
@@ -14,13 +14,34 @@ interface IProps {
}
export default function LibraryOptionsContextProvider({ children }: IProps) {
const [options, setOptions] = useLocalStorage<LibraryDisplayOptions>('libraryOptions',
{
showDownloadBadge: false, showUnreadBadge: false, gridLayout: 0, SourcegridLayout: 0,
});
const [options, setOptions] = useLocalStorage<LibraryOptions>('libraryOptions', DefaultLibraryOptions);
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 (
<LibraryOptionsContext.Provider value={{ options, setOptions }}>
<LibraryOptionsContext.Provider
value={{
options,
setOption,
setOptions,
active,
activeSort,
}}
>
{children}
</LibraryOptionsContext.Provider>
);

9
src/typings.d.ts vendored
View File

@@ -262,9 +262,16 @@ type ChapterOptionsReducerAction =
| { type: 'sortReverse' }
| { type: 'showChapterNumber' };
interface LibraryDisplayOptions {
interface LibraryOptions {
// display options
showDownloadBadge: boolean
showUnreadBadge: boolean
gridLayout: 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,
useReducer,
Reducer,
useCallback,
} from 'react';
import storage from './localStorage';
@@ -24,12 +25,16 @@ export default function useLocalStorage<T>(
storage.getItem(key, initialState),
);
const setValue = ((value: T | ((prevState: T) => T)) => {
// Allow value to be a function so we have same API as useState
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
storage.setItem(key, valueToStore);
}) as React.Dispatch<React.SetStateAction<T>>;
const setValue = useCallback<React.Dispatch<React.SetStateAction<T>>>(
((value) => {
setStoredValue((prevValue) => {
// Allow value to be a function so we have same API as useState
const valueToStore = value instanceof Function ? value(prevValue) : value;
storage.setItem(key, valueToStore);
return valueToStore;
});
}), [key],
);
return [storedValue, setValue];
}