From 25d99ee29458a953290dfcafa2210e4541a9fe36 Mon Sep 17 00:00:00 2001 From: DrMint Date: Sat, 22 Oct 2022 01:16:47 +0200 Subject: [PATCH] Reader settings are now saved in localStorage --- src/components/Panels/MainPanel.tsx | 2 +- src/contexts/AppLayoutContext.tsx | 26 +++-- src/contexts/ContainerQueriesContext.tsx | 19 ++-- src/contexts/LocalDataContext.tsx | 4 +- src/contexts/TerminalContext.tsx | 15 ++- src/contexts/UserSettingsContext.tsx | 30 ++--- src/hooks/useDarkMode.ts | 9 +- src/hooks/useLibraryItemUserStatus.ts | 5 +- src/hooks/useReaderSettings.ts | 115 +++++++++++++++++++ src/hooks/useStateWithLocalStorage.ts | 4 +- src/pages/library/[slug]/reader.tsx | 139 ++++++++++------------- 11 files changed, 239 insertions(+), 129 deletions(-) create mode 100644 src/hooks/useReaderSettings.ts diff --git a/src/components/Panels/MainPanel.tsx b/src/components/Panels/MainPanel.tsx index bb96a67..9328222 100644 --- a/src/components/Panels/MainPanel.tsx +++ b/src/components/Panels/MainPanel.tsx @@ -19,7 +19,7 @@ import { useLocalData } from "contexts/LocalDataContext"; export const MainPanel = (): JSX.Element => { const is3ColumnsLayout = useIs3ColumnsLayout(); - const { mainPanelReduced = false, toggleMainPanelReduced, setConfigPanelOpen } = useAppLayout(); + const { mainPanelReduced, toggleMainPanelReduced, setConfigPanelOpen } = useAppLayout(); const { langui } = useLocalData(); return ( diff --git a/src/contexts/AppLayoutContext.tsx b/src/contexts/AppLayoutContext.tsx index a94b214..cf26f3a 100644 --- a/src/contexts/AppLayoutContext.tsx +++ b/src/contexts/AppLayoutContext.tsx @@ -1,4 +1,12 @@ -import React, { ReactNode, useContext, useEffect, useState } from "react"; +import React, { + createContext, + Dispatch, + ReactNode, + SetStateAction, + useContext, + useEffect, + useState, +} from "react"; import { useRouter } from "next/router"; import { useLocalStorage, useSessionStorage } from "usehooks-ts"; import { isDefined } from "helpers/others"; @@ -9,27 +17,27 @@ import { useScrollIntoView } from "hooks/useScrollIntoView"; interface AppLayoutState { subPanelOpen: boolean; toggleSubPanelOpen: () => void; - setSubPanelOpen: React.Dispatch>; + setSubPanelOpen: Dispatch>; configPanelOpen: boolean; toggleConfigPanelOpen: () => void; - setConfigPanelOpen: React.Dispatch>; + setConfigPanelOpen: Dispatch>; mainPanelReduced: boolean; toggleMainPanelReduced: () => void; - setMainPanelReduced: React.Dispatch>; + setMainPanelReduced: Dispatch>; mainPanelOpen: boolean; toggleMainPanelOpen: () => void; - setMainPanelOpen: React.Dispatch>; + setMainPanelOpen: Dispatch>; menuGestures: boolean; toggleMenuGestures: () => void; - setMenuGestures: React.Dispatch>; + setMenuGestures: Dispatch>; hasDisgardedSafariWarning: boolean; - setHasDisgardedSafariWarning: React.Dispatch< - React.SetStateAction + setHasDisgardedSafariWarning: Dispatch< + SetStateAction >; } @@ -58,7 +66,7 @@ const initialState: RequiredNonNullable = { setHasDisgardedSafariWarning: () => null, }; -const AppLayoutContext = React.createContext(initialState); +const AppLayoutContext = createContext(initialState); export const useAppLayout = (): AppLayoutState => useContext(AppLayoutContext); diff --git a/src/contexts/ContainerQueriesContext.tsx b/src/contexts/ContainerQueriesContext.tsx index 8bdc61a..b1872c6 100644 --- a/src/contexts/ContainerQueriesContext.tsx +++ b/src/contexts/ContainerQueriesContext.tsx @@ -1,17 +1,22 @@ -import React, { ReactNode, useContext, useState } from "react"; +import React, { + createContext, + Dispatch, + ReactNode, + SetStateAction, + useContext, + useState, +} from "react"; import { RequiredNonNullable } from "types/types"; interface ContainerQueriesState { screenWidth: number; - setScreenWidth: React.Dispatch>; + setScreenWidth: Dispatch>; contentPanelWidth: number; - setContentPanelWidth: React.Dispatch< - React.SetStateAction - >; + setContentPanelWidth: Dispatch>; subPanelWidth: number; - setSubPanelWidth: React.Dispatch>; + setSubPanelWidth: Dispatch>; } const initialState: RequiredNonNullable = { @@ -25,7 +30,7 @@ const initialState: RequiredNonNullable = { setSubPanelWidth: () => null, }; -const ContainerQueriesContext = React.createContext(initialState); +const ContainerQueriesContext = createContext(initialState); export const useContainerQueries = (): ContainerQueriesState => useContext(ContainerQueriesContext); diff --git a/src/contexts/LocalDataContext.tsx b/src/contexts/LocalDataContext.tsx index f3e1b72..5dbee6c 100644 --- a/src/contexts/LocalDataContext.tsx +++ b/src/contexts/LocalDataContext.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useContext, useMemo } from "react"; +import React, { createContext, ReactNode, useContext, useMemo } from "react"; import { useRouter } from "next/router"; import { useFetch } from "usehooks-ts"; import { @@ -29,7 +29,7 @@ const initialState: RequiredNonNullable = { langui: {}, }; -const LocalDataContext = React.createContext(initialState); +const LocalDataContext = createContext(initialState); export const useLocalData = (): LocalDataState => useContext(LocalDataContext); diff --git a/src/contexts/TerminalContext.tsx b/src/contexts/TerminalContext.tsx index 2e7b652..08671ab 100644 --- a/src/contexts/TerminalContext.tsx +++ b/src/contexts/TerminalContext.tsx @@ -1,11 +1,18 @@ -import React, { ReactNode, useContext, useState } from "react"; +import React, { + createContext, + Dispatch, + ReactNode, + SetStateAction, + useContext, + useState, +} from "react"; import { RequiredNonNullable } from "types/types"; interface TerminalState { previousLines: string[]; previousCommands: string[]; - setPreviousLines: React.Dispatch>; - setPreviousCommands: React.Dispatch>; + setPreviousLines: Dispatch>; + setPreviousCommands: Dispatch>; } const initialState: RequiredNonNullable = { @@ -15,7 +22,7 @@ const initialState: RequiredNonNullable = { setPreviousCommands: () => null, }; -const TerminalContext = React.createContext(initialState); +const TerminalContext = createContext(initialState); export const useTerminalContext = (): TerminalState => useContext(TerminalContext); diff --git a/src/contexts/UserSettingsContext.tsx b/src/contexts/UserSettingsContext.tsx index 5fd144d..02d113d 100644 --- a/src/contexts/UserSettingsContext.tsx +++ b/src/contexts/UserSettingsContext.tsx @@ -1,5 +1,13 @@ import { useLocalStorage } from "usehooks-ts"; -import React, { ReactNode, useContext, useEffect, useLayoutEffect } from "react"; +import React, { + createContext, + Dispatch, + ReactNode, + SetStateAction, + useContext, + useEffect, + useLayoutEffect, +} from "react"; import { useRouter } from "next/router"; import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { RequiredNonNullable } from "types/types"; @@ -8,32 +16,28 @@ import { useDarkMode } from "hooks/useDarkMode"; interface UserSettingsState { fontSize: number; - setFontSize: React.Dispatch>; + setFontSize: Dispatch>; darkMode: boolean; toggleDarkMode: () => void; - setDarkMode: React.Dispatch>; + setDarkMode: Dispatch>; selectedThemeMode: boolean; toggleSelectedThemeMode: () => void; - setSelectedThemeMode: React.Dispatch< - React.SetStateAction - >; + setSelectedThemeMode: Dispatch>; dyslexic: boolean; toggleDyslexic: () => void; - setDyslexic: React.Dispatch>; + setDyslexic: Dispatch>; currency: string; - setCurrency: React.Dispatch>; + setCurrency: Dispatch>; playerName: string; - setPlayerName: React.Dispatch>; + setPlayerName: Dispatch>; preferredLanguages: string[]; - setPreferredLanguages: React.Dispatch< - React.SetStateAction - >; + setPreferredLanguages: Dispatch>; } const initialState: RequiredNonNullable = { @@ -62,7 +66,7 @@ const initialState: RequiredNonNullable = { setPreferredLanguages: () => null, }; -const UserSettingsContext = React.createContext(initialState); +const UserSettingsContext = createContext(initialState); export const useUserSettings = (): UserSettingsState => useContext(UserSettingsContext); diff --git a/src/hooks/useDarkMode.ts b/src/hooks/useDarkMode.ts index 6b6299d..3eb284f 100644 --- a/src/hooks/useDarkMode.ts +++ b/src/hooks/useDarkMode.ts @@ -1,16 +1,11 @@ -import { useEffect } from "react"; +import { Dispatch, SetStateAction, useEffect } from "react"; import { useLocalStorage } from "usehooks-ts"; import { usePrefersDarkMode } from "./useMediaQuery"; export const useDarkMode = ( key: string, initialValue: boolean -): [ - boolean, - boolean, - React.Dispatch>, - React.Dispatch> -] => { +): [boolean, boolean, Dispatch>, Dispatch>] => { const [darkMode, setDarkMode] = useLocalStorage(key, initialValue); const prefersDarkMode = usePrefersDarkMode(); const [selectedThemeMode, setSelectedThemeMode] = useLocalStorage("selectedThemeMode", false); diff --git a/src/hooks/useLibraryItemUserStatus.ts b/src/hooks/useLibraryItemUserStatus.ts index 4900299..8b17c67 100644 --- a/src/hooks/useLibraryItemUserStatus.ts +++ b/src/hooks/useLibraryItemUserStatus.ts @@ -1,11 +1,10 @@ import { useLocalStorage } from "usehooks-ts"; +import { Dispatch, SetStateAction } from "react"; import { LibraryItemUserStatus } from "types/types"; export const useLibraryItemUserStatus = (): { libraryItemUserStatus: Record; - setLibraryItemUserStatus: React.Dispatch< - React.SetStateAction> - >; + setLibraryItemUserStatus: Dispatch>>; } => { const [libraryItemUserStatus, setLibraryItemUserStatus] = useLocalStorage( "libraryItemUserStatus", diff --git a/src/hooks/useReaderSettings.ts b/src/hooks/useReaderSettings.ts new file mode 100644 index 0000000..672e06b --- /dev/null +++ b/src/hooks/useReaderSettings.ts @@ -0,0 +1,115 @@ +import { Dispatch, SetStateAction, useCallback, useMemo } from "react"; +import { useLocalStorage } from "usehooks-ts"; +import { ImageQuality } from "helpers/img"; + +export interface FilterSettings { + paperTexture: boolean; + bookFold: boolean; + lighting: boolean; + teint: number; + dropShadow: boolean; +} + +interface ReaderSettings extends FilterSettings { + pageQuality: ImageQuality; + isSidePagesEnabled: boolean; +} + +const DEFAULT_READER_SETTINGS: ReaderSettings = { + bookFold: true, + lighting: true, + paperTexture: true, + teint: 0.1, + dropShadow: true, + pageQuality: ImageQuality.Large, + isSidePagesEnabled: true, +}; + +export const useReaderSettings = (): { + filterSettings: FilterSettings; + isSidePagesEnabled: ReaderSettings["isSidePagesEnabled"]; + pageQuality: ReaderSettings["pageQuality"]; + toggleBookFold: () => void; + toggleLighting: () => void; + togglePaperTexture: () => void; + toggleDropShadow: () => void; + toggleIsSidePagesEnabled: () => void; + setPageQuality: Dispatch>; + setTeint: Dispatch>; + resetReaderSettings: () => void; +} => { + const [bookFold, setBookFold] = useLocalStorage( + "readerBookFold", + DEFAULT_READER_SETTINGS.bookFold + ); + const [lighting, setLighting] = useLocalStorage( + "readerLighting", + DEFAULT_READER_SETTINGS.lighting + ); + const [paperTexture, setPaperTexture] = useLocalStorage( + "readerPaperTexture", + DEFAULT_READER_SETTINGS.paperTexture + ); + const [teint, setTeint] = useLocalStorage("readerTeint", DEFAULT_READER_SETTINGS.teint); + const [dropShadow, setDropShadow] = useLocalStorage( + "readerDropShadow", + DEFAULT_READER_SETTINGS.dropShadow + ); + const [isSidePagesEnabled, setIsSidePagesEnabled] = useLocalStorage( + "readerIsSidePagesEnabled", + DEFAULT_READER_SETTINGS.isSidePagesEnabled + ); + const [pageQuality, setPageQuality] = useLocalStorage( + "readerPageQuality", + DEFAULT_READER_SETTINGS.pageQuality + ); + + const toggleBookFold = useCallback(() => setBookFold((current) => !current), [setBookFold]); + const toggleLighting = useCallback(() => setLighting((current) => !current), [setLighting]); + const togglePaperTexture = useCallback( + () => setPaperTexture((current) => !current), + [setPaperTexture] + ); + const toggleDropShadow = useCallback(() => setDropShadow((current) => !current), [setDropShadow]); + const toggleIsSidePagesEnabled = useCallback( + () => setIsSidePagesEnabled((current) => !current), + [setIsSidePagesEnabled] + ); + + const resetReaderSettings = useCallback(() => { + setBookFold(DEFAULT_READER_SETTINGS.bookFold); + setLighting(DEFAULT_READER_SETTINGS.lighting); + setPaperTexture(DEFAULT_READER_SETTINGS.paperTexture); + setTeint(DEFAULT_READER_SETTINGS.teint); + setDropShadow(DEFAULT_READER_SETTINGS.dropShadow); + setIsSidePagesEnabled(DEFAULT_READER_SETTINGS.isSidePagesEnabled); + setPageQuality(DEFAULT_READER_SETTINGS.pageQuality); + }, [ + setBookFold, + setDropShadow, + setIsSidePagesEnabled, + setLighting, + setPageQuality, + setPaperTexture, + setTeint, + ]); + + const filterSettings = useMemo( + () => ({ bookFold, lighting, paperTexture, teint, dropShadow }), + [bookFold, dropShadow, lighting, paperTexture, teint] + ); + + return { + filterSettings, + isSidePagesEnabled, + pageQuality, + toggleBookFold, + toggleLighting, + togglePaperTexture, + toggleDropShadow, + toggleIsSidePagesEnabled, + setPageQuality, + setTeint, + resetReaderSettings, + }; +}; diff --git a/src/hooks/useStateWithLocalStorage.ts b/src/hooks/useStateWithLocalStorage.ts index 5bad2b2..5c7d211 100644 --- a/src/hooks/useStateWithLocalStorage.ts +++ b/src/hooks/useStateWithLocalStorage.ts @@ -1,10 +1,10 @@ -import { useEffect, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { isDefined } from "helpers/others"; export const useStateWithLocalStorage = ( key: string, initialValue: T -): [T, React.Dispatch>] => { +): [T, Dispatch>] => { const [value, setValue] = useState(initialValue); const [isFromLocaleStorage, setFromLocaleStorage] = useState(false); diff --git a/src/pages/library/[slug]/reader.tsx b/src/pages/library/[slug]/reader.tsx index 8689303..329ff97 100644 --- a/src/pages/library/[slug]/reader.tsx +++ b/src/pages/library/[slug]/reader.tsx @@ -1,7 +1,6 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; import Hotkeys from "react-hot-keys"; -import { useBoolean } from "usehooks-ts"; import Slider from "rc-slider"; import { useRouter } from "next/router"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; @@ -45,6 +44,7 @@ import { prettyInlineTitle, prettySlug } from "helpers/formatters"; import { useFullscreen } from "hooks/useFullscreen"; import { useUserSettings } from "contexts/UserSettingsContext"; import { useLocalData } from "contexts/LocalDataContext"; +import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings"; const CUSTOM_DARK_DROPSHADOW = ` drop-shadow(0 0 0.5em rgb(var(--theme-color-shade) / 30%)) @@ -61,29 +61,9 @@ const CUSTOM_LIGHT_DROPSHADOW = ` const SIDEPAGES_PAGE_COUNT_ON_TEXTURE = 200; const SIDEPAGES_PAGE_WIDTH = 0.02; -const DEFAULT_READER_OPTIONS = { - pageQuality: ImageQuality.Large, - isSidePagesEnabled: true, - filterOptions: { - bookFold: true, - lighting: true, - paperTexture: true, - teint: 0.1, - dropShadow: true, - }, -}; - type BookType = "book" | "manga"; type DisplayMode = "double" | "single"; -interface FilterOptions { - paperTexture: boolean; - bookFold: boolean; - lighting: boolean; - teint: number; - dropShadow: boolean; -} - /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── @@ -113,6 +93,19 @@ const LibrarySlug = ({ const is1ColumnLayout = useIs1ColumnLayout(); const { langui } = useLocalData(); const { darkMode } = useUserSettings(); + const { + filterSettings, + isSidePagesEnabled, + pageQuality, + toggleBookFold, + toggleLighting, + togglePaperTexture, + toggleDropShadow, + toggleIsSidePagesEnabled, + setPageQuality, + setTeint, + resetReaderSettings, + } = useReaderSettings(); const [currentPageIndex, setCurrentPageIndex] = useState(0); const [currentZoom, setCurrentZoom] = useState(1); const [isGalleryMode, setIsGalleryMode] = useState(false); @@ -120,15 +113,7 @@ const LibrarySlug = ({ is1ColumnLayout ? "single" : "double" ); const router = useRouter(); - const [filterOptions, setFilterOptions] = useState( - DEFAULT_READER_OPTIONS.filterOptions - ); - const [pageQuality, setPageQuality] = useState(DEFAULT_READER_OPTIONS.pageQuality); - const { - value: isSidePagesEnabled, - toggle: toggleSidePagesEnabled, - setValue: setIsSidePagesEnabled, - } = useBoolean(DEFAULT_READER_OPTIONS.isSidePagesEnabled); + const { isFullscreen, toggleFullscreen } = useFullscreen(Ids.ContentPanel); const effectiveDisplayMode = useMemo( @@ -254,7 +239,7 @@ const LibrarySlug = ({ } 70% 0%, ${ - filterOptions.bookFold + filterSettings.bookFold ? `90% .25%, 95% .5%, 98% .8%, @@ -269,7 +254,7 @@ const LibrarySlug = ({ } 70% 100% )`, - [filterOptions.bookFold, isSidePagesEnabled, leftSidePagesWidth] + [filterSettings.bookFold, isSidePagesEnabled, leftSidePagesWidth] ); const rightSideClipPath = useMemo( @@ -284,7 +269,7 @@ const LibrarySlug = ({ } 30% 100%, ${ - filterOptions.bookFold + filterSettings.bookFold ? `10% 99.75%, 5% 99.5%, 2% 99.2%, @@ -299,7 +284,7 @@ const LibrarySlug = ({ } 30% 0% )`, - [filterOptions.bookFold, isSidePagesEnabled, rightSidePagesWidth] + [filterSettings.bookFold, isSidePagesEnabled, rightSidePagesWidth] ); const pageHeight = useMemo( @@ -314,43 +299,23 @@ const LibrarySlug = ({
- - setFilterOptions((current) => ({ ...current, paperTexture: !current.paperTexture })) - } - /> + - - setFilterOptions((current) => ({ ...current, bookFold: !current.bookFold })) - } - /> + - - setFilterOptions((current) => ({ ...current, lighting: !current.lighting })) - } - /> + - + - - setFilterOptions((current) => ({ ...current, dropShadow: !current.dropShadow })) - } - /> +
@@ -359,7 +324,7 @@ const LibrarySlug = ({ { let value = 0; if (Array.isArray(event)) { @@ -367,10 +332,7 @@ const LibrarySlug = ({ } else { value = event; } - setFilterOptions((current) => ({ - ...current, - teint: value / 10, - })); + setTeint(value / 10); }} /> @@ -418,9 +380,7 @@ const LibrarySlug = ({ text={langui.reset_all_options} icon={Icon.Replay} onClick={() => { - setFilterOptions(DEFAULT_READER_OPTIONS.filterOptions); - setPageQuality(DEFAULT_READER_OPTIONS.pageQuality); - setIsSidePagesEnabled(DEFAULT_READER_OPTIONS.isSidePagesEnabled); + resetReaderSettings(); setDisplayMode(is1ColumnLayout ? "single" : "double"); sendAnalytics("Reader", "Reset all options"); }} @@ -428,19 +388,36 @@ const LibrarySlug = ({ ), [ - langui, + langui.item, + langui.paper_texture, + langui.book_fold, + langui.lighting, + langui.side_pages, + langui.shadow, + langui.night_reader, + langui.reading_layout, + langui.single_page_view, + langui.double_page_view, + langui.quality, + langui.reset_all_options, itemSlug, - filterOptions.paperTexture, - filterOptions.bookFold, - filterOptions.lighting, - filterOptions.dropShadow, - filterOptions.teint, + filterSettings.paperTexture, + filterSettings.bookFold, + filterSettings.lighting, + filterSettings.dropShadow, + filterSettings.teint, + togglePaperTexture, + toggleBookFold, + toggleLighting, isSidePagesEnabled, - toggleSidePagesEnabled, + toggleIsSidePagesEnabled, + toggleDropShadow, displayMode, pageQuality, + setTeint, changeDisplayMode, - setIsSidePagesEnabled, + setPageQuality, + resetReaderSettings, is1ColumnLayout, ] ); @@ -468,7 +445,7 @@ const LibrarySlug = ({ gridAutoFlow: "column", display: "grid", placeContent: "center", - filter: filterOptions.dropShadow + filter: filterSettings.dropShadow ? darkMode ? CUSTOM_DARK_DROPSHADOW : CUSTOM_LIGHT_DROPSHADOW @@ -485,7 +462,7 @@ const LibrarySlug = ({ src={firstPage} quality={pageQuality} /> - +
currentZoom <= 1 && handlePageNavigation("left")} @@ -523,7 +500,7 @@ const LibrarySlug = ({ src={pageOrder === PageOrder.LeftToRight ? firstPage : secondPage} quality={pageQuality} /> - +
)} - +
)} @@ -647,7 +624,7 @@ const LibrarySlug = ({ [ is1ColumnLayout, currentZoom, - filterOptions, + filterSettings, darkMode, pageHeight, effectiveDisplayMode, @@ -811,7 +788,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => { interface PageFiltersProps { page: "left" | "right" | "single"; bookType: BookType; - options: FilterOptions; + options: FilterSettings; } const PageFilters = ({ page, bookType, options }: PageFiltersProps) => {