mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
temp
This commit is contained in:
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
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 { albumListSortMap } from '/@/shared/types/domain/album-domain-types';
|
||||||
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
|
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
|
||||||
import { albumArtistListSortMap } from '/@/shared/types/domain/artist-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 { 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 { ServerFeature } from '/@/shared/types/domain/server-domain-types';
|
||||||
import { LibraryItem, sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
|
import { LibraryItem, sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
|
||||||
import { Song, songListSortMap } from '/@/shared/types/domain/song-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 { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
|
||||||
import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types';
|
import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types';
|
||||||
import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types';
|
import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types';
|
||||||
import { genreListSortMap } from '/@/shared/types/domain/genre-domain-types';
|
|
||||||
import {
|
import {
|
||||||
playlistListSortMap,
|
|
||||||
PlaylistSongListRequest,
|
PlaylistSongListRequest,
|
||||||
PlaylistSongListResponse,
|
PlaylistSongListResponse,
|
||||||
} from '/@/shared/types/domain/playlist-domain-types';
|
} from '/@/shared/types/domain/playlist-domain-types';
|
||||||
@@ -24,7 +22,6 @@ import {
|
|||||||
} from '/@/shared/types/domain/server-domain-types';
|
} from '/@/shared/types/domain/server-domain-types';
|
||||||
import { sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
|
import { sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
|
||||||
import { Song, songListSortMap } from '/@/shared/types/domain/song-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 = [
|
const VERSION_INFO: VersionInfo = [
|
||||||
['0.55.0', { [ServerFeature.BFR]: [1] }],
|
['0.55.0', { [ServerFeature.BFR]: [1] }],
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import {
|
|||||||
import { AlbumListSort, sortAlbumList } from '/@/shared/types/domain/album-domain-types';
|
import { AlbumListSort, sortAlbumList } from '/@/shared/types/domain/album-domain-types';
|
||||||
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
|
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
|
||||||
import { sortAlbumArtistList } from '/@/shared/types/domain/artist-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 { ServerFeatures } from '/@/shared/types/domain/server-domain-types';
|
||||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||||
import { Song, sortSongList } from '/@/shared/types/domain/song-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 { PersistedTableColumn, useListStoreActions } from '/@/renderer/store';
|
||||||
import { ListKey, useListStoreByKey } from '/@/renderer/store/list.store';
|
import { ListKey, useListStoreByKey } from '/@/renderer/store/list.store';
|
||||||
import {
|
import {
|
||||||
BasePaginatedResponse,
|
|
||||||
BasePaginatedQuery,
|
BasePaginatedQuery,
|
||||||
|
BasePaginatedResponse,
|
||||||
} from '/@/shared/types/adapter/api-controller-types';
|
} from '/@/shared/types/adapter/api-controller-types';
|
||||||
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
|
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
|
||||||
import { LibraryItem } from '/@/shared/types/domain/shared-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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumDetailQuery } from '/@/shared/types/domain/album-domain-types';
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain/album-domain-types';
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { ArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
|
|
||||||
export const useRoles = (args: QueryHookArgs<object>) => {
|
export const useRoles = (args: RQueryHookArgs<object>) => {
|
||||||
const { options, serverId } = args;
|
const { options, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { TopSongListQuery } from '/@/shared/types/domain/song-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
|
import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
|
||||||
import { ListSortOrder } from '/@/shared/types/domain/shared-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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,21 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel';
|
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 { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
||||||
import { useAlbumList } from '/@/renderer/features/albums';
|
import { useAlbumList } from '/@/renderer/features/albums';
|
||||||
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
|
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
|
||||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
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 { useSongList } from '/@/renderer/features/songs';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
|
||||||
import {
|
import {
|
||||||
HomeItem,
|
HomeItem,
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
useGeneralSettings,
|
useGeneralSettings,
|
||||||
useWindowSettings,
|
useWindowSettings,
|
||||||
} from '/@/renderer/store';
|
} 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 { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { TextTitle } from '/@/shared/components/text-title/text-title';
|
import { AlbumListSort, AlbumListSortOptions } from '/@/shared/types/domain/album-domain-types';
|
||||||
import { AlbumListSort } from '/@/shared/types/domain/album-domain-types';
|
|
||||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||||
import { SongListSort } from '/@/shared/types/domain/song-domain-types';
|
import { SongListSort } from '/@/shared/types/domain/song-domain-types';
|
||||||
@@ -40,15 +35,15 @@ const HomeRoute = () => {
|
|||||||
|
|
||||||
const feature = useAlbumList({
|
const feature = useAlbumList({
|
||||||
options: {
|
options: {
|
||||||
gcTime: 1000 * 60,
|
|
||||||
enabled: homeFeature,
|
enabled: homeFeature,
|
||||||
|
gcTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
offset: 0,
|
||||||
sortBy: AlbumListSort.RANDOM,
|
sortBy: AlbumListSort.RANDOM,
|
||||||
sortOrder: ListSortOrder.DESC,
|
sortOrder: ListSortOrder.DESC,
|
||||||
offset: 0,
|
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
@@ -63,9 +58,9 @@ const HomeRoute = () => {
|
|||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
|
offset: 0,
|
||||||
sortBy: AlbumListSort.RANDOM,
|
sortBy: AlbumListSort.RANDOM,
|
||||||
sortOrder: ListSortOrder.ASC,
|
sortOrder: ListSortOrder.ASC,
|
||||||
offset: 0,
|
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
@@ -76,9 +71,9 @@ const HomeRoute = () => {
|
|||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
|
offset: 0,
|
||||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||||
sortOrder: ListSortOrder.DESC,
|
sortOrder: ListSortOrder.DESC,
|
||||||
offset: 0,
|
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
@@ -89,9 +84,9 @@ const HomeRoute = () => {
|
|||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
|
offset: 0,
|
||||||
sortBy: AlbumListSort.RECENTLY_ADDED,
|
sortBy: AlbumListSort.RECENTLY_ADDED,
|
||||||
sortOrder: ListSortOrder.DESC,
|
sortOrder: ListSortOrder.DESC,
|
||||||
offset: 0,
|
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
@@ -103,9 +98,9 @@ const HomeRoute = () => {
|
|||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
|
offset: 0,
|
||||||
sortBy: AlbumListSort.PLAY_COUNT,
|
sortBy: AlbumListSort.PLAY_COUNT,
|
||||||
sortOrder: ListSortOrder.DESC,
|
sortOrder: ListSortOrder.DESC,
|
||||||
offset: 0,
|
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
@@ -118,9 +113,9 @@ const HomeRoute = () => {
|
|||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
|
offset: 0,
|
||||||
sortBy: SongListSort.PLAY_COUNT,
|
sortBy: SongListSort.PLAY_COUNT,
|
||||||
sortOrder: ListSortOrder.DESC,
|
sortOrder: ListSortOrder.DESC,
|
||||||
offset: 0,
|
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
},
|
},
|
||||||
@@ -253,7 +248,15 @@ const HomeRoute = () => {
|
|||||||
px="2rem"
|
px="2rem"
|
||||||
>
|
>
|
||||||
{homeFeature && <FeatureCarousel data={featureItemsWithImage} />}
|
{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
|
<MemoizedSwiperGridCarousel
|
||||||
cardRows={[
|
cardRows={[
|
||||||
{
|
{
|
||||||
@@ -317,7 +320,7 @@ const HomeRoute = () => {
|
|||||||
}}
|
}}
|
||||||
uniqueId={carousel.uniqueId}
|
uniqueId={carousel.uniqueId}
|
||||||
/>
|
/>
|
||||||
))}
|
))} */}
|
||||||
</Stack>
|
</Stack>
|
||||||
</NativeScrollArea>
|
</NativeScrollArea>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import isElectron from 'is-electron';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById, useLyricsSettings } from '/@/renderer/store';
|
||||||
import { hasFeature } from '/@/shared/api/utils';
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
import {
|
import {
|
||||||
@@ -61,7 +61,7 @@ const formatLyrics = (lyrics: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useServerLyrics = (
|
export const useServerLyrics = (
|
||||||
args: QueryHookArgs<LyricsQuery>,
|
args: RQueryHookArgs<LyricsQuery>,
|
||||||
): UseQueryResult<null | string> => {
|
): UseQueryResult<null | string> => {
|
||||||
const { query, serverId } = args;
|
const { query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
@@ -81,7 +81,7 @@ export const useServerLyrics = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useSongLyricsBySong = (
|
export const useSongLyricsBySong = (
|
||||||
args: QueryHookArgs<LyricsQuery>,
|
args: RQueryHookArgs<LyricsQuery>,
|
||||||
song: QueueSong | undefined,
|
song: QueueSong | undefined,
|
||||||
): UseQueryResult<FullLyricsMetadata | StructuredLyric[]> => {
|
): UseQueryResult<FullLyricsMetadata | StructuredLyric[]> => {
|
||||||
const { query } = args;
|
const { query } = args;
|
||||||
@@ -170,7 +170,7 @@ export const useSongLyricsBySong = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useSongLyricsByRemoteId = (
|
export const useSongLyricsByRemoteId = (
|
||||||
args: QueryHookArgs<Partial<LyricGetQuery>>,
|
args: RQueryHookArgs<Partial<LyricGetQuery>>,
|
||||||
): UseQueryResult<null | string> => {
|
): UseQueryResult<null | string> => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { query, serverId } = args;
|
const { query, serverId } = args;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
import { RQueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
import {
|
import {
|
||||||
InternetProviderLyricSearchResponse,
|
InternetProviderLyricSearchResponse,
|
||||||
LyricSearchQuery,
|
LyricSearchQuery,
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
|
|
||||||
const lyricsIpc = isElectron() ? window.api.lyrics : null;
|
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;
|
const { options, query } = args;
|
||||||
|
|
||||||
return useQuery<Record<LyricSource, InternetProviderLyricSearchResponse[]>>({
|
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 { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
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 { Played } from '/@/shared/types/domain/player-domain-types';
|
||||||
import { ServerListItem, ServerType } from '/@/shared/types/domain/server-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 { RandomSongListQuery } from '/@/shared/types/domain/song-domain-types';
|
||||||
import { Play, PlayQueueAddOptions } from '/@/shared/types/types';
|
import { Play, PlayQueueAddOptions } from '/@/shared/types/types';
|
||||||
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
|
||||||
|
|
||||||
interface ShuffleAllSlice extends RandomSongListQuery {
|
interface ShuffleAllSlice extends RandomSongListQuery {
|
||||||
actions: {
|
actions: {
|
||||||
@@ -256,9 +256,9 @@ export const openShuffleAllModal = async (
|
|||||||
signal,
|
signal,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
|
offset: 0,
|
||||||
sortBy: GenreListSort.NAME,
|
sortBy: GenreListSort.NAME,
|
||||||
sortOrder: ListSortOrder.ASC,
|
sortOrder: ListSortOrder.ASC,
|
||||||
offset: 0,
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
queryKey: queryKeys.genres.list(server?.id),
|
queryKey: queryKeys.genres.list(server?.id),
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { useMutation } from '@tanstack/react-query';
|
|||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
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 { useServerById, useIncrementQueuePlayCount } from '/@/renderer/store';
|
||||||
import { usePlayEvent } from '/@/renderer/store/event.store';
|
import { usePlayEvent } from '/@/renderer/store/event.store';
|
||||||
import { ScrobbleRequest, ScrobbleResponse } from '/@/shared/types/domain/user-domain-types';
|
import { ScrobbleRequest, ScrobbleResponse } from '/@/shared/types/domain/user-domain-types';
|
||||||
|
|
||||||
export const useSendScrobble = (options?: MutationOptions) => {
|
export const useSendScrobble = (options?: RMutationOptions) => {
|
||||||
const incrementPlayCount = useIncrementQueuePlayCount();
|
const incrementPlayCount = useIncrementQueuePlayCount();
|
||||||
const sendPlayEvent = usePlayEvent();
|
const sendPlayEvent = usePlayEvent();
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import { MultiSelect } from '/@/shared/components/multi-select/multi-select';
|
|||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types';
|
import { PlaylistListSortOptions } from '/@/shared/types/domain/playlist-domain-types';
|
||||||
import { SongListQuery, SongListSort } from '/@/shared/types/domain/song-domain-types';
|
|
||||||
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||||
|
import { SongListQuery, SongListSort } from '/@/shared/types/domain/song-domain-types';
|
||||||
|
|
||||||
export const AddToPlaylistContextModal = ({
|
export const AddToPlaylistContextModal = ({
|
||||||
id,
|
id,
|
||||||
@@ -44,9 +44,9 @@ export const AddToPlaylistContextModal = ({
|
|||||||
smart: false,
|
smart: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sortBy: PlaylistListSort.NAME,
|
|
||||||
sortOrder: ListSortOrder.ASC,
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
sortBy: PlaylistListSortOptions.NAME,
|
||||||
|
sortOrder: ListSortOrder.ASC,
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
@@ -70,9 +70,9 @@ export const AddToPlaylistContextModal = ({
|
|||||||
const getSongsByAlbum = async (albumId: string) => {
|
const getSongsByAlbum = async (albumId: string) => {
|
||||||
const query: SongListQuery = {
|
const query: SongListQuery = {
|
||||||
albumIds: [albumId],
|
albumIds: [albumId],
|
||||||
|
offset: 0,
|
||||||
sortBy: SongListSort.ALBUM,
|
sortBy: SongListSort.ALBUM,
|
||||||
sortOrder: ListSortOrder.ASC,
|
sortOrder: ListSortOrder.ASC,
|
||||||
offset: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||||
@@ -88,9 +88,9 @@ export const AddToPlaylistContextModal = ({
|
|||||||
const getSongsByArtist = async (artistId: string) => {
|
const getSongsByArtist = async (artistId: string) => {
|
||||||
const query: SongListQuery = {
|
const query: SongListQuery = {
|
||||||
artistIds: [artistId],
|
artistIds: [artistId],
|
||||||
|
offset: 0,
|
||||||
sortBy: SongListSort.ARTIST,
|
sortBy: SongListSort.ARTIST,
|
||||||
sortOrder: ListSortOrder.ASC,
|
sortOrder: ListSortOrder.ASC,
|
||||||
offset: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
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 { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||||
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
||||||
import { Select } from '/@/shared/components/select/select';
|
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 { SongListSort } from '/@/shared/types/domain/song-domain-types';
|
||||||
import { QueryBuilderGroup, QueryBuilderRule } from '/@/shared/types/types';
|
import { QueryBuilderGroup, QueryBuilderRule } from '/@/shared/types/types';
|
||||||
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
|
||||||
|
|
||||||
type AddArgs = {
|
type AddArgs = {
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
@@ -111,7 +110,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: playlists } = usePlaylistList({
|
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,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { AxiosError } from 'axios';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import {
|
import {
|
||||||
AddToPlaylistArgs,
|
AddToPlaylistArgs,
|
||||||
AddToPlaylistResponse,
|
AddToPlaylistResponse,
|
||||||
} from '/@/shared/types/domain/playlist-domain-types';
|
} from '/@/shared/types/domain/playlist-domain-types';
|
||||||
|
|
||||||
export const useAddToPlaylist = (args: MutationHookArgs) => {
|
export const useAddToPlaylist = (args: RMutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { AxiosError } from 'axios';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { CreatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
import { CreatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||||
|
|
||||||
export const useCreatePlaylist = (args: MutationHookArgs) => {
|
export const useCreatePlaylist = (args: RMutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { AxiosError } from 'axios';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById, useCurrentServer } from '/@/renderer/store';
|
||||||
import { DeletePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
import { DeletePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||||
|
|
||||||
export const useDeletePlaylist = (args: MutationHookArgs) => {
|
export const useDeletePlaylist = (args: RMutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { AxiosError, AxiosError } from 'axios';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { RemoveFromPlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
import { RemoveFromPlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||||
|
|
||||||
export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
export const useRemoveFromPlaylist = (options?: RMutationOptions) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<
|
return useMutation<
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { AxiosError } from 'axios';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { UpdatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
import { UpdatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||||
|
|
||||||
export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
export const useUpdatePlaylist = (args: RMutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { PlaylistDetailQuery } from '/@/shared/types/domain/playlist-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import { useServerById } from '/@/renderer/store';
|
|||||||
import { PlaylistListQuery } from '/@/shared/types/domain/playlist-domain-types';
|
import { PlaylistListQuery } from '/@/shared/types/domain/playlist-domain-types';
|
||||||
|
|
||||||
export const usePlaylistList = (args: {
|
export const usePlaylistList = (args: {
|
||||||
options?: QueryOptions;
|
options?: RQueryOptions;
|
||||||
query: PlaylistListQuery;
|
query: PlaylistListQuery;
|
||||||
serverId?: string;
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { PlaylistSongListQuery } from '/@/shared/types/domain/playlist-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { SearchQuery } from '/@/shared/types/domain/search-domain-types';
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
+100
@@ -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 { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
|
||||||
import { useFavoriteEvent } from '/@/renderer/store/event.store';
|
import { useFavoriteEvent } from '/@/renderer/store/event.store';
|
||||||
import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
|
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;
|
const remote = isElectron() ? window.api.remote : null;
|
||||||
|
|
||||||
export const useCreateFavorite = (args: MutationHookArgs) => {
|
export const useCreateFavorite = (args: RMutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const setAlbumListData = useSetAlbumListItemDataById();
|
const setAlbumListData = useSetAlbumListItemDataById();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import isElectron from 'is-electron';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
|
||||||
import { useFavoriteEvent } from '/@/renderer/store/event.store';
|
import { useFavoriteEvent } from '/@/renderer/store/event.store';
|
||||||
import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
|
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;
|
const remote = isElectron() ? window.api.remote : null;
|
||||||
|
|
||||||
export const useDeleteFavorite = (args: MutationHookArgs) => {
|
export const useDeleteFavorite = (args: RMutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const setAlbumListData = useSetAlbumListItemDataById();
|
const setAlbumListData = useSetAlbumListItemDataById();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import isElectron from 'is-electron';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById, useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store';
|
||||||
import { useRatingEvent } from '/@/renderer/store/event.store';
|
import { useRatingEvent } from '/@/renderer/store/event.store';
|
||||||
import { Album, AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
|
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;
|
const remote = isElectron() ? window.api.remote : null;
|
||||||
|
|
||||||
export const useSetRating = (args: MutationHookArgs) => {
|
export const useSetRating = (args: RMutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const setAlbumListData = useSetAlbumListItemDataById();
|
const setAlbumListData = useSetAlbumListItemDataById();
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { ServerMusicFolderListQuery } from '/@/shared/types/domain/server-domain-types';
|
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 { options, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { useMutation } from '@tanstack/react-query';
|
|||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { AnyLibraryItems } from '/@/shared/types/domain/shared-domain-types';
|
import { AnyLibraryItems } from '/@/shared/types/domain/shared-domain-types';
|
||||||
import { ShareItemRequest, ShareItemResponse } from '/@/shared/types/domain/user-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 || {};
|
const { options } = args || {};
|
||||||
|
|
||||||
return useMutation<
|
return useMutation<
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
|||||||
import { ButtonProps } from '/@/shared/components/button/button';
|
import { ButtonProps } from '/@/shared/components/button/button';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
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 { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
@@ -140,9 +140,9 @@ export const SidebarPlaylistList = () => {
|
|||||||
|
|
||||||
const playlistsQuery = usePlaylistList({
|
const playlistsQuery = usePlaylistList({
|
||||||
query: {
|
query: {
|
||||||
sortBy: PlaylistListSort.NAME,
|
|
||||||
sortOrder: ListSortOrder.ASC,
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
sortBy: PlaylistListSortOptions.NAME,
|
||||||
|
sortOrder: ListSortOrder.ASC,
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
@@ -256,9 +256,9 @@ export const SidebarSharedPlaylistList = () => {
|
|||||||
|
|
||||||
const playlistsQuery = usePlaylistList({
|
const playlistsQuery = usePlaylistList({
|
||||||
query: {
|
query: {
|
||||||
sortBy: PlaylistListSort.NAME,
|
|
||||||
sortOrder: ListSortOrder.ASC,
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
sortBy: PlaylistListSortOptions.NAME,
|
||||||
|
sortOrder: ListSortOrder.ASC,
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { SimilarSongsQuery } from '/@/shared/types/domain/song-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { SongListQuery } from '/@/shared/types/domain/song-domain-types';
|
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 { options, query, serverId } = args;
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { SongListQuery } from '/@/shared/types/domain/song-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
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 { useServerById } from '/@/renderer/store';
|
||||||
import { hasFeature } from '/@/shared/api/utils';
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
import { ServerFeature } from '/@/shared/types/domain/server-domain-types';
|
import { ServerFeature } from '/@/shared/types/domain/server-domain-types';
|
||||||
import { TagQuery } from '/@/shared/types/domain/tag-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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
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';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useServerById } from '/@/renderer/store';
|
import { useServerById } from '/@/renderer/store';
|
||||||
import { UserListQuery } from '/@/shared/types/domain/user-domain-types';
|
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 { options, query, serverId } = args || {};
|
||||||
const server = useServerById(serverId);
|
const server = useServerById(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ export type InfiniteQueryOptions = {
|
|||||||
gcTime?: UseInfiniteQueryOptions['gcTime'];
|
gcTime?: UseInfiniteQueryOptions['gcTime'];
|
||||||
meta?: UseInfiniteQueryOptions['meta'];
|
meta?: UseInfiniteQueryOptions['meta'];
|
||||||
onError?: (err: any) => void;
|
onError?: (err: any) => void;
|
||||||
onSettled?: any;
|
|
||||||
onSuccess?: any;
|
|
||||||
queryKey?: UseInfiniteQueryOptions['queryKey'];
|
queryKey?: UseInfiniteQueryOptions['queryKey'];
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
refetchIntervalInBackground?: UseInfiniteQueryOptions['refetchIntervalInBackground'];
|
refetchIntervalInBackground?: UseInfiniteQueryOptions['refetchIntervalInBackground'];
|
||||||
@@ -53,11 +51,11 @@ export type InfiniteQueryOptions = {
|
|||||||
useErrorBoundary?: boolean;
|
useErrorBoundary?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MutationHookArgs = {
|
export type RMutationHookArgs = {
|
||||||
options?: MutationOptions;
|
options?: RMutationOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MutationOptions = {
|
export type RMutationOptions = {
|
||||||
mutationKey: UseMutationOptions['mutationKey'];
|
mutationKey: UseMutationOptions['mutationKey'];
|
||||||
onError?: (err: any) => void;
|
onError?: (err: any) => void;
|
||||||
onSettled?: any;
|
onSettled?: any;
|
||||||
@@ -67,19 +65,17 @@ export type MutationOptions = {
|
|||||||
useErrorBoundary?: boolean;
|
useErrorBoundary?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueryHookArgs<T> = {
|
export type RQueryHookArgs<T> = {
|
||||||
options?: QueryOptions;
|
options?: RQueryOptions;
|
||||||
query: T;
|
query: T;
|
||||||
serverId: string | undefined;
|
serverId: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueryOptions = {
|
export type RQueryOptions = {
|
||||||
enabled?: UseQueryOptions['enabled'];
|
enabled?: UseQueryOptions['enabled'];
|
||||||
gcTime?: UseQueryOptions['gcTime'];
|
gcTime?: UseQueryOptions['gcTime'];
|
||||||
meta?: UseQueryOptions['meta'];
|
meta?: UseQueryOptions['meta'];
|
||||||
onError?: (err: any) => void;
|
onError?: (err: any) => void;
|
||||||
onSettled?: any;
|
|
||||||
onSuccess?: any;
|
|
||||||
queryKey?: UseQueryOptions['queryKey'];
|
queryKey?: UseQueryOptions['queryKey'];
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground'];
|
refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground'];
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ import { createWithEqualityFn } from 'zustand/traditional';
|
|||||||
import { DataTableProps, PersistedTableColumn } from '/@/renderer/store/settings.store';
|
import { DataTableProps, PersistedTableColumn } from '/@/renderer/store/settings.store';
|
||||||
import { mergeOverridingColumns } from '/@/renderer/store/utils';
|
import { mergeOverridingColumns } from '/@/renderer/store/utils';
|
||||||
import { AlbumListRequest, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
|
import { AlbumListRequest, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
|
||||||
import {
|
import { AlbumArtistListSort, ArtistListRequest } from '/@/shared/types/domain/artist-domain-types';
|
||||||
AlbumArtistListRequest,
|
import { GenreListRequest, GenreListSortOptions } from '/@/shared/types/domain/genre-domain-types';
|
||||||
AlbumArtistListSort,
|
|
||||||
ArtistListRequest,
|
|
||||||
} from '/@/shared/types/domain/artist-domain-types';
|
|
||||||
import { GenreListRequest, GenreListSort } from '/@/shared/types/domain/genre-domain-types';
|
|
||||||
import {
|
import {
|
||||||
PlaylistListRequest,
|
PlaylistListRequest,
|
||||||
PlaylistListSort,
|
PlaylistListSortOptions,
|
||||||
} from '/@/shared/types/domain/playlist-domain-types';
|
} from '/@/shared/types/domain/playlist-domain-types';
|
||||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-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';
|
import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const generatePageKey = (page: string, id?: string) => {
|
export const generatePageKey = (page: string, id?: string) => {
|
||||||
return id ? `${page}_${id}` : page;
|
return id ? `${page}_${id}` : page;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AlbumArtistListFilter = Omit<AlbumArtistListRequest['query'], 'limit' | 'startIndex'>;
|
export type AlbumArtistListFilter = Omit<ArtistListRequest['query'], 'limit' | 'offset'>;
|
||||||
export type AlbumListFilter = Omit<AlbumListRequest['query'], 'limit' | 'startIndex'>;
|
export type AlbumListFilter = Omit<AlbumListRequest['query'], 'limit' | 'offset'>;
|
||||||
export type ArtistListFilter = Omit<ArtistListRequest['query'], 'limit' | 'startIndex'>;
|
export type ArtistListFilter = Omit<ArtistListRequest['query'], 'limit' | 'offset'>;
|
||||||
export type GenreListFilter = Omit<GenreListRequest['query'], 'limit' | 'startIndex'>;
|
export type GenreListFilter = Omit<GenreListRequest['query'], 'limit' | 'offset'>;
|
||||||
export type ListDeterministicArgs = { key: ListKey };
|
export type ListDeterministicArgs = { key: ListKey };
|
||||||
export type ListGridProps = {
|
export type ListGridProps = {
|
||||||
itemGap?: number;
|
itemGap?: number;
|
||||||
@@ -544,7 +544,7 @@ export const useListStore = createWithEqualityFn<ListSlice>()(
|
|||||||
genre: {
|
genre: {
|
||||||
display: ListDisplayType.TABLE,
|
display: ListDisplayType.TABLE,
|
||||||
filter: {
|
filter: {
|
||||||
sortBy: GenreListSort.NAME,
|
sortBy: GenreListSortOptions.NAME,
|
||||||
sortOrder: ListSortOrder.ASC,
|
sortOrder: ListSortOrder.ASC,
|
||||||
},
|
},
|
||||||
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
|
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
|
||||||
@@ -573,7 +573,7 @@ export const useListStore = createWithEqualityFn<ListSlice>()(
|
|||||||
playlist: {
|
playlist: {
|
||||||
display: ListDisplayType.GRID,
|
display: ListDisplayType.GRID,
|
||||||
filter: {
|
filter: {
|
||||||
sortBy: PlaylistListSort.NAME,
|
sortBy: PlaylistListSortOptions.NAME,
|
||||||
sortOrder: ListSortOrder.DESC,
|
sortOrder: ListSortOrder.DESC,
|
||||||
},
|
},
|
||||||
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
|
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
|
||||||
@@ -606,7 +606,7 @@ export const useListStore = createWithEqualityFn<ListSlice>()(
|
|||||||
song: {
|
song: {
|
||||||
display: ListDisplayType.TABLE,
|
display: ListDisplayType.TABLE,
|
||||||
filter: {
|
filter: {
|
||||||
sortBy: SongListSort.RECENTLY_ADDED,
|
sortBy: SongListSortOptions.RECENTLY_ADDED,
|
||||||
sortOrder: ListSortOrder.DESC,
|
sortOrder: ListSortOrder.DESC,
|
||||||
},
|
},
|
||||||
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
|
grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 },
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import { createWithEqualityFn } from 'zustand/traditional';
|
|||||||
import { PlaylistListFilter, SongListFilter } from '/@/renderer/store/list.store';
|
import { PlaylistListFilter, SongListFilter } from '/@/renderer/store/list.store';
|
||||||
import { DataTableProps } from '/@/renderer/store/settings.store';
|
import { DataTableProps } from '/@/renderer/store/settings.store';
|
||||||
import { mergeOverridingColumns } from '/@/renderer/store/utils';
|
import { mergeOverridingColumns } from '/@/renderer/store/utils';
|
||||||
import { PlaylistListSort, PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types';
|
import {
|
||||||
import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types';
|
PlaylistListSortOptions,
|
||||||
|
PlaylistListSortOptions,
|
||||||
|
} from '/@/shared/types/domain/playlist-domain-types';
|
||||||
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||||
|
import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types';
|
||||||
|
|
||||||
export interface PlaylistSlice extends PlaylistState {
|
export interface PlaylistSlice extends PlaylistState {
|
||||||
actions: {
|
actions: {
|
||||||
@@ -155,8 +158,8 @@ export const usePlaylistStore = createWithEqualityFn<PlaylistSlice>()(
|
|||||||
list: {
|
list: {
|
||||||
display: ListDisplayType.TABLE,
|
display: ListDisplayType.TABLE,
|
||||||
filter: {
|
filter: {
|
||||||
musicFolderId: undefined,
|
offset: 0,
|
||||||
sortBy: PlaylistListSort.NAME,
|
sortBy: PlaylistListSortOptions.NAME,
|
||||||
sortOrder: ListSortOrder.ASC,
|
sortOrder: ListSortOrder.ASC,
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const createApiClient = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const authMiddleware: (server: ServerListItem) => Middleware = (server: ServerListItem) => ({
|
const authMiddleware: (server: ServerListItem) => Middleware = (server: ServerListItem) => ({
|
||||||
onRequest: async ({ params }) => {
|
onRequest: async ({ params, request }) => {
|
||||||
const credential = deserializeCredential(server.credential);
|
const credential = deserializeCredential(server.credential);
|
||||||
|
|
||||||
if (params.query) {
|
if (params.query) {
|
||||||
@@ -67,6 +67,18 @@ const authMiddleware: (server: ServerListItem) => Middleware = (server: ServerLi
|
|||||||
params.query[key] = value;
|
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,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { AxiosHeaders } from 'axios';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { orderBy, shuffle } from 'lodash';
|
import { orderBy, shuffle } from 'lodash';
|
||||||
import { stringify } from 'querystring';
|
|
||||||
import semverCoerce from 'semver/functions/coerce';
|
import semverCoerce from 'semver/functions/coerce';
|
||||||
import semverGte from 'semver/functions/gte';
|
import semverGte from 'semver/functions/gte';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -388,7 +387,7 @@ function getListCountKey(options: {
|
|||||||
serverId: string;
|
serverId: string;
|
||||||
type: LibraryItem | 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}`;
|
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 { orderBy, shuffle } from 'lodash';
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { JFAlbumListSort } from '/@/shared/api/jellyfin.types';
|
import { JFAlbumListSort } from '/@/shared/api/jellyfin.types';
|
||||||
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
|
||||||
import { NDAlbumListSort } from '/@/shared/api/navidrome.types';
|
import { NDAlbumListSort } from '/@/shared/api/navidrome.types';
|
||||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
|
||||||
import {
|
import {
|
||||||
BasePaginatedQuery,
|
BasePaginatedQuery,
|
||||||
BasePaginatedResponse,
|
BasePaginatedResponse,
|
||||||
@@ -91,7 +88,7 @@ export interface AlbumListQuery extends BasePaginatedQuery<AlbumListSortOptions>
|
|||||||
|
|
||||||
export type AlbumListRequest = { query: AlbumListQuery; totalRecordCount?: number };
|
export type AlbumListRequest = { query: AlbumListQuery; totalRecordCount?: number };
|
||||||
|
|
||||||
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | undefined;
|
export type AlbumListResponse = BasePaginatedResponse<Album[]>;
|
||||||
|
|
||||||
type AlbumListSortMap = {
|
type AlbumListSortMap = {
|
||||||
jellyfin: Record<AlbumListSort, JFAlbumListSort | undefined>;
|
jellyfin: Record<AlbumListSort, JFAlbumListSort | undefined>;
|
||||||
@@ -199,7 +196,7 @@ export type AlbumDetailQuery = { id: string };
|
|||||||
|
|
||||||
export type AlbumDetailRequest = { query: AlbumDetailQuery };
|
export type AlbumDetailRequest = { query: AlbumDetailQuery };
|
||||||
|
|
||||||
export type AlbumDetailResponse = Album | null | undefined;
|
export type AlbumDetailResponse = Album;
|
||||||
|
|
||||||
export type AlbumInfo = {
|
export type AlbumInfo = {
|
||||||
imageUrl: null | string;
|
imageUrl: null | string;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export interface ArtistListQuery extends BasePaginatedQuery<ArtistListSortOption
|
|||||||
|
|
||||||
export type ArtistListRequest = { query: ArtistListQuery; totalRecordCount?: number };
|
export type ArtistListRequest = { query: ArtistListQuery; totalRecordCount?: number };
|
||||||
|
|
||||||
export type ArtistListResponse = BasePaginatedResponse<Artist[]> | null | undefined;
|
export type ArtistListResponse = BasePaginatedResponse<Artist[]>;
|
||||||
type ArtistListSortMap = {
|
type ArtistListSortMap = {
|
||||||
jellyfin: Record<ArtistListSort, JFArtistListSort | undefined>;
|
jellyfin: Record<ArtistListSort, JFArtistListSort | undefined>;
|
||||||
navidrome: Record<ArtistListSort, undefined>;
|
navidrome: Record<ArtistListSort, undefined>;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export interface GenreListQuery extends BasePaginatedQuery<GenreListSortOptions>
|
|||||||
|
|
||||||
export type GenreListRequest = { query: GenreListQuery; totalRecordCount?: number };
|
export type GenreListRequest = { query: GenreListQuery; totalRecordCount?: number };
|
||||||
|
|
||||||
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
|
export type GenreListResponse = BasePaginatedResponse<Genre[]>;
|
||||||
|
|
||||||
export type RelatedGenre = {
|
export type RelatedGenre = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export interface PlaylistListQuery extends BasePaginatedQuery<PlaylistListSortOp
|
|||||||
|
|
||||||
export type PlaylistListRequest = { query: PlaylistListQuery; totalRecordCount?: number };
|
export type PlaylistListRequest = { query: PlaylistListQuery; totalRecordCount?: number };
|
||||||
|
|
||||||
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]> | null | undefined;
|
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]>;
|
||||||
|
|
||||||
export type PlaylistSong = Song & {
|
export type PlaylistSong = Song & {
|
||||||
playlistItemId: string;
|
playlistItemId: string;
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export interface SongListQuery extends BasePaginatedQuery<SongListSort> {
|
|||||||
|
|
||||||
export type SongListRequest = { query: SongListQuery; totalRecordCount?: number };
|
export type SongListRequest = { query: SongListQuery; totalRecordCount?: number };
|
||||||
|
|
||||||
export type SongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
|
export type SongListResponse = BasePaginatedResponse<Song[]>;
|
||||||
type SongListSortMap = {
|
type SongListSortMap = {
|
||||||
jellyfin: Record<SongListSort, JFSongListSort | undefined>;
|
jellyfin: Record<SongListSort, JFSongListSort | undefined>;
|
||||||
navidrome: Record<SongListSort, NDSongListSort | undefined>;
|
navidrome: Record<SongListSort, NDSongListSort | undefined>;
|
||||||
@@ -263,7 +263,7 @@ export type TopSongListQuery = {
|
|||||||
|
|
||||||
export type TopSongListRequest = { query: TopSongListQuery; totalRecordCount?: number };
|
export type TopSongListRequest = { query: TopSongListQuery; totalRecordCount?: number };
|
||||||
|
|
||||||
export type TopSongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
|
export type TopSongListResponse = BasePaginatedResponse<Song[]>;
|
||||||
|
|
||||||
export const sortSongList = (
|
export const sortSongList = (
|
||||||
songs: QueueSong[],
|
songs: QueueSong[],
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ export enum Platform {
|
|||||||
WINDOWS = 'windows',
|
WINDOWS = 'windows',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Breakpoints = {
|
||||||
|
isLargerThan2xl: boolean;
|
||||||
|
isLargerThan3xl: boolean;
|
||||||
|
isLargerThanLg: boolean;
|
||||||
|
isLargerThanMd: boolean;
|
||||||
|
isLargerThanSm: boolean;
|
||||||
|
isLargerThanXl: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type CardRoute = {
|
export type CardRoute = {
|
||||||
route: AppRoute | string;
|
route: AppRoute | string;
|
||||||
slugs?: RouteSlug[];
|
slugs?: RouteSlug[];
|
||||||
|
|||||||
Reference in New Issue
Block a user