Add zustand dev tools

This commit is contained in:
schroda
2025-09-22 00:50:34 +02:00
parent f6b94c838b
commit b6d7f6da5d
11 changed files with 325 additions and 147 deletions

View File

@@ -50,6 +50,7 @@
"@mui/system": "7.3.1", "@mui/system": "7.3.1",
"@mui/utils": "7.3.1", "@mui/utils": "7.3.1",
"@mui/x-date-pickers": "8.10.0", "@mui/x-date-pickers": "8.10.0",
"@redux-devtools/extension": "3.3.0",
"@vibrant/color": "4.0.0", "@vibrant/color": "4.0.0",
"apollo-upload-client": "18.0.1", "apollo-upload-client": "18.0.1",
"csstype": "3.1.3", "csstype": "3.1.3",

View File

@@ -7,7 +7,7 @@
*/ */
import { createRef, MutableRefObject } from 'react'; import { createRef, MutableRefObject } from 'react';
import { ImmerStateCreator } from '@/lib/zustand/Zustand.types.ts'; import { SliceCreator } from '@/lib/zustand/Zustand.types.ts';
import { ScrollOffset } from '@/base/Base.types.ts'; import { ScrollOffset } from '@/base/Base.types.ts';
import { useAutomaticScrolling } from '@/base/hooks/useAutomaticScrolling.ts'; import { useAutomaticScrolling } from '@/base/hooks/useAutomaticScrolling.ts';
import { noOp } from '@/lib/HelperFunctions.ts'; import { noOp } from '@/lib/HelperFunctions.ts';
@@ -58,50 +58,86 @@ const DEFAULT_STATE = {
>; >;
export const createReaderAutoScrollStoreSlice = <T extends ReaderAutoScrollStoreSlice>( export const createReaderAutoScrollStoreSlice = <T extends ReaderAutoScrollStoreSlice>(
...[set, get]: Parameters<ImmerStateCreator<T>> ...[createActionName, set, get]: Parameters<SliceCreator<T>>
): ReaderAutoScrollStoreSlice => ): ReaderAutoScrollStoreSlice => ({
({ autoScroll: {
autoScroll: { ...DEFAULT_STATE,
...DEFAULT_STATE, reset: () => ({ autoScroll: { ...get().autoScroll, ...DEFAULT_STATE } }),
reset: () => ({ autoScroll: { ...get().autoScroll, ...DEFAULT_STATE } }), setIsActive: (active) =>
setIsActive: (active) => set(
set((draft) => { (draft) => {
draft.autoScroll.isActive = active; draft.autoScroll.isActive = active;
}), },
setIsPaused: (paused) => undefined,
set((draft) => { createActionName('setIsActive'),
),
setIsPaused: (paused) =>
set(
(draft) => {
draft.autoScroll.isPaused = paused; draft.autoScroll.isPaused = paused;
}), },
setStart: (start) => undefined,
set((draft) => { createActionName('setIsPaused'),
),
setStart: (start) =>
set(
(draft) => {
draft.autoScroll.start = start; draft.autoScroll.start = start;
}), },
setCancel: (cancel) => undefined,
set((draft) => { createActionName('setStart'),
),
setCancel: (cancel) =>
set(
(draft) => {
draft.autoScroll.cancel = cancel; draft.autoScroll.cancel = cancel;
}), },
setToggleActive: (toggleActive) => undefined,
set((draft) => { createActionName('setCancel'),
),
setToggleActive: (toggleActive) =>
set(
(draft) => {
draft.autoScroll.toggleActive = toggleActive; draft.autoScroll.toggleActive = toggleActive;
}), },
setPause: (pause) => undefined,
set((draft) => { createActionName('setToggleActive'),
),
setPause: (pause) =>
set(
(draft) => {
draft.autoScroll.pause = pause; draft.autoScroll.pause = pause;
}), },
setResume: (resume) => undefined,
set((draft) => { createActionName('setPause'),
),
setResume: (resume) =>
set(
(draft) => {
draft.autoScroll.resume = resume; draft.autoScroll.resume = resume;
}), },
setInvert: (invert) => undefined,
set((draft) => { createActionName('setResume'),
),
setInvert: (invert) =>
set(
(draft) => {
draft.autoScroll.invert = invert; draft.autoScroll.invert = invert;
}), },
setDirection: (direction) => undefined,
set((draft) => { createActionName('setInvert'),
),
setDirection: (direction) =>
set(
(draft) => {
draft.autoScroll.direction = direction; draft.autoScroll.direction = direction;
}), },
setScrollRef: (scrollElement) => undefined,
set((draft) => { createActionName('setDirection'),
),
setScrollRef: (scrollElement) =>
set(
(draft) => {
if (!scrollElement) { if (!scrollElement) {
draft.autoScroll.scrollRef = undefined; draft.autoScroll.scrollRef = undefined;
return; return;
@@ -111,6 +147,9 @@ export const createReaderAutoScrollStoreSlice = <T extends ReaderAutoScrollStore
ref.current = scrollElement; ref.current = scrollElement;
// @ts-expect-error - TS2322: Type MutableRefObject<HTMLElement | null> is not assignable to type WritableDraft<MutableRefObject<HTMLElement | null>> // @ts-expect-error - TS2322: Type MutableRefObject<HTMLElement | null> is not assignable to type WritableDraft<MutableRefObject<HTMLElement | null>>
draft.autoScroll.scrollRef = ref; draft.autoScroll.scrollRef = ref;
}), },
}, undefined,
}) satisfies ReaderAutoScrollStoreSlice; createActionName('setScrollRef'),
),
},
});

View File

@@ -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 { ImmerStateCreator } from '@/lib/zustand/Zustand.types.ts'; import { SliceCreator } from '@/lib/zustand/Zustand.types.ts';
export interface ReaderOverlayStoreSlice { export interface ReaderOverlayStoreSlice {
overlay: { overlay: {
@@ -21,14 +21,18 @@ const DEFAULT_STATE = {
} satisfies Pick<ReaderOverlayStoreSlice['overlay'], 'isVisible'>; } satisfies Pick<ReaderOverlayStoreSlice['overlay'], 'isVisible'>;
export const createReaderOverlayStoreSlice = <T extends ReaderOverlayStoreSlice>( export const createReaderOverlayStoreSlice = <T extends ReaderOverlayStoreSlice>(
...[set, get]: Parameters<ImmerStateCreator<T>> ...[createActionName, set, get]: Parameters<SliceCreator<T>>
): ReaderOverlayStoreSlice => ({ ): ReaderOverlayStoreSlice => ({
overlay: { overlay: {
isVisible: DEFAULT_STATE.isVisible, isVisible: DEFAULT_STATE.isVisible,
setIsVisible: (visible) => setIsVisible: (visible) =>
set((draft) => { set(
draft.overlay.isVisible = visible; (draft) => {
}), draft.overlay.isVisible = visible;
},
undefined,
createActionName('setIsVisible'),
),
reset: () => ({ overlay: { ...get().overlay, ...DEFAULT_STATE } }), reset: () => ({ overlay: { ...get().overlay, ...DEFAULT_STATE } }),
}, },
}); });

View File

@@ -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 { ImmerStateCreator } from '@/lib/zustand/Zustand.types.ts'; import { SliceCreator } from '@/lib/zustand/Zustand.types.ts';
import { TReaderProgressBarContext } from '@/features/reader/overlay/progress-bar/ReaderProgressBar.types.ts'; import { TReaderProgressBarContext } from '@/features/reader/overlay/progress-bar/ReaderProgressBar.types.ts';
export interface ReaderProgressBarStoreSlice { export interface ReaderProgressBarStoreSlice {
@@ -21,18 +21,26 @@ const DEFAULT_STATE = {
} satisfies Pick<ReaderProgressBarStoreSlice['progressBar'], 'isDragging' | 'isMaximized'>; } satisfies Pick<ReaderProgressBarStoreSlice['progressBar'], 'isDragging' | 'isMaximized'>;
export const createReaderProgressBarStoreSlice = <T extends ReaderProgressBarStoreSlice>( export const createReaderProgressBarStoreSlice = <T extends ReaderProgressBarStoreSlice>(
...[set, get]: Parameters<ImmerStateCreator<T>> ...[createActionName, set, get]: Parameters<SliceCreator<T>>
): ReaderProgressBarStoreSlice => ({ ): ReaderProgressBarStoreSlice => ({
progressBar: { progressBar: {
...DEFAULT_STATE, ...DEFAULT_STATE,
setIsMaximized: (maximized) => setIsMaximized: (maximized) =>
set((draft) => { set(
draft.progressBar.isMaximized = maximized; (draft) => {
}), draft.progressBar.isMaximized = maximized;
},
undefined,
createActionName('setIsMaximized'),
),
setIsDragging: (dragging) => setIsDragging: (dragging) =>
set((draft) => { set(
draft.progressBar.isDragging = dragging; (draft) => {
}), draft.progressBar.isDragging = dragging;
},
undefined,
createActionName('setIsDragging'),
),
reset: () => ({ progressBar: { ...get().progressBar, ...DEFAULT_STATE } }), reset: () => ({ progressBar: { ...get().progressBar, ...DEFAULT_STATE } }),
}, },
}); });

View File

@@ -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 { ImmerStateCreator } from '@/lib/zustand/Zustand.types.ts'; import { SliceCreator } from '@/lib/zustand/Zustand.types.ts';
import { ReaderStateChapters } from '@/features/reader/Reader.types.ts'; import { ReaderStateChapters } from '@/features/reader/Reader.types.ts';
export interface ReaderChaptersStoreSlice { export interface ReaderChaptersStoreSlice {
@@ -44,25 +44,29 @@ export const READER_DEFAULT_CHAPTERS_STATE = {
}; };
export const createReaderChaptersStoreSlice = <T extends ReaderChaptersStoreSlice>( export const createReaderChaptersStoreSlice = <T extends ReaderChaptersStoreSlice>(
...[set, get]: Parameters<ImmerStateCreator<T>> ...[createActionName, set, get]: Parameters<SliceCreator<T>>
): ReaderChaptersStoreSlice => ({ ): ReaderChaptersStoreSlice => ({
chapters: { chapters: {
...READER_DEFAULT_CHAPTERS_STATE.chapters, ...READER_DEFAULT_CHAPTERS_STATE.chapters,
reset: () => ({ chapters: { ...get().chapters, ...READER_DEFAULT_CHAPTERS_STATE.chapters } }), reset: () => ({ chapters: { ...get().chapters, ...READER_DEFAULT_CHAPTERS_STATE.chapters } }),
setReaderStateChapters: (state) => setReaderStateChapters: (state) =>
set((draft) => { set(
if (typeof state === 'function') { (draft) => {
if (typeof state === 'function') {
draft.chapters = {
...get().chapters,
...state(get().chapters),
};
return;
}
draft.chapters = { draft.chapters = {
...get().chapters, ...get().chapters,
...state(get().chapters), ...state,
}; };
return; },
} undefined,
createActionName('setReaderStateChapters'),
draft.chapters = { ),
...get().chapters,
...state,
};
}),
}, },
}); });

View File

@@ -7,7 +7,7 @@
*/ */
import { ReaderStatePages, ReaderTransitionPageMode } from '@/features/reader/Reader.types.ts'; import { ReaderStatePages, ReaderTransitionPageMode } from '@/features/reader/Reader.types.ts';
import { ImmerStateCreator } from '@/lib/zustand/Zustand.types.ts'; import { SliceCreator } from '@/lib/zustand/Zustand.types.ts';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
export interface ReaderPagesStoreSlice { export interface ReaderPagesStoreSlice {
@@ -48,47 +48,79 @@ export const READER_DEFAULT_PAGES_STATE: Omit<
}; };
export const createReaderPagesStoreSlice = <T extends ReaderPagesStoreSlice>( export const createReaderPagesStoreSlice = <T extends ReaderPagesStoreSlice>(
...[set, get]: Parameters<ImmerStateCreator<T>> ...[createActionName, set, get]: Parameters<SliceCreator<T>>
): ReaderPagesStoreSlice => ({ ): ReaderPagesStoreSlice => ({
pages: { pages: {
...READER_DEFAULT_PAGES_STATE, ...READER_DEFAULT_PAGES_STATE,
reset: () => ({ pages: { ...get().pages, ...READER_DEFAULT_PAGES_STATE } }), reset: () => ({ pages: { ...get().pages, ...READER_DEFAULT_PAGES_STATE } }),
setCurrentPageIndex: (index) => setCurrentPageIndex: (index) =>
set((draft) => { set(
draft.pages.currentPageIndex = index; (draft) => {
}), draft.pages.currentPageIndex = index;
},
undefined,
createActionName('setCurrentPageIndex'),
),
setPageToScrollToIndex: (index) => setPageToScrollToIndex: (index) =>
set((draft) => { set(
draft.pages.pageToScrollToIndex = index; (draft) => {
}), draft.pages.pageToScrollToIndex = index;
},
undefined,
createActionName('setPageToScrollToIndex'),
),
setTotalPages: (total) => setTotalPages: (total) =>
set((draft) => { set(
draft.pages.totalPages = total; (draft) => {
}), draft.pages.totalPages = total;
},
undefined,
createActionName('setTotalPages'),
),
setPageUrls: (urls) => setPageUrls: (urls) =>
set((draft) => { set(
draft.pages.pageUrls = urls; (draft) => {
}), draft.pages.pageUrls = urls;
},
undefined,
createActionName('setPageUrls'),
),
setPageLoadStates: (loadStates) => setPageLoadStates: (loadStates) =>
set((draft) => { set(
if (typeof loadStates === 'function') { (draft) => {
draft.pages.pageLoadStates = loadStates(get().pages.pageLoadStates); if (typeof loadStates === 'function') {
return; draft.pages.pageLoadStates = loadStates(get().pages.pageLoadStates);
} return;
}
draft.pages.pageLoadStates = loadStates; draft.pages.pageLoadStates = loadStates;
}), },
undefined,
createActionName('setPageLoadStates'),
),
setPages: (pages) => setPages: (pages) =>
set((draft) => { set(
draft.pages.pages = pages; (draft) => {
}), draft.pages.pages = pages;
},
undefined,
createActionName('setPages'),
),
setTransitionPageMode: (mode) => setTransitionPageMode: (mode) =>
set((draft) => { set(
draft.pages.transitionPageMode = mode; (draft) => {
}), draft.pages.transitionPageMode = mode;
},
undefined,
createActionName('setTransitionPageMode'),
),
setRetryFailedPagesKeyPrefix: (prefix) => setRetryFailedPagesKeyPrefix: (prefix) =>
set((draft) => { set(
draft.pages.retryFailedPagesKeyPrefix = prefix; (draft) => {
}), draft.pages.retryFailedPagesKeyPrefix = prefix;
},
undefined,
createActionName('setRetryFailedPagesKeyPrefix'),
),
}, },
}); });

View File

@@ -7,6 +7,7 @@
*/ */
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { TMangaReader } from '@/features/manga/Manga.types.ts'; import { TMangaReader } from '@/features/manga/Manga.types.ts';
@@ -33,6 +34,9 @@ import {
createReaderChaptersStoreSlice, createReaderChaptersStoreSlice,
ReaderChaptersStoreSlice, ReaderChaptersStoreSlice,
} from '@/features/reader/stores/ReaderChaptersStore.ts'; } from '@/features/reader/stores/ReaderChaptersStore.ts';
import { ZustandUtil } from '@/lib/zustand/ZustandUtil.ts';
const UNSERIALIZABLE_KEYS = ['scrollRef'];
interface ReaderStore interface ReaderStore
extends ReaderOverlayStoreSlice, extends ReaderOverlayStoreSlice,
@@ -64,59 +68,100 @@ const DEFAULT_STATE = {
} satisfies Pick<ReaderStore, 'manga'> & { scrollbar: Pick<ReaderStore['scrollbar'], 'xSize' | 'ySize'> }; } satisfies Pick<ReaderStore, 'manga'> & { scrollbar: Pick<ReaderStore['scrollbar'], 'xSize' | 'ySize'> };
const readerStore = create<ReaderStore>()( const readerStore = create<ReaderStore>()(
immer((set, get, store) => ({ devtools(
...DEFAULT_STATE, immer((set, get, store) => {
reset: () => const createActionName = ZustandUtil.createActionNameCreator('reader');
set(() => ({ const createActionNameCreator = (name: string) =>
...get(), ZustandUtil.createActionNameCreator(name, createActionName);
return {
...DEFAULT_STATE, ...DEFAULT_STATE,
reset: () =>
set(
() => ({
...get(),
...DEFAULT_STATE,
scrollbar: {
...get().scrollbar,
...DEFAULT_STATE.scrollbar,
},
settings: {
...get().settings,
...DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG,
},
...get().overlay.reset(),
...get().autoScroll.reset(),
...get().pages.reset(),
...get().chapters.reset(),
...get().progressBar.reset(),
...get().tapZone.reset(),
}),
undefined,
createActionName('reset'),
),
setManga: (manga) =>
set(
(draft) => {
draft.manga = manga;
},
undefined,
createActionName('setManga'),
),
scrollbar: { scrollbar: {
...get().scrollbar,
...DEFAULT_STATE.scrollbar, ...DEFAULT_STATE.scrollbar,
setXSize: (size) =>
set(
(draft) => {
draft.scrollbar.xSize = size;
},
undefined,
createActionName('scrollbar', 'setXSize'),
),
setYSize: (size) =>
set(
(draft) => {
draft.scrollbar.ySize = size;
},
undefined,
createActionName('scrollbar', 'setYSize'),
),
}, },
settings: { settings: {
...get().settings,
...DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG, ...DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG,
setSettings: (settings) =>
set(
(draft) => {
draft.settings = {
...get().settings,
...settings,
};
},
undefined,
createActionName('settings', 'setSettings'),
),
}, },
...get().overlay.reset(), ...createReaderOverlayStoreSlice(createActionNameCreator('overlay'), set, get, store),
...get().autoScroll.reset(), ...createReaderAutoScrollStoreSlice(createActionNameCreator('autoScroll'), set, get, store),
...get().pages.reset(), ...createReaderPagesStoreSlice(createActionNameCreator('pages'), set, get, store),
...get().chapters.reset(), ...createReaderChaptersStoreSlice(createActionNameCreator('chapters'), set, get, store),
...get().progressBar.reset(), ...createReaderProgressBarStoreSlice(createActionNameCreator('progressBar'), set, get, store),
...get().tapZone.reset(), ...createReaderTapZoneStoreSlice(createActionNameCreator('tapZone'), set, get, store),
})), };
setManga: (manga) => }),
set((draft) => { {
draft.manga = manga; name: 'ReaderStore',
}), anonymousActionType: 'reader/action',
scrollbar: { serialize: {
...DEFAULT_STATE.scrollbar, replacer: (key: string, value: any) => {
setXSize: (size) => if (UNSERIALIZABLE_KEYS.includes(key)) {
set((draft) => { return null;
draft.scrollbar.xSize = size; }
}),
setYSize: (size) => return value;
set((draft) => { },
draft.scrollbar.ySize = size; },
}),
}, },
settings: { ),
...DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG,
setSettings: (settings) =>
set((draft) => {
draft.settings = {
...get().settings,
...settings,
};
}),
},
...createReaderOverlayStoreSlice(set, get, store),
...createReaderAutoScrollStoreSlice(set, get, store),
...createReaderPagesStoreSlice(set, get, store),
...createReaderChaptersStoreSlice(set, get, store),
...createReaderProgressBarStoreSlice(set, get, store),
...createReaderTapZoneStoreSlice(set, get, store),
})),
); );
export const useReaderStore = <T>(selector: (state: ReaderStore) => T): T => readerStore(useShallow(selector)); export const useReaderStore = <T>(selector: (state: ReaderStore) => T): T => readerStore(useShallow(selector));
export const getReaderStore = () => readerStore.getState(); export const getReaderStore = () => readerStore.getState();

View File

@@ -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 { ImmerStateCreator } from '@/lib/zustand/Zustand.types.ts'; import { SliceCreator } from '@/lib/zustand/Zustand.types.ts';
import { TReaderTapZoneContext } from '@/features/reader/tap-zones/TapZoneLayout.types.ts'; import { TReaderTapZoneContext } from '@/features/reader/tap-zones/TapZoneLayout.types.ts';
export interface ReaderTapZoneStoreSlice { export interface ReaderTapZoneStoreSlice {
@@ -20,14 +20,18 @@ const DEFAULT_STATE = {
} satisfies Pick<ReaderTapZoneStoreSlice['tapZone'], 'showPreview'>; } satisfies Pick<ReaderTapZoneStoreSlice['tapZone'], 'showPreview'>;
export const createReaderTapZoneStoreSlice = <T extends ReaderTapZoneStoreSlice>( export const createReaderTapZoneStoreSlice = <T extends ReaderTapZoneStoreSlice>(
...[set, get]: Parameters<ImmerStateCreator<T>> ...[createActionName, set, get]: Parameters<SliceCreator<T>>
): ReaderTapZoneStoreSlice => ({ ): ReaderTapZoneStoreSlice => ({
tapZone: { tapZone: {
...DEFAULT_STATE, ...DEFAULT_STATE,
setShowPreview: (showPreview) => setShowPreview: (showPreview) =>
set((draft) => { set(
draft.tapZone.showPreview = showPreview; (draft) => {
}), draft.tapZone.showPreview = showPreview;
},
undefined,
createActionName('setShowPreview'),
),
reset: () => ({ tapZone: { ...get().tapZone, ...DEFAULT_STATE } }), reset: () => ({ tapZone: { ...get().tapZone, ...DEFAULT_STATE } }),
}, },
}); });

View File

@@ -6,6 +6,11 @@
* 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 { StateCreator } from 'zustand'; import { StateCreator as ZustandStateCreator } from 'zustand';
export type ImmerStateCreator<T> = StateCreator<T, [['zustand/immer', never], never], [], T>; export type StateCreator<T> = ZustandStateCreator<T, [['zustand/devtools', never], ['zustand/immer', never]], [], T>;
export type SliceCreator<T> = (
actionNameCreator: (...names: string[]) => string,
...args: Parameters<StateCreator<T>>
) => T;

View File

@@ -0,0 +1,23 @@
/*
* 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/.
*/
export class ZustandUtil {
static createActionName(...names: string[]) {
const storeAndSlices = names.slice(0, -1);
const action = names.slice(-1)[0];
return `${storeAndSlices.join(':')}/${action}`;
}
static createActionNameCreator(
name: string,
parentCreator: typeof ZustandUtil.createActionName = ZustandUtil.createActionName,
): typeof ZustandUtil.createActionName {
return (...names: string[]) => parentCreator(name, ...names);
}
}

View File

@@ -2634,6 +2634,14 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@redux-devtools/extension@3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@redux-devtools/extension/-/extension-3.3.0.tgz#bc775d289f15604c472112920beac2cf4dbb7907"
integrity sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==
dependencies:
"@babel/runtime" "^7.23.2"
immutable "^4.3.4"
"@remix-run/router@1.19.1": "@remix-run/router@1.19.1":
version "1.19.1" version "1.19.1"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.1.tgz#984771bfd1de2715f42394c87fb716c1349e014f" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.1.tgz#984771bfd1de2715f42394c87fb716c1349e014f"
@@ -5981,6 +5989,11 @@ immer@10.1.1:
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc" resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
immutable@^4.3.4:
version "4.3.7"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
immutable@~3.7.6: immutable@~3.7.6:
version "3.7.6" version "3.7.6"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"