This commit is contained in:
jeffvli
2025-07-29 19:07:58 -07:00
parent 98e8bda45d
commit a7430dae31
60 changed files with 583 additions and 155 deletions
+3 -1
View File
@@ -1,5 +1,7 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-preset-mantine': {
mixins: {},
},
},
};
@@ -9,9 +9,7 @@ import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils';
import { albumListSortMap } from '/@/shared/types/domain/album-domain-types';
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types';
import { genreListSortMap } from '/@/shared/types/domain/genre-domain-types';
import { Played } from '/@/shared/types/domain/player-domain-types';
import { playlistListSortMap } from '/@/shared/types/domain/playlist-domain-types';
import { ServerFeature } from '/@/shared/types/domain/server-domain-types';
import { LibraryItem, sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
import { Song, songListSortMap } from '/@/shared/types/domain/song-domain-types';
@@ -11,9 +11,7 @@ import { albumListSortMap } from '/@/shared/types/domain/album-domain-types';
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types';
import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types';
import { genreListSortMap } from '/@/shared/types/domain/genre-domain-types';
import {
playlistListSortMap,
PlaylistSongListRequest,
PlaylistSongListResponse,
} from '/@/shared/types/domain/playlist-domain-types';
@@ -24,7 +22,6 @@ import {
} from '/@/shared/types/domain/server-domain-types';
import { sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
import { Song, songListSortMap } from '/@/shared/types/domain/song-domain-types';
import { userListSortMap } from '/@/shared/types/domain/user-domain-types';
const VERSION_INFO: VersionInfo = [
['0.55.0', { [ServerFeature.BFR]: [1] }],
@@ -17,8 +17,6 @@ import {
import { AlbumListSort, sortAlbumList } from '/@/shared/types/domain/album-domain-types';
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
import { sortAlbumArtistList } from '/@/shared/types/domain/artist-domain-types';
import { GenreListSort } from '/@/shared/types/domain/genre-domain-types';
import { PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types';
import { ServerFeatures } from '/@/shared/types/domain/server-domain-types';
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { Song, sortSongList } from '/@/shared/types/domain/song-domain-types';
@@ -24,8 +24,8 @@ import { AppRoute } from '/@/renderer/router/routes';
import { PersistedTableColumn, useListStoreActions } from '/@/renderer/store';
import { ListKey, useListStoreByKey } from '/@/renderer/store/list.store';
import {
BasePaginatedResponse,
BasePaginatedQuery,
BasePaginatedResponse,
} from '/@/shared/types/adapter/api-controller-types';
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
@@ -0,0 +1,45 @@
import { queryOptions, UseQueryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api/api-controller';
import { AlbumListRequest } from '/@/shared/types/domain/album-domain-types';
export const getAlbumListQueryKey = (serverId: string, request?: AlbumListRequest) => {
if (!request) {
return [serverId, 'albums'];
}
return [serverId, 'albums', request];
};
export const getInfiniteAlbumListQueryKey = (serverId: string, request?: AlbumListRequest) => {
if (!request) {
return [serverId, 'albums', 'infinite'];
}
return [serverId, 'albums', 'infinite', request];
};
export const getAlbumList = async (serverId: string, request: AlbumListRequest) => {
const [error, response] = await api.controller[serverId]!.album.getList!({
query: request.query,
});
if (error) {
throw new Error(error.message);
}
return response;
};
export const getAlbumListQuery = (
serverId: string,
request: AlbumListRequest,
options?: UseQueryOptions,
) => {
return queryOptions({
enabled: !!serverId,
queryFn: () => getAlbumList(serverId, request),
queryKey: getAlbumListQueryKey(serverId, request),
...options,
});
};
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { AlbumDetailQuery } from '/@/shared/types/domain/album-domain-types';
export const useAlbumDetail = (args: QueryHookArgs<AlbumDetailQuery>) => {
export const useAlbumDetail = (args: RQueryHookArgs<AlbumDetailQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
export const useAlbumListCount = (args: QueryHookArgs<AlbumListQuery>) => {
export const useAlbumListCount = (args: RQueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
@@ -8,7 +8,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain/album-domain-types';
export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
export const useAlbumList = (args: RQueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -33,7 +33,7 @@ export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
});
};
export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => {
export const useAlbumListInfinite = (args: RQueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistDetail = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
export const useAlbumArtistDetail = (args: RQueryHookArgs<AlbumArtistDetailQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistListCount = (args: QueryHookArgs<AlbumArtistListQuery>) => {
export const useAlbumArtistListCount = (args: RQueryHookArgs<AlbumArtistListQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistList = (args: QueryHookArgs<AlbumArtistListQuery>) => {
export const useAlbumArtistList = (args: RQueryHookArgs<AlbumArtistListQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistInfo = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
export const useAlbumArtistInfo = (args: RQueryHookArgs<AlbumArtistDetailQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { ArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
export const useArtistListCount = (args: QueryHookArgs<ArtistListQuery>) => {
export const useArtistListCount = (args: RQueryHookArgs<ArtistListQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
export const useRoles = (args: QueryHookArgs<object>) => {
export const useRoles = (args: RQueryHookArgs<object>) => {
const { options, serverId } = args;
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { TopSongListQuery } from '/@/shared/types/domain/song-domain-types';
export const useTopSongsList = (args: QueryHookArgs<TopSongListQuery>) => {
export const useTopSongsList = (args: RQueryHookArgs<TopSongListQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types';
export const useGenreList = (args: QueryHookArgs<GenreListQuery>) => {
export const useGenreList = (args: RQueryHookArgs<GenreListQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -2,12 +2,12 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
export const useRecentlyPlayed = (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
export const useRecentlyPlayed = (args: RQueryHookArgs<Partial<AlbumListQuery>>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -4,26 +4,21 @@ import { useTranslation } from 'react-i18next';
import { queryKeys } from '/@/renderer/api/query-keys';
import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel';
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel';
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
import { useAlbumList } from '/@/renderer/features/albums';
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
import { AlbumInfiniteCarousel } from '/@/renderer/features/shared/components/infinite-album-carousel/infinite-album-carousel';
import { useSongList } from '/@/renderer/features/songs';
import { AppRoute } from '/@/renderer/router/routes';
import {
HomeItem,
useCurrentServer,
useGeneralSettings,
useWindowSettings,
} from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
import { Spinner } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title';
import { AlbumListSort } from '/@/shared/types/domain/album-domain-types';
import { AlbumListSort, AlbumListSortOptions } from '/@/shared/types/domain/album-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { SongListSort } from '/@/shared/types/domain/song-domain-types';
@@ -40,15 +35,15 @@ const HomeRoute = () => {
const feature = useAlbumList({
options: {
gcTime: 1000 * 60,
enabled: homeFeature,
gcTime: 1000 * 60,
staleTime: 1000 * 60,
},
query: {
limit: 20,
offset: 0,
sortBy: AlbumListSort.RANDOM,
sortOrder: ListSortOrder.DESC,
offset: 0,
},
serverId: server?.id,
});
@@ -63,9 +58,9 @@ const HomeRoute = () => {
},
query: {
limit: itemsPerPage,
offset: 0,
sortBy: AlbumListSort.RANDOM,
sortOrder: ListSortOrder.ASC,
offset: 0,
},
serverId: server?.id,
});
@@ -76,9 +71,9 @@ const HomeRoute = () => {
},
query: {
limit: itemsPerPage,
offset: 0,
sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: ListSortOrder.DESC,
offset: 0,
},
serverId: server?.id,
});
@@ -89,9 +84,9 @@ const HomeRoute = () => {
},
query: {
limit: itemsPerPage,
offset: 0,
sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: ListSortOrder.DESC,
offset: 0,
},
serverId: server?.id,
});
@@ -103,9 +98,9 @@ const HomeRoute = () => {
},
query: {
limit: itemsPerPage,
offset: 0,
sortBy: AlbumListSort.PLAY_COUNT,
sortOrder: ListSortOrder.DESC,
offset: 0,
},
serverId: server?.id,
});
@@ -118,9 +113,9 @@ const HomeRoute = () => {
},
query: {
limit: itemsPerPage,
offset: 0,
sortBy: SongListSort.PLAY_COUNT,
sortOrder: ListSortOrder.DESC,
offset: 0,
},
serverId: server?.id,
},
@@ -253,7 +248,15 @@ const HomeRoute = () => {
px="2rem"
>
{homeFeature && <FeatureCarousel data={featureItemsWithImage} />}
{sortedCarousel.map((carousel) => (
<AlbumInfiniteCarousel
serverId={server?.id ?? ''}
sortBy={AlbumListSortOptions.NAME}
sortOrder={ListSortOrder.ASC}
title={t('page.home.explore', { postProcess: 'sentenceCase' })}
/>
{/* {sortedCarousel.map((carousel) => (
<MemoizedSwiperGridCarousel
cardRows={[
{
@@ -317,7 +320,7 @@ const HomeRoute = () => {
}}
uniqueId={carousel.uniqueId}
/>
))}
))} */}
</Stack>
</NativeScrollArea>
</AnimatedPage>
@@ -3,7 +3,7 @@ import isElectron from 'is-electron';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById, useLyricsSettings } from '/@/renderer/store';
import { hasFeature } from '/@/shared/api/utils';
import {
@@ -61,7 +61,7 @@ const formatLyrics = (lyrics: string) => {
};
export const useServerLyrics = (
args: QueryHookArgs<LyricsQuery>,
args: RQueryHookArgs<LyricsQuery>,
): UseQueryResult<null | string> => {
const { query, serverId } = args;
const server = useServerById(serverId);
@@ -81,7 +81,7 @@ export const useServerLyrics = (
};
export const useSongLyricsBySong = (
args: QueryHookArgs<LyricsQuery>,
args: RQueryHookArgs<LyricsQuery>,
song: QueueSong | undefined,
): UseQueryResult<FullLyricsMetadata | StructuredLyric[]> => {
const { query } = args;
@@ -170,7 +170,7 @@ export const useSongLyricsBySong = (
};
export const useSongLyricsByRemoteId = (
args: QueryHookArgs<Partial<LyricGetQuery>>,
args: RQueryHookArgs<Partial<LyricGetQuery>>,
): UseQueryResult<null | string> => {
const queryClient = useQueryClient();
const { query, serverId } = args;
@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import isElectron from 'is-electron';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import {
InternetProviderLyricSearchResponse,
LyricSearchQuery,
@@ -11,7 +11,7 @@ import {
const lyricsIpc = isElectron() ? window.api.lyrics : null;
export const useLyricSearch = (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
export const useLyricSearch = (args: Omit<RQueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
const { options, query } = args;
return useQuery<Record<LyricSource, InternetProviderLyricSearchResponse[]>>({
@@ -19,12 +19,12 @@ import { Icon } from '/@/shared/components/icon/icon';
import { NumberInput } from '/@/shared/components/number-input/number-input';
import { Select } from '/@/shared/components/select/select';
import { Stack } from '/@/shared/components/stack/stack';
import { GenreListResponse, GenreListSort } from '/@/shared/types/domain/genre-domain-types';
import { GenreListResponse } from '/@/shared/types/domain/genre-domain-types';
import { Played } from '/@/shared/types/domain/player-domain-types';
import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { RandomSongListQuery } from '/@/shared/types/domain/song-domain-types';
import { Play, PlayQueueAddOptions } from '/@/shared/types/types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
interface ShuffleAllSlice extends RandomSongListQuery {
actions: {
@@ -256,9 +256,9 @@ export const openShuffleAllModal = async (
signal,
},
query: {
offset: 0,
sortBy: GenreListSort.NAME,
sortOrder: ListSortOrder.ASC,
offset: 0,
},
}),
queryKey: queryKeys.genres.list(server?.id),
@@ -2,12 +2,12 @@ import { useMutation } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { MutationOptions } from '/@/renderer/lib/react-query';
import { RMutationOptions } from '/@/renderer/lib/react-query';
import { useServerById, useIncrementQueuePlayCount } from '/@/renderer/store';
import { usePlayEvent } from '/@/renderer/store/event.store';
import { ScrobbleRequest, ScrobbleResponse } from '/@/shared/types/domain/user-domain-types';
export const useSendScrobble = (options?: MutationOptions) => {
export const useSendScrobble = (options?: RMutationOptions) => {
const incrementPlayCount = useIncrementQueuePlayCount();
const sendPlayEvent = usePlayEvent();
@@ -16,9 +16,9 @@ import { MultiSelect } from '/@/shared/components/multi-select/multi-select';
import { Stack } from '/@/shared/components/stack/stack';
import { Switch } from '/@/shared/components/switch/switch';
import { toast } from '/@/shared/components/toast/toast';
import { PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types';
import { SongListQuery, SongListSort } from '/@/shared/types/domain/song-domain-types';
import { PlaylistListSortOptions } from '/@/shared/types/domain/playlist-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { SongListQuery, SongListSort } from '/@/shared/types/domain/song-domain-types';
export const AddToPlaylistContextModal = ({
id,
@@ -44,9 +44,9 @@ export const AddToPlaylistContextModal = ({
smart: false,
},
},
sortBy: PlaylistListSort.NAME,
sortOrder: ListSortOrder.ASC,
offset: 0,
sortBy: PlaylistListSortOptions.NAME,
sortOrder: ListSortOrder.ASC,
},
serverId: server?.id,
});
@@ -70,9 +70,9 @@ export const AddToPlaylistContextModal = ({
const getSongsByAlbum = async (albumId: string) => {
const query: SongListQuery = {
albumIds: [albumId],
offset: 0,
sortBy: SongListSort.ALBUM,
sortOrder: ListSortOrder.ASC,
offset: 0,
};
const queryKey = queryKeys.songs.list(server?.id || '', query);
@@ -88,9 +88,9 @@ export const AddToPlaylistContextModal = ({
const getSongsByArtist = async (artistId: string) => {
const query: SongListQuery = {
artistIds: [artistId],
offset: 0,
sortBy: SongListSort.ARTIST,
sortOrder: ListSortOrder.ASC,
offset: 0,
};
const queryKey = queryKeys.songs.list(server?.id || '', query);
@@ -32,10 +32,9 @@ import { Icon } from '/@/shared/components/icon/icon';
import { NumberInput } from '/@/shared/components/number-input/number-input';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Select } from '/@/shared/components/select/select';
import { PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { SongListSort } from '/@/shared/types/domain/song-domain-types';
import { QueryBuilderGroup, QueryBuilderRule } from '/@/shared/types/types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
type AddArgs = {
groupIndex: number[];
@@ -111,7 +110,7 @@ export const PlaylistQueryBuilder = forwardRef(
);
const { data: playlists } = usePlaylistList({
query: { sortBy: PlaylistListSort.NAME, sortOrder: ListSortOrder.ASC, offset: 0 },
query: { offset: 0, sortBy: PlaylistListSort.NAME, sortOrder: ListSortOrder.ASC },
serverId: server?.id,
});
@@ -3,14 +3,14 @@ import { AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import {
AddToPlaylistArgs,
AddToPlaylistResponse,
} from '/@/shared/types/domain/playlist-domain-types';
export const useAddToPlaylist = (args: MutationHookArgs) => {
export const useAddToPlaylist = (args: RMutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
@@ -3,11 +3,11 @@ import { AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { CreatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
export const useCreatePlaylist = (args: MutationHookArgs) => {
export const useCreatePlaylist = (args: RMutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
@@ -3,11 +3,11 @@ import { AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById, useCurrentServer } from '/@/renderer/store';
import { DeletePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
export const useDeletePlaylist = (args: MutationHookArgs) => {
export const useDeletePlaylist = (args: RMutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
const server = useCurrentServer();
@@ -3,11 +3,11 @@ import { AxiosError, AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationOptions } from '/@/renderer/lib/react-query';
import { RMutationOptions } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { RemoveFromPlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
export const useRemoveFromPlaylist = (options?: MutationOptions) => {
export const useRemoveFromPlaylist = (options?: RMutationOptions) => {
const queryClient = useQueryClient();
return useMutation<
@@ -3,11 +3,11 @@ import { AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { UpdatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
export const useUpdatePlaylist = (args: MutationHookArgs) => {
export const useUpdatePlaylist = (args: RMutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { PlaylistDetailQuery } from '/@/shared/types/domain/playlist-domain-types';
export const usePlaylistDetail = (args: QueryHookArgs<PlaylistDetailQuery>) => {
export const usePlaylistDetail = (args: RQueryHookArgs<PlaylistDetailQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryOptions } from '/@/renderer/lib/react-query';
import type { RQueryOptions } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -8,7 +8,7 @@ import { useServerById } from '/@/renderer/store';
import { PlaylistListQuery } from '/@/shared/types/domain/playlist-domain-types';
export const usePlaylistList = (args: {
options?: QueryOptions;
options?: RQueryOptions;
query: PlaylistListQuery;
serverId?: string;
}) => {
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { PlaylistSongListQuery } from '/@/shared/types/domain/playlist-domain-types';
export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>) => {
export const usePlaylistSongList = (args: RQueryHookArgs<PlaylistSongListQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { SearchQuery } from '/@/shared/types/domain/search-domain-types';
export const useSearch = (args: QueryHookArgs<SearchQuery>) => {
export const useSearch = (args: RQueryHookArgs<SearchQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -0,0 +1,100 @@
import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
import { nanoid } from 'nanoid';
import { memo, useCallback, useMemo } from 'react';
import { PosterCard } from '/@/renderer/components/card/poster-card';
import {
getAlbumList,
getInfiniteAlbumListQueryKey,
} from '/@/renderer/features/albums/api/queries/get-album-list-query';
import { GridCarousel } from '/@/shared/components/grid-carousel/grid-carousel';
import { AlbumListResponse, AlbumListSortOptions } from '/@/shared/types/domain/album-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
interface AlbumCarouselProps {
rowCount?: number;
serverId: string;
sortBy: AlbumListSortOptions;
sortOrder: ListSortOrder;
title: string;
}
const MemoizedAlbumCard = memo(PosterCard);
export function AlbumInfiniteCarousel(props: AlbumCarouselProps) {
const { rowCount = 1, serverId, sortBy, sortOrder, title } = props;
const { data: albums, fetchNextPage } = useInfiniteAlbumList(serverId, sortBy, sortOrder, 20);
const cards = useMemo(
() =>
albums.pages.flatMap((page) => {
const loadedCards = page.items.map((album) => ({
content: <MemoizedAlbumCard controls={{}} data={album} uniqueId={album.id} />,
id: album.id,
}));
if (page.items.length === 20) {
return loadedCards;
}
return [
...loadedCards,
...Array.from({ length: 20 - page.items.length }).map(() => {
const id = nanoid();
return {
content: <MemoizedAlbumCard controls={{}} />,
id,
};
}),
];
}),
[albums.pages],
);
const handleNextPage = useCallback(() => {}, []);
const handlePrevPage = useCallback(() => {}, []);
if (albums.pages[0]?.items.length === 0) {
return null;
}
return (
<GridCarousel
cards={cards}
loadNextPage={fetchNextPage}
onNextPage={handleNextPage}
onPrevPage={handlePrevPage}
rowCount={rowCount}
title={title}
/>
);
}
function useInfiniteAlbumList(
serverId: string,
sortBy: AlbumListSortOptions,
sortOrder: ListSortOrder,
limit: number,
) {
const query = useSuspenseInfiniteQuery<AlbumListResponse>({
getNextPageParam: (lastPage, _allPages, lastPageParam) => {
if (lastPage.items.length < limit) {
return undefined;
}
const nextPageParam = Number(lastPageParam) + limit;
return String(nextPageParam);
},
initialPageParam: 0,
queryFn: ({ pageParam }) => {
return getAlbumList(serverId, {
query: { limit: limit, offset: Number(pageParam), sortBy, sortOrder },
});
},
queryKey: getInfiniteAlbumListQueryKey(serverId),
});
return query;
}
@@ -4,7 +4,7 @@ import isElectron from 'is-electron';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
import { useFavoriteEvent } from '/@/renderer/store/event.store';
import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
@@ -14,7 +14,7 @@ import { FavoriteResponse } from '/@/shared/types/domain/user-domain-types';
const remote = isElectron() ? window.api.remote : null;
export const useCreateFavorite = (args: MutationHookArgs) => {
export const useCreateFavorite = (args: RMutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
const setAlbumListData = useSetAlbumListItemDataById();
@@ -4,7 +4,7 @@ import isElectron from 'is-electron';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
import { useFavoriteEvent } from '/@/renderer/store/event.store';
import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
@@ -14,7 +14,7 @@ import { FavoriteResponse } from '/@/shared/types/domain/user-domain-types';
const remote = isElectron() ? window.api.remote : null;
export const useDeleteFavorite = (args: MutationHookArgs) => {
export const useDeleteFavorite = (args: RMutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
const setAlbumListData = useSetAlbumListItemDataById();
@@ -4,7 +4,7 @@ import isElectron from 'is-electron';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById, useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store';
import { useRatingEvent } from '/@/renderer/store/event.store';
import { Album, AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
@@ -14,7 +14,7 @@ import { RatingResponse, SetRatingRequest } from '/@/shared/types/domain/user-do
const remote = isElectron() ? window.api.remote : null;
export const useSetRating = (args: MutationHookArgs) => {
export const useSetRating = (args: RMutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
const setAlbumListData = useSetAlbumListItemDataById();
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { ServerMusicFolderListQuery } from '/@/shared/types/domain/server-domain-types';
export const useMusicFolders = (args: QueryHookArgs<ServerMusicFolderListQuery>) => {
export const useMusicFolders = (args: RQueryHookArgs<ServerMusicFolderListQuery>) => {
const { options, serverId } = args || {};
const server = useServerById(serverId);
@@ -2,12 +2,12 @@ import { useMutation } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { RMutationHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { AnyLibraryItems } from '/@/shared/types/domain/shared-domain-types';
import { ShareItemRequest, ShareItemResponse } from '/@/shared/types/domain/user-domain-types';
export const useShareItem = (args: MutationHookArgs) => {
export const useShareItem = (args: RMutationHookArgs) => {
const { options } = args || {};
return useMutation<
@@ -17,7 +17,7 @@ import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { ButtonProps } from '/@/shared/components/button/button';
import { Group } from '/@/shared/components/group/group';
import { Text } from '/@/shared/components/text/text';
import { Playlist, PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types';
import { Playlist, PlaylistListSortOptions } from '/@/shared/types/domain/playlist-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { Play } from '/@/shared/types/types';
@@ -140,9 +140,9 @@ export const SidebarPlaylistList = () => {
const playlistsQuery = usePlaylistList({
query: {
sortBy: PlaylistListSort.NAME,
sortOrder: ListSortOrder.ASC,
offset: 0,
sortBy: PlaylistListSortOptions.NAME,
sortOrder: ListSortOrder.ASC,
},
serverId: server?.id,
});
@@ -256,9 +256,9 @@ export const SidebarSharedPlaylistList = () => {
const playlistsQuery = usePlaylistList({
query: {
sortBy: PlaylistListSort.NAME,
sortOrder: ListSortOrder.ASC,
offset: 0,
sortBy: PlaylistListSortOptions.NAME,
sortOrder: ListSortOrder.ASC,
},
serverId: server?.id,
});
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { SimilarSongsQuery } from '/@/shared/types/domain/song-domain-types';
export const useSimilarSongs = (args: QueryHookArgs<SimilarSongsQuery>) => {
export const useSimilarSongs = (args: RQueryHookArgs<SimilarSongsQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { SongListQuery } from '/@/shared/types/domain/song-domain-types';
export const useSongListCount = (args: QueryHookArgs<SongListQuery>) => {
export const useSongListCount = (args: RQueryHookArgs<SongListQuery>) => {
const { options, query, serverId } = args;
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { SongListQuery } from '/@/shared/types/domain/song-domain-types';
export const useSongList = (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
export const useSongList = (args: RQueryHookArgs<SongListQuery>, imageSize?: number) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -2,13 +2,13 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useServerById } from '/@/renderer/store';
import { hasFeature } from '/@/shared/api/utils';
import { ServerFeature } from '/@/shared/types/domain/server-domain-types';
import { TagQuery } from '/@/shared/types/domain/tag-domain-types';
export const useTagList = (args: QueryHookArgs<TagQuery>) => {
export const useTagList = (args: RQueryHookArgs<TagQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
@@ -1,4 +1,4 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { useServerById } from '/@/renderer/store';
import { UserListQuery } from '/@/shared/types/domain/user-domain-types';
export const useUserList = (args: QueryHookArgs<UserListQuery>) => {
export const useUserList = (args: RQueryHookArgs<UserListQuery>) => {
const { options, query, serverId } = args || {};
const server = useServerById(serverId);
+6 -10
View File
@@ -41,8 +41,6 @@ export type InfiniteQueryOptions = {
gcTime?: UseInfiniteQueryOptions['gcTime'];
meta?: UseInfiniteQueryOptions['meta'];
onError?: (err: any) => void;
onSettled?: any;
onSuccess?: any;
queryKey?: UseInfiniteQueryOptions['queryKey'];
refetchInterval?: number;
refetchIntervalInBackground?: UseInfiniteQueryOptions['refetchIntervalInBackground'];
@@ -53,11 +51,11 @@ export type InfiniteQueryOptions = {
useErrorBoundary?: boolean;
};
export type MutationHookArgs = {
options?: MutationOptions;
export type RMutationHookArgs = {
options?: RMutationOptions;
};
export type MutationOptions = {
export type RMutationOptions = {
mutationKey: UseMutationOptions['mutationKey'];
onError?: (err: any) => void;
onSettled?: any;
@@ -67,19 +65,17 @@ export type MutationOptions = {
useErrorBoundary?: boolean;
};
export type QueryHookArgs<T> = {
options?: QueryOptions;
export type RQueryHookArgs<T> = {
options?: RQueryOptions;
query: T;
serverId: string | undefined;
};
export type QueryOptions = {
export type RQueryOptions = {
enabled?: UseQueryOptions['enabled'];
gcTime?: UseQueryOptions['gcTime'];
meta?: UseQueryOptions['meta'];
onError?: (err: any) => void;
onSettled?: any;
onSuccess?: any;
queryKey?: UseQueryOptions['queryKey'];
refetchInterval?: number;
refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground'];
+15 -15
View File
@@ -6,28 +6,28 @@ import { createWithEqualityFn } from 'zustand/traditional';
import { DataTableProps, PersistedTableColumn } from '/@/renderer/store/settings.store';
import { mergeOverridingColumns } from '/@/renderer/store/utils';
import { AlbumListRequest, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
import {
AlbumArtistListRequest,
AlbumArtistListSort,
ArtistListRequest,
} from '/@/shared/types/domain/artist-domain-types';
import { GenreListRequest, GenreListSort } from '/@/shared/types/domain/genre-domain-types';
import { AlbumArtistListSort, ArtistListRequest } from '/@/shared/types/domain/artist-domain-types';
import { GenreListRequest, GenreListSortOptions } from '/@/shared/types/domain/genre-domain-types';
import {
PlaylistListRequest,
PlaylistListSort,
PlaylistListSortOptions,
} from '/@/shared/types/domain/playlist-domain-types';
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { SongListRequest, SongListSort } from '/@/shared/types/domain/song-domain-types';
import {
SongListRequest,
SongListSort,
SongListSortOptions,
} from '/@/shared/types/domain/song-domain-types';
import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types';
export const generatePageKey = (page: string, id?: string) => {
return id ? `${page}_${id}` : page;
};
export type AlbumArtistListFilter = Omit<AlbumArtistListRequest['query'], 'limit' | 'startIndex'>;
export type AlbumListFilter = Omit<AlbumListRequest['query'], 'limit' | 'startIndex'>;
export type ArtistListFilter = Omit<ArtistListRequest['query'], 'limit' | 'startIndex'>;
export type GenreListFilter = Omit<GenreListRequest['query'], 'limit' | 'startIndex'>;
export type AlbumArtistListFilter = Omit<ArtistListRequest['query'], 'limit' | 'offset'>;
export type AlbumListFilter = Omit<AlbumListRequest['query'], 'limit' | 'offset'>;
export type ArtistListFilter = Omit<ArtistListRequest['query'], 'limit' | 'offset'>;
export type GenreListFilter = Omit<GenreListRequest['query'], 'limit' | 'offset'>;
export type ListDeterministicArgs = { key: ListKey };
export type ListGridProps = {
itemGap?: number;
@@ -544,7 +544,7 @@ export const useListStore = createWithEqualityFn<ListSlice>()(
genre: {
display: ListDisplayType.TABLE,
filter: {
sortBy: GenreListSort.NAME,
sortBy: GenreListSortOptions.NAME,
sortOrder: ListSortOrder.ASC,
},
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
@@ -573,7 +573,7 @@ export const useListStore = createWithEqualityFn<ListSlice>()(
playlist: {
display: ListDisplayType.GRID,
filter: {
sortBy: PlaylistListSort.NAME,
sortBy: PlaylistListSortOptions.NAME,
sortOrder: ListSortOrder.DESC,
},
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
@@ -606,7 +606,7 @@ export const useListStore = createWithEqualityFn<ListSlice>()(
song: {
display: ListDisplayType.TABLE,
filter: {
sortBy: SongListSort.RECENTLY_ADDED,
sortBy: SongListSortOptions.RECENTLY_ADDED,
sortOrder: ListSortOrder.DESC,
},
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
+7 -4
View File
@@ -5,9 +5,12 @@ import { createWithEqualityFn } from 'zustand/traditional';
import { PlaylistListFilter, SongListFilter } from '/@/renderer/store/list.store';
import { DataTableProps } from '/@/renderer/store/settings.store';
import { mergeOverridingColumns } from '/@/renderer/store/utils';
import { PlaylistListSort, PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types';
import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types';
import {
PlaylistListSortOptions,
PlaylistListSortOptions,
} from '/@/shared/types/domain/playlist-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types';
export interface PlaylistSlice extends PlaylistState {
actions: {
@@ -155,8 +158,8 @@ export const usePlaylistStore = createWithEqualityFn<PlaylistSlice>()(
list: {
display: ListDisplayType.TABLE,
filter: {
musicFolderId: undefined,
sortBy: PlaylistListSort.NAME,
offset: 0,
sortBy: PlaylistListSortOptions.NAME,
sortOrder: ListSortOrder.ASC,
},
table: {
+13 -1
View File
@@ -55,7 +55,7 @@ export const createApiClient = (
};
const authMiddleware: (server: ServerListItem) => Middleware = (server: ServerListItem) => ({
onRequest: async ({ params }) => {
onRequest: async ({ params, request }) => {
const credential = deserializeCredential(server.credential);
if (params.query) {
@@ -67,6 +67,18 @@ const authMiddleware: (server: ServerListItem) => Middleware = (server: ServerLi
params.query[key] = value;
}
}
const stringifiedParams = qs.stringify(params.query, { arrayFormat: 'repeat' });
const url = new URL(request.url);
url.search = stringifiedParams;
return new Request(url.toString(), {
body: request.body,
headers: request.headers,
method: request.method,
signal: request.signal,
});
},
});
+1 -2
View File
@@ -2,7 +2,6 @@ import { AxiosHeaders } from 'axios';
import dayjs from 'dayjs';
import isElectron from 'is-electron';
import { orderBy, shuffle } from 'lodash';
import { stringify } from 'querystring';
import semverCoerce from 'semver/functions/coerce';
import semverGte from 'semver/functions/gte';
import { z } from 'zod';
@@ -388,7 +387,7 @@ function getListCountKey(options: {
serverId: string;
type: LibraryItem | string;
}) {
const hash = stringify(options.query as Record<string, boolean | null | number | string>);
const hash = JSON.stringify(options.query as Record<string, boolean | null | number | string>);
return `${options.serverId}::${options.type}::${hash}`;
}
@@ -0,0 +1,54 @@
.grid-carousel {
display: flex;
flex-direction: column;
gap: base.$gap-md;
width: 100%;
margin: 0 auto;
container-name: grid-carousel;
container-type: inline-size;
}
.navigation {
display: flex;
align-items: center;
justify-content: space-between;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(20%, 1fr));
gap: 1rem;
height: calc(var(--row-count) * (100cqw / 2 + 3rem));
margin-bottom: 1rem;
overflow: hidden;
/* @mixin larger-than-sm {
grid-template-columns: repeat(4, minmax(20%, 1fr));
height: calc(var(--row-count) * (100cqw / 4 + 3rem));
}
@mixin larger-than-md {
grid-template-columns: repeat(5, minmax(15%, 1fr));
height: calc(var(--row-count) * (100cqw / 5 + 3rem));
}
@mixin larger-than-lg {
grid-template-columns: repeat(6, minmax(15%, 1fr));
height: calc(var(--row-count) * (100cqw / 6 + 3rem));
}
@mixin larger-than-xl {
grid-template-columns: repeat(7, minmax(10%, 1fr));
height: calc(var(--row-count) * (100cqw / 7 + 3rem));
}
@mixin larger-than-2xl {
grid-template-columns: repeat(8, minmax(5%, 1fr));
height: calc(var(--row-count) * (100cqw / 8 + 3rem));
}
@mixin larger-than-3xl {
grid-template-columns: repeat(9, minmax(5%, 1fr));
height: calc(var(--row-count) * (100cqw / 9 + 3rem));
} */
}
@@ -0,0 +1,162 @@
import { AnimatePresence, motion, Variants } from 'motion/react';
import { memo, ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
import styles from './grid-carousel.module.css';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Group } from '/@/shared/components/group/group';
import { TextTitle } from '/@/shared/components/text-title/text-title';
import { useContainerBreakpoints } from '/@/shared/hooks/use-container-breakpoints';
interface Card {
content: ReactNode;
id: string;
}
interface GridCarouselProps {
cards: Card[];
loadNextPage?: () => void;
onNextPage: (page: number) => void;
onPrevPage: (page: number) => void;
rowCount?: number;
title?: string;
}
const MemoizedCard = memo(({ content }: { content: ReactNode }) => (
<div className={styles.card}>{content}</div>
));
MemoizedCard.displayName = 'MemoizedCard';
const pageVariants: Variants = {
animate: { opacity: 1, transition: { duration: 0.3, ease: 'easeOut' }, x: 0 },
exit: (custom: { isNext: boolean }) => ({
opacity: 0,
transition: { duration: 0.3, ease: 'easeIn' },
x: custom.isNext ? -100 : 100,
}),
initial: (custom: { isNext: boolean }) => ({ opacity: 0, x: custom.isNext ? 100 : -100 }),
};
export function GridCarousel(props: GridCarouselProps) {
const { cards, loadNextPage, onNextPage, onPrevPage, rowCount = 1, title } = props;
const { breakpoints, ref: containerRef } = useContainerBreakpoints();
const [currentPage, setCurrentPage] = useState({
isNext: false,
page: 0,
});
const handlePrevPage = useCallback(() => {
setCurrentPage((prev) => ({
isNext: false,
page: prev.page > 0 ? prev.page - 1 : 0,
}));
onPrevPage(currentPage.page);
}, [currentPage, onPrevPage]);
const handleNextPage = useCallback(() => {
setCurrentPage((prev) => ({
isNext: true,
page: prev.page + 1,
}));
onNextPage(currentPage.page);
}, [currentPage, onNextPage]);
const cardsToShow = getCardsToShow(breakpoints);
const visibleCards = useMemo(() => {
return cards.slice(
currentPage.page * cardsToShow * rowCount,
(currentPage.page + 1) * cardsToShow * rowCount,
);
}, [cards, currentPage, cardsToShow, rowCount]);
const shouldLoadNextPage = visibleCards.length < cardsToShow * rowCount;
useEffect(() => {
if (shouldLoadNextPage) {
loadNextPage?.();
}
}, [loadNextPage, shouldLoadNextPage]);
const isPrevDisabled = currentPage.page === 0;
const isNextDisabled = visibleCards.length < cardsToShow * rowCount;
return (
<motion.div className={styles.gridCarousel} ref={containerRef}>
<div className={styles.navigation}>
<TextTitle order={1} size="lg">
{title}
</TextTitle>
<Group gap="xs" justify="end">
<ActionIcon
disabled={isPrevDisabled}
icon="arrowLeftS"
onClick={handlePrevPage}
size="lg"
variant="default"
/>
<ActionIcon
disabled={isNextDisabled}
icon="arrowRightS"
onClick={handleNextPage}
size="lg"
variant="default"
/>
</Group>
</div>
<AnimatePresence custom={currentPage} initial={false} mode="wait">
<motion.div
animate="animate"
className={styles.grid}
custom={currentPage}
exit="exit"
initial="initial"
key={currentPage.page}
style={{ '--row-count': rowCount } as React.CSSProperties}
variants={pageVariants}
>
{visibleCards.map((card) => (
<MemoizedCard content={card.content} key={card.id} />
))}
</motion.div>
</AnimatePresence>
</motion.div>
);
}
function getCardsToShow(breakpoints: {
isLargerThan2xl: boolean;
isLargerThan3xl: boolean;
isLargerThanLg: boolean;
isLargerThanMd: boolean;
isLargerThanSm: boolean;
isLargerThanXl: boolean;
}) {
if (breakpoints.isLargerThan3xl) {
return 10;
}
if (breakpoints.isLargerThan2xl) {
return 8;
}
if (breakpoints.isLargerThanXl) {
return 7;
}
if (breakpoints.isLargerThanLg) {
return 6;
}
if (breakpoints.isLargerThanMd) {
return 5;
}
if (breakpoints.isLargerThanSm) {
return 4;
}
return 2;
}
@@ -0,0 +1,54 @@
import { useResizeObserver } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { Breakpoints } from '/@/shared/types/types';
export function useContainerBreakpoints() {
const [ref, rect] = useResizeObserver();
const [globalBreakpoints, setGlobalBreakpoints] = useState({
lg: 0,
md: 0,
sm: 0,
xl: 0,
xxl: 0,
xxxl: 0,
});
useEffect(() => {
const root = document.documentElement;
const computedStyle = getComputedStyle(root);
const getBreakpointValue = (breakpoint: string) => {
const rootFontSize = 16;
const value = computedStyle.getPropertyValue(`--theme-breakpoint-${breakpoint}`).trim();
return parseInt(value, 10) * rootFontSize || 0;
};
setGlobalBreakpoints({
lg: getBreakpointValue('lg'),
md: getBreakpointValue('md'),
sm: getBreakpointValue('sm'),
xl: getBreakpointValue('xl'),
xxl: getBreakpointValue('xxl'),
xxxl: getBreakpointValue('xxxl'),
});
}, []);
const isLargerThanSm = rect?.width >= globalBreakpoints.sm;
const isLargerThanMd = rect?.width >= globalBreakpoints.md;
const isLargerThanLg = rect?.width >= globalBreakpoints.lg;
const isLargerThanXl = rect?.width >= globalBreakpoints.xl;
const isLargerThan2xl = rect?.width >= globalBreakpoints.xxl;
const isLargerThan3xl = rect?.width >= globalBreakpoints.xxxl;
const breakpoints: Breakpoints = {
isLargerThan2xl,
isLargerThan3xl,
isLargerThanLg,
isLargerThanMd,
isLargerThanSm,
isLargerThanXl,
};
return { breakpoints, rect, ref };
}
@@ -1,11 +1,8 @@
import { orderBy, shuffle } from 'lodash';
import { z } from 'zod';
import i18n from '/@/i18n/i18n';
import { JFAlbumListSort } from '/@/shared/api/jellyfin.types';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { NDAlbumListSort } from '/@/shared/api/navidrome.types';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import {
BasePaginatedQuery,
BasePaginatedResponse,
@@ -91,7 +88,7 @@ export interface AlbumListQuery extends BasePaginatedQuery<AlbumListSortOptions>
export type AlbumListRequest = { query: AlbumListQuery; totalRecordCount?: number };
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | undefined;
export type AlbumListResponse = BasePaginatedResponse<Album[]>;
type AlbumListSortMap = {
jellyfin: Record<AlbumListSort, JFAlbumListSort | undefined>;
@@ -199,7 +196,7 @@ export type AlbumDetailQuery = { id: string };
export type AlbumDetailRequest = { query: AlbumDetailQuery };
export type AlbumDetailResponse = Album | null | undefined;
export type AlbumDetailResponse = Album;
export type AlbumInfo = {
imageUrl: null | string;
@@ -133,7 +133,7 @@ export interface ArtistListQuery extends BasePaginatedQuery<ArtistListSortOption
export type ArtistListRequest = { query: ArtistListQuery; totalRecordCount?: number };
export type ArtistListResponse = BasePaginatedResponse<Artist[]> | null | undefined;
export type ArtistListResponse = BasePaginatedResponse<Artist[]>;
type ArtistListSortMap = {
jellyfin: Record<ArtistListSort, JFArtistListSort | undefined>;
navidrome: Record<ArtistListSort, undefined>;
@@ -36,7 +36,7 @@ export interface GenreListQuery extends BasePaginatedQuery<GenreListSortOptions>
export type GenreListRequest = { query: GenreListQuery; totalRecordCount?: number };
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
export type GenreListResponse = BasePaginatedResponse<Genre[]>;
export type RelatedGenre = {
id: string;
@@ -119,7 +119,7 @@ export interface PlaylistListQuery extends BasePaginatedQuery<PlaylistListSortOp
export type PlaylistListRequest = { query: PlaylistListQuery; totalRecordCount?: number };
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]> | null | undefined;
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]>;
export type PlaylistSong = Song & {
playlistItemId: string;
+2 -2
View File
@@ -153,7 +153,7 @@ export interface SongListQuery extends BasePaginatedQuery<SongListSort> {
export type SongListRequest = { query: SongListQuery; totalRecordCount?: number };
export type SongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
export type SongListResponse = BasePaginatedResponse<Song[]>;
type SongListSortMap = {
jellyfin: Record<SongListSort, JFSongListSort | undefined>;
navidrome: Record<SongListSort, NDSongListSort | undefined>;
@@ -263,7 +263,7 @@ export type TopSongListQuery = {
export type TopSongListRequest = { query: TopSongListQuery; totalRecordCount?: number };
export type TopSongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
export type TopSongListResponse = BasePaginatedResponse<Song[]>;
export const sortSongList = (
songs: QueueSong[],
+9
View File
@@ -24,6 +24,15 @@ export enum Platform {
WINDOWS = 'windows',
}
export type Breakpoints = {
isLargerThan2xl: boolean;
isLargerThan3xl: boolean;
isLargerThanLg: boolean;
isLargerThanMd: boolean;
isLargerThanSm: boolean;
isLargerThanXl: boolean;
};
export type CardRoute = {
route: AppRoute | string;
slugs?: RouteSlug[];