Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Create "Add current track" button in playlist view #560

Open
wants to merge 2 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"setRating": "set rating",
"toggleSmartPlaylistEditor": "toggle $t(entity.smartPlaylist) editor",
"viewPlaylists": "view $t(entity.playlist_other)",
"addCurrentSong": "$t(common.add) $t(common.currentSong)",
"openIn": {
"lastfm": "Open in Last.fm",
"musicbrainz": "Open in MusicBrainz"
Expand Down
74 changes: 67 additions & 7 deletions src/renderer/features/sidebar/components/sidebar-playlist-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { useCallback, useMemo, useState } from 'react';
import { Box, Flex, Group } from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import { RiAddBoxFill, RiAddCircleFill, RiPlayFill } from 'react-icons/ri';
import { RiAddBoxFill, RiAddCircleFill, RiPlayFill, RiPlayListAddFill } from 'react-icons/ri';
import { generatePath } from 'react-router';
import { Link } from 'react-router-dom';
import { LibraryItem, Playlist } from '/@/renderer/api/types';
import { Button, Text } from '/@/renderer/components';
import { Button, Text, toast } from '/@/renderer/components';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { usePlaylistList } from '/@/renderer/features/playlists';
import { useAddToPlaylist, usePlaylistList } from '/@/renderer/features/playlists';
import { AppRoute } from '/@/renderer/router/routes';
import { Play } from '/@/renderer/types';
import AutoSizer from 'react-virtualized-auto-sizer';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { useHideScrollbar } from '/@/renderer/hooks';
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
import { useCurrentServer, useGeneralSettings, useCurrentSong } from '/@/renderer/store';

interface SidebarPlaylistListProps {
data: ReturnType<typeof usePlaylistList>['data'];
Expand Down Expand Up @@ -84,6 +84,24 @@ const PlaylistRow = ({ index, data, style }: ListChildComponentProps) => {
right="0"
spacing="sm"
>
{!data?.items?.[index].rules && ( // hide button on smart playlists
<Button
compact
size="md"
tooltip={{
label: t('action.addCurrentSong', { postProcess: 'sentenceCase' }),
openDelay: 500,
}}
variant="default"
onClick={() => {
if (!data?.items?.[index].id) return;
data.handleAdd(data?.items[index]);
}}
>
<RiPlayListAddFill />
</Button>
)}

<Button
compact
size="md"
Expand Down Expand Up @@ -136,10 +154,16 @@ const PlaylistRow = ({ index, data, style }: ListChildComponentProps) => {
};

export const SidebarPlaylistList = ({ data }: SidebarPlaylistListProps) => {
const { t } = useTranslation();

const { isScrollbarHidden, hideScrollbarElementProps } = useHideScrollbar(0);
const handlePlayQueueAdd = usePlayQueueAdd();
const addToPlaylistMutation = useAddToPlaylist({});

const { defaultFullPlaylist } = useGeneralSettings();
const { type, username } = useCurrentServer() || {};
const { id: serverID, type, username } = useCurrentServer() || {};

const currentSong = useCurrentSong();

const [rect, setRect] = useState({
height: 0,
Expand All @@ -161,8 +185,44 @@ export const SidebarPlaylistList = ({ data }: SidebarPlaylistListProps) => {
[handlePlayQueueAdd],
);

const handleAddCurrent = useCallback(
(playlist: Playlist) => {
if (!currentSong) return;

addToPlaylistMutation.mutate(
{
body: { songId: [currentSong.id] },
query: { id: playlist.id },
serverId: serverID,
},
{
onError: (err) => {
toast.error({
message: `[${playlist?.name}] ${err.message}`,
title: t('error.genericError', { postProcess: 'sentenceCase' }),
});
},
onSuccess: () => {
toast.success({
message: t('form.addToPlaylist.success', {
message: 1,
numOfPlaylists: 1,
postProcess: 'sentenceCase',
}),
});
},
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should move the toast.success to onSuccess here

);
},
[addToPlaylistMutation, currentSong, serverID, t],
);

const memoizedItemData = useMemo(() => {
const base = { defaultFullPlaylist, handlePlay: handlePlayPlaylist };
const base = {
defaultFullPlaylist,
handleAdd: handleAddCurrent,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be enabled for Navidrome shared playlists (only the owner can change them)

handlePlay: handlePlayPlaylist,
};

if (!type || !username || !data?.items) {
return { ...base, items: data?.items };
Expand All @@ -185,7 +245,7 @@ export const SidebarPlaylistList = ({ data }: SidebarPlaylistListProps) => {
}

return { ...base, items: owned.concat(shared) };
}, [data?.items, defaultFullPlaylist, handlePlayPlaylist, type, username]);
}, [data?.items, defaultFullPlaylist, handleAddCurrent, handlePlayPlaylist, type, username]);

return (
<Flex
Expand Down