Simplify selecting zustand store state

This commit is contained in:
schroda
2026-03-11 22:10:34 +01:00
parent 72af9d52d1
commit 1cccd1ea46
24 changed files with 154 additions and 135 deletions

View File

@@ -31,10 +31,7 @@ const BaseReaderAutoScroll = ({
themeDirection: Direction; themeDirection: Direction;
combinedDirection: Direction; combinedDirection: Direction;
}) => { }) => {
const { scrollRef, direction } = useReaderAutoScrollStore((state) => ({ const { scrollRef, direction } = useReaderAutoScrollStore('scrollRef', 'direction');
scrollRef: state.scrollRef,
direction: state.direction,
}));
const { readingMode, autoScroll } = useReaderSettingsStore((state) => ({ const { readingMode, autoScroll } = useReaderSettingsStore((state) => ({
readingMode: state.readingMode.value, readingMode: state.readingMode.value,
autoScroll: state.autoScroll, autoScroll: state.autoScroll,

View File

@@ -11,9 +11,7 @@ import type { IReaderSettings } from '@/features/reader/Reader.types.ts';
import { getReaderAutoScrollStore, useReaderAutoScrollStore } from '@/features/reader/stores/ReaderStore.ts'; import { getReaderAutoScrollStore, useReaderAutoScrollStore } from '@/features/reader/stores/ReaderStore.ts';
export const useReaderAutoScroll = (isOverlayVisible: boolean, isStaticNav: IReaderSettings['isStaticNav']): void => { export const useReaderAutoScroll = (isOverlayVisible: boolean, isStaticNav: IReaderSettings['isStaticNav']): void => {
const { isPaused } = useReaderAutoScrollStore((state) => ({ const isPaused = useReaderAutoScrollStore('isPaused');
isPaused: state.isPaused,
}));
useEffect(() => { useEffect(() => {
const { pause, resume } = getReaderAutoScrollStore(); const { pause, resume } = getReaderAutoScrollStore();

View File

@@ -28,7 +28,7 @@ export const ReaderNavBarDesktopAutoScroll = ({
setAutoScroll: (newAutoScroll: IReaderSettings['autoScroll'], commit: boolean) => void; setAutoScroll: (newAutoScroll: IReaderSettings['autoScroll'], commit: boolean) => void;
}) => { }) => {
const { t } = useLingui(); const { t } = useLingui();
const isActive = useReaderAutoScrollStore((state) => state.isActive); const isActive = useReaderAutoScrollStore('isActive');
const updateTimeout = useRef<NodeJS.Timeout>(undefined); const updateTimeout = useRef<NodeJS.Timeout>(undefined);

View File

@@ -66,9 +66,7 @@ export const ReaderHotkeys = ({
}) => { }) => {
const readerThemeDirection = ReaderService.useGetThemeDirection(); const readerThemeDirection = ReaderService.useGetThemeDirection();
const { enableScope, disableScope } = useHotkeysContext(); const { enableScope, disableScope } = useHotkeysContext();
const { hotkeys } = useReaderSettingsStore((state) => ({ const hotkeys = useReaderSettingsStore('hotkeys');
hotkeys: state.hotkeys,
}));
const exitReader = ReaderService.useExit(); const exitReader = ReaderService.useExit();
useHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous')); useHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous'));

View File

@@ -23,7 +23,7 @@ const BaseReaderOverlay = ({
isDesktop, isDesktop,
isMobile, isMobile,
}: Pick<ReturnType<typeof ReaderService.useOverlayMode>, 'isDesktop' | 'isMobile'>) => { }: Pick<ReturnType<typeof ReaderService.useOverlayMode>, 'isDesktop' | 'isMobile'>) => {
const isVisible = useReaderOverlayStore((state) => state.isVisible); const isVisible = useReaderOverlayStore('isVisible');
const [areSettingsOpen, setAreSettingsOpen] = useState(false); const [areSettingsOpen, setAreSettingsOpen] = useState(false);

View File

@@ -30,17 +30,13 @@ const BaseReaderPageNumber = ({
}: Pick<ReturnType<typeof ReaderService.useOverlayMode>, 'isDesktop'> & }: Pick<ReturnType<typeof ReaderService.useOverlayMode>, 'isDesktop'> &
Pick<NavbarContextType, 'readerNavBarWidth'>) => { Pick<NavbarContextType, 'readerNavBarWidth'>) => {
const scrollbar = useReaderScrollbarStore((state) => state); const scrollbar = useReaderScrollbarStore((state) => state);
const { currentPageIndex, pages, totalPages } = useReaderPagesStore((state) => ({ const { currentPageIndex, pages, totalPages } = useReaderPagesStore('currentPageIndex', 'pages', 'totalPages');
currentPageIndex: state.currentPageIndex,
pages: state.pages,
totalPages: state.totalPages,
}));
const { readingDirection, shouldShowPageNumber, progressBarType } = useReaderSettingsStore((state) => ({ const { readingDirection, shouldShowPageNumber, progressBarType } = useReaderSettingsStore((state) => ({
readingDirection: state.readingDirection.value, readingDirection: state.readingDirection.value,
shouldShowPageNumber: state.shouldShowPageNumber, shouldShowPageNumber: state.shouldShowPageNumber,
progressBarType: state.progressBarType, progressBarType: state.progressBarType,
})); }));
const isMaximized = useReaderProgressBarStore((state) => state.isMaximized); const isMaximized = useReaderProgressBarStore('isMaximized');
const pageName = useMemo(() => { const pageName = useMemo(() => {
const currentPageName = getPage(currentPageIndex, pages).name; const currentPageName = getPage(currentPageIndex, pages).name;

View File

@@ -18,9 +18,7 @@ export const useReaderHideOverlayOnUserScroll = (
isOverlayVisible: boolean, isOverlayVisible: boolean,
scrollElementRef: MutableRefObject<HTMLDivElement | null>, scrollElementRef: MutableRefObject<HTMLDivElement | null>,
) => { ) => {
const { showPreview } = useReaderTapZoneStore((state) => ({ const showPreview = useReaderTapZoneStore('showPreview');
showPreview: state.showPreview,
}));
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {

View File

@@ -42,9 +42,9 @@ const DEFAULT_MANGA = { ...FALLBACK_MANGA, title: '' };
const BaseReaderOverlayHeaderMobile = ({ isVisible, ref }: MobileHeaderProps & { ref?: Ref<HTMLDivElement> }) => { const BaseReaderOverlayHeaderMobile = ({ isVisible, ref }: MobileHeaderProps & { ref?: Ref<HTMLDivElement> }) => {
const { t } = useLingui(); const { t } = useLingui();
const popupState = usePopupState({ popupId: 'reader-overlay-more-menu', variant: 'popover' }); const popupState = usePopupState({ popupId: 'reader-overlay-more-menu', variant: 'popover' });
const currentChapter = useReaderChaptersStore((state) => state.currentChapter); const currentChapter = useReaderChaptersStore('currentChapter');
const manga = useReaderStore((state) => state.manga); const manga = useReaderStore('manga');
const scrollbar = useReaderScrollbarStore((state) => state); const scrollbar = useReaderScrollbarStore((state) => state);
const { id: mangaId, title } = manga ?? DEFAULT_MANGA; const { id: mangaId, title } = manga ?? DEFAULT_MANGA;

View File

@@ -23,7 +23,7 @@ const ACTION_FALLBACK_MANGA = {
}; };
export const ReaderLibraryButton = memo(() => { export const ReaderLibraryButton = memo(() => {
const manga = useReaderStore((state) => state.manga); const manga = useReaderStore('manga');
const { inLibrary } = manga ?? ACTION_FALLBACK_MANGA; const { inLibrary } = manga ?? ACTION_FALLBACK_MANGA;

View File

@@ -59,7 +59,7 @@ const BaseReaderNavBarDesktop = ({
setReaderNavBarWidth, setReaderNavBarWidth,
}: ReaderNavBarDesktopProps & Pick<NavbarContextType, 'setReaderNavBarWidth'>) => { }: ReaderNavBarDesktopProps & Pick<NavbarContextType, 'setReaderNavBarWidth'>) => {
const { t } = useLingui(); const { t } = useLingui();
const manga = useReaderStore((state) => state.manga); const manga = useReaderStore('manga');
const { const {
chapters, chapters,
currentChapterId, currentChapterId,
@@ -77,7 +77,7 @@ const BaseReaderNavBarDesktop = ({
previousChapter: state.previousChapter, previousChapter: state.previousChapter,
nextChapter: state.nextChapter, nextChapter: state.nextChapter,
})); }));
const isStaticNav = useReaderSettingsStore((state) => state.isStaticNav); const isStaticNav = useReaderSettingsStore('isStaticNav');
const [navBarElement, setNavBarElement] = useState<HTMLDivElement | null>(); const [navBarElement, setNavBarElement] = useState<HTMLDivElement | null>();
useResizeObserver( useResizeObserver(

View File

@@ -72,7 +72,7 @@ export const ReaderNavBarDesktopActions = memo(() => {
})); }));
const { t } = useLingui(); const { t } = useLingui();
const pageLoadStates = useReaderPagesStore((state) => state.pageLoadStates); const pageLoadStates = useReaderPagesStore('pageLoadStates');
const pageRetryKeyPrefix = useRef<number>(0); const pageRetryKeyPrefix = useRef<number>(0);

View File

@@ -23,10 +23,7 @@ import { ReaderControls } from '@/features/reader/services/ReaderControls.ts';
const BaseReaderNavBarDesktopPageNavigation = () => { const BaseReaderNavBarDesktopPageNavigation = () => {
const { t } = useLingui(); const { t } = useLingui();
const getOptionForDirection = useGetOptionForDirection(); const getOptionForDirection = useGetOptionForDirection();
const { currentPageIndex, pages } = useReaderPagesStore((state) => ({ const { currentPageIndex, pages } = useReaderPagesStore('currentPageIndex', 'pages');
currentPageIndex: state.currentPageIndex,
pages: state.pages,
}));
const readingDirection = useReaderSettingsStore((state) => state.readingDirection.value); const readingDirection = useReaderSettingsStore((state) => state.readingDirection.value);
const currentPage = useMemo(() => getPage(currentPageIndex, pages), [currentPageIndex, pages]); const currentPage = useMemo(() => getPage(currentPageIndex, pages), [currentPageIndex, pages]);

View File

@@ -23,14 +23,14 @@ import { useReaderSettingsStore } from '@/features/reader/stores/ReaderStore.ts'
const BaseReaderNavBarDesktopQuickSettings = ({ openSettings }: Pick<ReaderNavBarDesktopProps, 'openSettings'>) => { const BaseReaderNavBarDesktopQuickSettings = ({ openSettings }: Pick<ReaderNavBarDesktopProps, 'openSettings'>) => {
const { t } = useLingui(); const { t } = useLingui();
const { readingMode, shouldOffsetDoubleSpreads, pageScaleMode, shouldStretchPage, readingDirection, autoScroll } = const { readingMode, shouldOffsetDoubleSpreads, pageScaleMode, shouldStretchPage, readingDirection, autoScroll } =
useReaderSettingsStore((state) => ({ useReaderSettingsStore(
readingMode: state.readingMode, 'readingMode',
shouldOffsetDoubleSpreads: state.shouldOffsetDoubleSpreads, 'shouldOffsetDoubleSpreads',
pageScaleMode: state.pageScaleMode, 'pageScaleMode',
shouldStretchPage: state.shouldStretchPage, 'shouldStretchPage',
readingDirection: state.readingDirection, 'readingDirection',
autoScroll: state.autoScroll, 'autoScroll',
})); );
return ( return (
<Stack sx={{ gap: 1 }}> <Stack sx={{ gap: 1 }}>

View File

@@ -23,12 +23,12 @@ import {
const BaseReaderBottomBarMobileQuickSettings = () => { const BaseReaderBottomBarMobileQuickSettings = () => {
const { t } = useLingui(); const { t } = useLingui();
const isActive = useReaderAutoScrollStore((state) => state.isActive); const isActive = useReaderAutoScrollStore('isActive');
const { readingMode, readingDirection, autoScroll } = useReaderSettingsStore((state) => ({ const { readingMode, readingDirection, autoScroll } = useReaderSettingsStore(
readingMode: state.readingMode, 'readingMode',
readingDirection: state.readingDirection, 'readingDirection',
autoScroll: state.autoScroll, 'autoScroll',
})); );
return ( return (
<Stack sx={{ gap: 2 }}> <Stack sx={{ gap: 2 }}>

View File

@@ -68,14 +68,14 @@ const BaseReaderProgressBar = ({
direction: ReturnType<typeof ReaderService.useGetThemeDirection>; direction: ReturnType<typeof ReaderService.useGetThemeDirection>;
fullSegmentClicks: boolean; fullSegmentClicks: boolean;
}) => { }) => {
const { pages, pageLoadStates, totalPages, currentPageIndex } = useReaderPagesStore((state) => ({ const { pages, pageLoadStates, totalPages, currentPageIndex } = useReaderPagesStore(
pages: state.pages, 'pages',
pageLoadStates: state.pageLoadStates, 'pageLoadStates',
totalPages: state.totalPages, 'totalPages',
currentPageIndex: state.currentPageIndex, 'currentPageIndex',
})); );
const readingMode = useReaderSettingsStore((state) => state.readingMode.value); const readingMode = useReaderSettingsStore((state) => state.readingMode.value);
const isDragging = useReaderProgressBarStore((state) => state.isDragging); const isDragging = useReaderProgressBarStore('isDragging');
const progressBarRef = useRef<HTMLDivElement | null>(null); const progressBarRef = useRef<HTMLDivElement | null>(null);
const draggingDetectionTimeout = useRef<NodeJS.Timeout>(undefined); const draggingDetectionTimeout = useRef<NodeJS.Timeout>(undefined);

View File

@@ -37,18 +37,15 @@ const BaseStandardReaderProgressBar = ({
const theme = useTheme(); const theme = useTheme();
const scrollbar = useReaderScrollbarStore((state) => state); const scrollbar = useReaderScrollbarStore((state) => state);
const totalPages = useReaderPagesStore((state) => state.totalPages); const totalPages = useReaderPagesStore('totalPages');
const { progressBarType, progressBarSize, progressBarPosition, progressBarPositionAutoVertical } = const { progressBarType, progressBarSize, progressBarPosition, progressBarPositionAutoVertical } =
useReaderSettingsStore((state) => ({ useReaderSettingsStore(
progressBarType: state.progressBarType, 'progressBarType',
progressBarSize: state.progressBarSize, 'progressBarSize',
progressBarPosition: state.progressBarPosition, 'progressBarPosition',
progressBarPositionAutoVertical: state.progressBarPositionAutoVertical, 'progressBarPositionAutoVertical',
})); );
const { isMaximized, isDragging } = useReaderProgressBarStore((state) => ({ const { isMaximized, isDragging } = useReaderProgressBarStore('isMaximized', 'isDragging');
isMaximized: state.isMaximized,
isDragging: state.isDragging,
}));
const [, setRefreshProgressBarPosition] = useState({}); const [, setRefreshProgressBarPosition] = useState({});
useResizeObserver( useResizeObserver(

View File

@@ -59,20 +59,14 @@ const BaseMobileReaderProgressBar = ({
bottomOffset?: number; bottomOffset?: number;
}) => { }) => {
const scrollbar = useReaderScrollbarStore((state) => state); const scrollbar = useReaderScrollbarStore((state) => state);
const isVisible = useReaderOverlayStore((state) => state.isVisible); const isVisible = useReaderOverlayStore('isVisible');
const { currentPageIndex, pages } = useReaderPagesStore((state) => ({ const { currentPageIndex, pages } = useReaderPagesStore('currentPageIndex', 'pages');
currentPageIndex: state.currentPageIndex, const { previousChapter, nextChapter } = useReaderChaptersStore('previousChapter', 'nextChapter');
pages: state.pages, const { progressBarPosition, progressBarPositionAutoVertical } = useReaderSettingsStore(
})); 'progressBarPosition',
const { previousChapter, nextChapter } = useReaderChaptersStore((state) => ({ 'progressBarPositionAutoVertical',
previousChapter: state.previousChapter, );
nextChapter: state.nextChapter, const isDragging = useReaderProgressBarStore('isDragging');
}));
const { progressBarPosition, progressBarPositionAutoVertical } = useReaderSettingsStore((state) => ({
progressBarPosition: state.progressBarPosition,
progressBarPositionAutoVertical: state.progressBarPositionAutoVertical,
}));
const isDragging = useReaderProgressBarStore((state) => state.isDragging);
const [, setRefreshProgressBarPosition] = useState({}); const [, setRefreshProgressBarPosition] = useState({});
useResizeObserver( useResizeObserver(

View File

@@ -49,14 +49,12 @@ const BaseReader = ({
readerNavBarWidth, readerNavBarWidth,
}: Pick<NavbarContextType, 'setOverride' | 'readerNavBarWidth'>) => { }: Pick<NavbarContextType, 'setOverride' | 'readerNavBarWidth'>) => {
const { t } = useLingui(); const { t } = useLingui();
const manga = useReaderStore((state) => state.manga); const manga = useReaderStore('manga');
const { mangaChapters, initialChapter, chapterForDuplicatesHandling, currentChapter } = useReaderChaptersStore( const { mangaChapters, initialChapter, chapterForDuplicatesHandling, currentChapter } = useReaderChaptersStore(
(state) => ({ 'mangaChapters',
mangaChapters: state.mangaChapters, 'initialChapter',
initialChapter: state.initialChapter, 'chapterForDuplicatesHandling',
chapterForDuplicatesHandling: state.chapterForDuplicatesHandling, 'currentChapter',
currentChapter: state.currentChapter,
}),
); );
const { const {
shouldSkipDupChapters, shouldSkipDupChapters,
@@ -67,16 +65,16 @@ const BaseReader = ({
tapZoneInvertMode, tapZoneInvertMode,
shouldShowReadingModePreview, shouldShowReadingModePreview,
shouldShowTapZoneLayoutPreview, shouldShowTapZoneLayoutPreview,
} = useReaderSettingsStore((state) => ({ } = useReaderSettingsStore(
shouldSkipDupChapters: state.shouldSkipDupChapters, 'shouldSkipDupChapters',
shouldSkipFilteredChapters: state.shouldSkipFilteredChapters, 'shouldSkipFilteredChapters',
backgroundColor: state.backgroundColor, 'backgroundColor',
readingMode: state.readingMode, 'readingMode',
tapZoneLayout: state.tapZoneLayout, 'tapZoneLayout',
tapZoneInvertMode: state.tapZoneInvertMode, 'tapZoneInvertMode',
shouldShowReadingModePreview: state.shouldShowReadingModePreview, 'shouldShowReadingModePreview',
shouldShowTapZoneLayoutPreview: state.shouldShowTapZoneLayoutPreview, 'shouldShowTapZoneLayoutPreview',
})); );
const scrollElementRef = useRef<HTMLDivElement | null>(null); const scrollElementRef = useRef<HTMLDivElement | null>(null);

View File

@@ -425,7 +425,7 @@ export class ReaderService {
static useOverlayMode(): { mode: ReaderOverlayMode; isDesktop: boolean; isMobile: boolean } { static useOverlayMode(): { mode: ReaderOverlayMode; isDesktop: boolean; isMobile: boolean } {
const isTouchDevice = MediaQuery.useIsTouchDevice(); const isTouchDevice = MediaQuery.useIsTouchDevice();
const overlayMode = useReaderSettingsStore((state) => state.overlayMode); const overlayMode = useReaderSettingsStore('overlayMode');
const isAutoModeSelected = overlayMode === ReaderOverlayMode.AUTO; const isAutoModeSelected = overlayMode === ReaderOverlayMode.AUTO;
const isDesktopModeSelected = overlayMode === ReaderOverlayMode.DESKTOP; const isDesktopModeSelected = overlayMode === ReaderOverlayMode.DESKTOP;
@@ -442,7 +442,7 @@ export class ReaderService {
} }
static useExit(): () => void { static useExit(): () => void {
const exitMode = useReaderSettingsStore((state) => state.exitMode); const exitMode = useReaderSettingsStore('exitMode');
const handleBack = useBackButton(); const handleBack = useBackButton();
const navigate = useNavigate(); const navigate = useNavigate();

View File

@@ -9,7 +9,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools } from 'zustand/middleware'; import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { useShallow } from 'zustand/react/shallow';
import type { TMangaReader } from '@/features/manga/Manga.types.ts'; import type { TMangaReader } from '@/features/manga/Manga.types.ts';
import type { ReaderOverlayStoreSlice } from '@/features/reader/overlay/ReaderOverlayStore.ts'; import type { ReaderOverlayStoreSlice } from '@/features/reader/overlay/ReaderOverlayStore.ts';
import { createReaderOverlayStoreSlice } from '@/features/reader/overlay/ReaderOverlayStore.ts'; import { createReaderOverlayStoreSlice } from '@/features/reader/overlay/ReaderOverlayStore.ts';
@@ -156,37 +155,29 @@ const readerStore = create<ReaderStore>()(
}, },
), ),
); );
export const useReaderStore = <T>(selector: (state: ReaderStore) => T): T => readerStore(useShallow(selector)); export const useReaderStore = ZustandUtil.createStoreHook(readerStore);
export const getReaderStore = () => readerStore.getState(); export const getReaderStore = () => readerStore.getState();
export const useReaderScrollbarStore = <T>(selector: (state: ReaderStore['scrollbar']) => T): T => export const useReaderScrollbarStore = ZustandUtil.createStoreHook(readerStore, 'scrollbar');
readerStore(useShallow((state) => selector(state.scrollbar)));
export const getReaderScrollbarStore = () => readerStore.getState().scrollbar; export const getReaderScrollbarStore = () => readerStore.getState().scrollbar;
export const useReaderSettingsStore = <T>(selector: (state: ReaderStore['settings']) => T): T => export const useReaderSettingsStore = ZustandUtil.createStoreHook(readerStore, 'settings');
readerStore(useShallow((state) => selector(state.settings)));
export const getReaderSettingsStore = () => readerStore.getState().settings; export const getReaderSettingsStore = () => readerStore.getState().settings;
export const useReaderOverlayStore = <T>(selector: (state: ReaderOverlayStoreSlice['overlay']) => T): T => export const useReaderOverlayStore = ZustandUtil.createStoreHook(readerStore, 'overlay');
readerStore(useShallow((state) => selector(state.overlay)));
export const getReaderOverlayStore = () => readerStore.getState().overlay; export const getReaderOverlayStore = () => readerStore.getState().overlay;
export const useReaderAutoScrollStore = <T>(selector: (state: ReaderAutoScrollStoreSlice['autoScroll']) => T): T => export const useReaderAutoScrollStore = ZustandUtil.createStoreHook(readerStore, 'autoScroll');
readerStore(useShallow((state) => selector(state.autoScroll)));
export const getReaderAutoScrollStore = () => readerStore.getState().autoScroll; export const getReaderAutoScrollStore = () => readerStore.getState().autoScroll;
export const useReaderPagesStore = <T>(selector: (state: ReaderPagesStoreSlice['pages']) => T): T => export const useReaderPagesStore = ZustandUtil.createStoreHook(readerStore, 'pages');
readerStore(useShallow((state) => selector(state.pages)));
export const getReaderPagesStore = () => readerStore.getState().pages; export const getReaderPagesStore = () => readerStore.getState().pages;
export const useReaderChaptersStore = <T>(selector: (state: ReaderChaptersStoreSlice['chapters']) => T): T => export const useReaderChaptersStore = ZustandUtil.createStoreHook(readerStore, 'chapters');
readerStore(useShallow((state) => selector(state.chapters)));
export const getReaderChaptersStore = () => readerStore.getState().chapters; export const getReaderChaptersStore = () => readerStore.getState().chapters;
export const useReaderProgressBarStore = <T>(selector: (state: ReaderProgressBarStoreSlice['progressBar']) => T): T => export const useReaderProgressBarStore = ZustandUtil.createStoreHook(readerStore, 'progressBar');
readerStore(useShallow((state) => selector(state.progressBar)));
export const getReaderProgressBarStore = () => readerStore.getState().progressBar; export const getReaderProgressBarStore = () => readerStore.getState().progressBar;
export const useReaderTapZoneStore = <T>(selector: (state: ReaderTapZoneStoreSlice['tapZone']) => T): T => export const useReaderTapZoneStore = ZustandUtil.createStoreHook(readerStore, 'tapZone');
readerStore(useShallow((state) => selector(state.tapZone)));
export const getReaderTapZoneStore = () => readerStore.getState().tapZone; export const getReaderTapZoneStore = () => readerStore.getState().tapZone;

View File

@@ -26,7 +26,7 @@ const BaseTapZoneLayout = ({ readerNavBarWidth }: Pick<NavbarContextType, 'reade
tapZoneInvertMode: state.tapZoneInvertMode.value, tapZoneInvertMode: state.tapZoneInvertMode.value,
readingDirection: state.readingDirection.value, readingDirection: state.readingDirection.value,
})); }));
const showPreview = useReaderTapZoneStore((state) => state.showPreview); const showPreview = useReaderTapZoneStore('showPreview');
const [width, setWidth] = useState(0); const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0); const [height, setHeight] = useState(0);

View File

@@ -68,24 +68,22 @@ const BaseReaderViewer = ({
ref?: ForwardedRef<HTMLDivElement | null>; ref?: ForwardedRef<HTMLDivElement | null>;
}) => { }) => {
const { direction: themeDirection } = useTheme(); const { direction: themeDirection } = useTheme();
const isOverlayVisible = useReaderOverlayStore((state) => state.isVisible); const isOverlayVisible = useReaderOverlayStore('isVisible');
const { currentPageIndex, pageToScrollToIndex, pages, totalPages, transitionPageMode, retryFailedPagesKeyPrefix } = const { currentPageIndex, pageToScrollToIndex, pages, totalPages, transitionPageMode, retryFailedPagesKeyPrefix } =
useReaderPagesStore((state) => ({ useReaderPagesStore(
currentPageIndex: state.currentPageIndex, 'currentPageIndex',
pageToScrollToIndex: state.pageToScrollToIndex, 'pageToScrollToIndex',
pages: state.pages, 'pages',
totalPages: state.totalPages, 'totalPages',
transitionPageMode: state.transitionPageMode, 'transitionPageMode',
retryFailedPagesKeyPrefix: state.retryFailedPagesKeyPrefix, 'retryFailedPagesKeyPrefix',
})); );
const { initialChapter, currentChapter, chapters, visibleChapters, isCurrentChapterReady } = useReaderChaptersStore( const { initialChapter, currentChapter, chapters, visibleChapters, isCurrentChapterReady } = useReaderChaptersStore(
(state) => ({ 'initialChapter',
initialChapter: state.initialChapter, 'currentChapter',
currentChapter: state.currentChapter, 'chapters',
chapters: state.chapters, 'visibleChapters',
visibleChapters: state.visibleChapters, 'isCurrentChapterReady',
isCurrentChapterReady: state.isCurrentChapterReady,
}),
); );
const { const {
readingMode, readingMode,

View File

@@ -99,9 +99,9 @@ const BaseReaderTransitionPage = ({
handleBack: () => void; handleBack: () => void;
}) => { }) => {
const { t } = useLingui(); const { t } = useLingui();
const manga = useReaderStore((state) => state.manga); const manga = useReaderStore('manga');
const scrollbar = useReaderScrollbarStore((state) => state); const scrollbar = useReaderScrollbarStore((state) => state);
const transitionPageMode = useReaderPagesStore((state) => state.transitionPageMode); const transitionPageMode = useReaderPagesStore('transitionPageMode');
const { readingMode, backgroundColor, shouldShowTransitionPage } = useReaderSettingsStore((state) => ({ const { readingMode, backgroundColor, shouldShowTransitionPage } = useReaderSettingsStore((state) => ({
readingMode: state.readingMode.value, readingMode: state.readingMode.value,
backgroundColor: state.backgroundColor, backgroundColor: state.backgroundColor,
@@ -226,7 +226,7 @@ export const ReaderTransitionPage = withPropsFrom(
memo(BaseReaderTransitionPage) as typeof BaseReaderTransitionPage, memo(BaseReaderTransitionPage) as typeof BaseReaderTransitionPage,
[ [
({ chapterId }: Pick<ComponentProps<typeof BaseReaderTransitionPage>, 'chapterId'>) => { ({ chapterId }: Pick<ComponentProps<typeof BaseReaderTransitionPage>, 'chapterId'>) => {
const chapters = useReaderChaptersStore((state) => state.chapters); const chapters = useReaderChaptersStore('chapters');
const currentChapterIndex = useMemo( const currentChapterIndex = useMemo(
() => chapters.findIndex((chapter) => chapter.id === chapterId), () => chapters.findIndex((chapter) => chapter.id === chapterId),
@@ -249,7 +249,7 @@ export const ReaderTransitionPage = withPropsFrom(
useNavBarContext, useNavBarContext,
({ chapterId, type }: Pick<ComponentProps<typeof BaseReaderTransitionPage>, 'chapterId' | 'type'>) => { ({ chapterId, type }: Pick<ComponentProps<typeof BaseReaderTransitionPage>, 'chapterId' | 'type'>) => {
const handleBack = useBackButton(); const handleBack = useBackButton();
const chapters = useReaderChaptersStore((state) => state.chapters); const chapters = useReaderChaptersStore('chapters');
const currentChapterIndex = useMemo( const currentChapterIndex = useMemo(
() => chapters.findIndex((chapter) => chapter.id === chapterId), () => chapters.findIndex((chapter) => chapter.id === chapterId),

View File

@@ -6,7 +6,64 @@
* 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 { useShallow } from 'zustand/react/shallow';
type UseBoundStoreAny<S> = <T>(selector: (state: S) => T) => T;
type StoreHook<State> = {
<T>(selector: (state: State) => T): T;
<Key extends keyof State>(key: Key): State[Key];
<Key extends keyof State>(...keys: Key[]): Pick<State, Key>;
};
export class ZustandUtil { export class ZustandUtil {
static createKeySelector<State, Key extends keyof State>(keys: Key[]): (state: State) => Pick<State, Key> {
return (state) => {
const result = {} as Pick<State, Key>;
for (const key of keys) {
result[key] = state[key];
}
return result;
};
}
static createStoreHook<FullState extends object, SliceKey extends keyof FullState>(
store: UseBoundStoreAny<FullState>,
sliceKey: SliceKey,
): StoreHook<FullState[SliceKey]>;
static createStoreHook<State>(store: UseBoundStoreAny<State>): StoreHook<State>;
static createStoreHook<State>(
store: UseBoundStoreAny<State>,
sliceKey?: keyof State,
): StoreHook<State[keyof State] | State> {
return ((...args: unknown[]) => {
let selector: (state: State) => unknown;
if (typeof args[0] === 'function') {
const fn = args[0] as (state: unknown) => unknown;
selector = sliceKey !== undefined ? (state: State) => fn(state[sliceKey]) : fn;
} else if (args.length === 1) {
const key = args[0];
selector =
sliceKey !== undefined
? (state: State) => (state[sliceKey] as Record<PropertyKey, unknown>)[key as PropertyKey]
: (state: State) => (state as Record<PropertyKey, unknown>)[key as PropertyKey];
} else {
const pickSelector = ZustandUtil.createKeySelector<Record<PropertyKey, unknown>, PropertyKey>(
args as PropertyKey[],
);
selector =
sliceKey !== undefined
? (state: State) => pickSelector(state[sliceKey] as Record<PropertyKey, unknown>)
: (pickSelector as (state: State) => unknown);
}
return store(useShallow(selector));
}) as StoreHook<State[keyof State] | State>;
}
static createActionName(...names: string[]) { static createActionName(...names: string[]) {
const storeAndSlices = names.slice(0, -1); const storeAndSlices = names.slice(0, -1);
const action = names.slice(-1)[0]; const action = names.slice(-1)[0];