Refactor all api instances in components
This commit is contained in:
parent
bdd023fde3
commit
314bd766df
56 changed files with 879 additions and 755 deletions
|
@ -27,8 +27,6 @@ export * from './text';
|
||||||
export * from './text-title';
|
export * from './text-title';
|
||||||
export * from './toast';
|
export * from './toast';
|
||||||
export * from './tooltip';
|
export * from './tooltip';
|
||||||
export * from './virtual-grid';
|
|
||||||
export * from './virtual-table';
|
|
||||||
export * from './motion';
|
export * from './motion';
|
||||||
export * from './context-menu';
|
export * from './context-menu';
|
||||||
export * from './query-builder';
|
export * from './query-builder';
|
||||||
|
|
|
@ -1,50 +1,13 @@
|
||||||
|
/* eslint-disable import/no-cycle */
|
||||||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||||
import { RiHeartFill, RiHeartLine } from 'react-icons/ri';
|
import { RiHeartFill, RiHeartLine } from 'react-icons/ri';
|
||||||
import { Button } from '/@/renderer/components/button';
|
import { Button } from '/@/renderer/components/button';
|
||||||
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
|
||||||
import { HTTPError } from 'ky';
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { RawFavoriteResponse, FavoriteArgs, LibraryItem } from '/@/renderer/api/types';
|
|
||||||
import { useCurrentServer, useSetAlbumListItemDataById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
const useCreateFavorite = () => {
|
|
||||||
const server = useCurrentServer();
|
|
||||||
const setAlbumListData = useSetAlbumListItemDataById();
|
|
||||||
|
|
||||||
return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({
|
|
||||||
mutationFn: (args) => api.controller.createFavorite({ ...args, server }),
|
|
||||||
onSuccess: (_data, variables) => {
|
|
||||||
for (const id of variables.query.id) {
|
|
||||||
// Set the userFavorite property to true for the album in the album list data store
|
|
||||||
if (variables.query.type === LibraryItem.ALBUM) {
|
|
||||||
setAlbumListData(id, { userFavorite: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const useDeleteFavorite = () => {
|
|
||||||
const server = useCurrentServer();
|
|
||||||
const setAlbumListData = useSetAlbumListItemDataById();
|
|
||||||
|
|
||||||
return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({
|
|
||||||
mutationFn: (args) => api.controller.deleteFavorite({ ...args, server }),
|
|
||||||
onSuccess: (_data, variables) => {
|
|
||||||
for (const id of variables.query.id) {
|
|
||||||
// Set the userFavorite property to false for the album in the album list data store
|
|
||||||
if (variables.query.type === LibraryItem.ALBUM) {
|
|
||||||
setAlbumListData(id, { userFavorite: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FavoriteCell = ({ value, data, node }: ICellRendererParams) => {
|
export const FavoriteCell = ({ value, data, node }: ICellRendererParams) => {
|
||||||
const createMutation = useCreateFavorite();
|
const createMutation = useCreateFavorite({});
|
||||||
const deleteMutation = useDeleteFavorite();
|
const deleteMutation = useDeleteFavorite({});
|
||||||
|
|
||||||
const handleToggleFavorite = () => {
|
const handleToggleFavorite = () => {
|
||||||
const newFavoriteValue = !value;
|
const newFavoriteValue = !value;
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
|
/* eslint-disable import/no-cycle */
|
||||||
import { MouseEvent } from 'react';
|
import { MouseEvent } from 'react';
|
||||||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||||
import { Rating } from '/@/renderer/components/rating';
|
import { Rating } from '/@/renderer/components/rating';
|
||||||
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||||
import { useUpdateRating } from '/@/renderer/components/virtual-table/hooks/use-rating';
|
import { useSetRating } from '/@/renderer/features/shared';
|
||||||
|
|
||||||
export const RatingCell = ({ value, node }: ICellRendererParams) => {
|
export const RatingCell = ({ value, node }: ICellRendererParams) => {
|
||||||
const updateRatingMutation = useUpdateRating();
|
const updateRatingMutation = useSetRating({});
|
||||||
|
|
||||||
const handleUpdateRating = (rating: number) => {
|
const handleUpdateRating = (rating: number) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
updateRatingMutation.mutate(
|
updateRatingMutation.mutate(
|
||||||
{
|
{
|
||||||
_serverId: value?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [value],
|
item: [value],
|
||||||
rating,
|
rating,
|
||||||
},
|
},
|
||||||
|
serverId: value?.serverId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
@ -31,11 +32,11 @@ export const RatingCell = ({ value, node }: ICellRendererParams) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
updateRatingMutation.mutate(
|
updateRatingMutation.mutate(
|
||||||
{
|
{
|
||||||
_serverId: value?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [value],
|
item: [value],
|
||||||
rating: 0,
|
rating: 0,
|
||||||
},
|
},
|
||||||
|
serverId: value?.serverId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable import/no-cycle */
|
||||||
import { Ref, forwardRef, useRef, useEffect, useCallback, useMemo } from 'react';
|
import { Ref, forwardRef, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||||
import type {
|
import type {
|
||||||
ICellRendererParams,
|
ICellRendererParams,
|
||||||
|
@ -28,8 +29,8 @@ import { GenericTableHeader } from '/@/renderer/components/virtual-table/headers
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { PersistedTableColumn } from '/@/renderer/store/settings.store';
|
import { PersistedTableColumn } from '/@/renderer/store/settings.store';
|
||||||
import { TableColumn } from '/@/renderer/types';
|
import { TableColumn } from '/@/renderer/types';
|
||||||
import { RatingCell } from '/@/renderer/components/virtual-table/cells/rating-cell';
|
|
||||||
import { FavoriteCell } from '/@/renderer/components/virtual-table/cells/favorite-cell';
|
import { FavoriteCell } from '/@/renderer/components/virtual-table/cells/favorite-cell';
|
||||||
|
import { RatingCell } from '/@/renderer/components/virtual-table/cells/rating-cell';
|
||||||
|
|
||||||
export * from './table-config-dropdown';
|
export * from './table-config-dropdown';
|
||||||
export * from './table-pagination';
|
export * from './table-pagination';
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import {
|
import { Button, GridCarousel, Text, TextTitle } from '/@/renderer/components';
|
||||||
Button,
|
|
||||||
getColumnDefs,
|
|
||||||
GridCarousel,
|
|
||||||
Text,
|
|
||||||
TextTitle,
|
|
||||||
useFixedTableHeader,
|
|
||||||
VirtualTable,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { ColDef, RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
import { ColDef, RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Box, Group, Stack } from '@mantine/core';
|
import { Box, Group, Stack } from '@mantine/core';
|
||||||
|
@ -33,6 +25,12 @@ import { PlayButton, useCreateFavorite, useDeleteFavorite } from '/@/renderer/fe
|
||||||
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
||||||
import { AlbumListSort, LibraryItem, QueueSong, SortOrder } from '/@/renderer/api/types';
|
import { AlbumListSort, LibraryItem, QueueSong, SortOrder } from '/@/renderer/api/types';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import {
|
||||||
|
getColumnDefs,
|
||||||
|
useFixedTableHeader,
|
||||||
|
VirtualTable,
|
||||||
|
} from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const isFullWidthRow = (node: RowNode) => {
|
const isFullWidthRow = (node: RowNode) => {
|
||||||
return node.id?.includes('disc-');
|
return node.id?.includes('disc-');
|
||||||
|
@ -60,7 +58,8 @@ interface AlbumDetailContentProps {
|
||||||
|
|
||||||
export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const detailQuery = useAlbumDetail({ id: albumId });
|
const server = useCurrentServer();
|
||||||
|
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
|
||||||
|
@ -165,26 +164,29 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||||
|
|
||||||
const itemsPerPage = cq.isXl ? 9 : cq.isLg ? 7 : cq.isMd ? 5 : cq.isSm ? 4 : 3;
|
const itemsPerPage = cq.isXl ? 9 : cq.isLg ? 7 : cq.isMd ? 5 : cq.isSm ? 4 : 3;
|
||||||
|
|
||||||
const artistQuery = useAlbumList(
|
const artistQuery = useAlbumList({
|
||||||
{
|
options: {
|
||||||
jfParams: {
|
|
||||||
albumArtistIds: detailQuery?.data?.albumArtists[0]?.id,
|
|
||||||
},
|
|
||||||
limit: itemsPerPage,
|
|
||||||
ndParams: {
|
|
||||||
artist_id: detailQuery?.data?.albumArtists[0]?.id,
|
|
||||||
},
|
|
||||||
sortBy: AlbumListSort.YEAR,
|
|
||||||
sortOrder: SortOrder.DESC,
|
|
||||||
startIndex: pagination.artist * itemsPerPage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cacheTime: 1000 * 60,
|
cacheTime: 1000 * 60,
|
||||||
enabled: detailQuery?.data?.albumArtists[0]?.id !== undefined,
|
enabled: detailQuery?.data?.albumArtists[0]?.id !== undefined,
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
staleTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
},
|
},
|
||||||
);
|
query: {
|
||||||
|
_custom: {
|
||||||
|
jellyfin: {
|
||||||
|
albumArtistIds: detailQuery?.data?.albumArtists[0]?.id,
|
||||||
|
},
|
||||||
|
navidrome: {
|
||||||
|
artist_id: detailQuery?.data?.albumArtists[0]?.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
limit: itemsPerPage,
|
||||||
|
sortBy: AlbumListSort.YEAR,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: pagination.artist * itemsPerPage,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
});
|
||||||
|
|
||||||
const carousels = [
|
const carousels = [
|
||||||
{
|
{
|
||||||
|
@ -227,8 +229,8 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFavoriteMutation = useCreateFavorite();
|
const createFavoriteMutation = useCreateFavorite({});
|
||||||
const deleteFavoriteMutation = useDeleteFavorite();
|
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||||
|
|
||||||
const handleFavorite = () => {
|
const handleFavorite = () => {
|
||||||
if (!detailQuery?.data) return;
|
if (!detailQuery?.data) return;
|
||||||
|
|
|
@ -5,9 +5,10 @@ import { Link } from 'react-router-dom';
|
||||||
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
||||||
import { Button, Rating, Text } from '/@/renderer/components';
|
import { Button, Rating, Text } from '/@/renderer/components';
|
||||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||||
import { LibraryHeader, useUpdateRating } from '/@/renderer/features/shared';
|
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
|
|
||||||
interface AlbumDetailHeaderProps {
|
interface AlbumDetailHeaderProps {
|
||||||
|
@ -17,7 +18,8 @@ interface AlbumDetailHeaderProps {
|
||||||
export const AlbumDetailHeader = forwardRef(
|
export const AlbumDetailHeader = forwardRef(
|
||||||
({ background }: AlbumDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
({ background }: AlbumDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const detailQuery = useAlbumDetail({ id: albumId });
|
const server = useCurrentServer();
|
||||||
|
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
const metadataItems = [
|
const metadataItems = [
|
||||||
|
@ -38,17 +40,17 @@ export const AlbumDetailHeader = forwardRef(
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const updateRatingMutation = useUpdateRating();
|
const updateRatingMutation = useSetRating({});
|
||||||
|
|
||||||
const handleUpdateRating = (rating: number) => {
|
const handleUpdateRating = (rating: number) => {
|
||||||
if (!detailQuery?.data) return;
|
if (!detailQuery?.data) return;
|
||||||
|
|
||||||
updateRatingMutation.mutate({
|
updateRatingMutation.mutate({
|
||||||
_serverId: detailQuery?.data.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [detailQuery.data],
|
item: [detailQuery.data],
|
||||||
rating,
|
rating,
|
||||||
},
|
},
|
||||||
|
serverId: detailQuery.data.serverId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,11 +58,11 @@ export const AlbumDetailHeader = forwardRef(
|
||||||
if (!detailQuery?.data || !detailQuery?.data.userRating) return;
|
if (!detailQuery?.data || !detailQuery?.data.userRating) return;
|
||||||
|
|
||||||
updateRatingMutation.mutate({
|
updateRatingMutation.mutate({
|
||||||
_serverId: detailQuery.data.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [detailQuery.data],
|
item: [detailQuery.data],
|
||||||
rating: 0,
|
rating: 0,
|
||||||
},
|
},
|
||||||
|
serverId: detailQuery.data.serverId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { ALBUM_CARD_ROWS } from '/@/renderer/components';
|
||||||
ALBUM_CARD_ROWS,
|
|
||||||
getColumnDefs,
|
|
||||||
TablePagination,
|
|
||||||
VirtualGridAutoSizerContainer,
|
|
||||||
VirtualInfiniteGrid,
|
|
||||||
VirtualInfiniteGridRef,
|
|
||||||
VirtualTable,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { ListDisplayType, CardRow } from '/@/renderer/types';
|
import { ListDisplayType, CardRow } from '/@/renderer/types';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
@ -40,6 +32,12 @@ import { generatePath, useNavigate } from 'react-router';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
|
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
|
||||||
import { useAlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
import { useAlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
||||||
|
import {
|
||||||
|
VirtualInfiniteGridRef,
|
||||||
|
VirtualGridAutoSizerContainer,
|
||||||
|
VirtualInfiniteGrid,
|
||||||
|
} from '/@/renderer/components/virtual-grid';
|
||||||
|
import { getColumnDefs, VirtualTable, TablePagination } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
interface AlbumListContentProps {
|
interface AlbumListContentProps {
|
||||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||||
|
@ -71,29 +69,36 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filter,
|
...filter,
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
jellyfin: {
|
||||||
},
|
...filter._custom?.jellyfin,
|
||||||
ndParams: {
|
},
|
||||||
...filter.ndParams,
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
return params.failCallback();
|
||||||
|
}
|
||||||
|
|
||||||
const albumsRes = await queryClient.fetchQuery(
|
const albumsRes = await queryClient.fetchQuery(
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumList({
|
api.controller.getAlbumList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const albums = api.normalize.albumList(albumsRes, server);
|
return params.successCallback(albumsRes?.items || [], albumsRes?.totalRecordCount || 0);
|
||||||
params.successCallback(albums?.items || [], albumsRes?.totalRecordCount || 0);
|
|
||||||
},
|
},
|
||||||
rowCount: undefined,
|
rowCount: undefined,
|
||||||
};
|
};
|
||||||
|
@ -165,15 +170,21 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont
|
||||||
|
|
||||||
const fetch = useCallback(
|
const fetch = useCallback(
|
||||||
async ({ skip, take }: { skip: number; take: number }) => {
|
async ({ skip, take }: { skip: number; take: number }) => {
|
||||||
|
if (!server) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const query: AlbumListQuery = {
|
const query: AlbumListQuery = {
|
||||||
limit: take,
|
limit: take,
|
||||||
startIndex: skip,
|
startIndex: skip,
|
||||||
...filter,
|
...filter,
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
jellyfin: {
|
||||||
},
|
...filter._custom?.jellyfin,
|
||||||
ndParams: {
|
},
|
||||||
...filter.ndParams,
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -181,13 +192,15 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont
|
||||||
|
|
||||||
const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
||||||
controller.getAlbumList({
|
controller.getAlbumList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return api.normalize.albumList(albums, server);
|
return albums;
|
||||||
},
|
},
|
||||||
[filter, queryClient, server],
|
[filter, queryClient, server],
|
||||||
);
|
);
|
||||||
|
@ -268,8 +281,8 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont
|
||||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFavoriteMutation = useCreateFavorite();
|
const createFavoriteMutation = useCreateFavorite({});
|
||||||
const deleteFavoriteMutation = useDeleteFavorite();
|
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||||
|
|
||||||
const handleFavorite = (options: {
|
const handleFavorite = (options: {
|
||||||
id: string[];
|
id: string[];
|
||||||
|
|
|
@ -20,16 +20,7 @@ import {
|
||||||
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 { AlbumListQuery, AlbumListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
import { AlbumListQuery, AlbumListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
||||||
import {
|
import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components';
|
||||||
ALBUM_TABLE_COLUMNS,
|
|
||||||
Button,
|
|
||||||
DropdownMenu,
|
|
||||||
MultiSelect,
|
|
||||||
Slider,
|
|
||||||
Switch,
|
|
||||||
Text,
|
|
||||||
VirtualInfiniteGridRef,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import {
|
import {
|
||||||
AlbumListFilter,
|
AlbumListFilter,
|
||||||
|
@ -43,6 +34,8 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters';
|
import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters';
|
||||||
import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters';
|
import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters';
|
||||||
import { useAlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
import { useAlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
||||||
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
|
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const FILTERS = {
|
const FILTERS = {
|
||||||
jellyfin: [
|
jellyfin: [
|
||||||
|
@ -100,7 +93,7 @@ export const AlbumListHeaderFilters = ({
|
||||||
const { display, filter, table, grid } = useAlbumListStore({ id, key: pageKey });
|
const { display, filter, table, grid } = useAlbumListStore({ id, key: pageKey });
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
const musicFoldersQuery = useMusicFolders();
|
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
@ -115,13 +108,15 @@ export const AlbumListHeaderFilters = ({
|
||||||
limit: take,
|
limit: take,
|
||||||
startIndex: skip,
|
startIndex: skip,
|
||||||
...filters,
|
...filters,
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filters.jfParams,
|
jellyfin: {
|
||||||
...customFilters?.jfParams,
|
...filters._custom?.jellyfin,
|
||||||
},
|
...customFilters?._custom?.jellyfin,
|
||||||
ndParams: {
|
},
|
||||||
...filters.ndParams,
|
navidrome: {
|
||||||
...customFilters?.ndParams,
|
...filters._custom?.navidrome,
|
||||||
|
...customFilters?._custom?.navidrome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
...customFilters,
|
...customFilters,
|
||||||
};
|
};
|
||||||
|
@ -132,14 +127,16 @@ export const AlbumListHeaderFilters = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumList({
|
api.controller.getAlbumList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
return api.normalize.albumList(albums, server);
|
return albums;
|
||||||
},
|
},
|
||||||
[customFilters, queryClient, server],
|
[customFilters, queryClient, server],
|
||||||
);
|
);
|
||||||
|
@ -157,13 +154,15 @@ export const AlbumListHeaderFilters = ({
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
...customFilters,
|
...customFilters,
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filters.jfParams,
|
jellyfin: {
|
||||||
...customFilters?.jfParams,
|
...filters._custom?.jellyfin,
|
||||||
},
|
...customFilters?._custom?.jellyfin,
|
||||||
ndParams: {
|
},
|
||||||
...filters.ndParams,
|
navidrome: {
|
||||||
...customFilters?.ndParams,
|
...filters._custom?.navidrome,
|
||||||
|
...customFilters?._custom?.navidrome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -173,15 +172,16 @@ export const AlbumListHeaderFilters = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumList({
|
api.controller.getAlbumList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const albums = api.normalize.albumList(albumsRes, server);
|
return params.successCallback(albumsRes?.items || [], albumsRes?.totalRecordCount || 0);
|
||||||
params.successCallback(albums?.items || [], albumsRes?.totalRecordCount || 0);
|
|
||||||
},
|
},
|
||||||
rowCount: undefined,
|
rowCount: undefined,
|
||||||
};
|
};
|
||||||
|
@ -218,6 +218,7 @@ export const AlbumListHeaderFilters = ({
|
||||||
handleFilterChange={handleFilterChange}
|
handleFilterChange={handleFilterChange}
|
||||||
id={id}
|
id={id}
|
||||||
pageKey={pageKey}
|
pageKey={pageKey}
|
||||||
|
serverId={server?.id}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<JellyfinAlbumFilters
|
<JellyfinAlbumFilters
|
||||||
|
@ -225,6 +226,7 @@ export const AlbumListHeaderFilters = ({
|
||||||
handleFilterChange={handleFilterChange}
|
handleFilterChange={handleFilterChange}
|
||||||
id={id}
|
id={id}
|
||||||
pageKey={pageKey}
|
pageKey={pageKey}
|
||||||
|
serverId={server?.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -293,30 +295,32 @@ export const AlbumListHeaderFilters = ({
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
|
||||||
const handlePlay = async (play: Play) => {
|
const handlePlay = async (play: Play) => {
|
||||||
if (!itemCount || itemCount === 0) return;
|
if (!itemCount || itemCount === 0 || !server) return;
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
...filter,
|
...filter,
|
||||||
...customFilters,
|
...customFilters,
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
jellyfin: {
|
||||||
...customFilters?.jfParams,
|
...filter._custom?.jellyfin,
|
||||||
},
|
...customFilters?._custom?.jellyfin,
|
||||||
ndParams: {
|
},
|
||||||
...filter.ndParams,
|
navidrome: {
|
||||||
...customFilters?.ndParams,
|
...filter._custom?.navidrome,
|
||||||
|
...customFilters?._custom?.navidrome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
||||||
|
|
||||||
const albumListRes = await queryClient.fetchQuery({
|
const albumListRes = await queryClient.fetchQuery({
|
||||||
queryFn: ({ signal }) => api.controller.getAlbumList({ query, server, signal }),
|
queryFn: ({ signal }) =>
|
||||||
|
api.controller.getAlbumList({ apiClientProps: { server, signal }, query }),
|
||||||
queryKey,
|
queryKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const albumIds =
|
const albumIds = albumListRes?.items?.map((a) => a.id) || [];
|
||||||
api.normalize.albumList(albumListRes, server).items?.map((item) => item.id) || [];
|
|
||||||
|
|
||||||
handlePlayQueueAdd?.({
|
handlePlayQueueAdd?.({
|
||||||
byItemType: {
|
byItemType: {
|
||||||
|
@ -382,16 +386,16 @@ export const AlbumListHeaderFilters = ({
|
||||||
const isFilterApplied = useMemo(() => {
|
const isFilterApplied = useMemo(() => {
|
||||||
const isNavidromeFilterApplied =
|
const isNavidromeFilterApplied =
|
||||||
server?.type === ServerType.NAVIDROME &&
|
server?.type === ServerType.NAVIDROME &&
|
||||||
filter.ndParams &&
|
filter?._custom.navidrome &&
|
||||||
Object.values(filter.ndParams).some((value) => value !== undefined);
|
Object.values(filter._custom.navidrome).some((value) => value !== undefined);
|
||||||
|
|
||||||
const isJellyfinFilterApplied =
|
const isJellyfinFilterApplied =
|
||||||
server?.type === ServerType.JELLYFIN &&
|
server?.type === ServerType.JELLYFIN &&
|
||||||
filter.jfParams &&
|
filter?._custom.jellyfin &&
|
||||||
Object.values(filter.jfParams).some((value) => value !== undefined);
|
Object.values(filter._custom.jellyfin).some((value) => value !== undefined);
|
||||||
|
|
||||||
return isNavidromeFilterApplied || isJellyfinFilterApplied;
|
return isNavidromeFilterApplied || isJellyfinFilterApplied;
|
||||||
}, [filter.jfParams, filter.ndParams, server?.type]);
|
}, [filter._custom.jellyfin, filter._custom.navidrome, server?.type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
|
@ -456,7 +460,7 @@ export const AlbumListHeaderFilters = ({
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
{musicFoldersQuery.data?.map((folder) => (
|
{musicFoldersQuery.data?.items.map((folder) => (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={`musicFolder-${folder.id}`}
|
key={`musicFolder-${folder.id}`}
|
||||||
$isActive={filter.musicFolderId === folder.id}
|
$isActive={filter.musicFolderId === folder.id}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { api } from '/@/renderer/api';
|
||||||
import { controller } from '/@/renderer/api/controller';
|
import { controller } from '/@/renderer/api/controller';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { AlbumListQuery, LibraryItem } from '/@/renderer/api/types';
|
import { AlbumListQuery, LibraryItem } from '/@/renderer/api/types';
|
||||||
import { PageHeader, SearchInput, VirtualInfiniteGridRef } from '/@/renderer/components';
|
import { PageHeader, SearchInput } from '/@/renderer/components';
|
||||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import {
|
import {
|
||||||
|
@ -24,6 +24,7 @@ import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/a
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { useAlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
import { useAlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
||||||
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
|
|
||||||
interface AlbumListHeaderProps {
|
interface AlbumListHeaderProps {
|
||||||
customFilters?: Partial<AlbumListFilter>;
|
customFilters?: Partial<AlbumListFilter>;
|
||||||
|
@ -54,15 +55,17 @@ export const AlbumListHeader = ({
|
||||||
limit: take,
|
limit: take,
|
||||||
startIndex: skip,
|
startIndex: skip,
|
||||||
...filters,
|
...filters,
|
||||||
jfParams: {
|
|
||||||
...filters.jfParams,
|
|
||||||
...customFilters?.jfParams,
|
|
||||||
},
|
|
||||||
ndParams: {
|
|
||||||
...filters.ndParams,
|
|
||||||
...customFilters?.ndParams,
|
|
||||||
},
|
|
||||||
...customFilters,
|
...customFilters,
|
||||||
|
_custom: {
|
||||||
|
jellyfin: {
|
||||||
|
...filters._custom?.jellyfin,
|
||||||
|
...customFilters?._custom?.jellyfin,
|
||||||
|
},
|
||||||
|
navidrome: {
|
||||||
|
...filters._custom?.navidrome,
|
||||||
|
...customFilters?._custom?.navidrome,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
||||||
|
@ -71,14 +74,16 @@ export const AlbumListHeader = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
controller.getAlbumList({
|
controller.getAlbumList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
return api.normalize.albumList(albums, server);
|
return albums;
|
||||||
},
|
},
|
||||||
[customFilters, queryClient, server],
|
[customFilters, queryClient, server],
|
||||||
);
|
);
|
||||||
|
@ -96,13 +101,15 @@ export const AlbumListHeader = ({
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
...customFilters,
|
...customFilters,
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filters.jfParams,
|
jellyfin: {
|
||||||
...customFilters?.jfParams,
|
...filters._custom?.jellyfin,
|
||||||
},
|
...customFilters?._custom?.jellyfin,
|
||||||
ndParams: {
|
},
|
||||||
...filters.ndParams,
|
navidrome: {
|
||||||
...customFilters?.ndParams,
|
...filters._custom?.navidrome,
|
||||||
|
...customFilters?._custom?.navidrome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,15 +119,16 @@ export const AlbumListHeader = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumList({
|
api.controller.getAlbumList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const albums = api.normalize.albumList(albumsRes, server);
|
params.successCallback(albumsRes?.items || [], albumsRes?.totalRecordCount || 0);
|
||||||
params.successCallback(albums?.items || [], albumsRes?.totalRecordCount || 0);
|
|
||||||
},
|
},
|
||||||
rowCount: undefined,
|
rowCount: undefined,
|
||||||
};
|
};
|
||||||
|
@ -164,24 +172,26 @@ export const AlbumListHeader = ({
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
...filter,
|
...filter,
|
||||||
...customFilters,
|
...customFilters,
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
jellyfin: {
|
||||||
...customFilters?.jfParams,
|
...filter._custom?.jellyfin,
|
||||||
},
|
...customFilters?._custom?.jellyfin,
|
||||||
ndParams: {
|
},
|
||||||
...filter.ndParams,
|
navidrome: {
|
||||||
...customFilters?.ndParams,
|
...filter._custom?.navidrome,
|
||||||
|
...customFilters?._custom?.navidrome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
||||||
|
|
||||||
const albumListRes = await queryClient.fetchQuery({
|
const albumListRes = await queryClient.fetchQuery({
|
||||||
queryFn: ({ signal }) => api.controller.getAlbumList({ query, server, signal }),
|
queryFn: ({ signal }) =>
|
||||||
|
api.controller.getAlbumList({ apiClientProps: { server, signal }, query }),
|
||||||
queryKey,
|
queryKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const albumIds =
|
const albumIds = albumListRes?.items?.map((item) => item.id) || [];
|
||||||
api.normalize.albumList(albumListRes, server).items?.map((item) => item.id) || [];
|
|
||||||
|
|
||||||
handlePlayQueueAdd?.({
|
handlePlayQueueAdd?.({
|
||||||
byItemType: {
|
byItemType: {
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface JellyfinAlbumFiltersProps {
|
||||||
handleFilterChange: (filters: AlbumListFilter) => void;
|
handleFilterChange: (filters: AlbumListFilter) => void;
|
||||||
id?: string;
|
id?: string;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JellyfinAlbumFilters = ({
|
export const JellyfinAlbumFilters = ({
|
||||||
|
@ -19,24 +20,25 @@ export const JellyfinAlbumFilters = ({
|
||||||
handleFilterChange,
|
handleFilterChange,
|
||||||
pageKey,
|
pageKey,
|
||||||
id,
|
id,
|
||||||
|
serverId,
|
||||||
}: JellyfinAlbumFiltersProps) => {
|
}: JellyfinAlbumFiltersProps) => {
|
||||||
const filter = useAlbumListFilter({ id, key: pageKey });
|
const filter = useAlbumListFilter({ id, key: pageKey });
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
|
|
||||||
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
||||||
const genreListQuery = useGenreList(null);
|
const genreListQuery = useGenreList({ query: null, serverId });
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
return genreListQuery.data.map((genre) => ({
|
return genreListQuery.data.items.map((genre) => ({
|
||||||
label: genre.name,
|
label: genre.name,
|
||||||
value: genre.id,
|
value: genre.id,
|
||||||
}));
|
}));
|
||||||
}, [genreListQuery.data]);
|
}, [genreListQuery.data]);
|
||||||
|
|
||||||
const selectedGenres = useMemo(() => {
|
const selectedGenres = useMemo(() => {
|
||||||
return filter.jfParams?.genreIds?.split(',');
|
return filter._custom?.jellyfin?.genreIds?.split(',');
|
||||||
}, [filter.jfParams?.genreIds]);
|
}, [filter._custom?.jellyfin?.genreIds]);
|
||||||
|
|
||||||
const toggleFilters = [
|
const toggleFilters = [
|
||||||
{
|
{
|
||||||
|
@ -44,17 +46,19 @@ export const JellyfinAlbumFilters = ({
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
includeItemTypes: 'Audio',
|
jellyfin: {
|
||||||
isFavorite: e.currentTarget.checked ? true : undefined,
|
...filter._custom?.jellyfin,
|
||||||
|
isFavorite: e.currentTarget.checked ? true : undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
}) as AlbumListFilter;
|
}) as AlbumListFilter;
|
||||||
handleFilterChange(updatedFilters);
|
handleFilterChange(updatedFilters);
|
||||||
},
|
},
|
||||||
value: filter.jfParams?.isFavorite,
|
value: filter._custom?.jellyfin?.isFavorite,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -62,9 +66,12 @@ export const JellyfinAlbumFilters = ({
|
||||||
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
minYear: e === '' ? undefined : (e as number),
|
jellyfin: {
|
||||||
|
...filter._custom?.jellyfin,
|
||||||
|
minYear: e === '' ? undefined : (e as number),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -76,9 +83,12 @@ export const JellyfinAlbumFilters = ({
|
||||||
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
maxYear: e === '' ? undefined : (e as number),
|
jellyfin: {
|
||||||
|
...filter._custom?.jellyfin,
|
||||||
|
maxYear: e === '' ? undefined : (e as number),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -90,9 +100,12 @@ export const JellyfinAlbumFilters = ({
|
||||||
const genreFilterString = e?.length ? e.join(',') : undefined;
|
const genreFilterString = e?.length ? e.join(',') : undefined;
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
genreIds: genreFilterString,
|
jellyfin: {
|
||||||
|
...filter._custom?.jellyfin,
|
||||||
|
genreIds: genreFilterString,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -102,17 +115,18 @@ export const JellyfinAlbumFilters = ({
|
||||||
|
|
||||||
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
||||||
|
|
||||||
const albumArtistListQuery = useAlbumArtistList(
|
const albumArtistListQuery = useAlbumArtistList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
sortBy: AlbumArtistListSort.NAME,
|
sortBy: AlbumArtistListSort.NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
{
|
serverId,
|
||||||
cacheTime: 1000 * 60 * 2,
|
});
|
||||||
staleTime: 1000 * 60 * 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectableAlbumArtists = useMemo(() => {
|
const selectableAlbumArtists = useMemo(() => {
|
||||||
if (!albumArtistListQuery?.data?.items) return [];
|
if (!albumArtistListQuery?.data?.items) return [];
|
||||||
|
@ -127,9 +141,12 @@ export const JellyfinAlbumFilters = ({
|
||||||
const albumArtistFilterString = e?.length ? e.join(',') : undefined;
|
const albumArtistFilterString = e?.length ? e.join(',') : undefined;
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
albumArtistIds: albumArtistFilterString,
|
jellyfin: {
|
||||||
|
...filter._custom?.jellyfin,
|
||||||
|
albumArtistIds: albumArtistFilterString,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -155,21 +172,21 @@ export const JellyfinAlbumFilters = ({
|
||||||
<Divider my="0.5rem" />
|
<Divider my="0.5rem" />
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
defaultValue={filter.jfParams?.minYear}
|
defaultValue={filter._custom?.jellyfin?.minYear}
|
||||||
hideControls={false}
|
hideControls={false}
|
||||||
label="From year"
|
label="From year"
|
||||||
max={2300}
|
max={2300}
|
||||||
min={1700}
|
min={1700}
|
||||||
required={!!filter.jfParams?.maxYear}
|
required={!!filter._custom?.jellyfin?.maxYear}
|
||||||
onChange={(e) => handleMinYearFilter(e)}
|
onChange={(e) => handleMinYearFilter(e)}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
defaultValue={filter.jfParams?.maxYear}
|
defaultValue={filter._custom?.jellyfin?.maxYear}
|
||||||
hideControls={false}
|
hideControls={false}
|
||||||
label="To year"
|
label="To year"
|
||||||
max={2300}
|
max={2300}
|
||||||
min={1700}
|
min={1700}
|
||||||
required={!!filter.jfParams?.minYear}
|
required={!!filter._custom?.jellyfin?.minYear}
|
||||||
onChange={(e) => handleMaxYearFilter(e)}
|
onChange={(e) => handleMaxYearFilter(e)}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -189,7 +206,7 @@ export const JellyfinAlbumFilters = ({
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
data={selectableAlbumArtists}
|
data={selectableAlbumArtists}
|
||||||
defaultValue={filter.jfParams?.albumArtistIds?.split(',')}
|
defaultValue={filter._custom?.jellyfin?.albumArtistIds?.split(',')}
|
||||||
disabled={disableArtistFilter}
|
disabled={disableArtistFilter}
|
||||||
label="Artist"
|
label="Artist"
|
||||||
limit={300}
|
limit={300}
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface NavidromeAlbumFiltersProps {
|
||||||
handleFilterChange: (filters: AlbumListFilter) => void;
|
handleFilterChange: (filters: AlbumListFilter) => void;
|
||||||
id?: string;
|
id?: string;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NavidromeAlbumFilters = ({
|
export const NavidromeAlbumFilters = ({
|
||||||
|
@ -19,15 +20,16 @@ export const NavidromeAlbumFilters = ({
|
||||||
disableArtistFilter,
|
disableArtistFilter,
|
||||||
pageKey,
|
pageKey,
|
||||||
id,
|
id,
|
||||||
|
serverId,
|
||||||
}: NavidromeAlbumFiltersProps) => {
|
}: NavidromeAlbumFiltersProps) => {
|
||||||
const filter = useAlbumListFilter({ id, key: pageKey });
|
const filter = useAlbumListFilter({ id, key: pageKey });
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
|
|
||||||
const genreListQuery = useGenreList(null);
|
const genreListQuery = useGenreList({ query: null, serverId });
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
return genreListQuery.data.map((genre) => ({
|
return genreListQuery.data.items.map((genre) => ({
|
||||||
label: genre.name,
|
label: genre.name,
|
||||||
value: genre.id,
|
value: genre.id,
|
||||||
}));
|
}));
|
||||||
|
@ -36,9 +38,12 @@ export const NavidromeAlbumFilters = ({
|
||||||
const handleGenresFilter = debounce((e: string | null) => {
|
const handleGenresFilter = debounce((e: string | null) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
...filter._custom,
|
||||||
genre_id: e || undefined,
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
genre_id: e || undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: 'album',
|
key: 'album',
|
||||||
|
@ -52,70 +57,89 @@ export const NavidromeAlbumFilters = ({
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
...filter._custom,
|
||||||
has_rating: e.currentTarget.checked ? true : undefined,
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
has_rating: e.currentTarget.checked ? true : undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
}) as AlbumListFilter;
|
}) as AlbumListFilter;
|
||||||
handleFilterChange(updatedFilters);
|
handleFilterChange(updatedFilters);
|
||||||
},
|
},
|
||||||
value: filter.ndParams?.has_rating,
|
value: filter._custom?.navidrome?.has_rating,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Is favorited',
|
label: 'Is favorited',
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
console.log('e.currentTarget.checked :>> ', e.currentTarget.checked);
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: { ...filter.ndParams, starred: e.currentTarget.checked ? true : undefined },
|
_custom: {
|
||||||
|
...filter._custom,
|
||||||
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
starred: e.currentTarget.checked ? true : undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
}) as AlbumListFilter;
|
}) as AlbumListFilter;
|
||||||
handleFilterChange(updatedFilters);
|
handleFilterChange(updatedFilters);
|
||||||
},
|
},
|
||||||
value: filter.ndParams?.starred,
|
value: filter._custom?.navidrome?.starred,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Is compilation',
|
label: 'Is compilation',
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
...filter._custom,
|
||||||
compilation: e.currentTarget.checked ? true : undefined,
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
compilation: e.currentTarget.checked ? true : undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
}) as AlbumListFilter;
|
}) as AlbumListFilter;
|
||||||
handleFilterChange(updatedFilters);
|
handleFilterChange(updatedFilters);
|
||||||
},
|
},
|
||||||
value: filter.ndParams?.compilation,
|
value: filter._custom?.navidrome?.compilation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Is recently played',
|
label: 'Is recently played',
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
...filter._custom,
|
||||||
recently_played: e.currentTarget.checked ? true : undefined,
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
recently_played: e.currentTarget.checked ? true : undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
}) as AlbumListFilter;
|
}) as AlbumListFilter;
|
||||||
handleFilterChange(updatedFilters);
|
handleFilterChange(updatedFilters);
|
||||||
},
|
},
|
||||||
value: filter.ndParams?.recently_played,
|
value: filter._custom?.navidrome?.recently_played,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleYearFilter = debounce((e: number | string) => {
|
const handleYearFilter = debounce((e: number | string) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
navidrome: {
|
||||||
year: e === '' ? undefined : (e as number),
|
...filter._custom?.navidrome,
|
||||||
|
year: e === '' ? undefined : (e as number),
|
||||||
|
},
|
||||||
|
...filter._custom,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -125,18 +149,19 @@ export const NavidromeAlbumFilters = ({
|
||||||
|
|
||||||
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
||||||
|
|
||||||
const albumArtistListQuery = useAlbumArtistList(
|
const albumArtistListQuery = useAlbumArtistList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
// searchTerm: debouncedSearchTerm,
|
// searchTerm: debouncedSearchTerm,
|
||||||
sortBy: AlbumArtistListSort.NAME,
|
sortBy: AlbumArtistListSort.NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
{
|
serverId,
|
||||||
cacheTime: 1000 * 60 * 2,
|
});
|
||||||
staleTime: 1000 * 60 * 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectableAlbumArtists = useMemo(() => {
|
const selectableAlbumArtists = useMemo(() => {
|
||||||
if (!albumArtistListQuery?.data?.items) return [];
|
if (!albumArtistListQuery?.data?.items) return [];
|
||||||
|
@ -150,9 +175,12 @@ export const NavidromeAlbumFilters = ({
|
||||||
const handleAlbumArtistFilter = (e: string | null) => {
|
const handleAlbumArtistFilter = (e: string | null) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
...filter._custom,
|
||||||
artist_id: e || undefined,
|
navidrome: {
|
||||||
|
...filter._custom?.navidrome,
|
||||||
|
artist_id: e || undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -177,7 +205,7 @@ export const NavidromeAlbumFilters = ({
|
||||||
<Divider my="0.5rem" />
|
<Divider my="0.5rem" />
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
defaultValue={filter.ndParams?.year}
|
defaultValue={filter._custom?.navidrome?.year}
|
||||||
hideControls={false}
|
hideControls={false}
|
||||||
label="Year"
|
label="Year"
|
||||||
max={5000}
|
max={5000}
|
||||||
|
@ -188,7 +216,7 @@ export const NavidromeAlbumFilters = ({
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
data={genreList}
|
data={genreList}
|
||||||
defaultValue={filter.ndParams?.genre_id}
|
defaultValue={filter._custom?.navidrome?.genre_id}
|
||||||
label="Genre"
|
label="Genre"
|
||||||
onChange={handleGenresFilter}
|
onChange={handleGenresFilter}
|
||||||
/>
|
/>
|
||||||
|
@ -198,7 +226,7 @@ export const NavidromeAlbumFilters = ({
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
data={selectableAlbumArtists}
|
data={selectableAlbumArtists}
|
||||||
defaultValue={filter.ndParams?.artist_id}
|
defaultValue={filter._custom?.navidrome?.artist_id}
|
||||||
disabled={disableArtistFilter}
|
disabled={disableArtistFilter}
|
||||||
label="Artist"
|
label="Artist"
|
||||||
limit={300}
|
limit={300}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
|
||||||
const AlbumDetailRoute = () => {
|
const AlbumDetailRoute = () => {
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
|
@ -17,7 +18,8 @@ const AlbumDetailRoute = () => {
|
||||||
const headerRef = useRef<HTMLDivElement>(null);
|
const headerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const detailQuery = useAlbumDetail({ id: albumId });
|
const server = useCurrentServer();
|
||||||
|
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
||||||
const background = useFastAverageColor(detailQuery.data?.imageUrl, !detailQuery.isLoading);
|
const background = useFastAverageColor(detailQuery.data?.imageUrl, !detailQuery.isLoading);
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header';
|
import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header';
|
||||||
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
|
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
|
||||||
|
@ -8,6 +7,7 @@ import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-que
|
||||||
import { generatePageKey, useAlbumListFilter, useCurrentServer } from '/@/renderer/store';
|
import { generatePageKey, useAlbumListFilter, useCurrentServer } from '/@/renderer/store';
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { AlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
import { AlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
|
||||||
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
|
|
||||||
const AlbumListRoute = () => {
|
const AlbumListRoute = () => {
|
||||||
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
|
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
|
||||||
|
@ -24,17 +24,18 @@ const AlbumListRoute = () => {
|
||||||
|
|
||||||
const albumListFilter = useAlbumListFilter({ id: albumArtistId || undefined, key: pageKey });
|
const albumListFilter = useAlbumListFilter({ id: albumArtistId || undefined, key: pageKey });
|
||||||
|
|
||||||
const itemCountCheck = useAlbumList(
|
const itemCountCheck = useAlbumList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
...albumListFilter,
|
...albumListFilter,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
cacheTime: 1000 * 60,
|
});
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemCount =
|
const itemCount =
|
||||||
itemCountCheck.data?.totalRecordCount === null
|
itemCountCheck.data?.totalRecordCount === null
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import {
|
import { Button, GridCarousel, Text, TextTitle } from '/@/renderer/components';
|
||||||
Button,
|
|
||||||
getColumnDefs,
|
|
||||||
GridCarousel,
|
|
||||||
Text,
|
|
||||||
TextTitle,
|
|
||||||
VirtualTable,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||||
import { Box, Group, Stack } from '@mantine/core';
|
import { Box, Group, Stack } from '@mantine/core';
|
||||||
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
||||||
|
@ -38,6 +31,7 @@ import {
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
||||||
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
|
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
|
||||||
|
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const ContentContainer = styled.div`
|
const ContentContainer = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -63,7 +57,7 @@ export const AlbumArtistDetailContent = () => {
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const itemsPerPage = cq.isXl ? 9 : cq.isLg ? 7 : cq.isMd ? 5 : cq.isSm ? 4 : 3;
|
const itemsPerPage = cq.isXl ? 9 : cq.isLg ? 7 : cq.isMd ? 5 : cq.isSm ? 4 : 3;
|
||||||
|
|
||||||
const detailQuery = useAlbumArtistDetail({ id: albumArtistId });
|
const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id });
|
||||||
|
|
||||||
const artistDiscographyLink = `${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY, {
|
const artistDiscographyLink = `${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY, {
|
||||||
albumArtistId,
|
albumArtistId,
|
||||||
|
@ -80,34 +74,57 @@ export const AlbumArtistDetailContent = () => {
|
||||||
})}`;
|
})}`;
|
||||||
|
|
||||||
const recentAlbumsQuery = useAlbumList({
|
const recentAlbumsQuery = useAlbumList({
|
||||||
jfParams: server?.type === ServerType.JELLYFIN ? { artistIds: albumArtistId } : undefined,
|
query: {
|
||||||
limit: itemsPerPage,
|
_custom: {
|
||||||
ndParams:
|
jellyfin: {
|
||||||
server?.type === ServerType.NAVIDROME
|
...(server?.type === ServerType.JELLYFIN ? { artistIds: albumArtistId } : undefined),
|
||||||
? { artist_id: albumArtistId, compilation: false }
|
},
|
||||||
: undefined,
|
navidrome: {
|
||||||
sortBy: AlbumListSort.RELEASE_DATE,
|
...(server?.type === ServerType.NAVIDROME
|
||||||
sortOrder: SortOrder.DESC,
|
? { artist_id: albumArtistId, compilation: false }
|
||||||
startIndex: 0,
|
: undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
limit: itemsPerPage,
|
||||||
|
sortBy: AlbumListSort.RELEASE_DATE,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const compilationAlbumsQuery = useAlbumList({
|
const compilationAlbumsQuery = useAlbumList({
|
||||||
jfParams:
|
query: {
|
||||||
server?.type === ServerType.JELLYFIN ? { contributingArtistIds: albumArtistId } : undefined,
|
_custom: {
|
||||||
limit: itemsPerPage,
|
jellyfin: {
|
||||||
ndParams:
|
...(server?.type === ServerType.JELLYFIN
|
||||||
server?.type === ServerType.NAVIDROME
|
? { contributingArtistIds: albumArtistId }
|
||||||
? { artist_id: albumArtistId, compilation: true }
|
: undefined),
|
||||||
: undefined,
|
},
|
||||||
sortBy: AlbumListSort.RELEASE_DATE,
|
navidrome: {
|
||||||
sortOrder: SortOrder.DESC,
|
...(server?.type === ServerType.NAVIDROME
|
||||||
startIndex: 0,
|
? { artist_id: albumArtistId, compilation: true }
|
||||||
|
: undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
limit: itemsPerPage,
|
||||||
|
sortBy: AlbumListSort.RELEASE_DATE,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const topSongsQuery = useTopSongsList(
|
const topSongsQuery = useTopSongsList({
|
||||||
{ artist: detailQuery?.data?.name || '', artistId: albumArtistId },
|
options: {
|
||||||
{ enabled: !!detailQuery?.data?.name },
|
enabled: !!detailQuery?.data?.name,
|
||||||
);
|
},
|
||||||
|
query: {
|
||||||
|
artist: detailQuery?.data?.name || '',
|
||||||
|
artistId: albumArtistId,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
});
|
||||||
|
|
||||||
const topSongsColumnDefs: ColDef[] = useMemo(
|
const topSongsColumnDefs: ColDef[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -242,8 +259,8 @@ export const AlbumArtistDetailContent = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFavoriteMutation = useCreateFavorite();
|
const createFavoriteMutation = useCreateFavorite({});
|
||||||
const deleteFavoriteMutation = useDeleteFavorite();
|
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||||
|
|
||||||
const handleFavorite = () => {
|
const handleFavorite = () => {
|
||||||
if (!detailQuery?.data) return;
|
if (!detailQuery?.data) return;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Group, Rating, Stack } from '@mantine/core';
|
|
||||||
import { forwardRef, Fragment, Ref, MouseEvent } from 'react';
|
import { forwardRef, Fragment, Ref, MouseEvent } from 'react';
|
||||||
|
import { Group, Rating, Stack } from '@mantine/core';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
||||||
import { Text } from '/@/renderer/components';
|
import { Text } from '/@/renderer/components';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
||||||
import { LibraryHeader, useUpdateRating } from '/@/renderer/features/shared';
|
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
|
import { useCurrentServer } from '../../../store/auth.store';
|
||||||
|
|
||||||
interface AlbumArtistDetailHeaderProps {
|
interface AlbumArtistDetailHeaderProps {
|
||||||
background: string;
|
background: string;
|
||||||
|
@ -16,7 +17,11 @@ interface AlbumArtistDetailHeaderProps {
|
||||||
export const AlbumArtistDetailHeader = forwardRef(
|
export const AlbumArtistDetailHeader = forwardRef(
|
||||||
({ background }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
({ background }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
||||||
const { albumArtistId } = useParams() as { albumArtistId: string };
|
const { albumArtistId } = useParams() as { albumArtistId: string };
|
||||||
const detailQuery = useAlbumArtistDetail({ id: albumArtistId });
|
const server = useCurrentServer();
|
||||||
|
const detailQuery = useAlbumArtistDetail({
|
||||||
|
query: { id: albumArtistId },
|
||||||
|
serverId: server?.id,
|
||||||
|
});
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
const metadataItems = [
|
const metadataItems = [
|
||||||
|
@ -37,17 +42,17 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const updateRatingMutation = useUpdateRating();
|
const updateRatingMutation = useSetRating({});
|
||||||
|
|
||||||
const handleUpdateRating = (rating: number) => {
|
const handleUpdateRating = (rating: number) => {
|
||||||
if (!detailQuery?.data) return;
|
if (!detailQuery?.data) return;
|
||||||
|
|
||||||
updateRatingMutation.mutate({
|
updateRatingMutation.mutate({
|
||||||
_serverId: detailQuery?.data.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [detailQuery.data],
|
item: [detailQuery.data],
|
||||||
rating,
|
rating,
|
||||||
},
|
},
|
||||||
|
serverId: detailQuery?.data.serverId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,11 +63,11 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||||
if (!isSameRatingAsPrevious) return;
|
if (!isSameRatingAsPrevious) return;
|
||||||
|
|
||||||
updateRatingMutation.mutate({
|
updateRatingMutation.mutate({
|
||||||
_serverId: detailQuery.data.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [detailQuery.data],
|
item: [detailQuery.data],
|
||||||
rating: 0,
|
rating: 0,
|
||||||
},
|
},
|
||||||
|
serverId: detailQuery.data.serverId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { MutableRefObject, useMemo } from 'react';
|
import { MutableRefObject, useMemo } from 'react';
|
||||||
import type { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
import type { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { getColumnDefs, VirtualGridAutoSizerContainer, VirtualTable } from '/@/renderer/components';
|
|
||||||
import { useCurrentServer, useSongListStore } from '/@/renderer/store';
|
import { useCurrentServer, useSongListStore } from '/@/renderer/store';
|
||||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||||
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { LibraryItem, QueueSong } from '/@/renderer/api/types';
|
import { LibraryItem, QueueSong } from '/@/renderer/api/types';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
|
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||||
|
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
interface AlbumArtistSongListContentProps {
|
interface AlbumArtistSongListContentProps {
|
||||||
data: QueueSong[];
|
data: QueueSong[];
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components';
|
||||||
ALBUMARTIST_CARD_ROWS,
|
|
||||||
getColumnDefs,
|
|
||||||
TablePagination,
|
|
||||||
VirtualGridAutoSizerContainer,
|
|
||||||
VirtualInfiniteGrid,
|
|
||||||
VirtualInfiniteGridRef,
|
|
||||||
VirtualTable,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { ListDisplayType, CardRow } from '/@/renderer/types';
|
import { ListDisplayType, CardRow } from '/@/renderer/types';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
@ -35,6 +27,12 @@ import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-a
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { useAlbumArtistListFilter, useListStoreActions } from '../../../store/list.store';
|
import { useAlbumArtistListFilter, useListStoreActions } from '../../../store/list.store';
|
||||||
import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
|
import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
|
||||||
|
import {
|
||||||
|
VirtualInfiniteGridRef,
|
||||||
|
VirtualGridAutoSizerContainer,
|
||||||
|
VirtualInfiniteGrid,
|
||||||
|
} from '/@/renderer/components/virtual-grid';
|
||||||
|
import { getColumnDefs, VirtualTable, TablePagination } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
interface AlbumArtistListContentProps {
|
interface AlbumArtistListContentProps {
|
||||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||||
|
@ -54,17 +52,18 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
|
||||||
|
|
||||||
const isPaginationEnabled = display === ListDisplayType.TABLE_PAGINATED;
|
const isPaginationEnabled = display === ListDisplayType.TABLE_PAGINATED;
|
||||||
|
|
||||||
const checkAlbumArtistList = useAlbumArtistList(
|
const checkAlbumArtistList = useAlbumArtistList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: Infinity,
|
||||||
|
staleTime: 60 * 1000 * 5,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
...filter,
|
...filter,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
cacheTime: Infinity,
|
});
|
||||||
staleTime: 60 * 1000 * 5,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const columnDefs: ColDef[] = useMemo(() => getColumnDefs(table.columns), [table.columns]);
|
const columnDefs: ColDef[] = useMemo(() => getColumnDefs(table.columns), [table.columns]);
|
||||||
|
|
||||||
|
@ -85,19 +84,23 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filter,
|
...filter,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const albums = api.normalize.albumArtistList(albumArtistsRes, server);
|
params.successCallback(
|
||||||
params.successCallback(albums?.items || [], albumArtistsRes?.totalRecordCount || 0);
|
albumArtistsRes?.items || [],
|
||||||
|
albumArtistsRes?.totalRecordCount || 0,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
rowCount: undefined,
|
rowCount: undefined,
|
||||||
};
|
};
|
||||||
|
@ -181,18 +184,20 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filter,
|
...filter,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
return api.normalize.albumArtistList(albumArtistsRes, server);
|
return albumArtistsRes;
|
||||||
},
|
},
|
||||||
[filter, queryClient, server],
|
[filter, queryClient, server],
|
||||||
);
|
);
|
||||||
|
@ -259,27 +264,29 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
|
||||||
{display === ListDisplayType.CARD || display === ListDisplayType.POSTER ? (
|
{display === ListDisplayType.CARD || display === ListDisplayType.POSTER ? (
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
{({ height, width }) => (
|
{({ height, width }) => (
|
||||||
<VirtualInfiniteGrid
|
<>
|
||||||
ref={gridRef}
|
<VirtualInfiniteGrid
|
||||||
cardRows={cardRows}
|
ref={gridRef}
|
||||||
display={display || ListDisplayType.CARD}
|
cardRows={cardRows}
|
||||||
fetchFn={fetch}
|
display={display || ListDisplayType.CARD}
|
||||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
fetchFn={fetch}
|
||||||
height={height}
|
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||||
initialScrollOffset={grid?.scrollOffset || 0}
|
height={height}
|
||||||
itemCount={checkAlbumArtistList?.data?.totalRecordCount || 0}
|
initialScrollOffset={grid?.scrollOffset || 0}
|
||||||
itemGap={20}
|
itemCount={checkAlbumArtistList?.data?.totalRecordCount || 0}
|
||||||
itemSize={grid?.itemsPerRow || 5}
|
itemGap={20}
|
||||||
itemType={LibraryItem.ALBUM_ARTIST}
|
itemSize={grid?.itemsPerRow || 5}
|
||||||
loading={checkAlbumArtistList.isLoading}
|
itemType={LibraryItem.ALBUM_ARTIST}
|
||||||
minimumBatchSize={40}
|
loading={checkAlbumArtistList.isLoading}
|
||||||
route={{
|
minimumBatchSize={40}
|
||||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
route={{
|
||||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||||
}}
|
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||||
width={width}
|
}}
|
||||||
onScroll={handleGridScroll}
|
width={width}
|
||||||
/>
|
onScroll={handleGridScroll}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -15,16 +15,7 @@ import {
|
||||||
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 { AlbumArtistListSort, SortOrder } from '/@/renderer/api/types';
|
import { AlbumArtistListSort, SortOrder } from '/@/renderer/api/types';
|
||||||
import {
|
import { DropdownMenu, Text, Button, Slider, MultiSelect, Switch } from '/@/renderer/components';
|
||||||
DropdownMenu,
|
|
||||||
ALBUMARTIST_TABLE_COLUMNS,
|
|
||||||
VirtualInfiniteGridRef,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Slider,
|
|
||||||
MultiSelect,
|
|
||||||
Switch,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { useMusicFolders } from '/@/renderer/features/shared';
|
import { useMusicFolders } from '/@/renderer/features/shared';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import {
|
import {
|
||||||
|
@ -36,6 +27,8 @@ import {
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { ListDisplayType, TableColumn, ServerType } from '/@/renderer/types';
|
import { ListDisplayType, TableColumn, ServerType } from '/@/renderer/types';
|
||||||
import { useAlbumArtistListContext } from '../context/album-artist-list-context';
|
import { useAlbumArtistListContext } from '../context/album-artist-list-context';
|
||||||
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
|
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const FILTERS = {
|
const FILTERS = {
|
||||||
jellyfin: [
|
jellyfin: [
|
||||||
|
@ -83,7 +76,7 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
const filter = useAlbumArtistListFilter({ key: pageKey });
|
const filter = useAlbumArtistListFilter({ key: pageKey });
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
const musicFoldersQuery = useMusicFolders();
|
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
@ -114,18 +107,20 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
return api.normalize.albumArtistList(albums, server);
|
return albums;
|
||||||
},
|
},
|
||||||
[queryClient, server],
|
[queryClient, server],
|
||||||
);
|
);
|
||||||
|
@ -148,20 +143,21 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const albumArtists = api.normalize.albumArtistList(albumArtistsRes, server);
|
|
||||||
params.successCallback(
|
params.successCallback(
|
||||||
albumArtists?.items || [],
|
albumArtistsRes?.items || [],
|
||||||
albumArtistsRes?.totalRecordCount || 0,
|
albumArtistsRes?.totalRecordCount || 0,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -355,7 +351,7 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
{musicFoldersQuery.data?.map((folder) => (
|
{musicFoldersQuery.data?.items.map((folder) => (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={`musicFolder-${folder.id}`}
|
key={`musicFolder-${folder.id}`}
|
||||||
$isActive={filter.musicFolderId === folder.id}
|
$isActive={filter.musicFolderId === folder.id}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
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 { PageHeader, SearchInput, VirtualInfiniteGridRef } from '/@/renderer/components';
|
import { PageHeader, SearchInput } from '/@/renderer/components';
|
||||||
import { LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import {
|
import {
|
||||||
|
@ -20,6 +20,7 @@ import { ListDisplayType } from '/@/renderer/types';
|
||||||
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
|
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
|
||||||
import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
|
import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
|
||||||
import { FilterBar } from '../../shared/components/filter-bar';
|
import { FilterBar } from '../../shared/components/filter-bar';
|
||||||
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
|
|
||||||
interface AlbumArtistListHeaderProps {
|
interface AlbumArtistListHeaderProps {
|
||||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||||
|
@ -51,18 +52,20 @@ export const AlbumArtistListHeader = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
return api.normalize.albumArtistList(albums, server);
|
return albums;
|
||||||
},
|
},
|
||||||
[queryClient, server],
|
[queryClient, server],
|
||||||
);
|
);
|
||||||
|
@ -85,20 +88,21 @@ export const AlbumArtistListHeader = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const albumArtists = api.normalize.albumArtistList(albumArtistsRes, server);
|
|
||||||
params.successCallback(
|
params.successCallback(
|
||||||
albumArtists?.items || [],
|
albumArtistsRes?.items || [],
|
||||||
albumArtistsRes?.totalRecordCount || 0,
|
albumArtistsRes?.totalRecordCount || 0,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,15 +9,17 @@ import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
||||||
import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header';
|
import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header';
|
||||||
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
|
||||||
const AlbumArtistDetailRoute = () => {
|
const AlbumArtistDetailRoute = () => {
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
const headerRef = useRef<HTMLDivElement>(null);
|
const headerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const { albumArtistId } = useParams() as { albumArtistId: string };
|
const { albumArtistId } = useParams() as { albumArtistId: string };
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
const detailQuery = useAlbumArtistDetail({ id: albumArtistId });
|
const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id });
|
||||||
const background = useFastAverageColor(detailQuery.data?.imageUrl, !detailQuery.isLoading);
|
const background = useFastAverageColor(detailQuery.data?.imageUrl, !detailQuery.isLoading);
|
||||||
|
|
||||||
const handlePlay = () => {
|
const handlePlay = () => {
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { AlbumArtistDetailTopSongsListContent } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-content';
|
import { AlbumArtistDetailTopSongsListContent } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-content';
|
||||||
import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header';
|
import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
||||||
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
|
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
|
import { useCurrentServer } from '../../../store/auth.store';
|
||||||
|
|
||||||
const AlbumArtistDetailTopSongsListRoute = () => {
|
const AlbumArtistDetailTopSongsListRoute = () => {
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
const { albumArtistId } = useParams() as { albumArtistId: string };
|
const { albumArtistId } = useParams() as { albumArtistId: string };
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const detailQuery = useAlbumArtistDetail({ id: albumArtistId });
|
const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id });
|
||||||
|
|
||||||
const topSongsQuery = useTopSongsList(
|
const topSongsQuery = useTopSongsList({
|
||||||
{ artist: detailQuery?.data?.name || '', artistId: albumArtistId },
|
options: { enabled: !!detailQuery?.data?.name },
|
||||||
{ enabled: !!detailQuery?.data?.name },
|
query: { artist: detailQuery?.data?.name || '', artistId: albumArtistId },
|
||||||
);
|
serverId: server?.id,
|
||||||
|
});
|
||||||
|
|
||||||
const itemCount = topSongsQuery?.data?.items?.length || 0;
|
const itemCount = topSongsQuery?.data?.items?.length || 0;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components';
|
|
||||||
import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header';
|
import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header';
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
@ -7,25 +6,29 @@ import { AlbumArtistListContent } from '/@/renderer/features/artists/components/
|
||||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
||||||
import { generatePageKey, useAlbumArtistListFilter } from '/@/renderer/store';
|
import { generatePageKey, useAlbumArtistListFilter } from '/@/renderer/store';
|
||||||
import { AlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
|
import { AlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
|
||||||
|
import { useCurrentServer } from '../../../store/auth.store';
|
||||||
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
|
|
||||||
const AlbumArtistListRoute = () => {
|
const AlbumArtistListRoute = () => {
|
||||||
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
|
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
const pageKey = generatePageKey('albumArtist', undefined);
|
const pageKey = generatePageKey('albumArtist', undefined);
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const albumArtistListFilter = useAlbumArtistListFilter({ id: undefined, key: pageKey });
|
const albumArtistListFilter = useAlbumArtistListFilter({ id: undefined, key: pageKey });
|
||||||
|
|
||||||
const itemCountCheck = useAlbumArtistList(
|
const itemCountCheck = useAlbumArtistList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
...albumArtistListFilter,
|
...albumArtistListFilter,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
cacheTime: 1000 * 60,
|
});
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemCount =
|
const itemCount =
|
||||||
itemCountCheck.data?.totalRecordCount === null
|
itemCountCheck.data?.totalRecordCount === null
|
||||||
|
|
|
@ -43,7 +43,7 @@ import {
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { useDeletePlaylist } from '/@/renderer/features/playlists';
|
import { useDeletePlaylist } from '/@/renderer/features/playlists';
|
||||||
import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation';
|
import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation';
|
||||||
import { useCreateFavorite, useDeleteFavorite, useUpdateRating } from '/@/renderer/features/shared';
|
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { useAuthStore, useCurrentServer, useQueueControls } from '/@/renderer/store';
|
import { useAuthStore, useCurrentServer, useQueueControls } from '/@/renderer/store';
|
||||||
import { usePlayerType } from '/@/renderer/store/settings.store';
|
import { usePlayerType } from '/@/renderer/store/settings.store';
|
||||||
import { Play, PlaybackType } from '/@/renderer/types';
|
import { Play, PlaybackType } from '/@/renderer/types';
|
||||||
|
@ -190,7 +190,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
[ctx.data, ctx.type, handlePlayQueueAdd],
|
[ctx.data, ctx.type, handlePlayQueueAdd],
|
||||||
);
|
);
|
||||||
|
|
||||||
const deletePlaylistMutation = useDeletePlaylist();
|
const deletePlaylistMutation = useDeletePlaylist({});
|
||||||
|
|
||||||
const handleDeletePlaylist = useCallback(() => {
|
const handleDeletePlaylist = useCallback(() => {
|
||||||
for (const item of ctx.data) {
|
for (const item of ctx.data) {
|
||||||
|
@ -236,8 +236,8 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
});
|
});
|
||||||
}, [ctx.data, handleDeletePlaylist]);
|
}, [ctx.data, handleDeletePlaylist]);
|
||||||
|
|
||||||
const createFavoriteMutation = useCreateFavorite();
|
const createFavoriteMutation = useCreateFavorite({});
|
||||||
const deleteFavoriteMutation = useDeleteFavorite();
|
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||||
const handleAddToFavorites = useCallback(() => {
|
const handleAddToFavorites = useCallback(() => {
|
||||||
if (!ctx.dataNodes && !ctx.data) return;
|
if (!ctx.dataNodes && !ctx.data) return;
|
||||||
|
|
||||||
|
@ -414,7 +414,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
serverType,
|
serverType,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const updateRatingMutation = useUpdateRating();
|
const updateRatingMutation = useSetRating({});
|
||||||
|
|
||||||
const handleUpdateRating = useCallback(
|
const handleUpdateRating = useCallback(
|
||||||
(rating: number) => {
|
(rating: number) => {
|
||||||
|
@ -450,11 +450,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
|
|
||||||
updateRatingMutation.mutate(
|
updateRatingMutation.mutate(
|
||||||
{
|
{
|
||||||
_serverId: serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: items,
|
item: items,
|
||||||
rating,
|
rating,
|
||||||
},
|
},
|
||||||
|
serverId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
|
@ -23,75 +23,80 @@ const HomeRoute = () => {
|
||||||
recentlyPlayed: 0,
|
recentlyPlayed: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const feature = useAlbumList(
|
const feature = useAlbumList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: 20,
|
limit: 20,
|
||||||
sortBy: AlbumListSort.RANDOM,
|
sortBy: AlbumListSort.RANDOM,
|
||||||
sortOrder: SortOrder.DESC,
|
sortOrder: SortOrder.DESC,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
cacheTime: 1000 * 60,
|
});
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const featureItemsWithImage = useMemo(() => {
|
const featureItemsWithImage = useMemo(() => {
|
||||||
return feature.data?.items?.filter((item) => item.imageUrl) ?? [];
|
return feature.data?.items?.filter((item) => item.imageUrl) ?? [];
|
||||||
}, [feature.data?.items]);
|
}, [feature.data?.items]);
|
||||||
|
|
||||||
const random = useAlbumList(
|
const random = useAlbumList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
keepPreviousData: true,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
sortBy: AlbumListSort.RANDOM,
|
sortBy: AlbumListSort.RANDOM,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
startIndex: pagination.random * itemsPerPage,
|
startIndex: pagination.random * itemsPerPage,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
cacheTime: 1000 * 60,
|
});
|
||||||
keepPreviousData: true,
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const recentlyPlayed = useRecentlyPlayed(
|
const recentlyPlayed = useRecentlyPlayed({
|
||||||
{
|
options: {
|
||||||
|
keepPreviousData: true,
|
||||||
|
staleTime: 0,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||||
sortOrder: SortOrder.DESC,
|
sortOrder: SortOrder.DESC,
|
||||||
startIndex: pagination.recentlyPlayed * itemsPerPage,
|
startIndex: pagination.recentlyPlayed * itemsPerPage,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
keepPreviousData: true,
|
});
|
||||||
staleTime: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const recentlyAdded = useAlbumList(
|
const recentlyAdded = useAlbumList({
|
||||||
{
|
options: {
|
||||||
|
keepPreviousData: true,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
sortBy: AlbumListSort.RECENTLY_ADDED,
|
sortBy: AlbumListSort.RECENTLY_ADDED,
|
||||||
sortOrder: SortOrder.DESC,
|
sortOrder: SortOrder.DESC,
|
||||||
startIndex: pagination.recentlyAdded * itemsPerPage,
|
startIndex: pagination.recentlyAdded * itemsPerPage,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
keepPreviousData: true,
|
});
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const mostPlayed = useAlbumList(
|
const mostPlayed = useAlbumList({
|
||||||
{
|
options: {
|
||||||
|
keepPreviousData: true,
|
||||||
|
staleTime: 1000 * 60 * 60,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
sortBy: AlbumListSort.PLAY_COUNT,
|
sortBy: AlbumListSort.PLAY_COUNT,
|
||||||
sortOrder: SortOrder.DESC,
|
sortOrder: SortOrder.DESC,
|
||||||
startIndex: pagination.mostPlayed * itemsPerPage,
|
startIndex: pagination.mostPlayed * itemsPerPage,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
keepPreviousData: true,
|
});
|
||||||
staleTime: 1000 * 60 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNextPage = useCallback(
|
const handleNextPage = useCallback(
|
||||||
(key: 'mostPlayed' | 'random' | 'recentlyAdded' | 'recentlyPlayed') => {
|
(key: 'mostPlayed' | 'random' | 'recentlyAdded' | 'recentlyPlayed') => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { MutableRefObject } from 'react';
|
import type { MutableRefObject } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Group } from '@mantine/core';
|
import { Group } from '@mantine/core';
|
||||||
import { Button, Popover, TableConfigDropdown } from '/@/renderer/components';
|
import { Button, Popover } from '/@/renderer/components';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownLine,
|
||||||
|
@ -16,6 +16,7 @@ import { usePlayerControls, useQueueControls } from '/@/renderer/store';
|
||||||
import { PlaybackType, TableType } from '/@/renderer/types';
|
import { PlaybackType, TableType } from '/@/renderer/types';
|
||||||
import { usePlayerType } from '/@/renderer/store/settings.store';
|
import { usePlayerType } from '/@/renderer/store/settings.store';
|
||||||
import { useSetCurrentTime } from '../../../store/player.store';
|
import { useSetCurrentTime } from '../../../store/player.store';
|
||||||
|
import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
} from '@ag-grid-community/core';
|
} from '@ag-grid-community/core';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import '@ag-grid-community/styles/ag-theme-alpine.css';
|
import '@ag-grid-community/styles/ag-theme-alpine.css';
|
||||||
import { VirtualGridAutoSizerContainer, getColumnDefs } from '/@/renderer/components';
|
|
||||||
import {
|
import {
|
||||||
useAppStoreActions,
|
useAppStoreActions,
|
||||||
useCurrentSong,
|
useCurrentSong,
|
||||||
|
@ -27,12 +26,13 @@ import { useMergedRef } from '@mantine/hooks';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { VirtualTable } from '/@/renderer/components/virtual-table';
|
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||||
import { ErrorFallback } from '/@/renderer/features/action-required';
|
import { ErrorFallback } from '/@/renderer/features/action-required';
|
||||||
import { PlaybackType, TableType } from '/@/renderer/types';
|
import { PlaybackType, TableType } from '/@/renderer/types';
|
||||||
import { LibraryItem, QueueSong } from '/@/renderer/api/types';
|
import { LibraryItem, QueueSong } from '/@/renderer/api/types';
|
||||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||||
import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
|
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
const utils = isElectron() ? window.electron.utils : null;
|
const utils = isElectron() ? window.electron.utils : null;
|
||||||
|
|
|
@ -4,8 +4,9 @@ import { NowPlayingHeader } from '/@/renderer/features/now-playing/components/no
|
||||||
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
||||||
import type { Song } from '/@/renderer/api/types';
|
import type { Song } from '/@/renderer/api/types';
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { Paper, VirtualGridContainer } from '/@/renderer/components';
|
import { Paper } from '/@/renderer/components';
|
||||||
import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls';
|
import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls';
|
||||||
|
import { VirtualGridContainer } from '/@/renderer/components/virtual-grid';
|
||||||
|
|
||||||
const NowPlayingRoute = () => {
|
const NowPlayingRoute = () => {
|
||||||
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
|
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Variants, motion } from 'framer-motion';
|
||||||
import { RiArrowDownSLine, RiSettings3Line } from 'react-icons/ri';
|
import { RiArrowDownSLine, RiSettings3Line } from 'react-icons/ri';
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button, Option, Popover, Switch, TableConfigDropdown } from '/@/renderer/components';
|
import { Button, Option, Popover, Switch } from '/@/renderer/components';
|
||||||
import {
|
import {
|
||||||
useCurrentSong,
|
useCurrentSong,
|
||||||
useFullScreenPlayerStore,
|
useFullScreenPlayerStore,
|
||||||
|
@ -14,6 +14,7 @@ import {
|
||||||
import { useFastAverageColor } from '../../../hooks/use-fast-average-color';
|
import { useFastAverageColor } from '../../../hooks/use-fast-average-color';
|
||||||
import { FullScreenPlayerImage } from '/@/renderer/features/player/components/full-screen-player-image';
|
import { FullScreenPlayerImage } from '/@/renderer/features/player/components/full-screen-player-image';
|
||||||
import { FullScreenPlayerQueue } from '/@/renderer/features/player/components/full-screen-player-queue';
|
import { FullScreenPlayerQueue } from '/@/renderer/features/player/components/full-screen-player-queue';
|
||||||
|
import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const Container = styled(motion.div)`
|
const Container = styled(motion.div)`
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
import { useRightControls } from '../hooks/use-right-controls';
|
import { useRightControls } from '../hooks/use-right-controls';
|
||||||
import { PlayerButton } from './player-button';
|
import { PlayerButton } from './player-button';
|
||||||
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
||||||
import { useCreateFavorite, useDeleteFavorite, useUpdateRating } from '/@/renderer/features/shared';
|
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { Rating } from '/@/renderer/components';
|
import { Rating } from '/@/renderer/components';
|
||||||
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
|
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
|
||||||
|
|
||||||
|
@ -32,9 +32,9 @@ export const RightControls = () => {
|
||||||
const { rightExpanded: isQueueExpanded } = useSidebarStore();
|
const { rightExpanded: isQueueExpanded } = useSidebarStore();
|
||||||
const { handleVolumeSlider, handleVolumeWheel, handleMute } = useRightControls();
|
const { handleVolumeSlider, handleVolumeWheel, handleMute } = useRightControls();
|
||||||
|
|
||||||
const updateRatingMutation = useUpdateRating();
|
const updateRatingMutation = useSetRating({});
|
||||||
const addToFavoritesMutation = useCreateFavorite();
|
const addToFavoritesMutation = useCreateFavorite({});
|
||||||
const removeFromFavoritesMutation = useDeleteFavorite();
|
const removeFromFavoritesMutation = useDeleteFavorite({});
|
||||||
|
|
||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!currentSong) return;
|
if (!currentSong) return;
|
||||||
|
@ -51,11 +51,11 @@ export const RightControls = () => {
|
||||||
if (!currentSong) return;
|
if (!currentSong) return;
|
||||||
|
|
||||||
updateRatingMutation.mutate({
|
updateRatingMutation.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [currentSong],
|
item: [currentSong],
|
||||||
rating,
|
rating,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,11 +63,11 @@ export const RightControls = () => {
|
||||||
if (!currentSong || !rating) return;
|
if (!currentSong || !rating) return;
|
||||||
|
|
||||||
updateRatingMutation.mutate({
|
updateRatingMutation.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
item: [currentSong],
|
item: [currentSong],
|
||||||
rating: 0,
|
rating: 0,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { api } from '/@/renderer/api/index';
|
import { api } from '/@/renderer/api/index';
|
||||||
import { jfNormalize } from '/@/renderer/api/jellyfin.api';
|
|
||||||
import { JFSong } from '/@/renderer/api/jellyfin.types';
|
|
||||||
import { ndNormalize } from '/@/renderer/api/navidrome.api';
|
|
||||||
import { NDSong } from '/@/renderer/api/navidrome.types';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import {
|
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
|
||||||
useAuthStore,
|
|
||||||
useCurrentServer,
|
|
||||||
usePlayerControls,
|
|
||||||
usePlayerStore,
|
|
||||||
} from '/@/renderer/store';
|
|
||||||
import { usePlayerType } from '/@/renderer/store/settings.store';
|
import { usePlayerType } from '/@/renderer/store/settings.store';
|
||||||
import { PlayQueueAddOptions, Play, PlaybackType } from '/@/renderer/types';
|
import { PlayQueueAddOptions, Play, PlaybackType } from '/@/renderer/types';
|
||||||
import { toast } from '/@/renderer/components/toast';
|
import { toast } from '/@/renderer/components/toast/index';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { nanoid } from 'nanoid/non-secure';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import { LibraryItem, SongListSort, SortOrder } from '/@/renderer/api/types';
|
import { LibraryItem, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||||
|
@ -25,7 +16,6 @@ const mpris = isElectron() && utils?.isLinux() ? window.electron.mpris : null;
|
||||||
export const useHandlePlayQueueAdd = () => {
|
export const useHandlePlayQueueAdd = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const playerType = usePlayerType();
|
const playerType = usePlayerType();
|
||||||
const deviceId = useAuthStore.getState().deviceId;
|
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const { play } = usePlayerControls();
|
const { play } = usePlayerControls();
|
||||||
|
|
||||||
|
@ -114,9 +104,11 @@ export const useHandlePlayQueueAdd = () => {
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getPlaylistSongList({
|
api.controller.getPlaylistSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
cacheTime: 1000 * 60,
|
cacheTime: 1000 * 60,
|
||||||
|
@ -128,9 +120,11 @@ export const useHandlePlayQueueAdd = () => {
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
cacheTime: 1000 * 60,
|
cacheTime: 1000 * 60,
|
||||||
|
@ -147,20 +141,7 @@ export const useHandlePlayQueueAdd = () => {
|
||||||
|
|
||||||
if (!songsList) return toast.warn({ message: 'No songs found' });
|
if (!songsList) return toast.warn({ message: 'No songs found' });
|
||||||
|
|
||||||
switch (server?.type) {
|
songs = songsList.items?.map((song) => ({ ...song, uniqueId: nanoid() }));
|
||||||
case 'jellyfin':
|
|
||||||
songs = songsList.items?.map((song) =>
|
|
||||||
jfNormalize.song(song as JFSong, server, deviceId),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'navidrome':
|
|
||||||
songs = songsList.items?.map((song) =>
|
|
||||||
ndNormalize.song(song as NDSong, server, deviceId),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'subsonic':
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (options.byData) {
|
} else if (options.byData) {
|
||||||
songs = options.byData.map((song) => ({ ...song, uniqueId: nanoid() }));
|
songs = options.byData.map((song) => ({ ...song, uniqueId: nanoid() }));
|
||||||
}
|
}
|
||||||
|
@ -207,7 +188,7 @@ export const useHandlePlayQueueAdd = () => {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[deviceId, play, playerType, queryClient, server],
|
[play, playerType, queryClient, server],
|
||||||
);
|
);
|
||||||
|
|
||||||
return handlePlayQueueAdd;
|
return handlePlayQueueAdd;
|
||||||
|
|
|
@ -67,13 +67,13 @@ export const useScrobble = () => {
|
||||||
currentSong?.serverType === ServerType.JELLYFIN ? currentTime * 1e7 : undefined;
|
currentSong?.serverType === ServerType.JELLYFIN ? currentTime * 1e7 : undefined;
|
||||||
|
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
event: 'timeupdate',
|
event: 'timeupdate',
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
position,
|
position,
|
||||||
submission: false,
|
submission: false,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[isScrobbleEnabled, sendScrobble],
|
[isScrobbleEnabled, sendScrobble],
|
||||||
|
@ -110,12 +110,12 @@ export const useScrobble = () => {
|
||||||
previousSong?.serverType === ServerType.JELLYFIN ? previousSongTime * 1e7 : undefined;
|
previousSong?.serverType === ServerType.JELLYFIN ? previousSongTime * 1e7 : undefined;
|
||||||
|
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: previousSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
id: previousSong.id,
|
id: previousSong.id,
|
||||||
position,
|
position,
|
||||||
submission: true,
|
submission: true,
|
||||||
},
|
},
|
||||||
|
serverId: previousSong?.serverId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,13 +130,13 @@ export const useScrobble = () => {
|
||||||
// Send start scrobble when song changes and the new song is playing
|
// Send start scrobble when song changes and the new song is playing
|
||||||
if (status === PlayerStatus.PLAYING && currentSong?.id) {
|
if (status === PlayerStatus.PLAYING && currentSong?.id) {
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
event: 'start',
|
event: 'start',
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
position: 0,
|
position: 0,
|
||||||
submission: false,
|
submission: false,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentSong?.serverType === ServerType.JELLYFIN) {
|
if (currentSong?.serverType === ServerType.JELLYFIN) {
|
||||||
|
@ -175,13 +175,13 @@ export const useScrobble = () => {
|
||||||
// Whenever the player is restarted, send a 'start' scrobble
|
// Whenever the player is restarted, send a 'start' scrobble
|
||||||
if (status === PlayerStatus.PLAYING) {
|
if (status === PlayerStatus.PLAYING) {
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
event: 'unpause',
|
event: 'unpause',
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
position,
|
position,
|
||||||
submission: false,
|
submission: false,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentSong?.serverType === ServerType.JELLYFIN) {
|
if (currentSong?.serverType === ServerType.JELLYFIN) {
|
||||||
|
@ -194,13 +194,13 @@ export const useScrobble = () => {
|
||||||
// Jellyfin is the only one that needs to send a 'pause' event to the server
|
// Jellyfin is the only one that needs to send a 'pause' event to the server
|
||||||
} else if (currentSong?.serverType === ServerType.JELLYFIN) {
|
} else if (currentSong?.serverType === ServerType.JELLYFIN) {
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
event: 'pause',
|
event: 'pause',
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
position,
|
position,
|
||||||
submission: false,
|
submission: false,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (progressIntervalId.current) {
|
if (progressIntervalId.current) {
|
||||||
|
@ -217,11 +217,11 @@ export const useScrobble = () => {
|
||||||
|
|
||||||
if (!isCurrentSongScrobbled && shouldSubmitScrobble) {
|
if (!isCurrentSongScrobbled && shouldSubmitScrobble) {
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
submission: true,
|
submission: true,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsCurrentSongScrobbled(true);
|
setIsCurrentSongScrobbled(true);
|
||||||
|
@ -261,24 +261,24 @@ export const useScrobble = () => {
|
||||||
|
|
||||||
if (!isCurrentSongScrobbled && shouldSubmitScrobble) {
|
if (!isCurrentSongScrobbled && shouldSubmitScrobble) {
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
position,
|
position,
|
||||||
submission: true,
|
submission: true,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSong?.serverType === ServerType.JELLYFIN) {
|
if (currentSong?.serverType === ServerType.JELLYFIN) {
|
||||||
sendScrobble.mutate({
|
sendScrobble.mutate({
|
||||||
_serverId: currentSong?.serverId,
|
|
||||||
query: {
|
query: {
|
||||||
event: 'start',
|
event: 'start',
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
position: 0,
|
position: 0,
|
||||||
submission: false,
|
submission: false,
|
||||||
},
|
},
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,15 +23,20 @@ export const AddToPlaylistContextModal = ({
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const addToPlaylistMutation = useAddToPlaylist();
|
const addToPlaylistMutation = useAddToPlaylist({});
|
||||||
|
|
||||||
const playlistList = usePlaylistList({
|
const playlistList = usePlaylistList({
|
||||||
ndParams: {
|
query: {
|
||||||
smart: false,
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
smart: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sortBy: PlaylistListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
sortBy: PlaylistListSort.NAME,
|
serverId: server?.id,
|
||||||
sortOrder: SortOrder.ASC,
|
|
||||||
startIndex: 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const playlistSelect = useMemo(() => {
|
const playlistSelect = useMemo(() => {
|
||||||
|
@ -60,11 +65,12 @@ export const AddToPlaylistContextModal = ({
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||||
|
|
||||||
const songsRes = await queryClient.fetchQuery(queryKey, ({ signal }) =>
|
const songsRes = await queryClient.fetchQuery(queryKey, ({ signal }) => {
|
||||||
api.controller.getSongList({ query, server, signal }),
|
if (!server) throw new Error('No server');
|
||||||
);
|
return api.controller.getSongList({ apiClientProps: { server, signal }, query });
|
||||||
|
});
|
||||||
|
|
||||||
return api.normalize.songList(songsRes, server);
|
return songsRes;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSongsByArtist = async (artistId: string) => {
|
const getSongsByArtist = async (artistId: string) => {
|
||||||
|
@ -77,11 +83,12 @@ export const AddToPlaylistContextModal = ({
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||||
|
|
||||||
const songsRes = await queryClient.fetchQuery(queryKey, ({ signal }) =>
|
const songsRes = await queryClient.fetchQuery(queryKey, ({ signal }) => {
|
||||||
api.controller.getSongList({ query, server, signal }),
|
if (!server) throw new Error('No server');
|
||||||
);
|
return api.controller.getSongList({ apiClientProps: { server, signal }, query });
|
||||||
|
});
|
||||||
|
|
||||||
return api.normalize.songList(songsRes, server);
|
return songsRes;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSubmitDisabled = form.values.playlistId.length === 0 || addToPlaylistMutation.isLoading;
|
const isSubmitDisabled = form.values.playlistId.length === 0 || addToPlaylistMutation.isLoading;
|
||||||
|
@ -118,17 +125,18 @@ export const AddToPlaylistContextModal = ({
|
||||||
|
|
||||||
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId, query);
|
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId, query);
|
||||||
|
|
||||||
const playlistSongsRes = await queryClient.fetchQuery(queryKey, ({ signal }) =>
|
const playlistSongsRes = await queryClient.fetchQuery(queryKey, ({ signal }) => {
|
||||||
api.controller.getPlaylistSongList({
|
if (!server) throw new Error('No server');
|
||||||
|
return api.controller.getPlaylistSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: { id: playlistId, startIndex: 0 },
|
query: { id: playlistId, startIndex: 0 },
|
||||||
server,
|
});
|
||||||
signal,
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const playlistSongIds = api.normalize
|
const playlistSongIds = playlistSongsRes?.items?.map((song) => song.id);
|
||||||
.songList(playlistSongsRes, server)
|
|
||||||
.items?.map((song) => song.id);
|
|
||||||
|
|
||||||
for (const songId of allSongIds) {
|
for (const songId of allSongIds) {
|
||||||
if (!playlistSongIds?.includes(songId)) {
|
if (!playlistSongIds?.includes(songId)) {
|
||||||
|
@ -138,10 +146,12 @@ export const AddToPlaylistContextModal = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.skipDuplicates ? uniqueSongIds.length > 0 : allSongIds.length > 0) {
|
if (values.skipDuplicates ? uniqueSongIds.length > 0 : allSongIds.length > 0) {
|
||||||
|
if (!server) return null;
|
||||||
addToPlaylistMutation.mutate(
|
addToPlaylistMutation.mutate(
|
||||||
{
|
{
|
||||||
body: { songId: values.skipDuplicates ? uniqueSongIds : allSongIds },
|
body: { songId: values.skipDuplicates ? uniqueSongIds : allSongIds },
|
||||||
query: { id: playlistId },
|
query: { id: playlistId },
|
||||||
|
serverId: server?.id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
|
|
|
@ -16,47 +16,54 @@ interface CreatePlaylistFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
||||||
const mutation = useCreatePlaylist();
|
const mutation = useCreatePlaylist({});
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const queryBuilderRef = useRef<PlaylistQueryBuilderRef>(null);
|
const queryBuilderRef = useRef<PlaylistQueryBuilderRef>(null);
|
||||||
|
|
||||||
const form = useForm<CreatePlaylistBody>({
|
const form = useForm<CreatePlaylistBody>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
public: false,
|
||||||
|
rules: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
comment: '',
|
comment: '',
|
||||||
name: '',
|
name: '',
|
||||||
ndParams: {
|
|
||||||
public: false,
|
|
||||||
rules: undefined,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [isSmartPlaylist, setIsSmartPlaylist] = useState(false);
|
const [isSmartPlaylist, setIsSmartPlaylist] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = form.onSubmit((values) => {
|
const handleSubmit = form.onSubmit((values) => {
|
||||||
if (isSmartPlaylist) {
|
if (isSmartPlaylist) {
|
||||||
values.ndParams = {
|
values._custom!.navidrome = {
|
||||||
...values.ndParams,
|
...values._custom?.navidrome,
|
||||||
rules: queryBuilderRef.current?.getFilters(),
|
rules: queryBuilderRef.current?.getFilters(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const smartPlaylist = queryBuilderRef.current?.getFilters();
|
const smartPlaylist = queryBuilderRef.current?.getFilters();
|
||||||
|
|
||||||
|
if (!server) return;
|
||||||
|
|
||||||
mutation.mutate(
|
mutation.mutate(
|
||||||
{
|
{
|
||||||
body: {
|
body: {
|
||||||
...values,
|
...values,
|
||||||
ndParams: {
|
_custom: {
|
||||||
...values.ndParams,
|
navidrome: {
|
||||||
rules:
|
...values._custom?.navidrome,
|
||||||
isSmartPlaylist && smartPlaylist?.filters
|
rules:
|
||||||
? {
|
isSmartPlaylist && smartPlaylist?.filters
|
||||||
...convertQueryGroupToNDQuery(smartPlaylist.filters),
|
? {
|
||||||
...smartPlaylist.extraFilters,
|
...convertQueryGroupToNDQuery(smartPlaylist.filters),
|
||||||
}
|
...smartPlaylist.extraFilters,
|
||||||
: undefined,
|
}
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
serverId: server.id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
|
|
|
@ -17,16 +17,12 @@ import {
|
||||||
UserListQuery,
|
UserListQuery,
|
||||||
UserListSort,
|
UserListSort,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
|
import { Button, ConfirmModal, DropdownMenu, MotionGroup, toast } from '/@/renderer/components';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
ConfirmModal,
|
|
||||||
DropdownMenu,
|
|
||||||
getColumnDefs,
|
getColumnDefs,
|
||||||
MotionGroup,
|
|
||||||
toast,
|
|
||||||
useFixedTableHeader,
|
useFixedTableHeader,
|
||||||
VirtualTable,
|
VirtualTable,
|
||||||
} from '/@/renderer/components';
|
} from '/@/renderer/components/virtual-table';
|
||||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||||
import {
|
import {
|
||||||
PLAYLIST_SONG_CONTEXT_MENU_ITEMS,
|
PLAYLIST_SONG_CONTEXT_MENU_ITEMS,
|
||||||
|
@ -68,19 +64,23 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const page = useSongListStore();
|
const page = useSongListStore();
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const server = useCurrentServer();
|
||||||
|
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const server = useCurrentServer();
|
|
||||||
|
|
||||||
const playlistSongsQueryInfinite = usePlaylistSongListInfinite(
|
const playlistSongsQueryInfinite = usePlaylistSongListInfinite({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 0,
|
||||||
|
keepPreviousData: false,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
id: playlistId,
|
id: playlistId,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
{ cacheTime: 0, keepPreviousData: false },
|
serverId: server?.id,
|
||||||
);
|
});
|
||||||
|
|
||||||
const handleLoadMore = () => {
|
const handleLoadMore = () => {
|
||||||
playlistSongsQueryInfinite.fetchNextPage();
|
playlistSongsQueryInfinite.fetchNextPage();
|
||||||
|
@ -105,17 +105,17 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
});
|
});
|
||||||
|
|
||||||
const playlistSongData = useMemo(
|
const playlistSongData = useMemo(
|
||||||
() => playlistSongsQueryInfinite.data?.pages.flatMap((p) => p.items),
|
() => playlistSongsQueryInfinite.data?.pages.flatMap((p) => p?.items),
|
||||||
[playlistSongsQueryInfinite.data?.pages],
|
[playlistSongsQueryInfinite.data?.pages],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { intersectRef, tableContainerRef } = useFixedTableHeader();
|
const { intersectRef, tableContainerRef } = useFixedTableHeader();
|
||||||
|
|
||||||
const deletePlaylistMutation = useDeletePlaylist();
|
const deletePlaylistMutation = useDeletePlaylist({});
|
||||||
|
|
||||||
const handleDeletePlaylist = () => {
|
const handleDeletePlaylist = () => {
|
||||||
deletePlaylistMutation.mutate(
|
deletePlaylistMutation.mutate(
|
||||||
{ query: { id: playlistId } },
|
{ query: { id: playlistId }, serverId: server?.id },
|
||||||
{
|
{
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
toast.error({
|
toast.error({
|
||||||
|
@ -165,30 +165,33 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!server) return;
|
||||||
|
|
||||||
const users = await queryClient.fetchQuery({
|
const users = await queryClient.fetchQuery({
|
||||||
queryFn: ({ signal }) => api.controller.getUserList({ query, server, signal }),
|
queryFn: ({ signal }) =>
|
||||||
|
api.controller.getUserList({ apiClientProps: { server, signal }, query }),
|
||||||
queryKey: queryKeys.users.list(server?.id || '', query),
|
queryKey: queryKeys.users.list(server?.id || '', query),
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizedUsers = api.normalize.userList(users, server);
|
|
||||||
|
|
||||||
openModal({
|
openModal({
|
||||||
children: (
|
children: (
|
||||||
<UpdatePlaylistForm
|
<UpdatePlaylistForm
|
||||||
body={{
|
body={{
|
||||||
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
owner: detailQuery?.data?.owner || undefined,
|
||||||
|
ownerId: detailQuery?.data?.ownerId || undefined,
|
||||||
|
public: detailQuery?.data?.public || false,
|
||||||
|
rules: detailQuery?.data?.rules || undefined,
|
||||||
|
sync: detailQuery?.data?.sync || undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
comment: detailQuery?.data?.description || undefined,
|
comment: detailQuery?.data?.description || undefined,
|
||||||
genres: detailQuery?.data?.genres,
|
genres: detailQuery?.data?.genres,
|
||||||
name: detailQuery?.data?.name,
|
name: detailQuery?.data?.name,
|
||||||
ndParams: {
|
|
||||||
owner: detailQuery?.data?.owner || undefined,
|
|
||||||
ownerId: detailQuery?.data?.ownerId || undefined,
|
|
||||||
public: detailQuery?.data?.public || false,
|
|
||||||
rules: detailQuery?.data?.rules || undefined,
|
|
||||||
sync: detailQuery?.data?.sync || undefined,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
query={{ id: playlistId }}
|
query={{ id: playlistId }}
|
||||||
users={normalizedUsers.items}
|
users={users?.items}
|
||||||
onCancel={closeAllModals}
|
onCancel={closeAllModals}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { LibraryHeader } from '/@/renderer/features/shared';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
|
import { useCurrentServer } from '../../../store/auth.store';
|
||||||
|
|
||||||
interface PlaylistDetailHeaderProps {
|
interface PlaylistDetailHeaderProps {
|
||||||
background: string;
|
background: string;
|
||||||
|
@ -20,7 +21,8 @@ export const PlaylistDetailHeader = forwardRef(
|
||||||
ref: Ref<HTMLDivElement>,
|
ref: Ref<HTMLDivElement>,
|
||||||
) => {
|
) => {
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const server = useCurrentServer();
|
||||||
|
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||||
|
|
||||||
const metadataItems = [
|
const metadataItems = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,12 +8,6 @@ import type {
|
||||||
RowDoubleClickedEvent,
|
RowDoubleClickedEvent,
|
||||||
} from '@ag-grid-community/core';
|
} from '@ag-grid-community/core';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import {
|
|
||||||
getColumnDefs,
|
|
||||||
TablePagination,
|
|
||||||
VirtualGridAutoSizerContainer,
|
|
||||||
VirtualTable,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import {
|
import {
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
usePlaylistDetailStore,
|
usePlaylistDetailStore,
|
||||||
|
@ -44,6 +38,8 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
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 { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||||
|
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||||
|
import { getColumnDefs, VirtualTable, TablePagination } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
interface PlaylistDetailContentProps {
|
interface PlaylistDetailContentProps {
|
||||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||||
|
@ -61,7 +57,7 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
|
||||||
};
|
};
|
||||||
}, [page?.table.id, playlistId]);
|
}, [page?.table.id, playlistId]);
|
||||||
|
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||||
|
|
||||||
const p = usePlaylistDetailTablePagination(playlistId);
|
const p = usePlaylistDetailTablePagination(playlistId);
|
||||||
const pagination = {
|
const pagination = {
|
||||||
|
@ -80,9 +76,12 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
|
||||||
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
|
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
|
||||||
|
|
||||||
const checkPlaylistList = usePlaylistSongList({
|
const checkPlaylistList = usePlaylistSongList({
|
||||||
id: playlistId,
|
query: {
|
||||||
limit: 1,
|
id: playlistId,
|
||||||
startIndex: 0,
|
limit: 1,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const columnDefs: ColDef[] = useMemo(
|
const columnDefs: ColDef[] = useMemo(
|
||||||
|
@ -104,24 +103,27 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!server) return;
|
||||||
|
|
||||||
const songsRes = await queryClient.fetchQuery(
|
const songsRes = await queryClient.fetchQuery(
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getPlaylistSongList({
|
api.controller.getPlaylistSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
id: playlistId,
|
id: playlistId,
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const songs = api.normalize.songList(songsRes, server);
|
params.successCallback(songsRes?.items || [], songsRes?.totalRecordCount || 0);
|
||||||
params.successCallback(songs?.items || [], songsRes?.totalRecordCount || 0);
|
|
||||||
},
|
},
|
||||||
rowCount: undefined,
|
rowCount: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,15 +18,7 @@ import {
|
||||||
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 { LibraryItem, PlaylistSongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
|
import { LibraryItem, PlaylistSongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||||
import {
|
import { DropdownMenu, Button, Slider, MultiSelect, Switch, Text } from '/@/renderer/components';
|
||||||
DropdownMenu,
|
|
||||||
SONG_TABLE_COLUMNS,
|
|
||||||
Button,
|
|
||||||
Slider,
|
|
||||||
MultiSelect,
|
|
||||||
Switch,
|
|
||||||
Text,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import {
|
import {
|
||||||
|
@ -41,6 +33,7 @@ import {
|
||||||
import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/types';
|
import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/types';
|
||||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const FILTERS = {
|
const FILTERS = {
|
||||||
jellyfin: [
|
jellyfin: [
|
||||||
|
@ -100,7 +93,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||||
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
|
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
|
||||||
};
|
};
|
||||||
|
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||||
const isSmartPlaylist = detailQuery.data?.rules;
|
const isSmartPlaylist = detailQuery.data?.rules;
|
||||||
|
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
@ -139,14 +132,16 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getPlaylistSongList({
|
api.controller.getPlaylistSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
id: playlistId,
|
id: playlistId,
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { PlaylistDetailSongListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header-filters';
|
import { PlaylistDetailSongListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header-filters';
|
||||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||||
import { LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { Play } from '/@/renderer/types';
|
import { Play } from '/@/renderer/types';
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ export const PlaylistDetailSongListHeader = ({
|
||||||
handleToggleShowQueryBuilder,
|
handleToggleShowQueryBuilder,
|
||||||
}: PlaylistDetailHeaderProps) => {
|
}: PlaylistDetailHeaderProps) => {
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const server = useCurrentServer();
|
||||||
|
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
|
||||||
const handlePlay = async (playType: Play) => {
|
const handlePlay = async (playType: Play) => {
|
||||||
|
|
|
@ -12,12 +12,6 @@ import { Stack } from '@mantine/core';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } 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 {
|
|
||||||
getColumnDefs,
|
|
||||||
TablePagination,
|
|
||||||
VirtualGridAutoSizerContainer,
|
|
||||||
VirtualTable,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import {
|
import {
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
usePlaylistListStore,
|
usePlaylistListStore,
|
||||||
|
@ -33,6 +27,8 @@ import { PLAYLIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/c
|
||||||
import { generatePath, useNavigate } from 'react-router';
|
import { generatePath, useNavigate } from 'react-router';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
|
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||||
|
import { getColumnDefs, VirtualTable, TablePagination } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
interface PlaylistListContentProps {
|
interface PlaylistListContentProps {
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
|
@ -81,13 +77,15 @@ export const PlaylistListContent = ({ tableRef, itemCount }: PlaylistListContent
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getPlaylistList({
|
api.controller.getPlaylistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...page.filter,
|
...page.filter,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,15 +7,7 @@ import { RiSortAsc, RiSortDesc, RiMoreFill, RiRefreshLine, RiSettings3Fill } fro
|
||||||
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 { SortOrder, PlaylistListSort } from '/@/renderer/api/types';
|
import { SortOrder, PlaylistListSort } from '/@/renderer/api/types';
|
||||||
import {
|
import { DropdownMenu, Text, Button, Slider, MultiSelect, Switch } from '/@/renderer/components';
|
||||||
DropdownMenu,
|
|
||||||
PLAYLIST_TABLE_COLUMNS,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Slider,
|
|
||||||
MultiSelect,
|
|
||||||
Switch,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import {
|
import {
|
||||||
PlaylistListFilter,
|
PlaylistListFilter,
|
||||||
|
@ -27,6 +19,7 @@ import {
|
||||||
useSetPlaylistTablePagination,
|
useSetPlaylistTablePagination,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { ListDisplayType, TableColumn } from '/@/renderer/types';
|
import { ListDisplayType, TableColumn } from '/@/renderer/types';
|
||||||
|
import { PLAYLIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const FILTERS = {
|
const FILTERS = {
|
||||||
jellyfin: [
|
jellyfin: [
|
||||||
|
@ -91,13 +84,15 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getPlaylistList({
|
api.controller.getPlaylistList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
limit,
|
limit,
|
||||||
startIndex,
|
startIndex,
|
||||||
...pageFilters,
|
...pageFilters,
|
||||||
},
|
},
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Group, Stack } from '@mantine/core';
|
import { Group, Stack } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { CreatePlaylistBody, RawCreatePlaylistResponse, ServerType } from '/@/renderer/api/types';
|
import { CreatePlaylistBody, CreatePlaylistResponse, ServerType } from '/@/renderer/api/types';
|
||||||
import { Button, Switch, TextInput, toast } from '/@/renderer/components';
|
import { Button, Switch, TextInput, toast } from '/@/renderer/components';
|
||||||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
@ -8,22 +8,24 @@ import { useCurrentServer } from '/@/renderer/store';
|
||||||
interface SaveAsPlaylistFormProps {
|
interface SaveAsPlaylistFormProps {
|
||||||
body: Partial<CreatePlaylistBody>;
|
body: Partial<CreatePlaylistBody>;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onSuccess: (data: RawCreatePlaylistResponse) => void;
|
onSuccess: (data: CreatePlaylistResponse) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SaveAsPlaylistForm = ({ body, onSuccess, onCancel }: SaveAsPlaylistFormProps) => {
|
export const SaveAsPlaylistForm = ({ body, onSuccess, onCancel }: SaveAsPlaylistFormProps) => {
|
||||||
const mutation = useCreatePlaylist();
|
const mutation = useCreatePlaylist({});
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const form = useForm<CreatePlaylistBody>({
|
const form = useForm<CreatePlaylistBody>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
public: false,
|
||||||
|
rules: undefined,
|
||||||
|
...body?._custom?.navidrome,
|
||||||
|
},
|
||||||
|
},
|
||||||
comment: body.comment || '',
|
comment: body.comment || '',
|
||||||
name: body.name || '',
|
name: body.name || '',
|
||||||
ndParams: {
|
|
||||||
public: false,
|
|
||||||
rules: undefined,
|
|
||||||
...body.ndParams,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ interface UpdatePlaylistFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlaylistFormProps) => {
|
export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlaylistFormProps) => {
|
||||||
const mutation = useUpdatePlaylist();
|
const mutation = useUpdatePlaylist({});
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const userList = users?.map((user) => ({
|
const userList = users?.map((user) => ({
|
||||||
|
@ -23,21 +23,27 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
|
||||||
|
|
||||||
const form = useForm<UpdatePlaylistBody>({
|
const form = useForm<UpdatePlaylistBody>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
owner: body?._custom?.navidrome?.owner || '',
|
||||||
|
ownerId: body?._custom?.navidrome?.ownerId || '',
|
||||||
|
public: body?._custom?.navidrome?.public || false,
|
||||||
|
rules: undefined,
|
||||||
|
sync: body?._custom?.navidrome?.sync || false,
|
||||||
|
},
|
||||||
|
},
|
||||||
comment: body?.comment || '',
|
comment: body?.comment || '',
|
||||||
name: body?.name || '',
|
name: body?.name || '',
|
||||||
ndParams: {
|
|
||||||
owner: body?.ndParams?.owner || '',
|
|
||||||
ownerId: body?.ndParams?.ownerId || '',
|
|
||||||
public: body?.ndParams?.public || false,
|
|
||||||
rules: undefined,
|
|
||||||
sync: body?.ndParams?.sync || false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = form.onSubmit((values) => {
|
const handleSubmit = form.onSubmit((values) => {
|
||||||
mutation.mutate(
|
mutation.mutate(
|
||||||
{ body: values, query },
|
{
|
||||||
|
body: values,
|
||||||
|
query,
|
||||||
|
serverId: server?.id,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
toast.error({ message: err.message, title: 'Error updating playlist' });
|
toast.error({ message: err.message, title: 'Error updating playlist' });
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { NativeScrollArea } from '/@/renderer/components';
|
import { NativeScrollArea } from '/@/renderer/components';
|
||||||
|
@ -10,14 +10,16 @@ import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playli
|
||||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
|
import { useCurrentServer } from '../../../store/auth.store';
|
||||||
|
|
||||||
const PlaylistDetailRoute = () => {
|
const PlaylistDetailRoute = () => {
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
const headerRef = useRef<HTMLDivElement>(null);
|
const headerRef = useRef<HTMLDivElement>(null);
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||||
const background = useFastAverageColor(
|
const background = useFastAverageColor(
|
||||||
detailQuery?.data?.imageUrl,
|
detailQuery?.data?.imageUrl,
|
||||||
!detailQuery?.isLoading,
|
!detailQuery?.isLoading,
|
||||||
|
|
|
@ -23,11 +23,11 @@ const PlaylistDetailSongListRoute = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const currentServer = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||||
const createPlaylistMutation = useCreatePlaylist();
|
const createPlaylistMutation = useCreatePlaylist({});
|
||||||
const deletePlaylistMutation = useDeletePlaylist();
|
const deletePlaylistMutation = useDeletePlaylist({});
|
||||||
|
|
||||||
const handleSave = (
|
const handleSave = (
|
||||||
filter: Record<string, any>,
|
filter: Record<string, any>,
|
||||||
|
@ -45,15 +45,17 @@ const PlaylistDetailSongListRoute = () => {
|
||||||
createPlaylistMutation.mutate(
|
createPlaylistMutation.mutate(
|
||||||
{
|
{
|
||||||
body: {
|
body: {
|
||||||
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
owner: detailQuery?.data?.owner || '',
|
||||||
|
ownerId: detailQuery?.data?.ownerId || '',
|
||||||
|
public: detailQuery?.data?.public || false,
|
||||||
|
rules,
|
||||||
|
sync: detailQuery?.data?.sync || false,
|
||||||
|
},
|
||||||
|
},
|
||||||
comment: detailQuery?.data?.description || '',
|
comment: detailQuery?.data?.description || '',
|
||||||
name: detailQuery?.data?.name,
|
name: detailQuery?.data?.name,
|
||||||
ndParams: {
|
|
||||||
owner: detailQuery?.data?.owner || '',
|
|
||||||
ownerId: detailQuery?.data?.ownerId || '',
|
|
||||||
public: detailQuery?.data?.public || false,
|
|
||||||
rules,
|
|
||||||
sync: detailQuery?.data?.sync || false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -73,19 +75,21 @@ const PlaylistDetailSongListRoute = () => {
|
||||||
children: (
|
children: (
|
||||||
<SaveAsPlaylistForm
|
<SaveAsPlaylistForm
|
||||||
body={{
|
body={{
|
||||||
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
owner: detailQuery?.data?.owner || '',
|
||||||
|
ownerId: detailQuery?.data?.ownerId || '',
|
||||||
|
public: detailQuery?.data?.public || false,
|
||||||
|
rules: {
|
||||||
|
...filter,
|
||||||
|
order: 'desc',
|
||||||
|
sort: 'year',
|
||||||
|
},
|
||||||
|
sync: detailQuery?.data?.sync || false,
|
||||||
|
},
|
||||||
|
},
|
||||||
comment: detailQuery?.data?.description || '',
|
comment: detailQuery?.data?.description || '',
|
||||||
name: detailQuery?.data?.name,
|
name: detailQuery?.data?.name,
|
||||||
ndParams: {
|
|
||||||
owner: detailQuery?.data?.owner || '',
|
|
||||||
ownerId: detailQuery?.data?.ownerId || '',
|
|
||||||
public: detailQuery?.data?.public || false,
|
|
||||||
rules: {
|
|
||||||
...filter,
|
|
||||||
order: 'desc',
|
|
||||||
sort: 'year',
|
|
||||||
},
|
|
||||||
sync: detailQuery?.data?.sync || false,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
onCancel={closeAllModals}
|
onCancel={closeAllModals}
|
||||||
onSuccess={(data) =>
|
onSuccess={(data) =>
|
||||||
|
@ -120,9 +124,7 @@ const PlaylistDetailSongListRoute = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSmartPlaylist =
|
const isSmartPlaylist =
|
||||||
!detailQuery?.isLoading &&
|
!detailQuery?.isLoading && detailQuery?.data?.rules && server?.type === ServerType.NAVIDROME;
|
||||||
detailQuery?.data?.rules &&
|
|
||||||
currentServer?.type === ServerType.NAVIDROME;
|
|
||||||
|
|
||||||
const [showQueryBuilder, setShowQueryBuilder] = useState(false);
|
const [showQueryBuilder, setShowQueryBuilder] = useState(false);
|
||||||
const [isQueryBuilderExpanded, setIsQueryBuilderExpanded] = useState(false);
|
const [isQueryBuilderExpanded, setIsQueryBuilderExpanded] = useState(false);
|
||||||
|
@ -142,18 +144,19 @@ const PlaylistDetailSongListRoute = () => {
|
||||||
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
|
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
|
||||||
};
|
};
|
||||||
|
|
||||||
const itemCountCheck = usePlaylistSongList(
|
const itemCountCheck = usePlaylistSongList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 60 * 2,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
id: playlistId,
|
id: playlistId,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
cacheTime: 1000 * 60 * 60 * 2,
|
});
|
||||||
staleTime: 1000 * 60 * 60 * 2,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemCount =
|
const itemCount =
|
||||||
itemCountCheck.data?.totalRecordCount === null
|
itemCountCheck.data?.totalRecordCount === null
|
||||||
|
|
|
@ -9,18 +9,18 @@ import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
const PlaylistListRoute = () => {
|
const PlaylistListRoute = () => {
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
|
|
||||||
const itemCountCheck = usePlaylistList(
|
const itemCountCheck = usePlaylistList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 60 * 2,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
sortBy: PlaylistListSort.NAME,
|
sortBy: PlaylistListSort.NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
cacheTime: 1000 * 60 * 60 * 2,
|
|
||||||
staleTime: 1000 * 60 * 60 * 2,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemCount =
|
const itemCount =
|
||||||
itemCountCheck.data?.totalRecordCount === null
|
itemCountCheck.data?.totalRecordCount === null
|
||||||
|
|
|
@ -77,7 +77,7 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
||||||
setCurrentServer(serverItem);
|
setCurrentServer(serverItem);
|
||||||
closeAllModals();
|
closeAllModals();
|
||||||
|
|
||||||
if (serverList.length === 0) {
|
if (Object.keys(serverList).length === 0) {
|
||||||
toast.success({ message: 'Server has been added, reloading...' });
|
toast.success({ message: 'Server has been added, reloading...' });
|
||||||
setTimeout(() => window.location.reload(), 2000);
|
setTimeout(() => window.location.reload(), 2000);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -62,6 +62,7 @@ export const ServerList = () => {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 55,
|
right: 55,
|
||||||
transform: 'translateY(-3.5rem)',
|
transform: 'translateY(-3.5rem)',
|
||||||
|
zIndex: 2000,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
@ -77,21 +78,24 @@ export const ServerList = () => {
|
||||||
</Group>
|
</Group>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Accordion variant="separated">
|
<Accordion variant="separated">
|
||||||
{serverListQuery?.map((s) => (
|
{Object.keys(serverListQuery)?.map((serverId) => {
|
||||||
<Accordion.Item
|
const server = serverListQuery[serverId];
|
||||||
key={s.id}
|
return (
|
||||||
value={s.name}
|
<Accordion.Item
|
||||||
>
|
key={server.id}
|
||||||
<Accordion.Control icon={<RiServerFill size={15} />}>
|
value={server.name}
|
||||||
<Group position="apart">
|
>
|
||||||
{titleCase(s.type)} - {s.name}
|
<Accordion.Control icon={<RiServerFill size={15} />}>
|
||||||
</Group>
|
<Group position="apart">
|
||||||
</Accordion.Control>
|
{titleCase(server?.type)} - {server?.name}
|
||||||
<Accordion.Panel>
|
</Group>
|
||||||
<ServerListItem server={s} />
|
</Accordion.Control>
|
||||||
</Accordion.Panel>
|
<Accordion.Panel>
|
||||||
</Accordion.Item>
|
<ServerListItem server={server} />
|
||||||
))}
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Group>
|
<Group>
|
||||||
|
|
|
@ -6,5 +6,5 @@ export * from './components/library-header';
|
||||||
export * from './components/library-header-bar';
|
export * from './components/library-header-bar';
|
||||||
export * from './mutations/create-favorite-mutation';
|
export * from './mutations/create-favorite-mutation';
|
||||||
export * from './mutations/delete-favorite-mutation';
|
export * from './mutations/delete-favorite-mutation';
|
||||||
export * from './mutations/update-rating-mutation';
|
export * from './mutations/set-rating-mutation';
|
||||||
export * from './components/filter-bar';
|
export * from './components/filter-bar';
|
||||||
|
|
|
@ -100,9 +100,12 @@ export const Sidebar = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const playlistsQuery = usePlaylistList({
|
const playlistsQuery = usePlaylistList({
|
||||||
sortBy: PlaylistListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: PlaylistListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setFullScreenPlayerStore = useSetFullScreenPlayerStore();
|
const setFullScreenPlayerStore = useSetFullScreenPlayerStore();
|
||||||
|
|
|
@ -9,30 +9,32 @@ interface JellyfinSongFiltersProps {
|
||||||
handleFilterChange: (filters: SongListFilter) => void;
|
handleFilterChange: (filters: SongListFilter) => void;
|
||||||
id?: string;
|
id?: string;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JellyfinSongFilters = ({
|
export const JellyfinSongFilters = ({
|
||||||
id,
|
id,
|
||||||
pageKey,
|
pageKey,
|
||||||
handleFilterChange,
|
handleFilterChange,
|
||||||
|
serverId,
|
||||||
}: JellyfinSongFiltersProps) => {
|
}: JellyfinSongFiltersProps) => {
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
const filter = useSongListFilter({ id, key: pageKey });
|
const filter = useSongListFilter({ id, key: pageKey });
|
||||||
|
|
||||||
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
||||||
const genreListQuery = useGenreList(null);
|
const genreListQuery = useGenreList({ query: null, serverId });
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
return genreListQuery.data.map((genre) => ({
|
return genreListQuery.data.items.map((genre) => ({
|
||||||
label: genre.name,
|
label: genre.name,
|
||||||
value: genre.id,
|
value: genre.id,
|
||||||
}));
|
}));
|
||||||
}, [genreListQuery.data]);
|
}, [genreListQuery.data]);
|
||||||
|
|
||||||
const selectedGenres = useMemo(() => {
|
const selectedGenres = useMemo(() => {
|
||||||
return filter.jfParams?.genreIds?.split(',');
|
return filter._custom?.jellyfin?.genreIds?.split(',');
|
||||||
}, [filter.jfParams?.genreIds]);
|
}, [filter._custom?.jellyfin?.genreIds]);
|
||||||
|
|
||||||
const toggleFilters = [
|
const toggleFilters = [
|
||||||
{
|
{
|
||||||
|
@ -40,17 +42,20 @@ export const JellyfinSongFilters = ({
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
includeItemTypes: 'Audio',
|
jellyfin: {
|
||||||
isFavorite: e.currentTarget.checked ? true : undefined,
|
...filter._custom?.jellyfin,
|
||||||
|
includeItemTypes: 'Audio',
|
||||||
|
isFavorite: e.currentTarget.checked ? true : undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
}) as SongListFilter;
|
}) as SongListFilter;
|
||||||
handleFilterChange(updatedFilters);
|
handleFilterChange(updatedFilters);
|
||||||
},
|
},
|
||||||
value: filter.jfParams?.isFavorite,
|
value: filter._custom?.jellyfin?.isFavorite,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -58,10 +63,13 @@ export const JellyfinSongFilters = ({
|
||||||
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
includeItemTypes: 'Audio',
|
jellyfin: {
|
||||||
minYear: e === '' ? undefined : (e as number),
|
...filter._custom?.jellyfin,
|
||||||
|
includeItemTypes: 'Audio',
|
||||||
|
minYear: e === '' ? undefined : (e as number),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -73,10 +81,13 @@ export const JellyfinSongFilters = ({
|
||||||
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
includeItemTypes: 'Audio',
|
jellyfin: {
|
||||||
maxYear: e === '' ? undefined : (e as number),
|
...filter._custom?.jellyfin,
|
||||||
|
includeItemTypes: 'Audio',
|
||||||
|
maxYear: e === '' ? undefined : (e as number),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -88,10 +99,13 @@ export const JellyfinSongFilters = ({
|
||||||
const genreFilterString = e?.length ? e.join(',') : undefined;
|
const genreFilterString = e?.length ? e.join(',') : undefined;
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
jfParams: {
|
_custom: {
|
||||||
...filter.jfParams,
|
...filter._custom,
|
||||||
genreIds: genreFilterString,
|
jellyfin: {
|
||||||
includeItemTypes: 'Audio',
|
...filter._custom?.jellyfin,
|
||||||
|
genreIds: genreFilterString,
|
||||||
|
includeItemTypes: 'Audio',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -117,14 +131,14 @@ export const JellyfinSongFilters = ({
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
defaultValue={filter.jfParams?.minYear}
|
defaultValue={filter._custom?.jellyfin?.minYear}
|
||||||
label="From year"
|
label="From year"
|
||||||
max={2300}
|
max={2300}
|
||||||
min={1700}
|
min={1700}
|
||||||
onChange={handleMinYearFilter}
|
onChange={handleMinYearFilter}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
defaultValue={filter.jfParams?.maxYear}
|
defaultValue={filter._custom?.jellyfin?.maxYear}
|
||||||
label="To year"
|
label="To year"
|
||||||
max={2300}
|
max={2300}
|
||||||
min={1700}
|
min={1700}
|
||||||
|
|
|
@ -9,21 +9,23 @@ interface NavidromeSongFiltersProps {
|
||||||
handleFilterChange: (filters: SongListFilter) => void;
|
handleFilterChange: (filters: SongListFilter) => void;
|
||||||
id?: string;
|
id?: string;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NavidromeSongFilters = ({
|
export const NavidromeSongFilters = ({
|
||||||
handleFilterChange,
|
handleFilterChange,
|
||||||
pageKey,
|
pageKey,
|
||||||
id,
|
id,
|
||||||
|
serverId,
|
||||||
}: NavidromeSongFiltersProps) => {
|
}: NavidromeSongFiltersProps) => {
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
const filter = useSongListFilter({ id, key: pageKey });
|
const filter = useSongListFilter({ id, key: pageKey });
|
||||||
|
|
||||||
const genreListQuery = useGenreList(null);
|
const genreListQuery = useGenreList({ query: null, serverId });
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
return genreListQuery.data.map((genre) => ({
|
return genreListQuery.data.items.map((genre) => ({
|
||||||
label: genre.name,
|
label: genre.name,
|
||||||
value: genre.id,
|
value: genre.id,
|
||||||
}));
|
}));
|
||||||
|
@ -32,9 +34,11 @@ export const NavidromeSongFilters = ({
|
||||||
const handleGenresFilter = debounce((e: string | null) => {
|
const handleGenresFilter = debounce((e: string | null) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
...filter._custom,
|
||||||
genre_id: e || undefined,
|
navidrome: {
|
||||||
|
genre_id: e || undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -48,23 +52,30 @@ export const NavidromeSongFilters = ({
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: { ...filter.ndParams, starred: e.currentTarget.checked ? true : undefined },
|
_custom: {
|
||||||
|
...filter._custom,
|
||||||
|
navidrome: {
|
||||||
|
starred: e.currentTarget.checked ? true : undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
}) as SongListFilter;
|
}) as SongListFilter;
|
||||||
|
|
||||||
handleFilterChange(updatedFilters);
|
handleFilterChange(updatedFilters);
|
||||||
},
|
},
|
||||||
value: filter.ndParams?.starred,
|
value: filter._custom?.navidrome?.starred,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleYearFilter = debounce((e: number | string) => {
|
const handleYearFilter = debounce((e: number | string) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
data: {
|
data: {
|
||||||
ndParams: {
|
_custom: {
|
||||||
...filter.ndParams,
|
...filter._custom,
|
||||||
year: e === '' ? undefined : (e as number),
|
navidrome: {
|
||||||
|
year: e === '' ? undefined : (e as number),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
@ -94,7 +105,7 @@ export const NavidromeSongFilters = ({
|
||||||
label="Year"
|
label="Year"
|
||||||
max={5000}
|
max={5000}
|
||||||
min={0}
|
min={0}
|
||||||
value={filter.ndParams?.year}
|
value={filter._custom?.navidrome?.year}
|
||||||
width={50}
|
width={50}
|
||||||
onChange={(e) => handleYearFilter(e)}
|
onChange={(e) => handleYearFilter(e)}
|
||||||
/>
|
/>
|
||||||
|
@ -102,7 +113,7 @@ export const NavidromeSongFilters = ({
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
data={genreList}
|
data={genreList}
|
||||||
defaultValue={filter.ndParams?.genre_id}
|
defaultValue={filter._custom?.navidrome?.genre_id}
|
||||||
label="Genre"
|
label="Genre"
|
||||||
width={150}
|
width={150}
|
||||||
onChange={handleGenresFilter}
|
onChange={handleGenresFilter}
|
||||||
|
|
|
@ -12,12 +12,6 @@ import { Stack } from '@mantine/core';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } 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 {
|
|
||||||
getColumnDefs,
|
|
||||||
TablePagination,
|
|
||||||
VirtualGridAutoSizerContainer,
|
|
||||||
VirtualTable,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import {
|
import {
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
useListStoreActions,
|
useListStoreActions,
|
||||||
|
@ -33,6 +27,8 @@ import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { LibraryItem, QueueSong, SongListQuery } from '/@/renderer/api/types';
|
import { LibraryItem, QueueSong, SongListQuery } from '/@/renderer/api/types';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
|
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
|
||||||
|
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||||
|
import { getColumnDefs, VirtualTable, TablePagination } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
interface SongListContentProps {
|
interface SongListContentProps {
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
|
@ -74,9 +70,11 @@ export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) =
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,15 +17,7 @@ import {
|
||||||
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 { LibraryItem, SongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
|
import { LibraryItem, SongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||||
import {
|
import { DropdownMenu, Button, Slider, MultiSelect, Switch, Text } from '/@/renderer/components';
|
||||||
DropdownMenu,
|
|
||||||
SONG_TABLE_COLUMNS,
|
|
||||||
Button,
|
|
||||||
Slider,
|
|
||||||
MultiSelect,
|
|
||||||
Switch,
|
|
||||||
Text,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { useMusicFolders } from '/@/renderer/features/shared';
|
import { useMusicFolders } from '/@/renderer/features/shared';
|
||||||
import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters';
|
import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters';
|
||||||
|
@ -42,6 +34,7 @@ import {
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/types';
|
import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/types';
|
||||||
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
|
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
|
||||||
|
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
|
|
||||||
const FILTERS = {
|
const FILTERS = {
|
||||||
jellyfin: [
|
jellyfin: [
|
||||||
|
@ -100,7 +93,7 @@ export const SongListHeaderFilters = ({
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
const musicFoldersQuery = useMusicFolders();
|
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
@ -133,9 +126,11 @@ export const SongListHeaderFilters = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
@ -306,18 +301,18 @@ export const SongListHeaderFilters = ({
|
||||||
const isFilterApplied = useMemo(() => {
|
const isFilterApplied = useMemo(() => {
|
||||||
const isNavidromeFilterApplied =
|
const isNavidromeFilterApplied =
|
||||||
server?.type === ServerType.NAVIDROME &&
|
server?.type === ServerType.NAVIDROME &&
|
||||||
filter.ndParams &&
|
filter._custom?.navidrome &&
|
||||||
Object.values(filter.ndParams).some((value) => value !== undefined);
|
Object.values(filter._custom?.navidrome).some((value) => value !== undefined);
|
||||||
|
|
||||||
const isJellyfinFilterApplied =
|
const isJellyfinFilterApplied =
|
||||||
server?.type === ServerType.JELLYFIN &&
|
server?.type === ServerType.JELLYFIN &&
|
||||||
filter.jfParams &&
|
filter._custom?.jellyfin &&
|
||||||
Object.values(filter.jfParams)
|
Object.values(filter._custom?.jellyfin)
|
||||||
.filter((value) => value !== 'Audio') // Don't account for includeItemTypes: Audio
|
.filter((value) => value !== 'Audio') // Don't account for includeItemTypes: Audio
|
||||||
.some((value) => value !== undefined);
|
.some((value) => value !== undefined);
|
||||||
|
|
||||||
return isNavidromeFilterApplied || isJellyfinFilterApplied;
|
return isNavidromeFilterApplied || isJellyfinFilterApplied;
|
||||||
}, [filter.jfParams, filter.ndParams, server?.type]);
|
}, [filter._custom?.jellyfin, filter._custom?.navidrome, server?.type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
|
@ -382,7 +377,7 @@ export const SongListHeaderFilters = ({
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
{musicFoldersQuery.data?.map((folder) => (
|
{musicFoldersQuery.data?.items.map((folder) => (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={`musicFolder-${folder.id}`}
|
key={`musicFolder-${folder.id}`}
|
||||||
$isActive={filter.musicFolderId === folder.id}
|
$isActive={filter.musicFolderId === folder.id}
|
||||||
|
|
|
@ -64,15 +64,16 @@ export const SongListHeader = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
async ({ signal }) =>
|
async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
query,
|
query,
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
{ cacheTime: 1000 * 60 * 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const songs = api.normalize.songList(songsRes, server);
|
params.successCallback(songsRes?.items || [], songsRes?.totalRecordCount || 0);
|
||||||
params.successCallback(songs?.items || [], songsRes?.totalRecordCount || 0);
|
|
||||||
},
|
},
|
||||||
rowCount: undefined,
|
rowCount: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,17 +19,18 @@ const TrackListRoute = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const songListFilter = useSongListFilter({ id: albumArtistId, key: pageKey });
|
const songListFilter = useSongListFilter({ id: albumArtistId, key: pageKey });
|
||||||
const itemCountCheck = useSongList(
|
const itemCountCheck = useSongList({
|
||||||
{
|
options: {
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
...songListFilter,
|
...songListFilter,
|
||||||
},
|
},
|
||||||
{
|
serverId: server?.id,
|
||||||
cacheTime: 1000 * 60,
|
});
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemCount =
|
const itemCount =
|
||||||
itemCountCheck.data?.totalRecordCount === null
|
itemCountCheck.data?.totalRecordCount === null
|
||||||
|
|
|
@ -60,22 +60,23 @@ export const AppMenu = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenu.Label>Select a server</DropdownMenu.Label>
|
<DropdownMenu.Label>Select a server</DropdownMenu.Label>
|
||||||
{serverList.map((s) => {
|
{Object.keys(serverList).map((serverId) => {
|
||||||
const isNavidromeExpired = s.type === ServerType.NAVIDROME && !s.ndCredential;
|
const server = serverList[serverId];
|
||||||
|
const isNavidromeExpired = server.type === ServerType.NAVIDROME && !server.ndCredential;
|
||||||
const isJellyfinExpired = false;
|
const isJellyfinExpired = false;
|
||||||
const isSessionExpired = isNavidromeExpired || isJellyfinExpired;
|
const isSessionExpired = isNavidromeExpired || isJellyfinExpired;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={`server-${s.id}`}
|
key={`server-${server.id}`}
|
||||||
$isActive={s.id === currentServer?.id}
|
$isActive={server.id === currentServer?.id}
|
||||||
icon={isSessionExpired ? <RiLockLine color="var(--danger-color)" /> : <RiServerFill />}
|
icon={isSessionExpired ? <RiLockLine color="var(--danger-color)" /> : <RiServerFill />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isSessionExpired) return handleSetCurrentServer(s);
|
if (!isSessionExpired) return handleSetCurrentServer(server);
|
||||||
return handleCredentialsModal(s);
|
return handleCredentialsModal(server);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Group>{s.name}</Group>
|
<Group>{server.name}</Group>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
Reference in a new issue