pl-fe: Replace immer with mutative

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-11-05 20:45:57 +01:00
parent 7ca17884f8
commit cc858d4f0f
10 changed files with 133 additions and 108 deletions

View file

@ -87,7 +87,6 @@
"fuzzysort": "^3.0.2", "fuzzysort": "^3.0.2",
"graphemesplit": "^2.4.4", "graphemesplit": "^2.4.4",
"html-react-parser": "^5.1.16", "html-react-parser": "^5.1.16",
"immer": "^10.1.1",
"immutable": "^4.3.7", "immutable": "^4.3.7",
"intersection-observer": "^0.12.2", "intersection-observer": "^0.12.2",
"intl-messageformat": "^10.5.14", "intl-messageformat": "^10.5.14",
@ -100,6 +99,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.9.1", "mini-css-extract-plugin": "^2.9.1",
"multiselect-react-dropdown": "^2.0.25", "multiselect-react-dropdown": "^2.0.25",
"mutative": "^1.0.11",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"pl-api": "^0.1.9", "pl-api": "^0.1.9",
"postcss": "^8.4.47", "postcss": "^8.4.47",
@ -142,7 +142,8 @@
"vite-plugin-html": "^3.2.2", "vite-plugin-html": "^3.2.2",
"vite-plugin-require": "^1.2.14", "vite-plugin-require": "^1.2.14",
"vite-plugin-static-copy": "^1.0.6", "vite-plugin-static-copy": "^1.0.6",
"zustand": "^5.0.0-rc.2" "zustand": "^5.0.0-rc.2",
"zustand-mutative": "^1.0.5"
}, },
"devDependencies": { "devDependencies": {
"@formatjs/cli": "^6.2.12", "@formatjs/cli": "^6.2.12",

View file

@ -1,4 +1,4 @@
import { produce, enableMapSet } from 'immer'; import { create, type Immutable } from 'mutative';
import { import {
ENTITIES_IMPORT, ENTITIES_IMPORT,
@ -17,12 +17,10 @@ import { createCache, createList, updateStore, updateList } from './utils';
import type { DeleteEntitiesOpts } from './actions'; import type { DeleteEntitiesOpts } from './actions';
import type { EntitiesTransaction, Entity, EntityCache, EntityListState, ImportPosition } from './types'; import type { EntitiesTransaction, Entity, EntityCache, EntityListState, ImportPosition } from './types';
enableMapSet();
/** Entity reducer state. */ /** Entity reducer state. */
interface State { type State = Immutable<{
[entityType: string]: EntityCache | undefined; [entityType: string]: EntityCache | undefined;
} }>;
/** Import entities into the cache. */ /** Import entities into the cache. */
const importEntities = ( const importEntities = (
@ -33,7 +31,7 @@ const importEntities = (
pos?: ImportPosition, pos?: ImportPosition,
newState?: EntityListState, newState?: EntityListState,
overwrite = false, overwrite = false,
): State => produce(state, draft => { ): State => create(state, draft => {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
cache.store = updateStore(cache.store, entities); cache.store = updateStore(cache.store, entities);
@ -54,14 +52,16 @@ const importEntities = (
} }
draft[entityType] = cache; draft[entityType] = cache;
});
return draft;
}, { enableAutoFreeze: true });
const deleteEntities = ( const deleteEntities = (
state: State, state: State,
entityType: string, entityType: string,
ids: Iterable<string>, ids: Iterable<string>,
opts: DeleteEntitiesOpts, opts: DeleteEntitiesOpts,
) => produce(state, draft => { ) => create(state, draft => {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
for (const id of ids) { for (const id of ids) {
@ -88,7 +88,7 @@ const dismissEntities = (
entityType: string, entityType: string,
ids: Iterable<string>, ids: Iterable<string>,
listKey: string, listKey: string,
) => produce(state, draft => { ) => create(state, draft => {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
const list = cache.lists[listKey]; const list = cache.lists[listKey];
@ -110,7 +110,7 @@ const incrementEntities = (
entityType: string, entityType: string,
listKey: string, listKey: string,
diff: number, diff: number,
) => produce(state, draft => { ) => create(state, draft => {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
const list = cache.lists[listKey]; const list = cache.lists[listKey];
@ -126,7 +126,7 @@ const setFetching = (
listKey: string | undefined, listKey: string | undefined,
isFetching: boolean, isFetching: boolean,
error?: any, error?: any,
) => produce(state, draft => { ) => create(state, draft => {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
if (typeof listKey === 'string') { if (typeof listKey === 'string') {
@ -139,13 +139,13 @@ const setFetching = (
draft[entityType] = cache; draft[entityType] = cache;
}); });
const invalidateEntityList = (state: State, entityType: string, listKey: string) => produce(state, draft => { const invalidateEntityList = (state: State, entityType: string, listKey: string) => create(state, draft => {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
const list = cache.lists[listKey] ?? createList(); const list = cache.lists[listKey] ?? createList();
list.state.invalid = true; list.state.invalid = true;
}); });
const doTransaction = (state: State, transaction: EntitiesTransaction) => produce(state, draft => { const doTransaction = (state: State, transaction: EntitiesTransaction) => create(state, draft => {
for (const [entityType, changes] of Object.entries(transaction)) { for (const [entityType, changes] of Object.entries(transaction)) {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
for (const [id, change] of Object.entries(changes)) { for (const [id, change] of Object.entries(changes)) {

View file

@ -39,9 +39,9 @@ const UserIndex: React.FC = () => {
updateQuery(); updateQuery();
}, []); }, []);
const hasMore = items.count() < total && !!next; const hasMore = items.length < total && !!next;
const showLoading = isLoading && items.isEmpty(); const showLoading = isLoading && !items.length;
return ( return (
<Column label={intl.formatMessage(messages.heading)}> <Column label={intl.formatMessage(messages.heading)}>

View file

@ -2,8 +2,7 @@
* Accounts Meta: private user data only the owner should see. * Accounts Meta: private user data only the owner should see.
* @module pl-fe/reducers/accounts_meta * @module pl-fe/reducers/accounts_meta
*/ */
import { create, type Immutable } from 'mutative';
import { produce } from 'immer';
import { VERIFY_CREDENTIALS_SUCCESS, AUTH_ACCOUNT_REMEMBER_SUCCESS, type AuthAction } from 'pl-fe/actions/auth'; import { VERIFY_CREDENTIALS_SUCCESS, AUTH_ACCOUNT_REMEMBER_SUCCESS, type AuthAction } from 'pl-fe/actions/auth';
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, type MeAction } from 'pl-fe/actions/me'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, type MeAction } from 'pl-fe/actions/me';
@ -15,17 +14,17 @@ interface AccountMeta {
source: Account['__meta']['source']; source: Account['__meta']['source'];
} }
type State = Record<string, AccountMeta | undefined>; type State = Immutable<Record<string, AccountMeta | undefined>>;
const importAccount = (state: State, account: CredentialAccount): State => const importAccount = (state: State, account: CredentialAccount): State =>
produce(state, draft => { create(state, draft => {
const existing = draft[account.id]; const existing = draft[account.id];
draft[account.id] = { draft[account.id] = {
pleroma: account.__meta.pleroma ?? existing?.pleroma, pleroma: account.__meta.pleroma ?? existing?.pleroma,
source: account.__meta.source ?? existing?.source, source: account.__meta.source ?? existing?.source,
}; };
}); }, { enableAutoFreeze: true });
const accounts_meta = (state: Readonly<State> = {}, action: AuthAction | MeAction): State => { const accounts_meta = (state: Readonly<State> = {}, action: AuthAction | MeAction): State => {
switch (action.type) { switch (action.type) {

View file

@ -1,4 +1,4 @@
import { OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord } from 'immutable'; import { create } from 'mutative';
import { import {
ADMIN_USER_INDEX_EXPAND_FAIL, ADMIN_USER_INDEX_EXPAND_FAIL,
@ -10,58 +10,74 @@ import {
ADMIN_USER_INDEX_QUERY_SET, ADMIN_USER_INDEX_QUERY_SET,
} from 'pl-fe/actions/admin'; } from 'pl-fe/actions/admin';
import type { AnyAction } from '@reduxjs/toolkit';
import type { AdminAccount, AdminGetAccountsParams, PaginatedResponse } from 'pl-api'; import type { AdminAccount, AdminGetAccountsParams, PaginatedResponse } from 'pl-api';
import type { APIEntity } from 'pl-fe/types/entities'; import type { APIEntity } from 'pl-fe/types/entities';
import type { AnyAction } from 'redux';
const ReducerRecord = ImmutableRecord({ type State = {
isLoading: boolean;
loaded: boolean;
items: Array<string>;
total: number;
page: number;
query: string;
next: (() => Promise<PaginatedResponse<AdminAccount>>) | null;
params: AdminGetAccountsParams | null;
}
const initialState: State = {
isLoading: false, isLoading: false,
loaded: false, loaded: false,
items: ImmutableOrderedSet<string>(), items: [],
total: Infinity, total: Infinity,
page: -1, page: -1,
query: '', query: '',
next: null as (() => Promise<PaginatedResponse<AdminAccount>>) | null, next: null,
params: null as AdminGetAccountsParams | null, params: null,
}); };
type State = ReturnType<typeof ReducerRecord>; const admin_user_index = (state: State = initialState, action: AnyAction): State => {
const admin_user_index = (state: State = ReducerRecord(), action: AnyAction): State => {
switch (action.type) { switch (action.type) {
case ADMIN_USER_INDEX_QUERY_SET: case ADMIN_USER_INDEX_QUERY_SET:
return state.set('query', action.query); return create(state, draft => {
draft.query = action.query;
});
case ADMIN_USER_INDEX_FETCH_REQUEST: case ADMIN_USER_INDEX_FETCH_REQUEST:
return state return create(state, draft => {
.set('isLoading', true) draft.isLoading = true;
.set('loaded', true) draft.loaded = true;
.set('items', ImmutableOrderedSet()) draft.items = [];
.set('total', action.total) draft.total = action.total;
.set('page', 0) draft.page = 0;
.set('next', null); draft.next = null;
});
case ADMIN_USER_INDEX_FETCH_SUCCESS: case ADMIN_USER_INDEX_FETCH_SUCCESS:
return state return create(state, draft => {
.set('isLoading', false) draft.isLoading = false;
.set('loaded', true) draft.loaded = true;
.set('items', ImmutableOrderedSet(action.users.map((user: APIEntity) => user.id))) draft.items = action.users.map((user: APIEntity) => user.id);
.set('total', action.total) draft.total = action.total;
.set('page', 1) draft.page = 1;
.set('next', action.next); draft.next = action.next;
});
case ADMIN_USER_INDEX_FETCH_FAIL: case ADMIN_USER_INDEX_FETCH_FAIL:
case ADMIN_USER_INDEX_EXPAND_FAIL: case ADMIN_USER_INDEX_EXPAND_FAIL:
return state return create(state, draft => {
.set('isLoading', false); draft.isLoading = false;
});
case ADMIN_USER_INDEX_EXPAND_REQUEST: case ADMIN_USER_INDEX_EXPAND_REQUEST:
return state return create(state, draft => {
.set('isLoading', true); draft.isLoading = true;
});
case ADMIN_USER_INDEX_EXPAND_SUCCESS: case ADMIN_USER_INDEX_EXPAND_SUCCESS:
return state return create(state, draft => {
.set('isLoading', false) draft.isLoading = false;
.set('loaded', true) draft.loaded = true;
.set('items', state.items.union(action.users.map((user: APIEntity) => user.id))) draft.items = [...new Set(draft.items.concat(action.users.map((user: APIEntity) => user.id)))];
.set('total', action.total) draft.total = action.total;
.set('page', 1) draft.page = 1;
.set('next', action.next); draft.next = action.next;
});
default: default:
return state; return state;
} }

View file

@ -1,5 +1,5 @@
import { produce } from 'immer';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
import { create } from 'mutative';
import { type Instance, instanceSchema } from 'pl-api'; import { type Instance, instanceSchema } from 'pl-api';
import * as v from 'valibot'; import * as v from 'valibot';
@ -11,9 +11,11 @@ import ConfigDB from 'pl-fe/utils/config-db';
import type { AnyAction } from 'redux'; import type { AnyAction } from 'redux';
const initialState: Instance = v.parse(instanceSchema, {}); const initialState: State = v.parse(instanceSchema, {});
const preloadImport = (state: Instance, action: Record<string, any>, path: string) => { type State = Instance;
const preloadImport = (state: State, action: Record<string, any>, path: string) => {
const instance = action.data[path]; const instance = action.data[path];
return instance ? v.parse(instanceSchema, instance) : state; return instance ? v.parse(instanceSchema, instance) : state;
}; };
@ -25,32 +27,30 @@ const getConfigValue = (instanceConfig: ImmutableMap<string, any>, key: string)
return v ? v.getIn(['tuple', 1]) : undefined; return v ? v.getIn(['tuple', 1]) : undefined;
}; };
const importConfigs = (state: Instance, configs: ImmutableList<any>) => { const importConfigs = (state: State, configs: ImmutableList<any>) => {
// FIXME: This is pretty hacked together. Need to make a cleaner map. // FIXME: This is pretty hacked together. Need to make a cleaner map.
const config = ConfigDB.find(configs, ':pleroma', ':instance'); const config = ConfigDB.find(configs, ':pleroma', ':instance');
const simplePolicy = ConfigDB.toSimplePolicy(configs); const simplePolicy = ConfigDB.toSimplePolicy(configs);
if (!config && !simplePolicy) return state; if (!config && !simplePolicy) return state;
return produce(state, (draft) => { if (config) {
if (config) { const value = config.get('value', ImmutableList());
const value = config.get('value', ImmutableList()); const registrationsOpen = getConfigValue(value, ':registrations_open') as boolean | undefined;
const registrationsOpen = getConfigValue(value, ':registrations_open') as boolean | undefined; const approvalRequired = getConfigValue(value, ':account_approval_required') as boolean | undefined;
const approvalRequired = getConfigValue(value, ':account_approval_required') as boolean | undefined;
draft.registrations = { state.registrations = {
enabled: registrationsOpen ?? draft.registrations.enabled, enabled: registrationsOpen ?? state.registrations.enabled,
approval_required: approvalRequired ?? draft.registrations.approval_required, approval_required: approvalRequired ?? state.registrations.approval_required,
}; };
} }
if (simplePolicy) { if (simplePolicy) {
draft.pleroma.metadata.federation.mrf_simple = simplePolicy; state.pleroma.metadata.federation.mrf_simple = simplePolicy;
} }
});
}; };
const handleAuthFetch = (state: Instance) => { const handleAuthFetch = (state: State) => {
// Authenticated fetch is enabled, so make the instance appear censored // Authenticated fetch is enabled, so make the instance appear censored
return { return {
...state, ...state,
@ -78,7 +78,7 @@ const persistInstance = (instance: { domain: string }, host: string | null = get
} }
}; };
const handleInstanceFetchFail = (state: Instance, error: Record<string, any>) => { const handleInstanceFetchFail = (state: State, error: Record<string, any>) => {
if (error.response?.status === 401) { if (error.response?.status === 401) {
return handleAuthFetch(state); return handleAuthFetch(state);
} else { } else {
@ -86,10 +86,10 @@ const handleInstanceFetchFail = (state: Instance, error: Record<string, any>) =>
} }
}; };
const instance = (state = initialState, action: AnyAction | InstanceAction | PreloadAction): Instance => { const instance = (state = initialState, action: AnyAction | InstanceAction | PreloadAction): State => {
switch (action.type) { switch (action.type) {
case PLEROMA_PRELOAD_IMPORT: case PLEROMA_PRELOAD_IMPORT:
return preloadImport(state, action, '/api/v1/instance'); return create(state, (draft) => preloadImport(draft, action, '/api/v1/instance'));
case INSTANCE_FETCH_SUCCESS: case INSTANCE_FETCH_SUCCESS:
persistInstance(action.instance); persistInstance(action.instance);
return action.instance; return action.instance;
@ -97,10 +97,9 @@ const instance = (state = initialState, action: AnyAction | InstanceAction | Pre
return handleInstanceFetchFail(state, action.error); return handleInstanceFetchFail(state, action.error);
case ADMIN_CONFIG_UPDATE_REQUEST: case ADMIN_CONFIG_UPDATE_REQUEST:
case ADMIN_CONFIG_UPDATE_SUCCESS: case ADMIN_CONFIG_UPDATE_SUCCESS:
return importConfigs(state, ImmutableList(fromJS(action.configs))); return create(state, (draft) => importConfigs(draft, ImmutableList(fromJS(action.configs))));
default: default:
return state; return state;
} }
}; };
export { instance as default }; export { instance as default };

View file

@ -1,12 +1,10 @@
import { configureStore, Tuple } from '@reduxjs/toolkit'; import { configureStore, Tuple, type AnyAction } from '@reduxjs/toolkit';
import { thunk, type ThunkDispatch } from 'redux-thunk'; import { thunk, type ThunkDispatch } from 'redux-thunk';
import errorsMiddleware from './middleware/errors'; import errorsMiddleware from './middleware/errors';
import soundsMiddleware from './middleware/sounds'; import soundsMiddleware from './middleware/sounds';
import appReducer from './reducers'; import appReducer from './reducers';
import type { AnyAction } from 'redux';
const store = configureStore({ const store = configureStore({
reducer: appReducer, reducer: appReducer,
middleware: () => new Tuple( middleware: () => new Tuple(
@ -17,6 +15,8 @@ const store = configureStore({
devTools: true, devTools: true,
}); });
(window as any).store = store;
type Store = typeof store; type Store = typeof store;
// Infer the `RootState` and `AppDispatch` types from the store itself // Infer the `RootState` and `AppDispatch` types from the store itself

View file

@ -1,5 +1,5 @@
import { produce } from 'immer';
import { create } from 'zustand'; import { create } from 'zustand';
import { mutative } from 'zustand-mutative';
import type { ICryptoAddress } from 'pl-fe/features/crypto-donate/components/crypto-address'; import type { ICryptoAddress } from 'pl-fe/features/crypto-donate/components/crypto-address';
import type { ModalType } from 'pl-fe/features/ui/components/modal-root'; import type { ModalType } from 'pl-fe/features/ui/components/modal-root';
@ -86,12 +86,12 @@ type State = {
closeModal: (modalType?: ModalType) => void; closeModal: (modalType?: ModalType) => void;
}; };
const useModalsStore = create<State>((set) => ({ const useModalsStore = create<State>()(mutative((set) => ({
modals: [], modals: [],
openModal: (...[modalType, modalProps]) => set(produce((state: State) => { openModal: (...[modalType, modalProps]) => set((state: State) => {
state.modals.push({ modalType, modalProps }); state.modals.push({ modalType, modalProps });
})), }),
closeModal: (modalType) => set(produce((state: State) => { closeModal: (modalType) => set((state: State) => {
if (state.modals.length === 0) { if (state.modals.length === 0) {
return; return;
} }
@ -100,7 +100,7 @@ const useModalsStore = create<State>((set) => ({
} else if (state.modals.some((modal) => modalType === modal.modalType)) { } else if (state.modals.some((modal) => modalType === modal.modalType)) {
state.modals = state.modals.slice(0, state.modals.findLastIndex((modal) => modalType === modal.modalType)); state.modals = state.modals.slice(0, state.modals.findLastIndex((modal) => modalType === modal.modalType));
} }
})), }),
})); }), { enableAutoFreeze: true }));
export { useModalsStore }; export { useModalsStore };

View file

@ -1,6 +1,6 @@
import { produce } from 'immer';
import * as v from 'valibot'; import * as v from 'valibot';
import { create } from 'zustand'; import { create } from 'zustand';
import { mutative } from 'zustand-mutative';
import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings'; import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings';
@ -35,39 +35,39 @@ const changeSetting = (object: APIEntity, path: string[], value: any) => {
const mergeSettings = (state: State) => state.settings = { ...state.defaultSettings, ...state.userSettings }; const mergeSettings = (state: State) => state.settings = { ...state.defaultSettings, ...state.userSettings };
const useSettingsStore = create<State>((set) => ({ const useSettingsStore = create<State>()(mutative((set) => ({
defaultSettings: v.parse(settingsSchema, {}), defaultSettings: v.parse(settingsSchema, {}),
userSettings: {}, userSettings: {},
settings: v.parse(settingsSchema, {}), settings: v.parse(settingsSchema, {}),
loadDefaultSettings: (settings: APIEntity) => set(produce((state: State) => { loadDefaultSettings: (settings: APIEntity) => set((state: State) => {
if (typeof settings !== 'object') return; if (typeof settings !== 'object') return;
state.defaultSettings = v.parse(settingsSchema, settings); state.defaultSettings = v.parse(settingsSchema, settings);
mergeSettings(state); mergeSettings(state);
})), }),
loadUserSettings: (settings?: APIEntity) => set(produce((state: State) => { loadUserSettings: (settings?: APIEntity) => set((state: State) => {
if (typeof settings !== 'object') return; if (typeof settings !== 'object') return;
state.userSettings = v.parse(settingsSchemaPartial, settings); state.userSettings = v.parse(settingsSchemaPartial, settings);
mergeSettings(state); mergeSettings(state);
})), }),
userSettingsSaving: () => set(produce((state: State) => { userSettingsSaving: () => set((state: State) => {
state.userSettings.saved = true; state.userSettings.saved = true;
mergeSettings(state); mergeSettings(state);
})), }),
changeSetting: (path: string[], value: any) => set(produce((state: State) => { changeSetting: (path: string[], value: any) => set((state: State) => {
changeSetting(state.userSettings, path, value); changeSetting(state.userSettings, path, value);
mergeSettings(state); mergeSettings(state);
})), }),
rememberEmojiUse: (emoji: Emoji) => set(produce((state: State) => { rememberEmojiUse: (emoji: Emoji) => set((state: State) => {
const settings = state.userSettings; const settings = state.userSettings;
if (!settings.frequentlyUsedEmojis) settings.frequentlyUsedEmojis = {}; if (!settings.frequentlyUsedEmojis) settings.frequentlyUsedEmojis = {};
@ -75,9 +75,9 @@ const useSettingsStore = create<State>((set) => ({
settings.saved = false; settings.saved = false;
mergeSettings(state); mergeSettings(state);
})), }),
rememberLanguageUse: (language: string) => set(produce((state: State) => { rememberLanguageUse: (language: string) => set((state: State) => {
const settings = state.userSettings; const settings = state.userSettings;
if (!settings.frequentlyUsedLanguages) settings.frequentlyUsedLanguages = {}; if (!settings.frequentlyUsedLanguages) settings.frequentlyUsedLanguages = {};
@ -85,8 +85,8 @@ const useSettingsStore = create<State>((set) => ({
settings.saved = false; settings.saved = false;
mergeSettings(state); mergeSettings(state);
})), }),
})); }), { enableAutoFreeze: true }));
export { useSettingsStore }; export { useSettingsStore };

View file

@ -5812,7 +5812,7 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
immer@^10.0.3, immer@^10.1.1: immer@^10.0.3:
version "10.1.1" version "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==
@ -7146,6 +7146,11 @@ multiselect-react-dropdown@^2.0.25:
resolved "https://registry.yarnpkg.com/multiselect-react-dropdown/-/multiselect-react-dropdown-2.0.25.tgz#0c8d16f20d78023d5be2f3af4f15a4a164b6b427" resolved "https://registry.yarnpkg.com/multiselect-react-dropdown/-/multiselect-react-dropdown-2.0.25.tgz#0c8d16f20d78023d5be2f3af4f15a4a164b6b427"
integrity sha512-z8kUSyBNOuV7vn4Dk35snzXWtIfTdSEEXhgDdLMvOmR+xJFx35vc1voUlSuOvrk3khb+hXC219Qs9szOvNm25Q== integrity sha512-z8kUSyBNOuV7vn4Dk35snzXWtIfTdSEEXhgDdLMvOmR+xJFx35vc1voUlSuOvrk3khb+hXC219Qs9szOvNm25Q==
mutative@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/mutative/-/mutative-1.0.11.tgz#a03b48151800129fbc464257eff9ffa869c648a8"
integrity sha512-DfxsNvHfJlxp5yul7jhcNSI0EEWlP1vatiOr6Q7cvr8RNFBbIU5nENilUULbNJiOtbXznOxgbxHf4cYbqPDPlg==
mz@^2.7.0: mz@^2.7.0:
version "2.7.0" version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
@ -10429,6 +10434,11 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zustand-mutative@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zustand-mutative/-/zustand-mutative-1.0.5.tgz#00ce93900a25d812b48d3f0d360da8a3890d0dd9"
integrity sha512-/Rc2huxi2SxD21EcSrwW6moCRv+phFcrv3bMyrsQMDkI49LqLqMzhfNCZnOv0TmUctgG0u9xD6gHLUoYEwjkkQ==
zustand@^5.0.0-rc.2: zustand@^5.0.0-rc.2:
version "5.0.0-rc.2" version "5.0.0-rc.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.0-rc.2.tgz#d28d95ffb6f0b20cadbaea39210f18446a5bf989" resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.0-rc.2.tgz#d28d95ffb6f0b20cadbaea39210f18446a5bf989"