From a7430dae31d6e27a6668f94b0166fb4abc99b5c3 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 29 Jul 2025 19:07:58 -0700 Subject: [PATCH] temp --- postcss.config.cjs | 4 +- .../api/jellyfin/jellyfin-controller.ts | 2 - .../api/navidrome/navidrome-controller.ts | 3 - .../api/subsonic/subsonic-controller.ts | 2 - .../virtual-table/hooks/use-virtual-table.ts | 2 +- .../api/queries/get-album-list-query.ts | 45 +++++ .../albums/queries/album-detail-query.ts | 4 +- .../albums/queries/album-list-count-query.ts | 4 +- .../albums/queries/album-list-query.ts | 6 +- .../queries/album-artist-detail-query.ts | 4 +- .../queries/album-artist-list-count-query.ts | 4 +- .../queries/album-artist-list-query.ts | 4 +- .../artists/queries/artist-info-query.ts | 4 +- .../queries/artist-list-count-query.ts | 4 +- .../features/artists/queries/roles-query.ts | 4 +- .../artists/queries/top-songs-list-query.ts | 4 +- .../genres/queries/genre-list-query.ts | 4 +- .../home/queries/recently-played-query.ts | 4 +- .../features/home/routes/home-route.tsx | 35 ++-- .../features/lyrics/queries/lyric-query.ts | 8 +- .../lyrics/queries/lyric-search-query.ts | 4 +- .../player/components/shuffle-all-modal.tsx | 6 +- .../player/mutations/scrobble-mutation.ts | 4 +- .../add-to-playlist-context-modal.tsx | 12 +- .../components/playlist-query-builder.tsx | 5 +- .../mutations/add-to-playlist-mutation.ts | 4 +- .../mutations/create-playlist-mutation.ts | 4 +- .../mutations/delete-playlist-mutation.ts | 4 +- .../remove-from-playlist-mutation.ts | 4 +- .../mutations/update-playlist-mutation.ts | 4 +- .../queries/playlist-detail-query.ts | 4 +- .../playlists/queries/playlist-list-query.ts | 4 +- .../queries/playlist-song-list-query.ts | 4 +- .../features/search/queries/search-query.ts | 4 +- .../infinite-album-carousel.tsx | 100 +++++++++++ .../mutations/create-favorite-mutation.ts | 4 +- .../mutations/delete-favorite-mutation.ts | 4 +- .../shared/mutations/set-rating-mutation.ts | 4 +- .../shared/queries/music-folders-query.ts | 4 +- .../sharing/mutations/share-item-mutation.ts | 4 +- .../components/sidebar-playlist-list.tsx | 10 +- .../queries/similar-song-queries.tsx | 4 +- .../songs/queries/song-list-count-query.ts | 4 +- .../features/songs/queries/song-list-query.ts | 4 +- .../features/tag/queries/use-tag-list.ts | 4 +- .../features/users/queries/user-list-query.ts | 4 +- src/renderer/lib/react-query.ts | 16 +- src/renderer/store/list.store.ts | 30 ++-- src/renderer/store/playlist.store.ts | 11 +- .../api/subsonic/subsonic-controller.ts | 14 +- src/shared/api/utils.ts | 3 +- .../grid-carousel/grid-carousel.module.css | 54 ++++++ .../grid-carousel/grid-carousel.tsx | 162 ++++++++++++++++++ src/shared/hooks/use-container-breakpoints.ts | 54 ++++++ src/shared/types/domain/album-domain-types.ts | 7 +- .../types/domain/artist-domain-types.ts | 2 +- src/shared/types/domain/genre-domain-types.ts | 2 +- .../types/domain/playlist-domain-types.ts | 2 +- src/shared/types/domain/song-domain-types.ts | 4 +- src/shared/types/types.ts | 9 + 60 files changed, 583 insertions(+), 155 deletions(-) create mode 100644 src/renderer/features/albums/api/queries/get-album-list-query.ts create mode 100644 src/renderer/features/shared/components/infinite-album-carousel/infinite-album-carousel.tsx create mode 100644 src/shared/components/grid-carousel/grid-carousel.module.css create mode 100644 src/shared/components/grid-carousel/grid-carousel.tsx create mode 100644 src/shared/hooks/use-container-breakpoints.ts diff --git a/postcss.config.cjs b/postcss.config.cjs index 9cc0bd2d2..1ad276325 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,5 +1,7 @@ module.exports = { plugins: { - 'postcss-preset-mantine': {}, + 'postcss-preset-mantine': { + mixins: {}, + }, }, }; diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index 71c482a32..917f90838 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -9,9 +9,7 @@ import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils'; import { albumListSortMap } from '/@/shared/types/domain/album-domain-types'; import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types'; import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types'; -import { genreListSortMap } from '/@/shared/types/domain/genre-domain-types'; import { Played } from '/@/shared/types/domain/player-domain-types'; -import { playlistListSortMap } from '/@/shared/types/domain/playlist-domain-types'; import { ServerFeature } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem, sortOrderMap } from '/@/shared/types/domain/shared-domain-types'; import { Song, songListSortMap } from '/@/shared/types/domain/song-domain-types'; diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 02013a3d4..b1041b9a5 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -11,9 +11,7 @@ import { albumListSortMap } from '/@/shared/types/domain/album-domain-types'; import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types'; import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types'; import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types'; -import { genreListSortMap } from '/@/shared/types/domain/genre-domain-types'; import { - playlistListSortMap, PlaylistSongListRequest, PlaylistSongListResponse, } from '/@/shared/types/domain/playlist-domain-types'; @@ -24,7 +22,6 @@ import { } from '/@/shared/types/domain/server-domain-types'; import { sortOrderMap } from '/@/shared/types/domain/shared-domain-types'; import { Song, songListSortMap } from '/@/shared/types/domain/song-domain-types'; -import { userListSortMap } from '/@/shared/types/domain/user-domain-types'; const VERSION_INFO: VersionInfo = [ ['0.55.0', { [ServerFeature.BFR]: [1] }], diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index 36e6b53d0..eda312c4b 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -17,8 +17,6 @@ import { import { AlbumListSort, sortAlbumList } from '/@/shared/types/domain/album-domain-types'; import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types'; import { sortAlbumArtistList } from '/@/shared/types/domain/artist-domain-types'; -import { GenreListSort } from '/@/shared/types/domain/genre-domain-types'; -import { PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types'; import { ServerFeatures } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; import { Song, sortSongList } from '/@/shared/types/domain/song-domain-types'; diff --git a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts index a95426ad0..babe8a877 100644 --- a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts +++ b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts @@ -24,8 +24,8 @@ import { AppRoute } from '/@/renderer/router/routes'; import { PersistedTableColumn, useListStoreActions } from '/@/renderer/store'; import { ListKey, useListStoreByKey } from '/@/renderer/store/list.store'; import { - BasePaginatedResponse, BasePaginatedQuery, + BasePaginatedResponse, } from '/@/shared/types/adapter/api-controller-types'; import { ServerListItem } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types'; diff --git a/src/renderer/features/albums/api/queries/get-album-list-query.ts b/src/renderer/features/albums/api/queries/get-album-list-query.ts new file mode 100644 index 000000000..930d1d401 --- /dev/null +++ b/src/renderer/features/albums/api/queries/get-album-list-query.ts @@ -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, + }); +}; diff --git a/src/renderer/features/albums/queries/album-detail-query.ts b/src/renderer/features/albums/queries/album-detail-query.ts index e4ff40c71..f25c8b284 100644 --- a/src/renderer/features/albums/queries/album-detail-query.ts +++ b/src/renderer/features/albums/queries/album-detail-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { AlbumDetailQuery } from '/@/shared/types/domain/album-domain-types'; -export const useAlbumDetail = (args: QueryHookArgs) => { +export const useAlbumDetail = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/albums/queries/album-list-count-query.ts b/src/renderer/features/albums/queries/album-list-count-query.ts index 05f0a5879..84d7ff9bb 100644 --- a/src/renderer/features/albums/queries/album-list-count-query.ts +++ b/src/renderer/features/albums/queries/album-list-count-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types'; -export const useAlbumListCount = (args: QueryHookArgs) => { +export const useAlbumListCount = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/albums/queries/album-list-query.ts b/src/renderer/features/albums/queries/album-list-query.ts index 70c7e1f06..8dab07b78 100644 --- a/src/renderer/features/albums/queries/album-list-query.ts +++ b/src/renderer/features/albums/queries/album-list-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; @@ -8,7 +8,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain/album-domain-types'; -export const useAlbumList = (args: QueryHookArgs) => { +export const useAlbumList = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); @@ -33,7 +33,7 @@ export const useAlbumList = (args: QueryHookArgs) => { }); }; -export const useAlbumListInfinite = (args: QueryHookArgs) => { +export const useAlbumListInfinite = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/artists/queries/album-artist-detail-query.ts b/src/renderer/features/artists/queries/album-artist-detail-query.ts index 3e79afdd3..feb9c4fe3 100644 --- a/src/renderer/features/artists/queries/album-artist-detail-query.ts +++ b/src/renderer/features/artists/queries/album-artist-detail-query.ts @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types'; -export const useAlbumArtistDetail = (args: QueryHookArgs) => { +export const useAlbumArtistDetail = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/artists/queries/album-artist-list-count-query.ts b/src/renderer/features/artists/queries/album-artist-list-count-query.ts index c1c8603c7..3d8f3058a 100644 --- a/src/renderer/features/artists/queries/album-artist-list-count-query.ts +++ b/src/renderer/features/artists/queries/album-artist-list-count-query.ts @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types'; -export const useAlbumArtistListCount = (args: QueryHookArgs) => { +export const useAlbumArtistListCount = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/artists/queries/album-artist-list-query.ts b/src/renderer/features/artists/queries/album-artist-list-query.ts index eb7518037..66f6b6a86 100644 --- a/src/renderer/features/artists/queries/album-artist-list-query.ts +++ b/src/renderer/features/artists/queries/album-artist-list-query.ts @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types'; -export const useAlbumArtistList = (args: QueryHookArgs) => { +export const useAlbumArtistList = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/artists/queries/artist-info-query.ts b/src/renderer/features/artists/queries/artist-info-query.ts index 383d3b54c..05b156395 100644 --- a/src/renderer/features/artists/queries/artist-info-query.ts +++ b/src/renderer/features/artists/queries/artist-info-query.ts @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types'; -export const useAlbumArtistInfo = (args: QueryHookArgs) => { +export const useAlbumArtistInfo = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/artists/queries/artist-list-count-query.ts b/src/renderer/features/artists/queries/artist-list-count-query.ts index 141e0bcb3..6e8be5328 100644 --- a/src/renderer/features/artists/queries/artist-list-count-query.ts +++ b/src/renderer/features/artists/queries/artist-list-count-query.ts @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { ArtistListQuery } from '/@/shared/types/domain/artist-domain-types'; -export const useArtistListCount = (args: QueryHookArgs) => { +export const useArtistListCount = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/artists/queries/roles-query.ts b/src/renderer/features/artists/queries/roles-query.ts index c3ab2d671..d6c85b0e3 100644 --- a/src/renderer/features/artists/queries/roles-query.ts +++ b/src/renderer/features/artists/queries/roles-query.ts @@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; -export const useRoles = (args: QueryHookArgs) => { +export const useRoles = (args: RQueryHookArgs) => { const { options, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/artists/queries/top-songs-list-query.ts b/src/renderer/features/artists/queries/top-songs-list-query.ts index e8d83ee44..b5047dc30 100644 --- a/src/renderer/features/artists/queries/top-songs-list-query.ts +++ b/src/renderer/features/artists/queries/top-songs-list-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { TopSongListQuery } from '/@/shared/types/domain/song-domain-types'; -export const useTopSongsList = (args: QueryHookArgs) => { +export const useTopSongsList = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/genres/queries/genre-list-query.ts b/src/renderer/features/genres/queries/genre-list-query.ts index 7afa90bfa..9a13ce69f 100644 --- a/src/renderer/features/genres/queries/genre-list-query.ts +++ b/src/renderer/features/genres/queries/genre-list-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types'; -export const useGenreList = (args: QueryHookArgs) => { +export const useGenreList = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/home/queries/recently-played-query.ts b/src/renderer/features/home/queries/recently-played-query.ts index 418f7cd7c..bcb4ef0c4 100644 --- a/src/renderer/features/home/queries/recently-played-query.ts +++ b/src/renderer/features/home/queries/recently-played-query.ts @@ -2,12 +2,12 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types'; import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; -export const useRecentlyPlayed = (args: QueryHookArgs>) => { +export const useRecentlyPlayed = (args: RQueryHookArgs>) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/home/routes/home-route.tsx b/src/renderer/features/home/routes/home-route.tsx index 94e5d82a2..0c1e65abc 100644 --- a/src/renderer/features/home/routes/home-route.tsx +++ b/src/renderer/features/home/routes/home-route.tsx @@ -4,26 +4,21 @@ import { useTranslation } from 'react-i18next'; import { queryKeys } from '/@/renderer/api/query-keys'; import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel'; -import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel'; import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area'; import { useAlbumList } from '/@/renderer/features/albums'; import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query'; import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared'; +import { AlbumInfiniteCarousel } from '/@/renderer/features/shared/components/infinite-album-carousel/infinite-album-carousel'; import { useSongList } from '/@/renderer/features/songs'; -import { AppRoute } from '/@/renderer/router/routes'; import { HomeItem, useCurrentServer, useGeneralSettings, useWindowSettings, } from '/@/renderer/store'; -import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; -import { Group } from '/@/shared/components/group/group'; -import { Icon } from '/@/shared/components/icon/icon'; import { Spinner } from '/@/shared/components/spinner/spinner'; import { Stack } from '/@/shared/components/stack/stack'; -import { TextTitle } from '/@/shared/components/text-title/text-title'; -import { AlbumListSort } from '/@/shared/types/domain/album-domain-types'; +import { AlbumListSort, AlbumListSortOptions } from '/@/shared/types/domain/album-domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; import { SongListSort } from '/@/shared/types/domain/song-domain-types'; @@ -40,15 +35,15 @@ const HomeRoute = () => { const feature = useAlbumList({ options: { - gcTime: 1000 * 60, enabled: homeFeature, + gcTime: 1000 * 60, staleTime: 1000 * 60, }, query: { limit: 20, + offset: 0, sortBy: AlbumListSort.RANDOM, sortOrder: ListSortOrder.DESC, - offset: 0, }, serverId: server?.id, }); @@ -63,9 +58,9 @@ const HomeRoute = () => { }, query: { limit: itemsPerPage, + offset: 0, sortBy: AlbumListSort.RANDOM, sortOrder: ListSortOrder.ASC, - offset: 0, }, serverId: server?.id, }); @@ -76,9 +71,9 @@ const HomeRoute = () => { }, query: { limit: itemsPerPage, + offset: 0, sortBy: AlbumListSort.RECENTLY_PLAYED, sortOrder: ListSortOrder.DESC, - offset: 0, }, serverId: server?.id, }); @@ -89,9 +84,9 @@ const HomeRoute = () => { }, query: { limit: itemsPerPage, + offset: 0, sortBy: AlbumListSort.RECENTLY_ADDED, sortOrder: ListSortOrder.DESC, - offset: 0, }, serverId: server?.id, }); @@ -103,9 +98,9 @@ const HomeRoute = () => { }, query: { limit: itemsPerPage, + offset: 0, sortBy: AlbumListSort.PLAY_COUNT, sortOrder: ListSortOrder.DESC, - offset: 0, }, serverId: server?.id, }); @@ -118,9 +113,9 @@ const HomeRoute = () => { }, query: { limit: itemsPerPage, + offset: 0, sortBy: SongListSort.PLAY_COUNT, sortOrder: ListSortOrder.DESC, - offset: 0, }, serverId: server?.id, }, @@ -253,7 +248,15 @@ const HomeRoute = () => { px="2rem" > {homeFeature && } - {sortedCarousel.map((carousel) => ( + + + + {/* {sortedCarousel.map((carousel) => ( { }} uniqueId={carousel.uniqueId} /> - ))} + ))} */} diff --git a/src/renderer/features/lyrics/queries/lyric-query.ts b/src/renderer/features/lyrics/queries/lyric-query.ts index 68203a054..040de946b 100644 --- a/src/renderer/features/lyrics/queries/lyric-query.ts +++ b/src/renderer/features/lyrics/queries/lyric-query.ts @@ -3,7 +3,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById, useLyricsSettings } from '/@/renderer/store'; import { hasFeature } from '/@/shared/api/utils'; import { @@ -61,7 +61,7 @@ const formatLyrics = (lyrics: string) => { }; export const useServerLyrics = ( - args: QueryHookArgs, + args: RQueryHookArgs, ): UseQueryResult => { const { query, serverId } = args; const server = useServerById(serverId); @@ -81,7 +81,7 @@ export const useServerLyrics = ( }; export const useSongLyricsBySong = ( - args: QueryHookArgs, + args: RQueryHookArgs, song: QueueSong | undefined, ): UseQueryResult => { const { query } = args; @@ -170,7 +170,7 @@ export const useSongLyricsBySong = ( }; export const useSongLyricsByRemoteId = ( - args: QueryHookArgs>, + args: RQueryHookArgs>, ): UseQueryResult => { const queryClient = useQueryClient(); const { query, serverId } = args; diff --git a/src/renderer/features/lyrics/queries/lyric-search-query.ts b/src/renderer/features/lyrics/queries/lyric-search-query.ts index 542020c22..c3602e999 100644 --- a/src/renderer/features/lyrics/queries/lyric-search-query.ts +++ b/src/renderer/features/lyrics/queries/lyric-search-query.ts @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'; import isElectron from 'is-electron'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { InternetProviderLyricSearchResponse, LyricSearchQuery, @@ -11,7 +11,7 @@ import { const lyricsIpc = isElectron() ? window.api.lyrics : null; -export const useLyricSearch = (args: Omit, 'serverId'>) => { +export const useLyricSearch = (args: Omit, 'serverId'>) => { const { options, query } = args; return useQuery>({ diff --git a/src/renderer/features/player/components/shuffle-all-modal.tsx b/src/renderer/features/player/components/shuffle-all-modal.tsx index 4369d83be..ad5f27087 100644 --- a/src/renderer/features/player/components/shuffle-all-modal.tsx +++ b/src/renderer/features/player/components/shuffle-all-modal.tsx @@ -19,12 +19,12 @@ import { Icon } from '/@/shared/components/icon/icon'; import { NumberInput } from '/@/shared/components/number-input/number-input'; import { Select } from '/@/shared/components/select/select'; import { Stack } from '/@/shared/components/stack/stack'; -import { GenreListResponse, GenreListSort } from '/@/shared/types/domain/genre-domain-types'; +import { GenreListResponse } from '/@/shared/types/domain/genre-domain-types'; import { Played } from '/@/shared/types/domain/player-domain-types'; import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types'; +import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; import { RandomSongListQuery } from '/@/shared/types/domain/song-domain-types'; import { Play, PlayQueueAddOptions } from '/@/shared/types/types'; -import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; interface ShuffleAllSlice extends RandomSongListQuery { actions: { @@ -256,9 +256,9 @@ export const openShuffleAllModal = async ( signal, }, query: { + offset: 0, sortBy: GenreListSort.NAME, sortOrder: ListSortOrder.ASC, - offset: 0, }, }), queryKey: queryKeys.genres.list(server?.id), diff --git a/src/renderer/features/player/mutations/scrobble-mutation.ts b/src/renderer/features/player/mutations/scrobble-mutation.ts index 70c7d4978..611f98bd3 100644 --- a/src/renderer/features/player/mutations/scrobble-mutation.ts +++ b/src/renderer/features/player/mutations/scrobble-mutation.ts @@ -2,12 +2,12 @@ import { useMutation } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; -import { MutationOptions } from '/@/renderer/lib/react-query'; +import { RMutationOptions } from '/@/renderer/lib/react-query'; import { useServerById, useIncrementQueuePlayCount } from '/@/renderer/store'; import { usePlayEvent } from '/@/renderer/store/event.store'; import { ScrobbleRequest, ScrobbleResponse } from '/@/shared/types/domain/user-domain-types'; -export const useSendScrobble = (options?: MutationOptions) => { +export const useSendScrobble = (options?: RMutationOptions) => { const incrementPlayCount = useIncrementQueuePlayCount(); const sendPlayEvent = usePlayEvent(); diff --git a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx index 646499249..8a9490252 100644 --- a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx +++ b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx @@ -16,9 +16,9 @@ import { MultiSelect } from '/@/shared/components/multi-select/multi-select'; import { Stack } from '/@/shared/components/stack/stack'; import { Switch } from '/@/shared/components/switch/switch'; import { toast } from '/@/shared/components/toast/toast'; -import { PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types'; -import { SongListQuery, SongListSort } from '/@/shared/types/domain/song-domain-types'; +import { PlaylistListSortOptions } from '/@/shared/types/domain/playlist-domain-types'; import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; +import { SongListQuery, SongListSort } from '/@/shared/types/domain/song-domain-types'; export const AddToPlaylistContextModal = ({ id, @@ -44,9 +44,9 @@ export const AddToPlaylistContextModal = ({ smart: false, }, }, - sortBy: PlaylistListSort.NAME, - sortOrder: ListSortOrder.ASC, offset: 0, + sortBy: PlaylistListSortOptions.NAME, + sortOrder: ListSortOrder.ASC, }, serverId: server?.id, }); @@ -70,9 +70,9 @@ export const AddToPlaylistContextModal = ({ const getSongsByAlbum = async (albumId: string) => { const query: SongListQuery = { albumIds: [albumId], + offset: 0, sortBy: SongListSort.ALBUM, sortOrder: ListSortOrder.ASC, - offset: 0, }; const queryKey = queryKeys.songs.list(server?.id || '', query); @@ -88,9 +88,9 @@ export const AddToPlaylistContextModal = ({ const getSongsByArtist = async (artistId: string) => { const query: SongListQuery = { artistIds: [artistId], + offset: 0, sortBy: SongListSort.ARTIST, sortOrder: ListSortOrder.ASC, - offset: 0, }; const queryKey = queryKeys.songs.list(server?.id || '', query); diff --git a/src/renderer/features/playlists/components/playlist-query-builder.tsx b/src/renderer/features/playlists/components/playlist-query-builder.tsx index 8d8995d3d..aa538b737 100644 --- a/src/renderer/features/playlists/components/playlist-query-builder.tsx +++ b/src/renderer/features/playlists/components/playlist-query-builder.tsx @@ -32,10 +32,9 @@ import { Icon } from '/@/shared/components/icon/icon'; import { NumberInput } from '/@/shared/components/number-input/number-input'; import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; import { Select } from '/@/shared/components/select/select'; -import { PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types'; +import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; import { SongListSort } from '/@/shared/types/domain/song-domain-types'; import { QueryBuilderGroup, QueryBuilderRule } from '/@/shared/types/types'; -import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; type AddArgs = { groupIndex: number[]; @@ -111,7 +110,7 @@ export const PlaylistQueryBuilder = forwardRef( ); const { data: playlists } = usePlaylistList({ - query: { sortBy: PlaylistListSort.NAME, sortOrder: ListSortOrder.ASC, offset: 0 }, + query: { offset: 0, sortBy: PlaylistListSort.NAME, sortOrder: ListSortOrder.ASC }, serverId: server?.id, }); diff --git a/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts b/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts index 1a4f3b3f8..9f4379684 100644 --- a/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts @@ -3,14 +3,14 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { AddToPlaylistArgs, AddToPlaylistResponse, } from '/@/shared/types/domain/playlist-domain-types'; -export const useAddToPlaylist = (args: MutationHookArgs) => { +export const useAddToPlaylist = (args: RMutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); diff --git a/src/renderer/features/playlists/mutations/create-playlist-mutation.ts b/src/renderer/features/playlists/mutations/create-playlist-mutation.ts index 34be65736..7fa425fe2 100644 --- a/src/renderer/features/playlists/mutations/create-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/create-playlist-mutation.ts @@ -3,11 +3,11 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { CreatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types'; -export const useCreatePlaylist = (args: MutationHookArgs) => { +export const useCreatePlaylist = (args: RMutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); diff --git a/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts b/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts index e4cd45702..b5ee21e4a 100644 --- a/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts @@ -3,11 +3,11 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById, useCurrentServer } from '/@/renderer/store'; import { DeletePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types'; -export const useDeletePlaylist = (args: MutationHookArgs) => { +export const useDeletePlaylist = (args: RMutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); const server = useCurrentServer(); diff --git a/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts b/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts index 69b9d24db..bfad965fe 100644 --- a/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts @@ -3,11 +3,11 @@ import { AxiosError, AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationOptions } from '/@/renderer/lib/react-query'; +import { RMutationOptions } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { RemoveFromPlaylistResponse } from '/@/shared/types/domain/playlist-domain-types'; -export const useRemoveFromPlaylist = (options?: MutationOptions) => { +export const useRemoveFromPlaylist = (options?: RMutationOptions) => { const queryClient = useQueryClient(); return useMutation< diff --git a/src/renderer/features/playlists/mutations/update-playlist-mutation.ts b/src/renderer/features/playlists/mutations/update-playlist-mutation.ts index 43f6c0573..4e0172dbf 100644 --- a/src/renderer/features/playlists/mutations/update-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/update-playlist-mutation.ts @@ -3,11 +3,11 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { UpdatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types'; -export const useUpdatePlaylist = (args: MutationHookArgs) => { +export const useUpdatePlaylist = (args: RMutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); diff --git a/src/renderer/features/playlists/queries/playlist-detail-query.ts b/src/renderer/features/playlists/queries/playlist-detail-query.ts index 0e9830dbe..f1daa351e 100644 --- a/src/renderer/features/playlists/queries/playlist-detail-query.ts +++ b/src/renderer/features/playlists/queries/playlist-detail-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { PlaylistDetailQuery } from '/@/shared/types/domain/playlist-domain-types'; -export const usePlaylistDetail = (args: QueryHookArgs) => { +export const usePlaylistDetail = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/playlists/queries/playlist-list-query.ts b/src/renderer/features/playlists/queries/playlist-list-query.ts index 234599778..7af64fc27 100644 --- a/src/renderer/features/playlists/queries/playlist-list-query.ts +++ b/src/renderer/features/playlists/queries/playlist-list-query.ts @@ -1,4 +1,4 @@ -import type { QueryOptions } from '/@/renderer/lib/react-query'; +import type { RQueryOptions } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -8,7 +8,7 @@ import { useServerById } from '/@/renderer/store'; import { PlaylistListQuery } from '/@/shared/types/domain/playlist-domain-types'; export const usePlaylistList = (args: { - options?: QueryOptions; + options?: RQueryOptions; query: PlaylistListQuery; serverId?: string; }) => { diff --git a/src/renderer/features/playlists/queries/playlist-song-list-query.ts b/src/renderer/features/playlists/queries/playlist-song-list-query.ts index 7dcf049e8..b48185253 100644 --- a/src/renderer/features/playlists/queries/playlist-song-list-query.ts +++ b/src/renderer/features/playlists/queries/playlist-song-list-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { PlaylistSongListQuery } from '/@/shared/types/domain/playlist-domain-types'; -export const usePlaylistSongList = (args: QueryHookArgs) => { +export const usePlaylistSongList = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/search/queries/search-query.ts b/src/renderer/features/search/queries/search-query.ts index 9e18b9bf5..e6204fd32 100644 --- a/src/renderer/features/search/queries/search-query.ts +++ b/src/renderer/features/search/queries/search-query.ts @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { SearchQuery } from '/@/shared/types/domain/search-domain-types'; -export const useSearch = (args: QueryHookArgs) => { +export const useSearch = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/shared/components/infinite-album-carousel/infinite-album-carousel.tsx b/src/renderer/features/shared/components/infinite-album-carousel/infinite-album-carousel.tsx new file mode 100644 index 000000000..b323936d8 --- /dev/null +++ b/src/renderer/features/shared/components/infinite-album-carousel/infinite-album-carousel.tsx @@ -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: , + 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: , + id, + }; + }), + ]; + }), + [albums.pages], + ); + + const handleNextPage = useCallback(() => {}, []); + + const handlePrevPage = useCallback(() => {}, []); + + if (albums.pages[0]?.items.length === 0) { + return null; + } + + return ( + + ); +} + +function useInfiniteAlbumList( + serverId: string, + sortBy: AlbumListSortOptions, + sortOrder: ListSortOrder, + limit: number, +) { + const query = useSuspenseInfiniteQuery({ + 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; +} diff --git a/src/renderer/features/shared/mutations/create-favorite-mutation.ts b/src/renderer/features/shared/mutations/create-favorite-mutation.ts index 8c1dd3ce2..898d9412e 100644 --- a/src/renderer/features/shared/mutations/create-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/create-favorite-mutation.ts @@ -4,7 +4,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; import { useFavoriteEvent } from '/@/renderer/store/event.store'; import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types'; @@ -14,7 +14,7 @@ import { FavoriteResponse } from '/@/shared/types/domain/user-domain-types'; const remote = isElectron() ? window.api.remote : null; -export const useCreateFavorite = (args: MutationHookArgs) => { +export const useCreateFavorite = (args: RMutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); const setAlbumListData = useSetAlbumListItemDataById(); diff --git a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts index d6ef2c554..bbeee1bc7 100644 --- a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts @@ -4,7 +4,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; import { useFavoriteEvent } from '/@/renderer/store/event.store'; import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types'; @@ -14,7 +14,7 @@ import { FavoriteResponse } from '/@/shared/types/domain/user-domain-types'; const remote = isElectron() ? window.api.remote : null; -export const useDeleteFavorite = (args: MutationHookArgs) => { +export const useDeleteFavorite = (args: RMutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); const setAlbumListData = useSetAlbumListItemDataById(); diff --git a/src/renderer/features/shared/mutations/set-rating-mutation.ts b/src/renderer/features/shared/mutations/set-rating-mutation.ts index b6b200d0b..699a88e3e 100644 --- a/src/renderer/features/shared/mutations/set-rating-mutation.ts +++ b/src/renderer/features/shared/mutations/set-rating-mutation.ts @@ -4,7 +4,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById, useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store'; import { useRatingEvent } from '/@/renderer/store/event.store'; import { Album, AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types'; @@ -14,7 +14,7 @@ import { RatingResponse, SetRatingRequest } from '/@/shared/types/domain/user-do const remote = isElectron() ? window.api.remote : null; -export const useSetRating = (args: MutationHookArgs) => { +export const useSetRating = (args: RMutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); const setAlbumListData = useSetAlbumListItemDataById(); diff --git a/src/renderer/features/shared/queries/music-folders-query.ts b/src/renderer/features/shared/queries/music-folders-query.ts index c6d85e044..161a1a852 100644 --- a/src/renderer/features/shared/queries/music-folders-query.ts +++ b/src/renderer/features/shared/queries/music-folders-query.ts @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { ServerMusicFolderListQuery } from '/@/shared/types/domain/server-domain-types'; -export const useMusicFolders = (args: QueryHookArgs) => { +export const useMusicFolders = (args: RQueryHookArgs) => { const { options, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/sharing/mutations/share-item-mutation.ts b/src/renderer/features/sharing/mutations/share-item-mutation.ts index d8b84da25..3d03c9d47 100644 --- a/src/renderer/features/sharing/mutations/share-item-mutation.ts +++ b/src/renderer/features/sharing/mutations/share-item-mutation.ts @@ -2,12 +2,12 @@ import { useMutation } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; -import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { RMutationHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { AnyLibraryItems } from '/@/shared/types/domain/shared-domain-types'; import { ShareItemRequest, ShareItemResponse } from '/@/shared/types/domain/user-domain-types'; -export const useShareItem = (args: MutationHookArgs) => { +export const useShareItem = (args: RMutationHookArgs) => { const { options } = args || {}; return useMutation< diff --git a/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx index 4c6c14af7..85b4d52ca 100644 --- a/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx +++ b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx @@ -17,7 +17,7 @@ import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ButtonProps } from '/@/shared/components/button/button'; import { Group } from '/@/shared/components/group/group'; import { Text } from '/@/shared/components/text/text'; -import { Playlist, PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types'; +import { Playlist, PlaylistListSortOptions } from '/@/shared/types/domain/playlist-domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; import { Play } from '/@/shared/types/types'; @@ -140,9 +140,9 @@ export const SidebarPlaylistList = () => { const playlistsQuery = usePlaylistList({ query: { - sortBy: PlaylistListSort.NAME, - sortOrder: ListSortOrder.ASC, offset: 0, + sortBy: PlaylistListSortOptions.NAME, + sortOrder: ListSortOrder.ASC, }, serverId: server?.id, }); @@ -256,9 +256,9 @@ export const SidebarSharedPlaylistList = () => { const playlistsQuery = usePlaylistList({ query: { - sortBy: PlaylistListSort.NAME, - sortOrder: ListSortOrder.ASC, offset: 0, + sortBy: PlaylistListSortOptions.NAME, + sortOrder: ListSortOrder.ASC, }, serverId: server?.id, }); diff --git a/src/renderer/features/similar-songs/queries/similar-song-queries.tsx b/src/renderer/features/similar-songs/queries/similar-song-queries.tsx index c11f9b5a5..e8b526b61 100644 --- a/src/renderer/features/similar-songs/queries/similar-song-queries.tsx +++ b/src/renderer/features/similar-songs/queries/similar-song-queries.tsx @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { SimilarSongsQuery } from '/@/shared/types/domain/song-domain-types'; -export const useSimilarSongs = (args: QueryHookArgs) => { +export const useSimilarSongs = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/songs/queries/song-list-count-query.ts b/src/renderer/features/songs/queries/song-list-count-query.ts index 84620bcc0..f4f62b5d3 100644 --- a/src/renderer/features/songs/queries/song-list-count-query.ts +++ b/src/renderer/features/songs/queries/song-list-count-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { SongListQuery } from '/@/shared/types/domain/song-domain-types'; -export const useSongListCount = (args: QueryHookArgs) => { +export const useSongListCount = (args: RQueryHookArgs) => { const { options, query, serverId } = args; const server = useServerById(serverId); diff --git a/src/renderer/features/songs/queries/song-list-query.ts b/src/renderer/features/songs/queries/song-list-query.ts index 3a24e7ed0..ca25075d1 100644 --- a/src/renderer/features/songs/queries/song-list-query.ts +++ b/src/renderer/features/songs/queries/song-list-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { SongListQuery } from '/@/shared/types/domain/song-domain-types'; -export const useSongList = (args: QueryHookArgs, imageSize?: number) => { +export const useSongList = (args: RQueryHookArgs, imageSize?: number) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/tag/queries/use-tag-list.ts b/src/renderer/features/tag/queries/use-tag-list.ts index 352a27619..fc502db28 100644 --- a/src/renderer/features/tag/queries/use-tag-list.ts +++ b/src/renderer/features/tag/queries/use-tag-list.ts @@ -2,13 +2,13 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { QueryHookArgs } from '/@/renderer/lib/react-query'; +import { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useServerById } from '/@/renderer/store'; import { hasFeature } from '/@/shared/api/utils'; import { ServerFeature } from '/@/shared/types/domain/server-domain-types'; import { TagQuery } from '/@/shared/types/domain/tag-domain-types'; -export const useTagList = (args: QueryHookArgs) => { +export const useTagList = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/features/users/queries/user-list-query.ts b/src/renderer/features/users/queries/user-list-query.ts index 1f711143d..185aca525 100644 --- a/src/renderer/features/users/queries/user-list-query.ts +++ b/src/renderer/features/users/queries/user-list-query.ts @@ -1,4 +1,4 @@ -import type { QueryHookArgs } from '/@/renderer/lib/react-query'; +import type { RQueryHookArgs } from '/@/renderer/lib/react-query'; import { useQuery } from '@tanstack/react-query'; @@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useServerById } from '/@/renderer/store'; import { UserListQuery } from '/@/shared/types/domain/user-domain-types'; -export const useUserList = (args: QueryHookArgs) => { +export const useUserList = (args: RQueryHookArgs) => { const { options, query, serverId } = args || {}; const server = useServerById(serverId); diff --git a/src/renderer/lib/react-query.ts b/src/renderer/lib/react-query.ts index 3cd563750..5f5898ac4 100644 --- a/src/renderer/lib/react-query.ts +++ b/src/renderer/lib/react-query.ts @@ -41,8 +41,6 @@ export type InfiniteQueryOptions = { gcTime?: UseInfiniteQueryOptions['gcTime']; meta?: UseInfiniteQueryOptions['meta']; onError?: (err: any) => void; - onSettled?: any; - onSuccess?: any; queryKey?: UseInfiniteQueryOptions['queryKey']; refetchInterval?: number; refetchIntervalInBackground?: UseInfiniteQueryOptions['refetchIntervalInBackground']; @@ -53,11 +51,11 @@ export type InfiniteQueryOptions = { useErrorBoundary?: boolean; }; -export type MutationHookArgs = { - options?: MutationOptions; +export type RMutationHookArgs = { + options?: RMutationOptions; }; -export type MutationOptions = { +export type RMutationOptions = { mutationKey: UseMutationOptions['mutationKey']; onError?: (err: any) => void; onSettled?: any; @@ -67,19 +65,17 @@ export type MutationOptions = { useErrorBoundary?: boolean; }; -export type QueryHookArgs = { - options?: QueryOptions; +export type RQueryHookArgs = { + options?: RQueryOptions; query: T; serverId: string | undefined; }; -export type QueryOptions = { +export type RQueryOptions = { enabled?: UseQueryOptions['enabled']; gcTime?: UseQueryOptions['gcTime']; meta?: UseQueryOptions['meta']; onError?: (err: any) => void; - onSettled?: any; - onSuccess?: any; queryKey?: UseQueryOptions['queryKey']; refetchInterval?: number; refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground']; diff --git a/src/renderer/store/list.store.ts b/src/renderer/store/list.store.ts index 0306b54ba..fa6815066 100644 --- a/src/renderer/store/list.store.ts +++ b/src/renderer/store/list.store.ts @@ -6,28 +6,28 @@ import { createWithEqualityFn } from 'zustand/traditional'; import { DataTableProps, PersistedTableColumn } from '/@/renderer/store/settings.store'; import { mergeOverridingColumns } from '/@/renderer/store/utils'; import { AlbumListRequest, AlbumListSort } from '/@/shared/types/domain/album-domain-types'; -import { - AlbumArtistListRequest, - AlbumArtistListSort, - ArtistListRequest, -} from '/@/shared/types/domain/artist-domain-types'; -import { GenreListRequest, GenreListSort } from '/@/shared/types/domain/genre-domain-types'; +import { AlbumArtistListSort, ArtistListRequest } from '/@/shared/types/domain/artist-domain-types'; +import { GenreListRequest, GenreListSortOptions } from '/@/shared/types/domain/genre-domain-types'; import { PlaylistListRequest, - PlaylistListSort, + PlaylistListSortOptions, } from '/@/shared/types/domain/playlist-domain-types'; import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; -import { SongListRequest, SongListSort } from '/@/shared/types/domain/song-domain-types'; +import { + SongListRequest, + SongListSort, + SongListSortOptions, +} from '/@/shared/types/domain/song-domain-types'; import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types'; export const generatePageKey = (page: string, id?: string) => { return id ? `${page}_${id}` : page; }; -export type AlbumArtistListFilter = Omit; -export type AlbumListFilter = Omit; -export type ArtistListFilter = Omit; -export type GenreListFilter = Omit; +export type AlbumArtistListFilter = Omit; +export type AlbumListFilter = Omit; +export type ArtistListFilter = Omit; +export type GenreListFilter = Omit; export type ListDeterministicArgs = { key: ListKey }; export type ListGridProps = { itemGap?: number; @@ -544,7 +544,7 @@ export const useListStore = createWithEqualityFn()( genre: { display: ListDisplayType.TABLE, filter: { - sortBy: GenreListSort.NAME, + sortBy: GenreListSortOptions.NAME, sortOrder: ListSortOrder.ASC, }, grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, @@ -573,7 +573,7 @@ export const useListStore = createWithEqualityFn()( playlist: { display: ListDisplayType.GRID, filter: { - sortBy: PlaylistListSort.NAME, + sortBy: PlaylistListSortOptions.NAME, sortOrder: ListSortOrder.DESC, }, grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, @@ -606,7 +606,7 @@ export const useListStore = createWithEqualityFn()( song: { display: ListDisplayType.TABLE, filter: { - sortBy: SongListSort.RECENTLY_ADDED, + sortBy: SongListSortOptions.RECENTLY_ADDED, sortOrder: ListSortOrder.DESC, }, grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, diff --git a/src/renderer/store/playlist.store.ts b/src/renderer/store/playlist.store.ts index 08645026f..0ac589a18 100644 --- a/src/renderer/store/playlist.store.ts +++ b/src/renderer/store/playlist.store.ts @@ -5,9 +5,12 @@ import { createWithEqualityFn } from 'zustand/traditional'; import { PlaylistListFilter, SongListFilter } from '/@/renderer/store/list.store'; import { DataTableProps } from '/@/renderer/store/settings.store'; import { mergeOverridingColumns } from '/@/renderer/store/utils'; -import { PlaylistListSort, PlaylistListSort } from '/@/shared/types/domain/playlist-domain-types'; -import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types'; +import { + PlaylistListSortOptions, + PlaylistListSortOptions, +} from '/@/shared/types/domain/playlist-domain-types'; import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; +import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types'; export interface PlaylistSlice extends PlaylistState { actions: { @@ -155,8 +158,8 @@ export const usePlaylistStore = createWithEqualityFn()( list: { display: ListDisplayType.TABLE, filter: { - musicFolderId: undefined, - sortBy: PlaylistListSort.NAME, + offset: 0, + sortBy: PlaylistListSortOptions.NAME, sortOrder: ListSortOrder.ASC, }, table: { diff --git a/src/shared/api/subsonic/subsonic-controller.ts b/src/shared/api/subsonic/subsonic-controller.ts index cf5eabcd9..2b9c2ddde 100644 --- a/src/shared/api/subsonic/subsonic-controller.ts +++ b/src/shared/api/subsonic/subsonic-controller.ts @@ -55,7 +55,7 @@ export const createApiClient = ( }; const authMiddleware: (server: ServerListItem) => Middleware = (server: ServerListItem) => ({ - onRequest: async ({ params }) => { + onRequest: async ({ params, request }) => { const credential = deserializeCredential(server.credential); if (params.query) { @@ -67,6 +67,18 @@ const authMiddleware: (server: ServerListItem) => Middleware = (server: ServerLi params.query[key] = value; } } + + const stringifiedParams = qs.stringify(params.query, { arrayFormat: 'repeat' }); + + const url = new URL(request.url); + url.search = stringifiedParams; + + return new Request(url.toString(), { + body: request.body, + headers: request.headers, + method: request.method, + signal: request.signal, + }); }, }); diff --git a/src/shared/api/utils.ts b/src/shared/api/utils.ts index 436a1b533..473846611 100644 --- a/src/shared/api/utils.ts +++ b/src/shared/api/utils.ts @@ -2,7 +2,6 @@ import { AxiosHeaders } from 'axios'; import dayjs from 'dayjs'; import isElectron from 'is-electron'; import { orderBy, shuffle } from 'lodash'; -import { stringify } from 'querystring'; import semverCoerce from 'semver/functions/coerce'; import semverGte from 'semver/functions/gte'; import { z } from 'zod'; @@ -388,7 +387,7 @@ function getListCountKey(options: { serverId: string; type: LibraryItem | string; }) { - const hash = stringify(options.query as Record); + const hash = JSON.stringify(options.query as Record); return `${options.serverId}::${options.type}::${hash}`; } diff --git a/src/shared/components/grid-carousel/grid-carousel.module.css b/src/shared/components/grid-carousel/grid-carousel.module.css new file mode 100644 index 000000000..54f38992d --- /dev/null +++ b/src/shared/components/grid-carousel/grid-carousel.module.css @@ -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)); + } */ +} diff --git a/src/shared/components/grid-carousel/grid-carousel.tsx b/src/shared/components/grid-carousel/grid-carousel.tsx new file mode 100644 index 000000000..81ab732ab --- /dev/null +++ b/src/shared/components/grid-carousel/grid-carousel.tsx @@ -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 }) => ( +
{content}
+)); + +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 ( + +
+ + {title} + + + + + +
+ + + {visibleCards.map((card) => ( + + ))} + + +
+ ); +} + +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; +} diff --git a/src/shared/hooks/use-container-breakpoints.ts b/src/shared/hooks/use-container-breakpoints.ts new file mode 100644 index 000000000..31f9d40e7 --- /dev/null +++ b/src/shared/hooks/use-container-breakpoints.ts @@ -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 }; +} diff --git a/src/shared/types/domain/album-domain-types.ts b/src/shared/types/domain/album-domain-types.ts index 2e9f96b7c..d3edeff63 100644 --- a/src/shared/types/domain/album-domain-types.ts +++ b/src/shared/types/domain/album-domain-types.ts @@ -1,11 +1,8 @@ import { orderBy, shuffle } from 'lodash'; -import { z } from 'zod'; import i18n from '/@/i18n/i18n'; import { JFAlbumListSort } from '/@/shared/api/jellyfin.types'; -import { jfType } from '/@/shared/api/jellyfin/jellyfin-types'; import { NDAlbumListSort } from '/@/shared/api/navidrome.types'; -import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { BasePaginatedQuery, BasePaginatedResponse, @@ -91,7 +88,7 @@ export interface AlbumListQuery extends BasePaginatedQuery export type AlbumListRequest = { query: AlbumListQuery; totalRecordCount?: number }; -export type AlbumListResponse = BasePaginatedResponse | null | undefined; +export type AlbumListResponse = BasePaginatedResponse; type AlbumListSortMap = { jellyfin: Record; @@ -199,7 +196,7 @@ export type AlbumDetailQuery = { id: string }; export type AlbumDetailRequest = { query: AlbumDetailQuery }; -export type AlbumDetailResponse = Album | null | undefined; +export type AlbumDetailResponse = Album; export type AlbumInfo = { imageUrl: null | string; diff --git a/src/shared/types/domain/artist-domain-types.ts b/src/shared/types/domain/artist-domain-types.ts index 6b10c54ea..2f8189a09 100644 --- a/src/shared/types/domain/artist-domain-types.ts +++ b/src/shared/types/domain/artist-domain-types.ts @@ -133,7 +133,7 @@ export interface ArtistListQuery extends BasePaginatedQuery | null | undefined; +export type ArtistListResponse = BasePaginatedResponse; type ArtistListSortMap = { jellyfin: Record; navidrome: Record; diff --git a/src/shared/types/domain/genre-domain-types.ts b/src/shared/types/domain/genre-domain-types.ts index 5d7742e81..7ae907c1a 100644 --- a/src/shared/types/domain/genre-domain-types.ts +++ b/src/shared/types/domain/genre-domain-types.ts @@ -36,7 +36,7 @@ export interface GenreListQuery extends BasePaginatedQuery export type GenreListRequest = { query: GenreListQuery; totalRecordCount?: number }; -export type GenreListResponse = BasePaginatedResponse | null | undefined; +export type GenreListResponse = BasePaginatedResponse; export type RelatedGenre = { id: string; diff --git a/src/shared/types/domain/playlist-domain-types.ts b/src/shared/types/domain/playlist-domain-types.ts index 3a4447717..5b8edcf57 100644 --- a/src/shared/types/domain/playlist-domain-types.ts +++ b/src/shared/types/domain/playlist-domain-types.ts @@ -119,7 +119,7 @@ export interface PlaylistListQuery extends BasePaginatedQuery | null | undefined; +export type PlaylistListResponse = BasePaginatedResponse; export type PlaylistSong = Song & { playlistItemId: string; diff --git a/src/shared/types/domain/song-domain-types.ts b/src/shared/types/domain/song-domain-types.ts index 8ee222450..31ea5d303 100644 --- a/src/shared/types/domain/song-domain-types.ts +++ b/src/shared/types/domain/song-domain-types.ts @@ -153,7 +153,7 @@ export interface SongListQuery extends BasePaginatedQuery { export type SongListRequest = { query: SongListQuery; totalRecordCount?: number }; -export type SongListResponse = BasePaginatedResponse | null | undefined; +export type SongListResponse = BasePaginatedResponse; type SongListSortMap = { jellyfin: Record; navidrome: Record; @@ -263,7 +263,7 @@ export type TopSongListQuery = { export type TopSongListRequest = { query: TopSongListQuery; totalRecordCount?: number }; -export type TopSongListResponse = BasePaginatedResponse | null | undefined; +export type TopSongListResponse = BasePaginatedResponse; export const sortSongList = ( songs: QueueSong[], diff --git a/src/shared/types/types.ts b/src/shared/types/types.ts index b5176dcdc..183bd62c8 100644 --- a/src/shared/types/types.ts +++ b/src/shared/types/types.ts @@ -24,6 +24,15 @@ export enum Platform { WINDOWS = 'windows', } +export type Breakpoints = { + isLargerThan2xl: boolean; + isLargerThan3xl: boolean; + isLargerThanLg: boolean; + isLargerThanMd: boolean; + isLargerThanSm: boolean; + isLargerThanXl: boolean; +}; + export type CardRoute = { route: AppRoute | string; slugs?: RouteSlug[];