diff --git a/src/renderer/features/player/components/left-controls.tsx b/src/renderer/features/player/components/left-controls.tsx index 3ee9399a..f7ec41cd 100644 --- a/src/renderer/features/player/components/left-controls.tsx +++ b/src/renderer/features/player/components/left-controls.tsx @@ -1,19 +1,22 @@ import React from 'react'; -import { Center } from '@mantine/core'; +import { Center, Group } from '@mantine/core'; +import { openContextModal } from '@mantine/modals'; import { motion, AnimatePresence, LayoutGroup } from 'framer-motion'; -import { RiArrowUpSLine, RiDiscLine } from 'react-icons/ri'; +import { RiArrowUpSLine, RiDiscLine, RiMore2Fill } from 'react-icons/ri'; import { generatePath, Link } from 'react-router-dom'; import styled from 'styled-components'; -import { Button, Text } from '/@/renderer/components'; +import { Button, DropdownMenu, Text } from '/@/renderer/components'; import { AppRoute } from '/@/renderer/router/routes'; import { useAppStoreActions, useAppStore, useCurrentSong } from '/@/renderer/store'; import { fadeIn } from '/@/renderer/styles'; +import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared'; +import { LibraryItem } from '/@/renderer/api/types'; const LeftControlsContainer = styled.div` display: flex; width: 100%; height: 100%; - margin-left: 1rem; + padding-left: 1rem; `; const ImageWrapper = styled.div` @@ -77,6 +80,44 @@ export const LeftControls = () => { const title = currentSong?.name; const artists = currentSong?.artists; + const isSongDefined = Boolean(currentSong?.id); + + const openAddToPlaylistModal = () => { + openContextModal({ + innerProps: { + songId: [currentSong?.id], + }, + modal: 'addToPlaylist', + size: 'md', + title: 'Add to playlist', + }); + }; + + const addToFavoritesMutation = useCreateFavorite(); + const removeFromFavoritesMutation = useDeleteFavorite(); + + const handleAddToFavorites = () => { + if (!isSongDefined || !currentSong) return; + + addToFavoritesMutation.mutate({ + query: { + id: [currentSong.id], + type: LibraryItem.SONG, + }, + }); + }; + + const handleRemoveFromFavorites = () => { + if (!isSongDefined || !currentSong) return; + + removeFromFavoritesMutation.mutate({ + query: { + id: [currentSong.id], + type: LibraryItem.SONG, + }, + }); + }; + return ( @@ -139,16 +180,45 @@ export const LeftControls = () => { - - {title || '—'} - + + {title || '—'} + + {isSongDefined && ( + + + + + + + Add to playlist + + + + Add to favorites + + + Remove from favorites + + + + )} + {artists?.map((artist, index) => ( @@ -157,7 +227,7 @@ export const LeftControls = () => { , @@ -167,7 +237,7 @@ export const LeftControls = () => { $link component={Link} overflow="hidden" - size="xs" + size="md" to={ artist.id ? generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { @@ -187,7 +257,7 @@ export const LeftControls = () => { $link component={Link} overflow="hidden" - size="xs" + size="md" to={ currentSong?.albumId ? generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { diff --git a/src/renderer/features/player/components/right-controls.tsx b/src/renderer/features/player/components/right-controls.tsx index 2be999d5..c38a2977 100644 --- a/src/renderer/features/player/components/right-controls.tsx +++ b/src/renderer/features/player/components/right-controls.tsx @@ -1,11 +1,28 @@ -import { Group } from '@mantine/core'; +import { Flex, Group } from '@mantine/core'; import { HiOutlineQueueList } from 'react-icons/hi2'; -import { RiVolumeUpFill, RiVolumeDownFill, RiVolumeMuteFill } from 'react-icons/ri'; +import { + RiVolumeUpFill, + RiVolumeDownFill, + RiVolumeMuteFill, + RiHeartLine, + RiHeartFill, +} from 'react-icons/ri'; import styled from 'styled-components'; -import { useAppStoreActions, useMuted, useSidebarStore, useVolume } from '/@/renderer/store'; +import { + useAppStoreActions, + useCurrentServer, + useCurrentSong, + useMuted, + useSetQueueFavorite, + useSidebarStore, + useVolume, +} from '/@/renderer/store'; import { useRightControls } from '../hooks/use-right-controls'; import { PlayerButton } from './player-button'; import { Slider } from './slider'; +import { LibraryItem, ServerType } from '/@/renderer/api/types'; +import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared'; +import { Rating } from '/@/renderer/components'; const RightControlsContainer = styled.div` display: flex; @@ -13,7 +30,6 @@ const RightControlsContainer = styled.div` justify-content: flex-end; width: 100%; height: 100%; - padding-right: 1rem; `; const VolumeSliderWrapper = styled.div` @@ -35,47 +51,141 @@ const MetadataStack = styled.div` export const RightControls = () => { const volume = useVolume(); const muted = useMuted(); + const server = useCurrentServer(); + const currentSong = useCurrentSong(); const { setSidebar } = useAppStoreActions(); const { rightExpanded: isQueueExpanded } = useSidebarStore(); const { handleVolumeSlider, handleVolumeSliderState, handleMute } = useRightControls(); + const addToFavoritesMutation = useCreateFavorite(); + const removeFromFavoritesMutation = useDeleteFavorite(); + const setFavorite = useSetQueueFavorite(); + + const handleAddToFavorites = () => { + if (!currentSong) return; + + addToFavoritesMutation.mutate( + { + query: { + id: [currentSong.id], + type: LibraryItem.SONG, + }, + }, + { + onSuccess: () => { + setFavorite([currentSong.id], true); + }, + }, + ); + }; + + const handleRemoveFromFavorites = () => { + if (!currentSong) return; + + removeFromFavoritesMutation.mutate( + { + query: { + id: [currentSong.id], + type: LibraryItem.SONG, + }, + }, + { + onSuccess: () => { + setFavorite([currentSong.id], false); + }, + }, + ); + }; + + const handleToggleFavorite = () => { + if (!currentSong) return; + + if (currentSong.userFavorite) { + handleRemoveFromFavorites(); + } else { + handleAddToFavorites(); + } + }; + + const isSongDefined = Boolean(currentSong?.id); + const showRating = isSongDefined && server?.type === ServerType.NAVIDROME; + return ( - - - } - tooltip={{ label: 'View queue', openDelay: 500 }} - variant="secondary" - onClick={() => setSidebar({ rightExpanded: !isQueueExpanded })} - /> - - - + + {showRating && ( + + + + )} + + - ) : volume > 50 ? ( - + currentSong?.userFavorite ? ( + ) : ( - + ) } - tooltip={{ label: muted ? 'Muted' : volume, openDelay: 500 }} + sx={{ + svg: { + fill: !currentSong?.userFavorite ? undefined : 'var(--primary-color) !important', + }, + }} + tooltip={{ + label: currentSong?.userFavorite ? 'Unfavorite' : 'Favorite', + openDelay: 500, + }} variant="secondary" - onClick={handleMute} + onClick={handleToggleFavorite} /> - } + tooltip={{ label: 'View queue', openDelay: 500 }} + variant="secondary" + onClick={() => setSidebar({ rightExpanded: !isQueueExpanded })} /> - - - + + + + + ) : volume > 50 ? ( + + ) : ( + + ) + } + tooltip={{ label: muted ? 'Muted' : volume, openDelay: 500 }} + variant="secondary" + onClick={handleMute} + /> + + + + + ); }; diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index e0ffdd7b..b8b80d10 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -659,6 +659,15 @@ export const usePlayerStore = create()( } } + const currentSongId = get().current.song?.id; + if (currentSongId && ids.includes(currentSongId)) { + set((state) => { + if (state.current.song) { + state.current.song.userFavorite = favorite; + } + }); + } + return foundUniqueIds; }, setMuted: (muted: boolean) => {