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:
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
9
src/typings.d.ts
vendored
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user