re-add useTransition to router

This commit is contained in:
jeffvli
2026-04-05 21:54:07 -07:00
parent 56cd50e0ed
commit c8e8f58cce
19 changed files with 375 additions and 300 deletions
-1
View File
@@ -121,7 +121,6 @@
"mpris-service": "^2.1.2",
"nanoid": "^3.3.11",
"node-mpv": "github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f",
"nuqs": "^2.8.9",
"overlayscrollbars": "^2.14.0",
"overlayscrollbars-react": "^0.5.6",
"qs": "^6.15.0",
-51
View File
@@ -156,9 +156,6 @@ importers:
node-mpv:
specifier: github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f
version: https://codeload.github.com/jeffvli/Node-MPV/tar.gz/32b4d64395289ad710c41d481d2707a7acfc228f
nuqs:
specifier: ^2.8.9
version: 2.8.9(react-router-dom@7.9.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-router@7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
overlayscrollbars:
specifier: ^2.14.0
version: 2.14.0
@@ -2018,9 +2015,6 @@ packages:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'}
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
'@surma/rollup-plugin-off-main-thread@2.2.3':
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
@@ -4283,27 +4277,6 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
nuqs@2.8.9:
resolution: {integrity: sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ==}
peerDependencies:
'@remix-run/react': '>=2'
'@tanstack/react-router': ^1
next: '>=14.2.0'
react: '>=18.2.0 || ^19.0.0-0'
react-router: 7.14.0
react-router-dom: ^5 || ^6 || ^7
peerDependenciesMeta:
'@remix-run/react':
optional: true
'@tanstack/react-router':
optional: true
next:
optional: true
react-router:
optional: true
react-router-dom:
optional: true
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -4710,13 +4683,6 @@ packages:
'@types/react':
optional: true
react-router-dom@7.9.4:
resolution: {integrity: sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
react-router@7.14.0:
resolution: {integrity: sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==}
engines: {node: '>=20.0.0'}
@@ -7645,8 +7611,6 @@ snapshots:
'@sindresorhus/is@4.6.0': {}
'@standard-schema/spec@1.0.0': {}
'@surma/rollup-plugin-off-main-thread@2.2.3':
dependencies:
ejs: 3.1.10
@@ -10304,14 +10268,6 @@ snapshots:
dependencies:
boolbase: 1.0.0
nuqs@2.8.9(react-router-dom@7.9.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-router@7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4):
dependencies:
'@standard-schema/spec': 1.0.0
react: 19.2.4
optionalDependencies:
react-router: 7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react-router-dom: 7.9.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
@@ -10695,13 +10651,6 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.14
react-router-dom@7.9.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
react-router: 7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
optional: true
react-router@7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
cookie: 1.1.1
@@ -349,9 +349,12 @@ export const useItemListInfiniteLoader = ({
mutationKey: getListRefreshMutationKey(eventKey),
});
const refreshMutationRef = useRef(refreshMutation);
refreshMutationRef.current = refreshMutation;
const refresh = useCallback(
async (force?: boolean) => refreshMutation.mutateAsync(force),
[refreshMutation],
async (force?: boolean) => refreshMutationRef.current.mutateAsync(force),
[],
);
const updateItems = useCallback(
@@ -383,7 +386,7 @@ export const useItemListInfiniteLoader = ({
return;
}
refreshMutation.mutate(true);
refreshMutationRef.current.mutate(true);
};
eventEmitter.on('ITEM_LIST_REFRESH', handleRefresh);
@@ -391,7 +394,7 @@ export const useItemListInfiniteLoader = ({
return () => {
eventEmitter.off('ITEM_LIST_REFRESH', handleRefresh);
};
}, [eventKey, refreshMutation]);
}, [eventKey]);
useEffect(() => {
const handleFavorite = (payload: UserFavoriteEventPayload) => {
@@ -5,7 +5,7 @@ import {
useSuspenseQuery,
UseSuspenseQueryOptions,
} from '@tanstack/react-query';
import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { queryKeys } from '/@/renderer/api/query-keys';
import { useListContext } from '/@/renderer/context/list-context';
@@ -115,6 +115,9 @@ export const useItemListPaginatedLoader = ({
mutationKey: getListRefreshMutationKey(eventKey ?? 'paginated'),
});
const refreshMutationRef = useRef(refreshMutation);
refreshMutationRef.current = refreshMutation;
const updateItems = useCallback(
(indexes: number[], value: object) => {
return queryClient.setQueryData(
@@ -153,7 +156,7 @@ export const useItemListPaginatedLoader = ({
return;
}
refreshMutation.mutate(true);
refreshMutationRef.current.mutate(true);
};
const handleFavorite = (payload: UserFavoriteEventPayload) => {
@@ -220,7 +223,7 @@ export const useItemListPaginatedLoader = ({
eventEmitter.off('USER_FAVORITE', handleFavorite);
eventEmitter.off('USER_RATING', handleRating);
};
}, [data, eventKey, itemType, refreshMutation, serverId, updateItems]);
}, [data, eventKey, itemType, serverId, updateItems]);
return { data: data?.items || [], pageCount, totalItemCount };
};
@@ -13,6 +13,7 @@ import {
setMultipleSearchParams,
setSearchParam,
} from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types';
@@ -74,8 +75,10 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setGenreId = useCallback(
(value: null | string[]) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.GENRE_ID, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.GENRE_ID, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -83,8 +86,13 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setAlbumArtist = useCallback(
(value: null | string[]) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.ARTIST_IDS, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.ARTIST_IDS, value),
{
replace: true,
},
);
});
},
[setSearchParams],
@@ -92,8 +100,10 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setMinYear = useCallback(
(value: null | number) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.MIN_YEAR, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.MIN_YEAR, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -101,8 +111,10 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setMaxYear = useCallback(
(value: null | number) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.MAX_YEAR, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.MAX_YEAR, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -110,8 +122,10 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setFavorite = useCallback(
(value: boolean | null) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.FAVORITE, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.FAVORITE, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -119,8 +133,13 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setCompilation = useCallback(
(value: boolean | null) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.COMPILATION, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.COMPILATION, value),
{
replace: true,
},
);
});
},
[setSearchParams],
@@ -128,8 +147,13 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setHasRating = useCallback(
(value: boolean | null) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.HAS_RATING, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.HAS_RATING, value),
{
replace: true,
},
);
});
},
[setSearchParams],
@@ -137,65 +161,71 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
const setRecentlyPlayed = useCallback(
(value: boolean | null) => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.RECENTLY_PLAYED, value),
{
replace: true,
},
);
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.RECENTLY_PLAYED, value),
{
replace: true,
},
);
});
},
[setSearchParams],
);
const setCustom = useCallback(
(value: null | Record<string, any>) => {
setSearchParams(
(prev) => {
const previousValue = prev.get(FILTER_KEYS.ALBUM._CUSTOM);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const previousValue = prev.get(FILTER_KEYS.ALBUM._CUSTOM);
const newCustom = {
...(previousValue ? JSON.parse(previousValue) : {}),
...value,
};
const newCustom = {
...(previousValue ? JSON.parse(previousValue) : {}),
...value,
};
const filteredNewCustom = Object.fromEntries(
Object.entries(newCustom).filter(
([, value]) => value !== null && value !== undefined,
),
);
const filteredNewCustom = Object.fromEntries(
Object.entries(newCustom).filter(
([, value]) => value !== null && value !== undefined,
),
);
prev.set(FILTER_KEYS.ALBUM._CUSTOM, JSON.stringify(filteredNewCustom));
return prev;
},
{
replace: true,
},
);
prev.set(FILTER_KEYS.ALBUM._CUSTOM, JSON.stringify(filteredNewCustom));
return prev;
},
{
replace: true,
},
);
});
},
[setSearchParams],
);
const clear = useCallback(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(
prev,
{
[FILTER_KEYS.ALBUM._CUSTOM]: null,
[FILTER_KEYS.ALBUM.ARTIST_IDS]: null,
[FILTER_KEYS.ALBUM.COMPILATION]: null,
[FILTER_KEYS.ALBUM.FAVORITE]: null,
[FILTER_KEYS.ALBUM.GENRE_ID]: null,
[FILTER_KEYS.ALBUM.HAS_RATING]: null,
[FILTER_KEYS.ALBUM.MAX_YEAR]: null,
[FILTER_KEYS.ALBUM.MIN_YEAR]: null,
[FILTER_KEYS.ALBUM.RECENTLY_PLAYED]: null,
[FILTER_KEYS.SHARED.SEARCH_TERM]: null,
},
new Set([FILTER_KEYS.ALBUM._CUSTOM]),
),
{ replace: true },
);
runInUrlTransition(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(
prev,
{
[FILTER_KEYS.ALBUM._CUSTOM]: null,
[FILTER_KEYS.ALBUM.ARTIST_IDS]: null,
[FILTER_KEYS.ALBUM.COMPILATION]: null,
[FILTER_KEYS.ALBUM.FAVORITE]: null,
[FILTER_KEYS.ALBUM.GENRE_ID]: null,
[FILTER_KEYS.ALBUM.HAS_RATING]: null,
[FILTER_KEYS.ALBUM.MAX_YEAR]: null,
[FILTER_KEYS.ALBUM.MIN_YEAR]: null,
[FILTER_KEYS.ALBUM.RECENTLY_PLAYED]: null,
[FILTER_KEYS.SHARED.SEARCH_TERM]: null,
},
new Set([FILTER_KEYS.ALBUM._CUSTOM]),
),
{ replace: true },
);
});
}, [setSearchParams]);
const query = useMemo(
@@ -6,6 +6,7 @@ import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-f
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { setMultipleSearchParams } from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { AlbumArtistListSort } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types';
@@ -19,13 +20,15 @@ export const useAlbumArtistListFilters = () => {
const [, setSearchParams] = useSearchParams();
const clear = useCallback(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(prev, {
[FILTER_KEYS.SHARED.SEARCH_TERM]: null,
}),
{ replace: true },
);
runInUrlTransition(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(prev, {
[FILTER_KEYS.SHARED.SEARCH_TERM]: null,
}),
{ replace: true },
);
});
}, [setSearchParams]);
const query = {
@@ -6,6 +6,7 @@ import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-f
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { parseJsonParam, setJsonSearchParam } from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { SongListSort, SortOrder } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types';
@@ -29,13 +30,19 @@ export const useFolderListFilters = () => {
}, [searchParams]);
const setFolderPath = (path: FolderPathItem[]) => {
setSearchParams(
(prev) => {
const newParams = setJsonSearchParam(prev, FILTER_KEYS.FOLDER.FOLDER_PATH, path);
return newParams;
},
{ replace: false },
);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const newParams = setJsonSearchParam(
prev,
FILTER_KEYS.FOLDER.FOLDER_PATH,
path,
);
return newParams;
},
{ replace: false },
);
});
};
// Navigate to a folder (adds to path)
@@ -6,6 +6,7 @@ import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-f
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { parseCustomFiltersParam } from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { PlaylistListSort } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types';
@@ -24,28 +25,30 @@ export const usePlaylistListFilters = () => {
const setCustom = useCallback(
(value: null | Record<string, any>) => {
setSearchParams(
(prev) => {
const previousValue = prev.get(FILTER_KEYS.ALBUM._CUSTOM);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const previousValue = prev.get(FILTER_KEYS.ALBUM._CUSTOM);
const newCustom = {
...(previousValue ? JSON.parse(previousValue) : {}),
...value,
};
const newCustom = {
...(previousValue ? JSON.parse(previousValue) : {}),
...value,
};
const filteredNewCustom = Object.fromEntries(
Object.entries(newCustom).filter(
([, value]) => value !== null && value !== undefined,
),
);
const filteredNewCustom = Object.fromEntries(
Object.entries(newCustom).filter(
([, value]) => value !== null && value !== undefined,
),
);
prev.set(FILTER_KEYS.ALBUM._CUSTOM, JSON.stringify(filteredNewCustom));
return prev;
},
{
replace: true,
},
);
prev.set(FILTER_KEYS.ALBUM._CUSTOM, JSON.stringify(filteredNewCustom));
return prev;
},
{
replace: true,
},
);
});
},
[setSearchParams],
);
@@ -14,6 +14,7 @@ import {
setMultipleSearchParams,
setSearchParam,
} from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { SongListSort, SortOrder } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types';
@@ -74,18 +75,22 @@ export const usePlaylistSongListFilters = () => {
const setAlbumArtistIds = useCallback(
(value: null | string[]) => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.SONG.ALBUM_ARTIST_IDS, value),
{ replace: true },
);
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.SONG.ALBUM_ARTIST_IDS, value),
{ replace: true },
);
});
},
[setSearchParams],
);
const setGenreId = useCallback(
(value: null | string[]) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.GENRE_ID, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.GENRE_ID, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -93,8 +98,13 @@ export const usePlaylistSongListFilters = () => {
const setArtistIds = useCallback(
(value: null | string[]) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.ARTIST_IDS, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.SONG.ARTIST_IDS, value),
{
replace: true,
},
);
});
},
[setSearchParams],
@@ -102,8 +112,10 @@ export const usePlaylistSongListFilters = () => {
const setMinYear = useCallback(
(value: null | number) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MIN_YEAR, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MIN_YEAR, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -111,8 +123,10 @@ export const usePlaylistSongListFilters = () => {
const setMaxYear = useCallback(
(value: null | number) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MAX_YEAR, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MAX_YEAR, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -120,8 +134,10 @@ export const usePlaylistSongListFilters = () => {
const setFavorite = useCallback(
(value: boolean | null) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.FAVORITE, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.FAVORITE, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -129,8 +145,13 @@ export const usePlaylistSongListFilters = () => {
const setHasRating = useCallback(
(value: boolean | null) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.HAS_RATING, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.SONG.HAS_RATING, value),
{
replace: true,
},
);
});
},
[setSearchParams],
@@ -153,51 +174,55 @@ export const usePlaylistSongListFilters = () => {
const setCustom = useCallback(
(value: null | Record<string, any>) => {
setSearchParams(
(prev) => {
const previousValue = prev.get(FILTER_KEYS.ALBUM._CUSTOM);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const previousValue = prev.get(FILTER_KEYS.ALBUM._CUSTOM);
const newCustom = {
...(previousValue ? JSON.parse(previousValue) : {}),
...value,
};
const newCustom = {
...(previousValue ? JSON.parse(previousValue) : {}),
...value,
};
const filteredNewCustom = Object.fromEntries(
Object.entries(newCustom).filter(
([, value]) => value !== null && value !== undefined,
),
);
const filteredNewCustom = Object.fromEntries(
Object.entries(newCustom).filter(
([, value]) => value !== null && value !== undefined,
),
);
prev.set(FILTER_KEYS.ALBUM._CUSTOM, JSON.stringify(filteredNewCustom));
return prev;
},
{
replace: true,
},
);
prev.set(FILTER_KEYS.ALBUM._CUSTOM, JSON.stringify(filteredNewCustom));
return prev;
},
{
replace: true,
},
);
});
},
[setSearchParams],
);
const clear = useCallback(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(
prev,
{
[FILTER_KEYS.SONG._CUSTOM]: null,
[FILTER_KEYS.SONG.ALBUM_ARTIST_IDS]: null,
[FILTER_KEYS.SONG.ARTIST_IDS]: null,
[FILTER_KEYS.SONG.FAVORITE]: null,
[FILTER_KEYS.SONG.GENRE_ID]: null,
[FILTER_KEYS.SONG.HAS_RATING]: null,
[FILTER_KEYS.SONG.MAX_YEAR]: null,
[FILTER_KEYS.SONG.MIN_YEAR]: null,
},
new Set([FILTER_KEYS.SONG._CUSTOM]),
),
{ replace: true },
);
runInUrlTransition(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(
prev,
{
[FILTER_KEYS.SONG._CUSTOM]: null,
[FILTER_KEYS.SONG.ALBUM_ARTIST_IDS]: null,
[FILTER_KEYS.SONG.ARTIST_IDS]: null,
[FILTER_KEYS.SONG.FAVORITE]: null,
[FILTER_KEYS.SONG.GENRE_ID]: null,
[FILTER_KEYS.SONG.HAS_RATING]: null,
[FILTER_KEYS.SONG.MAX_YEAR]: null,
[FILTER_KEYS.SONG.MIN_YEAR]: null,
},
new Set([FILTER_KEYS.SONG._CUSTOM]),
),
{ replace: true },
);
});
}, [setSearchParams]);
const query = useMemo(
@@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from 'react';
import { useCallback, useDeferredValue, useRef, useState } from 'react';
import { Command, CommandPalettePages } from '/@/renderer/features/search/components/command';
import { GoToCommands } from '/@/renderer/features/search/components/go-to-commands';
@@ -49,6 +49,7 @@ function CommandPaletteSearch({
setQuery,
}: CommandPaletteSearchProps) {
const [debouncedQuery] = useDebouncedValue(query, 400);
const deferredSearchQuery = useDeferredValue(debouncedQuery ?? '');
const searchSectionsExpanded = useAppStore(
(state) => state.commandPaletteSearchSectionsExpanded,
);
@@ -83,7 +84,7 @@ function CommandPaletteSearch({
<Command.List>
<Stack gap="xs">
<SearchAlbumsSection
debouncedQuery={debouncedQuery ?? ''}
debouncedQuery={deferredSearchQuery}
expanded={searchSectionsExpanded[SEARCH_SECTION_IDS.albums] ?? true}
isHome={isHome}
onSelectResult={onSelectResult}
@@ -96,7 +97,7 @@ function CommandPaletteSearch({
query={query}
/>
<SearchAlbumArtistsSection
debouncedQuery={debouncedQuery ?? ''}
debouncedQuery={deferredSearchQuery}
expanded={searchSectionsExpanded[SEARCH_SECTION_IDS.artists] ?? true}
isHome={isHome}
onSelectResult={onSelectResult}
@@ -109,7 +110,7 @@ function CommandPaletteSearch({
query={query}
/>
<SearchSongsSection
debouncedQuery={debouncedQuery ?? ''}
debouncedQuery={deferredSearchQuery}
expanded={searchSectionsExpanded[SEARCH_SECTION_IDS.tracks] ?? true}
isHome={isHome}
onSelectResult={onSelectResult}
@@ -1,5 +1,5 @@
import { motion } from 'motion/react';
import { createContext, ReactNode, useContext, useMemo, useRef } from 'react';
import { createContext, ReactNode, useContext, useMemo, useState } from 'react';
import styles from './list-with-sidebar-container.module.css';
@@ -8,7 +8,7 @@ import { animationProps } from '/@/shared/components/animations/animation-props'
import { Portal } from '/@/shared/components/portal/portal';
interface ListWithSidebarContainerContextValue {
sidebarRef: React.RefObject<HTMLDivElement | null>;
sidebarElement: HTMLDivElement | null;
}
const ListWithSidebarContainerContext = createContext<ListWithSidebarContainerContextValue | null>(
@@ -36,12 +36,12 @@ function Sidebar({ children }: SidebarProps) {
throw new Error('Sidebar must be used within ListWithSidebarContainer');
}
if (!context.sidebarRef?.current) {
if (!context.sidebarElement) {
return null;
}
return (
<Portal target={context.sidebarRef.current}>
<Portal target={context.sidebarElement}>
<motion.div {...animationProps.slideInLeft} style={{ height: '100%', width: '100%' }}>
{children}
</motion.div>
@@ -56,25 +56,25 @@ function SidebarPortal({ children }: SidebarPortalProps) {
throw new Error('SidebarPortal must be used within ListWithSidebarContainer');
}
if (!context.sidebarRef?.current) {
if (!context.sidebarElement) {
return null;
}
return <Portal target={context.sidebarRef.current}>{children}</Portal>;
return <Portal target={context.sidebarElement}>{children}</Portal>;
}
export const ListWithSidebarContainer = ({
children,
useBreakpoint = false,
}: ListWithSidebarContainerProps) => {
const sidebarRef = useRef<HTMLDivElement>(null);
const [sidebarElement, setSidebarElement] = useState<HTMLDivElement | null>(null);
const { isSidebarOpen = false } = useListContext();
const contextValue = useMemo(
() => ({
sidebarRef,
sidebarElement,
}),
[],
[sidebarElement],
);
return (
@@ -84,7 +84,7 @@ export const ListWithSidebarContainer = ({
data-sidebar-open={useBreakpoint ? undefined : isSidebarOpen}
data-use-breakpoint={useBreakpoint}
>
<div className={styles.sidebarContainer} ref={sidebarRef} />
<div className={styles.sidebarContainer} ref={setSidebarElement} />
<div className={styles.contentContainer}>{children}</div>
</div>
</ListWithSidebarContainerContext.Provider>
@@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router';
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { parseStringParam, setSearchParam } from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
export const useSearchTermFilter = (defaultValue?: string) => {
@@ -14,17 +15,19 @@ export const useSearchTermFilter = (defaultValue?: string) => {
}, [searchParams, defaultValue]);
const handleSetSearchTerm = (value: null | string) => {
setSearchParams(
(prev) => {
const newParams = setSearchParam(
prev,
FILTER_KEYS.SHARED.SEARCH_TERM,
value === '' ? null : value,
);
return newParams;
},
{ replace: true },
);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const newParams = setSearchParam(
prev,
FILTER_KEYS.SHARED.SEARCH_TERM,
value === '' ? null : value,
);
return newParams;
},
{ replace: true },
);
});
};
const debouncedSetSearchTerm = useDebouncedCallback(handleSetSearchTerm, 300);
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';
import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
@@ -8,21 +8,26 @@ export const useSetFavorite = () => {
const createFavoriteMutation = useCreateFavorite({});
const deleteFavoriteMutation = useDeleteFavorite({});
const createFavoriteMutationRef = useRef(createFavoriteMutation);
const deleteFavoriteMutationRef = useRef(deleteFavoriteMutation);
createFavoriteMutationRef.current = createFavoriteMutation;
deleteFavoriteMutationRef.current = deleteFavoriteMutation;
const setFavorite = useCallback(
(serverId: string, id: string[], itemType: LibraryItem, isFavorite: boolean) => {
if (isFavorite) {
createFavoriteMutation.mutate({
createFavoriteMutationRef.current.mutate({
apiClientProps: { serverId },
query: { id, type: itemType },
});
} else {
deleteFavoriteMutation.mutate({
deleteFavoriteMutationRef.current.mutate({
apiClientProps: { serverId },
query: { id, type: itemType },
});
}
},
[createFavoriteMutation, deleteFavoriteMutation],
[],
);
return setFavorite;
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';
import { useSetRatingMutation } from '/@/renderer/features/shared/mutations/set-rating-mutation';
import { LibraryItem } from '/@/shared/types/domain-types';
@@ -6,14 +6,17 @@ import { LibraryItem } from '/@/shared/types/domain-types';
export const useSetRating = () => {
const setRatingMutation = useSetRatingMutation({});
const setRatingMutationRef = useRef(setRatingMutation);
setRatingMutationRef.current = setRatingMutation;
const setRating = useCallback(
(serverId: string, id: string[], itemType: LibraryItem, rating: number) => {
setRatingMutation.mutate({
setRatingMutationRef.current.mutate({
apiClientProps: { serverId },
query: { id, rating, type: itemType },
});
},
[setRatingMutation],
[],
);
return setRating;
@@ -5,6 +5,7 @@ import { useListFilterPersistence } from '/@/renderer/features/shared/hooks/use-
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { useCurrentServer } from '/@/renderer/store';
import { parseStringParam, setSearchParam } from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { ItemListKey } from '/@/shared/types/types';
export const useSortByFilter = <TSortBy>(defaultValue: null | string, listKey: ItemListKey) => {
@@ -20,14 +21,16 @@ export const useSortByFilter = <TSortBy>(defaultValue: null | string, listKey: I
}, [searchParams, persisted, defaultValue]);
const handleSetSortBy = (sortBy: string) => {
setSearchParams(
(prev) => {
const newParams = setSearchParam(prev, FILTER_KEYS.SHARED.SORT_BY, sortBy);
return newParams;
},
{ replace: true },
);
setFilter(FILTER_KEYS.SHARED.SORT_BY, sortBy);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const newParams = setSearchParam(prev, FILTER_KEYS.SHARED.SORT_BY, sortBy);
return newParams;
},
{ replace: true },
);
setFilter(FILTER_KEYS.SHARED.SORT_BY, sortBy);
});
};
return {
@@ -5,6 +5,7 @@ import { useListFilterPersistence } from '/@/renderer/features/shared/hooks/use-
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { useCurrentServer } from '/@/renderer/store';
import { parseStringParam, setSearchParam } from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { SortOrder } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types';
@@ -21,14 +22,20 @@ export const useSortOrderFilter = (defaultValue: null | string, listKey: ItemLis
}, [searchParams, persisted, defaultValue]);
const handleSetSortOrder = (sortOrder: SortOrder) => {
setSearchParams(
(prev) => {
const newParams = setSearchParam(prev, FILTER_KEYS.SHARED.SORT_ORDER, sortOrder);
return newParams;
},
{ replace: true },
);
setFilter(FILTER_KEYS.SHARED.SORT_ORDER, sortOrder);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const newParams = setSearchParam(
prev,
FILTER_KEYS.SHARED.SORT_ORDER,
sortOrder,
);
return newParams;
},
{ replace: true },
);
setFilter(FILTER_KEYS.SHARED.SORT_ORDER, sortOrder);
});
};
return {
@@ -14,6 +14,7 @@ import {
setMultipleSearchParams,
setSearchParam,
} from '/@/renderer/utils/query-params';
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
import { SongListSort, SortOrder } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types';
@@ -65,8 +66,10 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
const setGenreId = useCallback(
(value: null | string[]) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.GENRE_ID, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.GENRE_ID, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -74,8 +77,13 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
const setArtistIds = useCallback(
(value: null | string[]) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.ARTIST_IDS, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.SONG.ARTIST_IDS, value),
{
replace: true,
},
);
});
},
[setSearchParams],
@@ -83,8 +91,10 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
const setMinYear = useCallback(
(value: null | number) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MIN_YEAR, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MIN_YEAR, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -92,8 +102,10 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
const setMaxYear = useCallback(
(value: null | number) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MAX_YEAR, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.MAX_YEAR, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -101,8 +113,10 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
const setFavorite = useCallback(
(value: boolean | null) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.FAVORITE, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.FAVORITE, value), {
replace: true,
});
});
},
[setSearchParams],
@@ -110,8 +124,13 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
const setHasRating = useCallback(
(value: boolean | null) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.HAS_RATING, value), {
replace: true,
runInUrlTransition(() => {
setSearchParams(
(prev) => setSearchParam(prev, FILTER_KEYS.SONG.HAS_RATING, value),
{
replace: true,
},
);
});
},
[setSearchParams],
@@ -124,46 +143,53 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
| null
| Record<string, any>,
) => {
setSearchParams(
(prev) => {
const currentCustom = parseCustomFiltersParam(prev, FILTER_KEYS.SONG._CUSTOM);
let newValue =
typeof value === 'function' ? value(currentCustom ?? null) : value;
// Convert empty objects to null to clear them from URL
if (
newValue &&
typeof newValue === 'object' &&
Object.keys(newValue).length === 0
) {
newValue = null;
}
return setJsonSearchParam(prev, FILTER_KEYS.SONG._CUSTOM, newValue);
},
{ replace: true },
);
runInUrlTransition(() => {
setSearchParams(
(prev) => {
const currentCustom = parseCustomFiltersParam(
prev,
FILTER_KEYS.SONG._CUSTOM,
);
let newValue =
typeof value === 'function' ? value(currentCustom ?? null) : value;
// Convert empty objects to null to clear them from URL
if (
newValue &&
typeof newValue === 'object' &&
Object.keys(newValue).length === 0
) {
newValue = null;
}
return setJsonSearchParam(prev, FILTER_KEYS.SONG._CUSTOM, newValue);
},
{ replace: true },
);
});
},
[setSearchParams],
);
const clear = useCallback(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(
prev,
{
[FILTER_KEYS.SHARED.SEARCH_TERM]: null,
[FILTER_KEYS.SONG._CUSTOM]: null,
[FILTER_KEYS.SONG.ARTIST_IDS]: null,
[FILTER_KEYS.SONG.FAVORITE]: null,
[FILTER_KEYS.SONG.GENRE_ID]: null,
[FILTER_KEYS.SONG.HAS_RATING]: null,
[FILTER_KEYS.SONG.MAX_YEAR]: null,
[FILTER_KEYS.SONG.MIN_YEAR]: null,
},
new Set([FILTER_KEYS.SONG._CUSTOM]),
),
{ replace: true },
);
runInUrlTransition(() => {
setSearchParams(
(prev) =>
setMultipleSearchParams(
prev,
{
[FILTER_KEYS.SHARED.SEARCH_TERM]: null,
[FILTER_KEYS.SONG._CUSTOM]: null,
[FILTER_KEYS.SONG.ARTIST_IDS]: null,
[FILTER_KEYS.SONG.FAVORITE]: null,
[FILTER_KEYS.SONG.GENRE_ID]: null,
[FILTER_KEYS.SONG.HAS_RATING]: null,
[FILTER_KEYS.SONG.MAX_YEAR]: null,
[FILTER_KEYS.SONG.MIN_YEAR]: null,
},
new Set([FILTER_KEYS.SONG._CUSTOM]),
),
{ replace: true },
);
});
}, [setSearchParams]);
const query = useMemo(
+1 -1
View File
@@ -200,7 +200,7 @@ const appRouterModals = {
export const AppRouter = () => {
const router = (
<HashRouter unstable_useTransitions={false}>
<HashRouter unstable_useTransitions>
<ModalsProvider modals={appRouterModals}>
<RouterErrorBoundary>
<Routes>
+5
View File
@@ -0,0 +1,5 @@
import { startTransition } from 'react';
export function runInUrlTransition(update: () => void): void {
startTransition(update);
}