Add sticky table header to detail pages
This commit is contained in:
parent
b59c86f78f
commit
4c275ea878
3 changed files with 242 additions and 233 deletions
|
@ -5,11 +5,12 @@ import {
|
||||||
getColumnDefs,
|
getColumnDefs,
|
||||||
GridCarousel,
|
GridCarousel,
|
||||||
TextTitle,
|
TextTitle,
|
||||||
|
useFixedTableHeader,
|
||||||
VirtualTable,
|
VirtualTable,
|
||||||
} from '/@/renderer/components';
|
} from '/@/renderer/components';
|
||||||
import { CellContextMenuEvent, ColDef } from '@ag-grid-community/core';
|
import { CellContextMenuEvent, ColDef } 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 { Group, Stack } from '@mantine/core';
|
import { Box, Group, Stack } from '@mantine/core';
|
||||||
import { useSetState } from '@mantine/hooks';
|
import { useSetState } from '@mantine/hooks';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import { RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
import { RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
||||||
|
@ -29,19 +30,16 @@ import { AlbumListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
|
|
||||||
const ContentContainer = styled.div`
|
const ContentContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-width: 1920px;
|
max-width: 1920px;
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem 5rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.ag-theme-alpine-dark {
|
.ag-theme-alpine-dark {
|
||||||
--ag-header-background-color: rgba(0, 0, 0, 0%);
|
--ag-header-background-color: rgba(0, 0, 0, 0%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-header-container {
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface AlbumDetailContentProps {
|
interface AlbumDetailContentProps {
|
||||||
|
@ -170,9 +168,13 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { intersectRef, tableContainerRef } = useFixedTableHeader();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<Group
|
<Group
|
||||||
|
ref={intersectRef}
|
||||||
|
className="test"
|
||||||
pb="2rem"
|
pb="2rem"
|
||||||
pt="1rem"
|
pt="1rem"
|
||||||
spacing="lg"
|
spacing="lg"
|
||||||
|
@ -210,6 +212,7 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
<Box ref={tableContainerRef}>
|
||||||
<VirtualTable
|
<VirtualTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
animateRows
|
animateRows
|
||||||
|
@ -238,6 +241,7 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||||
params.api.sizeColumnsToFit();
|
params.api.sizeColumnsToFit();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
<Stack
|
<Stack
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
mt="5rem"
|
mt="5rem"
|
||||||
|
|
|
@ -1,18 +1,39 @@
|
||||||
import { CellContextMenuEvent, ColDef } from '@ag-grid-community/core';
|
import { CellContextMenuEvent, ColDef } 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 { Group } from '@mantine/core';
|
import { Box, Group } from '@mantine/core';
|
||||||
|
import { closeAllModals, openModal } from '@mantine/modals';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
import { MutableRefObject, useMemo } from 'react';
|
import { MutableRefObject, useMemo, useRef } from 'react';
|
||||||
import { generatePath, useParams } from 'react-router';
|
import { RiMoreFill } from 'react-icons/ri';
|
||||||
|
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { api } from '/@/renderer/api';
|
||||||
import { Button, getColumnDefs, Text, VirtualTable } from '/@/renderer/components';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { LibraryItem, SortOrder, UserListQuery, UserListSort } from '/@/renderer/api/types';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ConfirmModal,
|
||||||
|
DropdownMenu,
|
||||||
|
getColumnDefs,
|
||||||
|
MotionGroup,
|
||||||
|
toast,
|
||||||
|
useFixedTableHeader,
|
||||||
|
VirtualTable,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import { openContextMenu } from '/@/renderer/features/context-menu';
|
import { openContextMenu } 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 { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
|
import { UpdatePlaylistForm } from '/@/renderer/features/playlists/components/update-playlist-form';
|
||||||
|
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||||
|
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||||
import { usePlaylistSongListInfinite } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
|
import { usePlaylistSongListInfinite } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
|
||||||
|
import { PlayButton, PLAY_TYPES } from '/@/renderer/features/shared';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useSongListStore } from '/@/renderer/store';
|
import { useCurrentServer, useSongListStore } from '/@/renderer/store';
|
||||||
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
|
import { Play } from '/@/renderer/types';
|
||||||
|
|
||||||
const ContentContainer = styled.div`
|
const ContentContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -24,10 +45,6 @@ const ContentContainer = styled.div`
|
||||||
.ag-theme-alpine-dark {
|
.ag-theme-alpine-dark {
|
||||||
--ag-header-background-color: rgba(0, 0, 0, 0%);
|
--ag-header-background-color: rgba(0, 0, 0, 0%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-header-container {
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface PlaylistDetailContentProps {
|
interface PlaylistDetailContentProps {
|
||||||
|
@ -35,8 +52,14 @@ interface PlaylistDetailContentProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps) => {
|
export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const page = useSongListStore();
|
const page = useSongListStore();
|
||||||
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
const detailQuery = usePlaylistDetail({ id: playlistId });
|
||||||
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const playlistSongsQueryInfinite = usePlaylistSongListInfinite(
|
const playlistSongsQueryInfinite = usePlaylistSongListInfinite(
|
||||||
{
|
{
|
||||||
|
@ -44,7 +67,7 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
limit: 50,
|
limit: 50,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
{ keepPreviousData: false },
|
{ cacheTime: 0, keepPreviousData: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLoadMore = () => {
|
const handleLoadMore = () => {
|
||||||
|
@ -94,8 +117,142 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
[playlistSongsQueryInfinite.data?.pages],
|
[playlistSongsQueryInfinite.data?.pages],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('playlistSongData', playlistSongData);
|
||||||
|
|
||||||
|
const { intersectRef, tableContainerRef } = useFixedTableHeader();
|
||||||
|
|
||||||
|
const deletePlaylistMutation = useDeletePlaylist();
|
||||||
|
|
||||||
|
const handleDeletePlaylist = () => {
|
||||||
|
deletePlaylistMutation.mutate(
|
||||||
|
{ query: { id: playlistId } },
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error({
|
||||||
|
message: err.message,
|
||||||
|
title: 'Error deleting playlist',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success({
|
||||||
|
message: `${detailQuery?.data?.name} was successfully deleted`,
|
||||||
|
title: 'Playlist deleted',
|
||||||
|
});
|
||||||
|
closeAllModals();
|
||||||
|
navigate(AppRoute.PLAYLISTS);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openDeletePlaylist = () => {
|
||||||
|
openModal({
|
||||||
|
children: (
|
||||||
|
<ConfirmModal
|
||||||
|
loading={deletePlaylistMutation.isLoading}
|
||||||
|
onConfirm={handleDeletePlaylist}
|
||||||
|
>
|
||||||
|
Are you sure you want to delete this playlist?
|
||||||
|
</ConfirmModal>
|
||||||
|
),
|
||||||
|
title: 'Delete playlist',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePlay = (playType?: Play) => {
|
||||||
|
handlePlayQueueAdd?.({
|
||||||
|
byItemType: {
|
||||||
|
id: [playlistId],
|
||||||
|
type: LibraryItem.PLAYLIST,
|
||||||
|
},
|
||||||
|
play: playType || playButtonBehavior,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openUpdatePlaylistModal = async () => {
|
||||||
|
const query: UserListQuery = {
|
||||||
|
sortBy: UserListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const users = await queryClient.fetchQuery({
|
||||||
|
queryFn: ({ signal }) => api.controller.getUserList({ query, server, signal }),
|
||||||
|
queryKey: queryKeys.users.list(server?.id || '', query),
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizedUsers = api.normalize.userList(users, server);
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
children: (
|
||||||
|
<UpdatePlaylistForm
|
||||||
|
body={{
|
||||||
|
comment: detailQuery?.data?.description || undefined,
|
||||||
|
genres: detailQuery?.data?.genres,
|
||||||
|
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 }}
|
||||||
|
users={normalizedUsers.items}
|
||||||
|
onCancel={closeAllModals}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
title: 'Edit playlist',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadMoreRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
|
<Group
|
||||||
|
ref={intersectRef}
|
||||||
|
maw="1920px"
|
||||||
|
p="1rem"
|
||||||
|
position="apart"
|
||||||
|
>
|
||||||
|
<Group>
|
||||||
|
<PlayButton onClick={() => handlePlay()} />
|
||||||
|
<DropdownMenu position="bottom-start">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<RiMoreFill size={20} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
{PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
key={`playtype-${type.play}`}
|
||||||
|
onClick={() => handlePlay(type.play)}
|
||||||
|
>
|
||||||
|
{type.label}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
))}
|
||||||
|
<DropdownMenu.Divider />
|
||||||
|
<DropdownMenu.Item onClick={openUpdatePlaylistModal}>Edit playlist</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onClick={openDeletePlaylist}>Delete playlist</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
component={Link}
|
||||||
|
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })}
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
View full playlist
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
<Box ref={tableContainerRef}>
|
||||||
<VirtualTable
|
<VirtualTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
animateRows
|
animateRows
|
||||||
|
@ -111,7 +268,7 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
defaultColDef={defaultColumnDefs}
|
defaultColDef={defaultColumnDefs}
|
||||||
enableCellChangeFlash={false}
|
enableCellChangeFlash={false}
|
||||||
getRowId={(data) => data.data.uniqueId}
|
getRowId={(data) => data.data.id}
|
||||||
rowData={playlistSongData}
|
rowData={playlistSongData}
|
||||||
rowHeight={60}
|
rowHeight={60}
|
||||||
rowSelection="multiple"
|
rowSelection="multiple"
|
||||||
|
@ -124,11 +281,14 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
params.api.sizeColumnsToFit();
|
params.api.sizeColumnsToFit();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Group
|
</Box>
|
||||||
|
<MotionGroup
|
||||||
p="2rem"
|
p="2rem"
|
||||||
position="center"
|
position="center"
|
||||||
|
onViewportEnter={handleLoadMore}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
ref={loadMoreRef}
|
||||||
compact
|
compact
|
||||||
disabled={!playlistSongsQueryInfinite.hasNextPage}
|
disabled={!playlistSongsQueryInfinite.hasNextPage}
|
||||||
loading={playlistSongsQueryInfinite.isFetchingNextPage}
|
loading={playlistSongsQueryInfinite.isFetchingNextPage}
|
||||||
|
@ -137,16 +297,7 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||||
>
|
>
|
||||||
Load more
|
Load more
|
||||||
</Button>
|
</Button>
|
||||||
<Text>or</Text>
|
</MotionGroup>
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
component={Link}
|
|
||||||
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })}
|
|
||||||
variant="subtle"
|
|
||||||
>
|
|
||||||
View full playlist
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
import { forwardRef, Fragment, Ref } from 'react';
|
import { forwardRef, Fragment, Ref } from 'react';
|
||||||
import { Group, Stack } from '@mantine/core';
|
import { Group, Stack } from '@mantine/core';
|
||||||
import { closeAllModals, openModal } from '@mantine/modals';
|
import { useParams } from 'react-router';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { Text } from '/@/renderer/components';
|
||||||
import { RiMoreFill } from 'react-icons/ri';
|
|
||||||
import { generatePath, useNavigate, useParams } from 'react-router';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { DropdownMenu, Button, ConfirmModal, toast, Text } from '/@/renderer/components';
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
|
||||||
import { UpdatePlaylistForm } from './update-playlist-form';
|
|
||||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
|
||||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||||
import { LibraryHeader, PlayButton, PLAY_TYPES } from '/@/renderer/features/shared';
|
import { LibraryHeader } from '/@/renderer/features/shared';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
|
||||||
import { Play } from '/@/renderer/types';
|
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
import { UserListSort, SortOrder, UserListQuery, LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
|
|
||||||
interface PlaylistDetailHeaderProps {
|
interface PlaylistDetailHeaderProps {
|
||||||
background: string;
|
background: string;
|
||||||
|
@ -31,99 +19,8 @@ export const PlaylistDetailHeader = forwardRef(
|
||||||
{ background, imageUrl, imagePlaceholderUrl }: PlaylistDetailHeaderProps,
|
{ background, imageUrl, imagePlaceholderUrl }: PlaylistDetailHeaderProps,
|
||||||
ref: Ref<HTMLDivElement>,
|
ref: Ref<HTMLDivElement>,
|
||||||
) => {
|
) => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const detailQuery = usePlaylistDetail({ id: playlistId });
|
const detailQuery = usePlaylistDetail({ id: playlistId });
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
|
||||||
const server = useCurrentServer();
|
|
||||||
|
|
||||||
const handlePlay = (playType?: Play) => {
|
|
||||||
handlePlayQueueAdd?.({
|
|
||||||
byItemType: {
|
|
||||||
id: [playlistId],
|
|
||||||
type: LibraryItem.PLAYLIST,
|
|
||||||
},
|
|
||||||
play: playType || playButtonBehavior,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const openUpdatePlaylistModal = async () => {
|
|
||||||
const query: UserListQuery = {
|
|
||||||
sortBy: UserListSort.NAME,
|
|
||||||
sortOrder: SortOrder.ASC,
|
|
||||||
startIndex: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const users = await queryClient.fetchQuery({
|
|
||||||
queryFn: ({ signal }) => api.controller.getUserList({ query, server, signal }),
|
|
||||||
queryKey: queryKeys.users.list(server?.id || '', query),
|
|
||||||
});
|
|
||||||
|
|
||||||
const normalizedUsers = api.normalize.userList(users, server);
|
|
||||||
|
|
||||||
openModal({
|
|
||||||
children: (
|
|
||||||
<UpdatePlaylistForm
|
|
||||||
body={{
|
|
||||||
comment: detailQuery?.data?.description || undefined,
|
|
||||||
genres: detailQuery?.data?.genres,
|
|
||||||
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 }}
|
|
||||||
users={normalizedUsers.items}
|
|
||||||
onCancel={closeAllModals}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
title: 'Edit playlist',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const deletePlaylistMutation = useDeletePlaylist();
|
|
||||||
|
|
||||||
const handleDeletePlaylist = () => {
|
|
||||||
deletePlaylistMutation.mutate(
|
|
||||||
{ query: { id: playlistId } },
|
|
||||||
{
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error({
|
|
||||||
message: err.message,
|
|
||||||
title: 'Error deleting playlist',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success({
|
|
||||||
message: `${detailQuery?.data?.name} was successfully deleted`,
|
|
||||||
title: 'Playlist deleted',
|
|
||||||
});
|
|
||||||
closeAllModals();
|
|
||||||
navigate(AppRoute.PLAYLISTS);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openDeletePlaylist = () => {
|
|
||||||
openModal({
|
|
||||||
children: (
|
|
||||||
<ConfirmModal
|
|
||||||
loading={deletePlaylistMutation.isLoading}
|
|
||||||
onConfirm={handleDeletePlaylist}
|
|
||||||
>
|
|
||||||
Are you sure you want to delete this playlist?
|
|
||||||
</ConfirmModal>
|
|
||||||
),
|
|
||||||
title: 'Delete playlist',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const metadataItems = [
|
const metadataItems = [
|
||||||
{
|
{
|
||||||
|
@ -160,49 +57,6 @@ export const PlaylistDetailHeader = forwardRef(
|
||||||
<Text lineClamp={3}>{detailQuery?.data?.description}</Text>
|
<Text lineClamp={3}>{detailQuery?.data?.description}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</LibraryHeader>
|
</LibraryHeader>
|
||||||
<Group
|
|
||||||
maw="1920px"
|
|
||||||
p="1rem"
|
|
||||||
position="apart"
|
|
||||||
>
|
|
||||||
<Group>
|
|
||||||
<PlayButton onClick={() => handlePlay()} />
|
|
||||||
<DropdownMenu position="bottom-start">
|
|
||||||
<DropdownMenu.Target>
|
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
variant="subtle"
|
|
||||||
>
|
|
||||||
<RiMoreFill size={20} />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenu.Target>
|
|
||||||
<DropdownMenu.Dropdown>
|
|
||||||
{PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => (
|
|
||||||
<DropdownMenu.Item
|
|
||||||
key={`playtype-${type.play}`}
|
|
||||||
onClick={() => handlePlay(type.play)}
|
|
||||||
>
|
|
||||||
{type.label}
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
))}
|
|
||||||
<DropdownMenu.Divider />
|
|
||||||
<DropdownMenu.Item onClick={openUpdatePlaylistModal}>
|
|
||||||
Edit playlist
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item onClick={openDeletePlaylist}>Delete playlist</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Dropdown>
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
component={Link}
|
|
||||||
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })}
|
|
||||||
variant="subtle"
|
|
||||||
>
|
|
||||||
View full playlist
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
Reference in a new issue