diff --git a/src/components/player/NowPlayingMiniView.tsx b/src/components/player/NowPlayingMiniView.tsx index 423b838..f49a54b 100644 --- a/src/components/player/NowPlayingMiniView.tsx +++ b/src/components/player/NowPlayingMiniView.tsx @@ -1,7 +1,9 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; +import _ from 'lodash'; import settings from 'electron-settings'; -import { ButtonToolbar, FlexboxGrid, Icon } from 'rsuite'; +import { ButtonToolbar, FlexboxGrid, Icon, Whisper, ControlLabel } from 'rsuite'; import { useHotkeys } from 'react-hotkeys-hook'; +import { useQuery } from 'react-query'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { toggleSelected, @@ -23,15 +25,32 @@ import { setPlaybackSetting, removeFromPlayQueue, setStar, + setPlayQueue, + appendPlayQueue, } from '../../redux/playQueueSlice'; import { resetPlayer, setStatus } from '../../redux/playerSlice'; import ListViewType from '../viewtypes/ListViewType'; import GenericPage from '../layout/GenericPage'; -import { StyledCheckbox, StyledIconButton } from '../shared/styled'; +import { + StyledButton, + StyledCheckbox, + StyledInputNumber, + StyledInputPicker, + StyledInputPickerContainer, + StyledPopover, +} from '../shared/styled'; import { MiniViewContainer } from './styled'; -import { getCurrentEntryList } from '../../shared/utils'; -import { star, unstar } from '../../api/api'; -import CustomTooltip from '../shared/CustomTooltip'; +import { errorMessages, getCurrentEntryList, isFailedResponse } from '../../shared/utils'; +import { getGenres, getRandomSongs, star, unstar } from '../../api/api'; +import { + AutoPlaylistButton, + ClearQueueButton, + MoveBottomButton, + MoveTopButton, + RemoveSelectedButton, + ShuffleButton, +} from '../shared/ToolbarButtons'; +import { notifyToast } from '../shared/toast'; const NowPlayingMiniView = () => { const tableRef = useRef(); @@ -39,6 +58,27 @@ const NowPlayingMiniView = () => { const playQueue = useAppSelector((state) => state.playQueue); const multiSelect = useAppSelector((state) => state.multiSelect); const config = useAppSelector((state) => state.config); + const [autoPlaylistTrackCount, setRandomPlaylistTrackCount] = useState( + Number(settings.getSync('randomPlaylistTrackCount')) + ); + const pickerContainerRef = useRef(null); + const autoPlaylistTriggerRef = useRef(); + const [autoPlaylistFromYear, setRandomPlaylistFromYear] = useState(0); + const [autoPlaylistToYear, setRandomPlaylistToYear] = useState(0); + const [randomPlaylistGenre, setRandomPlaylistGenre] = useState(''); + const [isLoadingRandom, setIsLoadingRandom] = useState(false); + + const { data: genres }: any = useQuery(['genreList'], async () => { + const res = await getGenres(); + const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc'); + return genresOrderedBySongCount.map((genre: any) => { + return { + label: `${genre.value} (${genre.songCount})`, + value: genre.value, + role: 'Genre', + }; + }); + }); useHotkeys( 'del', @@ -127,6 +167,60 @@ const NowPlayingMiniView = () => { } }; + const handlePlayRandom = async (action: 'play' | 'addNext' | 'addLater') => { + setIsLoadingRandom(true); + const res = await getRandomSongs({ + size: autoPlaylistTrackCount, + fromYear: autoPlaylistFromYear !== 0 ? autoPlaylistFromYear : undefined, + toYear: autoPlaylistToYear !== 0 ? autoPlaylistToYear : undefined, + genre: randomPlaylistGenre, + }); + + if (isFailedResponse(res)) { + autoPlaylistTriggerRef.current.close(); + return notifyToast('error', errorMessages(res)[0]); + } + + const cleanedSongs = res.song.filter((song: any) => { + // Remove invalid songs that may break the player + return song.bitRate && song.duration; + }); + + const difference = res.song.length - cleanedSongs.length; + + if (action === 'play') { + dispatch(setPlayQueue({ entries: cleanedSongs })); + dispatch(setStatus('PLAYING')); + notifyToast( + 'info', + `Playing ${cleanedSongs.length} ${ + difference !== 0 ? `(-${difference} invalid)` : '' + } song(s)` + ); + } else if (action === 'addLater') { + dispatch(appendPlayQueue({ entries: cleanedSongs, type: 'later' })); + if (playQueue.entry.length < 1) { + dispatch(setStatus('PLAYING')); + } + notifyToast( + 'info', + `Added ${cleanedSongs.length} ${difference !== 0 ? `(-${difference} invalid)` : ''} song(s)` + ); + } else { + dispatch(appendPlayQueue({ entries: cleanedSongs, type: 'next' })); + if (playQueue.entry.length < 1) { + dispatch(setStatus('PLAYING')); + } + notifyToast( + 'info', + `Added ${cleanedSongs.length} ${difference !== 0 ? `(-${difference} invalid)` : ''} song(s)` + ); + } + dispatch(fixPlayer2Index()); + setIsLoadingRandom(false); + return autoPlaylistTriggerRef.current.close(); + }; + const handleRowFavorite = async (rowData: any) => { if (!rowData.starred) { await star(rowData.id, 'music'); @@ -149,12 +243,11 @@ const NowPlayingMiniView = () => { padding="0px" header={ <> - + - } + { dispatch(clearPlayQueue()); dispatch(setStatus('PAUSED')); @@ -163,9 +256,8 @@ const NowPlayingMiniView = () => { setTimeout(() => dispatch(resetPlayer()), 200); }} /> - } + { if (playQueue.shuffle) { dispatch(shuffleInPlace()); @@ -174,60 +266,156 @@ const NowPlayingMiniView = () => { } }} /> - {multiSelect.selected.length > 0 && ( - <> - - } - onClick={() => { - dispatch(moveUp({ selectedEntries: multiSelect.selected })); - - if (playQueue.currentPlayer === 1) { - dispatch(fixPlayer2Index()); - } + + How many tracks? (1-500)* + { + settings.setSync('randomPlaylistTrackCount', Number(e)); + setRandomPlaylistTrackCount(Number(e)); }} /> - - - } - onClick={() => { - dispatch(moveDown({ selectedEntries: multiSelect.selected })); +
+ + + From year +
+ { + setRandomPlaylistFromYear(Number(e)); + }} + /> +
+
+ + To year +
+ setRandomPlaylistToYear(Number(e))} + /> +
+
+
+
+ Genre + + pickerContainerRef.current} + data={genres} + value={randomPlaylistGenre} + virtualized + onChange={(e: string) => setRandomPlaylistGenre(e)} + /> + +
+ + handlePlayRandom('addNext')} + loading={isLoadingRandom} + disabled={!(typeof autoPlaylistTrackCount === 'number')} + > + Add + (next) + + handlePlayRandom('addLater')} + loading={isLoadingRandom} + disabled={!(typeof autoPlaylistTrackCount === 'number')} + > + Add (later) + + + + handlePlayRandom('play')} + loading={isLoadingRandom} + disabled={!(typeof autoPlaylistTrackCount === 'number')} + > + + Play + + + + } + > + + autoPlaylistTriggerRef.current.state.isOverlayShown + ? autoPlaylistTriggerRef.current.close() + : autoPlaylistTriggerRef.current.open() + } + /> + + { + dispatch(moveUp({ selectedEntries: multiSelect.selected })); - if (playQueue.currentPlayer === 1) { - dispatch(fixPlayer2Index()); - } - }} - /> -
+ if (playQueue.currentPlayer === 1) { + dispatch(fixPlayer2Index()); + } + }} + /> + { + dispatch(moveDown({ selectedEntries: multiSelect.selected })); - - } - onClick={() => { - if (multiSelect.selected.length === playQueue.entry.length) { - // Clear the queue instead of removing individually - dispatch(clearPlayQueue()); - dispatch(setStatus('PAUSED')); - setTimeout(() => dispatch(resetPlayer()), 200); - } else { - dispatch(removeFromPlayQueue({ entries: multiSelect.selected })); - dispatch(clearSelected()); - if (playQueue.currentPlayer === 1) { - dispatch(fixPlayer2Index()); - } - } - }} - /> - - - )} + if (playQueue.currentPlayer === 1) { + dispatch(fixPlayer2Index()); + } + }} + /> + { + if (multiSelect.selected.length === playQueue.entry.length) { + // Clear the queue instead of removing individually + dispatch(clearPlayQueue()); + dispatch(setStatus('PAUSED')); + setTimeout(() => dispatch(resetPlayer()), 200); + } else { + dispatch(removeFromPlayQueue({ entries: multiSelect.selected })); + dispatch(clearSelected()); + if (playQueue.currentPlayer === 1) { + dispatch(fixPlayer2Index()); + } + } + }} + />
- { } /> + + { + dispatch(moveToTop({ selectedEntries: multiSelect.selected })); + + if (playQueue.currentPlayer === 1) { + dispatch(fixPlayer2Index()); + } + }} + /> + { + dispatch(moveToBottom({ selectedEntries: multiSelect.selected })); + + if (playQueue.currentPlayer === 1) { + dispatch(fixPlayer2Index()); + } + }} + /> + { + if (multiSelect.selected.length === playQueue.entry.length) { + // Clear the queue instead of removing individually + dispatch(clearPlayQueue()); + dispatch(setStatus('PAUSED')); + setTimeout(() => dispatch(resetPlayer()), 200); + } else { + dispatch(removeFromPlayQueue({ entries: multiSelect.selected })); + dispatch(clearSelected()); + if (playQueue.currentPlayer === 1) { + dispatch(fixPlayer2Index()); + } + } + }} + /> + } diff --git a/src/components/shared/ToolbarButtons.tsx b/src/components/shared/ToolbarButtons.tsx index ad59ecc..576874b 100644 --- a/src/components/shared/ToolbarButtons.tsx +++ b/src/components/shared/ToolbarButtons.tsx @@ -5,7 +5,7 @@ import { StyledButton, StyledIconButton } from './styled'; export const PlayButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -13,7 +13,7 @@ export const PlayButton = ({ ...rest }) => { export const PlayShuffleButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -21,7 +21,7 @@ export const PlayShuffleButton = ({ ...rest }) => { export const PlayAppendButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -29,7 +29,7 @@ export const PlayAppendButton = ({ ...rest }) => { export const PlayAppendNextButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -37,7 +37,7 @@ export const PlayAppendNextButton = ({ ...rest }) => { export const PlayShuffleAppendButton = ({ ...rest }) => { return ( - + } /> ); @@ -45,7 +45,7 @@ export const PlayShuffleAppendButton = ({ ...rest }) => { export const SaveButton = ({ text, ...rest }: any) => { return ( - + } {...rest} /> ); @@ -53,7 +53,7 @@ export const SaveButton = ({ text, ...rest }: any) => { export const EditButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -61,7 +61,7 @@ export const EditButton = ({ ...rest }) => { export const UndoButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -69,7 +69,7 @@ export const UndoButton = ({ ...rest }) => { export const DeleteButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -77,7 +77,7 @@ export const DeleteButton = ({ ...rest }) => { export const FavoriteButton = ({ isFavorite, ...rest }: any) => { return ( - + } @@ -89,7 +89,7 @@ export const FavoriteButton = ({ isFavorite, ...rest }: any) => { export const DownloadButton = ({ ...rest }) => { return ( - + } {...rest} /> ); @@ -97,7 +97,7 @@ export const DownloadButton = ({ ...rest }) => { export const ShuffleButton = ({ ...rest }) => { return ( - + @@ -107,7 +107,7 @@ export const ShuffleButton = ({ ...rest }) => { export const ClearQueueButton = ({ ...rest }) => { return ( - + @@ -133,11 +133,53 @@ export const RefreshButton = ({ ...rest }) => { ); }; -export const AutoPlaylistButton = ({ ...rest }) => { +export const AutoPlaylistButton = ({ noText, ...rest }: any) => { return ( - - - Auto playlist - + + + + {!noText && 'Auto playlist'} + + + ); +}; + +export const MoveUpButton = ({ ...rest }) => { + return ( + + } tabIndex={0} {...rest} /> + + ); +}; + +export const MoveDownButton = ({ ...rest }) => { + return ( + + } tabIndex={0} {...rest} /> + + ); +}; + +export const MoveTopButton = ({ ...rest }) => { + return ( + + } tabIndex={0} {...rest} /> + + ); +}; + +export const MoveBottomButton = ({ ...rest }) => { + return ( + + } tabIndex={0} {...rest} /> + + ); +}; + +export const RemoveSelectedButton = ({ ...rest }) => { + return ( + + } tabIndex={0} {...rest} /> + ); };