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