From debdb92dcf6d487d64e6eccb6a656527de33f8f0 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 21 May 2023 07:33:22 -0700 Subject: [PATCH] Add shuffle all feature --- .../player/components/center-controls.tsx | 21 ++ .../player/components/player-button.tsx | 54 ++-- .../player/components/shuffle-all-modal.tsx | 262 ++++++++++++++++++ 3 files changed, 314 insertions(+), 23 deletions(-) create mode 100644 src/renderer/features/player/components/shuffle-all-modal.tsx diff --git a/src/renderer/features/player/components/center-controls.tsx b/src/renderer/features/player/components/center-controls.tsx index ec65e5c0..53f2f5b4 100644 --- a/src/renderer/features/player/components/center-controls.tsx +++ b/src/renderer/features/player/components/center-controls.tsx @@ -1,9 +1,11 @@ import { useEffect, useState } from 'react'; import { useHotkeys } from '@mantine/hooks'; +import { useQueryClient } from '@tanstack/react-query'; import formatDuration from 'format-duration'; import isElectron from 'is-electron'; import { IoIosPause } from 'react-icons/io'; import { + RiMenuAddFill, RiPlayFill, RiRepeat2Line, RiRepeatOneLine, @@ -34,6 +36,8 @@ import { } from '/@/renderer/store/settings.store'; import { PlayerStatus, PlaybackType, PlayerShuffle, PlayerRepeat } from '/@/renderer/types'; import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider'; +import { openShuffleAllModal } from './shuffle-all-modal'; +import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; interface CenterControlsProps { playersRef: any; @@ -72,6 +76,7 @@ const SliderWrapper = styled.div` `; export const CenterControls = ({ playersRef }: CenterControlsProps) => { + const queryClient = useQueryClient(); const [isSeeking, setIsSeeking] = useState(false); const currentSong = useCurrentSong(); const songDuration = currentSong?.duration; @@ -99,6 +104,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { handlePause, handlePlay, } = useCenterControls({ playersRef }); + const handlePlayQueueAdd = usePlayQueueAdd(); const currentTime = useCurrentTime(); const currentPlayerRef = player === 1 ? player1 : player2; @@ -237,6 +243,21 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { variant="tertiary" onClick={handleToggleRepeat} /> + + } + tooltip={{ + label: 'Shuffle all', + openDelay: 500, + }} + variant="tertiary" + onClick={() => + openShuffleAllModal({ + handlePlayQueueAdd, + queryClient, + }) + } + /> diff --git a/src/renderer/features/player/components/player-button.tsx b/src/renderer/features/player/components/player-button.tsx index 183a4972..60768d7e 100644 --- a/src/renderer/features/player/components/player-button.tsx +++ b/src/renderer/features/player/components/player-button.tsx @@ -1,5 +1,5 @@ /* stylelint-disable no-descending-specificity */ -import type { ComponentPropsWithoutRef, ReactNode } from 'react'; +import { ComponentPropsWithoutRef, forwardRef, ReactNode } from 'react'; import type { TooltipProps, UnstyledButtonProps } from '@mantine/core'; import { UnstyledButton } from '@mantine/core'; import { motion } from 'framer-motion'; @@ -118,33 +118,41 @@ const StyledPlayerButton = styled(UnstyledButton)` : ButtonTertiaryVariant}; `; -export const PlayerButton = ({ tooltip, variant, icon, ...rest }: PlayerButtonProps) => { - if (tooltip) { - return ( - - - ( + ({ tooltip, variant, icon, ...rest }: PlayerButtonProps, ref) => { + if (tooltip) { + return ( + + - {icon} - - - - ); - } + + {icon} + + + + ); + } - return ( - - - {icon} - - - ); -}; + + {icon} + + + ); + }, +); PlayerButton.defaultProps = { $isActive: false, diff --git a/src/renderer/features/player/components/shuffle-all-modal.tsx b/src/renderer/features/player/components/shuffle-all-modal.tsx new file mode 100644 index 00000000..5c477bf9 --- /dev/null +++ b/src/renderer/features/player/components/shuffle-all-modal.tsx @@ -0,0 +1,262 @@ +import { useMemo } from 'react'; +import { Divider, Group, Stack } from '@mantine/core'; +import { closeAllModals, openModal } from '@mantine/modals'; +import { QueryClient } from '@tanstack/react-query'; +import merge from 'lodash/merge'; +import { RiAddBoxFill, RiPlayFill, RiAddCircleFill } from 'react-icons/ri'; +import { Button, Checkbox, NumberInput, Select } from '/@/renderer/components'; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import { immer } from 'zustand/middleware/immer'; +import { + GenreListResponse, + RandomSongListQuery, + MusicFolderListResponse, + ServerType, +} from '/@/renderer/api/types'; +import { api } from '/@/renderer/api'; +import { useAuthStore } from '/@/renderer/store'; +import { queryKeys } from '/@/renderer/api/query-keys'; +import { Play, PlayQueueAddOptions, ServerListItem } from '/@/renderer/types'; + +interface ShuffleAllSlice extends RandomSongListQuery { + actions: { + setStore: (data: Partial) => void; + }; + enableMaxYear: boolean; + enableMinYear: boolean; +} + +const useShuffleAllStore = create()( + persist( + immer((set, get) => ({ + actions: { + setStore: (data) => { + set({ ...get(), ...data }); + }, + }, + enableMaxYear: false, + enableMinYear: false, + genre: '', + maxYear: 2020, + minYear: 2000, + musicFolder: '', + songCount: 100, + })), + { + merge: (persistedState, currentState) => merge(currentState, persistedState), + name: 'store_shuffle_all', + version: 1, + }, + ), +); + +export const useShuffleAllStoreActions = () => useShuffleAllStore((state) => state.actions); + +interface ShuffleAllModalProps { + genres: GenreListResponse | undefined; + handlePlayQueueAdd: ((options: PlayQueueAddOptions) => void) | undefined; + musicFolders: MusicFolderListResponse | undefined; + queryClient: QueryClient; + server: ServerListItem | null; +} + +export const ShuffleAllModal = ({ + handlePlayQueueAdd, + queryClient, + server, + genres, + musicFolders, +}: ShuffleAllModalProps) => { + const { genre, limit, maxYear, minYear, enableMaxYear, enableMinYear, musicFolderId } = + useShuffleAllStore(); + const { setStore } = useShuffleAllStoreActions(); + + const handlePlay = async (playType: Play) => { + const res = await queryClient.fetchQuery({ + cacheTime: 0, + queryFn: ({ signal }) => + api.controller.getRandomSongList({ + apiClientProps: { + server, + signal, + }, + query: { + genre: genre || undefined, + limit, + maxYear: enableMaxYear ? maxYear || undefined : undefined, + minYear: enableMinYear ? minYear || undefined : undefined, + musicFolderId: musicFolderId || undefined, + }, + }), + queryKey: queryKeys.songs.randomSongList(server?.id), + staleTime: 0, + }); + + handlePlayQueueAdd?.({ + byData: res?.items || [], + playType, + }); + + closeAllModals(); + }; + + const genreData = useMemo(() => { + if (!genres) return []; + + return genres.items.map((genre) => { + const value = + server?.type === ServerType.NAVIDROME || server?.type === ServerType.SUBSONIC + ? genre.name + : genre.id; + return { + label: genre.name, + value, + }; + }); + }, [genres, server?.type]); + + const musicFolderData = useMemo(() => { + if (!musicFolders) return []; + return musicFolders.items.map((musicFolder) => ({ + label: musicFolder.name, + value: String(musicFolder.id), + })); + }, [musicFolders]); + + return ( + + setStore({ limit: e ? Number(e) : 0 })} + /> + + setStore({ enableMinYear: e.currentTarget.checked })} + /> + } + value={minYear} + onChange={(e) => setStore({ minYear: e ? Number(e) : 0 })} + /> + + setStore({ enableMaxYear: e.currentTarget.checked })} + /> + } + value={maxYear} + onChange={(e) => setStore({ maxYear: e ? Number(e) : 0 })} + /> + + { + console.log('e :>> ', e); + setStore({ musicFolderId: e ? String(e) : '' }); + }} + /> + + + + + + + + ); +}; + +export const openShuffleAllModal = async ( + props: Pick, +) => { + const server = useAuthStore.getState().currentServer; + + const genres = await props.queryClient.fetchQuery({ + cacheTime: 1000 * 60 * 60 * 4, + queryFn: ({ signal }) => + api.controller.getGenreList({ + apiClientProps: { + server, + signal, + }, + query: null, + }), + queryKey: queryKeys.genres.list(server?.id), + staleTime: 1000 * 60 * 5, + }); + + const musicFolders = await props.queryClient.fetchQuery({ + cacheTime: 1000 * 60 * 60 * 4, + queryFn: ({ signal }) => + api.controller.getMusicFolderList({ + apiClientProps: { + server, + signal, + }, + }), + queryKey: queryKeys.musicFolders.list(server?.id), + staleTime: 1000 * 60 * 5, + }); + + openModal({ + children: ( + + ), + size: 'sm', + title: 'Shuffle all', + }); +};