From 518fb4c9f03339bf04e11c2f45f9a2eb2e90f5b2 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 1 Oct 2021 19:00:32 -0700 Subject: [PATCH] Add genre list page - - Add genre sort to getAllAlbums api - Add genres to sortType picker - Add router query param for sortType on album list - Remove deprecated LibraryView page --- src/App.tsx | 6 +- src/api/api.ts | 33 ++++- src/components/library/AlbumList.tsx | 76 ++++++---- src/components/library/GenreList.tsx | 93 ++++++++++++ src/components/library/LibraryView.tsx | 133 ------------------ .../ConfigPanels/LookAndFeelConfig.tsx | 29 +++- src/components/settings/ListViewColumns.ts | 54 +++++++ src/components/shared/setDefaultSettings.ts | 45 ++++++ 8 files changed, 303 insertions(+), 166 deletions(-) create mode 100644 src/components/library/GenreList.tsx delete mode 100644 src/components/library/LibraryView.tsx diff --git a/src/App.tsx b/src/App.tsx index 77e6523..11e5f89 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,13 +10,13 @@ import NowPlayingView from './components/player/NowPlayingView'; import Login from './components/settings/Login'; import StarredView from './components/starred/StarredView'; import Dashboard from './components/dashboard/Dashboard'; -import LibraryView from './components/library/LibraryView'; import PlayerBar from './components/player/PlayerBar'; import AlbumView from './components/library/AlbumView'; import ArtistView from './components/library/ArtistView'; import setDefaultSettings from './components/shared/setDefaultSettings'; import AlbumList from './components/library/AlbumList'; import ArtistList from './components/library/ArtistList'; +import GenreList from './components/library/GenreList'; import { MockFooter } from './components/settings/styled'; import { defaultDark, defaultLight } from './styles/styledTheme'; import { useAppSelector } from './redux/hooks'; @@ -66,10 +66,10 @@ const App = () => { - + - + diff --git a/src/api/api.ts b/src/api/api.ts index 37f0426..61a8d06 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -281,9 +281,14 @@ export const getAllAlbums = ( const albums: any = api .get(`/getAlbumList2`, { params: { - type: sortType, + type: sortType.match('alphabeticalByName|alphabeticalByArtist|frequent|newest|recent') + ? sortType + : 'byGenre', size: 500, offset, + genre: sortType.match('alphabeticalByName|alphabeticalByArtist|frequent|newest|recent') + ? undefined + : sortType, }, }) .then((res) => { @@ -654,3 +659,29 @@ export const clearPlaylist = async (playlistId: string) => { return data; }; + +export const getGenres = async () => { + const { data } = await api.get(`/getGenres`); + + return (data.genres.genre || []).map((entry: any, index: any) => ({ + ...entry, + name: entry.value, + index, + uniqueId: nanoid(), + })); +}; + +// return { +// ...data.artist, +// image: getCoverArtUrl(data.artist, coverArtSize), +// type: 'artist', +// album: (data.artist.album || []).map((entry: any, index: any) => ({ +// ...entry, +// albumId: entry.id, +// type: 'album', +// image: getCoverArtUrl(entry, coverArtSize), +// starred: entry.starred || undefined, +// index, +// uniqueId: nanoid(), +// })), +// }; diff --git a/src/components/library/AlbumList.tsx b/src/components/library/AlbumList.tsx index 325cd77..ee3af0c 100644 --- a/src/components/library/AlbumList.tsx +++ b/src/components/library/AlbumList.tsx @@ -1,14 +1,15 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import _ from 'lodash'; import settings from 'electron-settings'; import { ButtonToolbar } from 'rsuite'; import { useQuery, useQueryClient } from 'react-query'; -import { useHistory } from 'react-router'; +import { useHistory } from 'react-router-dom'; import GridViewType from '../viewtypes/GridViewType'; import ListViewType from '../viewtypes/ListViewType'; import useSearchQuery from '../../hooks/useSearchQuery'; import GenericPageHeader from '../layout/GenericPageHeader'; import GenericPage from '../layout/GenericPage'; -import { getAlbumsDirect, getAllAlbums } from '../../api/api'; +import { getAlbumsDirect, getAllAlbums, getGenres } from '../../api/api'; import PageLoader from '../loader/PageLoader'; import { useAppDispatch } from '../../redux/hooks'; import { @@ -19,22 +20,25 @@ import { } from '../../redux/multiSelectSlice'; import { StyledInputPicker } from '../shared/styled'; import { RefreshButton } from '../shared/ToolbarButtons'; +import useRouterQuery from '../../hooks/useRouterQuery'; const ALBUM_SORT_TYPES = [ - { label: 'A-Z (Name)', value: 'alphabeticalByName' }, - { label: 'A-Z (Artist)', value: 'alphabeticalByArtist' }, - { label: 'Most Played', value: 'frequent' }, - { label: 'Newly Added', value: 'newest' }, - { label: 'Random', value: 'random' }, - { label: 'Recently Played', value: 'recent' }, + { label: 'A-Z (Name)', value: 'alphabeticalByName', role: 'Default' }, + { label: 'A-Z (Artist)', value: 'alphabeticalByArtist', role: 'Default' }, + { label: 'Most Played', value: 'frequent', role: 'Default' }, + { label: 'Newly Added', value: 'newest', role: 'Default' }, + { label: 'Random', value: 'random', role: 'Default' }, + { label: 'Recently Played', value: 'recent', role: 'Default' }, ]; const AlbumList = () => { const dispatch = useAppDispatch(); const history = useHistory(); + const query = useRouterQuery(); const queryClient = useQueryClient(); const [isRefreshing, setIsRefreshing] = useState(false); - const [sortBy, setSortBy] = useState('random'); + const [sortBy, setSortBy] = useState(query.get('sortType') || 'random'); + const [sortTypes, setSortTypes] = useState(); const [offset, setOffset] = useState(0); const [viewType, setViewType] = useState(settings.getSync('albumViewType')); const { isLoading, isError, data: albums, error }: any = useQuery( @@ -49,9 +53,30 @@ const AlbumList = () => { staleTime: Infinity, // Only allow manual refresh } ); + const { data: genres }: any = useQuery( + ['genreList'], + async () => { + const res = await getGenres(); + return res.map((genre: any) => { + if (genre.albumCount !== 0) { + return { + label: `${genre.value} (${genre.albumCount})`, + value: genre.value, + role: 'Genre', + }; + } + return null; + }); + }, + { refetchOnWindowFocus: false } + ); const [searchQuery, setSearchQuery] = useState(''); const filteredData = useSearchQuery(searchQuery, albums, ['name', 'artist', 'genre', 'year']); + useEffect(() => { + setSortTypes(_.compact(_.concat(ALBUM_SORT_TYPES, genres))); + }, [genres]); + let timeout: any = null; const handleRowClick = (e: any, rowData: any) => { if (timeout === null) { @@ -84,6 +109,7 @@ const AlbumList = () => { return ( { } subsidetitle={ - { - await queryClient.cancelQueries(['albumList', offset, sortBy]); - setSearchQuery(''); - setOffset(0); - setSortBy(value); - }} - /> + <> + { + await queryClient.cancelQueries(['albumList', offset, sortBy]); + setSearchQuery(''); + setOffset(0); + setSortBy(value); + }} + /> + } searchQuery={searchQuery} handleSearch={(e: any) => setSearchQuery(e)} @@ -138,7 +167,6 @@ const AlbumList = () => { disabledContextMenuOptions={['moveSelectedTo', 'removeFromCurrent', 'deletePlaylist']} /> )} - {!isLoading && !isError && viewType === 'grid' && ( { + const dispatch = useAppDispatch(); + const history = useHistory(); + const { isLoading, isError, data: genres, error }: any = useQuery(['genreList'], () => + getGenres() + ); + const [searchQuery, setSearchQuery] = useState(''); + const filteredData = useSearchQuery(searchQuery, genres, ['value']); + + let timeout: any = null; + const handleRowClick = (e: any, rowData: any) => { + if (timeout === null) { + timeout = window.setTimeout(() => { + timeout = null; + + if (e.ctrlKey) { + dispatch(toggleSelected(rowData)); + } else if (e.shiftKey) { + dispatch(setRangeSelected(rowData)); + dispatch(toggleRangeSelected(searchQuery !== '' ? filteredData : genres)); + } + }, 100); + } + }; + + const handleRowDoubleClick = (rowData: any) => { + window.clearTimeout(timeout); + timeout = null; + + dispatch(clearSelected()); + history.push(`/library/album?sortType=${rowData.value}`); + }; + + return ( + setSearchQuery(e)} + clearSearchQuery={() => setSearchQuery('')} + viewTypeSetting="genre" + showSearchBar + /> + } + > + {isLoading && } + {isError &&
Error: {error}
} + {!isLoading && !isError && ( + + )} +
+ ); +}; + +export default GenreList; diff --git a/src/components/library/LibraryView.tsx b/src/components/library/LibraryView.tsx deleted file mode 100644 index 312fe88..0000000 --- a/src/components/library/LibraryView.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useState } from 'react'; -import { Nav, SelectPicker } from 'rsuite'; -import { useQuery } from 'react-query'; -import VisibilitySensor from 'react-visibility-sensor'; -import settings from 'electron-settings'; -import _ from 'lodash'; -import useSearchQuery from '../../hooks/useSearchQuery'; -import { getAlbumsDirect, getArtists } from '../../api/api'; -import GenericPage from '../layout/GenericPage'; -import GenericPageHeader from '../layout/GenericPageHeader'; -import ArtistList from './ArtistList'; -import PageLoader from '../loader/PageLoader'; - -const ALBUM_SORT_TYPES = [ - { label: 'A-Z (Name)', value: 'alphabeticalByName' }, - { - label: 'A-Z (Artist)', - value: 'alphabeticalByArtist', - }, - { label: 'Most Played', value: 'frequent' }, - { label: 'Newly Added', value: 'newest' }, - { label: 'Recently Played', value: 'recent' }, -]; - -const LibraryView = () => { - const [currentPage, setCurrentPage] = useState('albums'); - const [sortBy, setSortBy] = useState(null); - const [data, setData] = useState([]); - const [offset, setOffset] = useState(0); - const [viewType, setViewType] = useState(settings.getSync('albumViewType')); - const { isLoading: isLoadingArtists, data: artists }: any = useQuery('artists', getArtists, { - enabled: currentPage === 'artists', - }); - const [searchQuery, setSearchQuery] = useState(''); - const filteredData = useSearchQuery( - searchQuery, - currentPage === 'artists' ? artists : currentPage === 'albums' ? data : data, - ['name', 'artist'] - ); - - const onChange = (isVisible: boolean) => { - if (isVisible) { - setOffset(offset + 50); - - setTimeout(async () => { - const res = await getAlbumsDirect({ - type: sortBy || 'random', - size: 50, - offset, - }); - - const combinedData = data.concat(res); - - // Ensure that no duplicates are added in the case of random fetching - const uniqueCombinedData = _.uniqBy(combinedData, (e: any) => e.id); - - return setData(uniqueCombinedData); - }, 0); - } - }; - - const handleNavClick = (e: React.SetStateAction) => { - setData([]); - setOffset(0); - setCurrentPage(e); - }; - - return ( - - Albums - Artists - Genres - - } - subsidetitle={ - currentPage === 'albums' ? ( - { - setData([]); - setOffset(0); - setSortBy(value); - }} - /> - ) : undefined - } - searchQuery={searchQuery} - handleSearch={(e: any) => setSearchQuery(e)} - clearSearchQuery={() => setSearchQuery('')} - showViewTypeButtons={currentPage === 'albums'} - viewTypeSetting="album" - showSearchBar - handleListClick={() => setViewType('list')} - handleGridClick={() => setViewType('grid')} - /> - } - > - {isLoadingArtists && } - - {artists && ( - <> - {currentPage === 'artists' && ( - - )} - - )} - - {data.length !== 1 && searchQuery === '' && currentPage === 'albums' && ( - -
- -
-
- )} -
- ); -}; - -export default LibraryView; diff --git a/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx b/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx index f139d53..0d56f99 100644 --- a/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx +++ b/src/components/settings/ConfigPanels/LookAndFeelConfig.tsx @@ -22,6 +22,8 @@ import { playlistColumnList, artistColumnPicker, artistColumnList, + genreColumnPicker, + genreColumnList, } from '../ListViewColumns'; const LookAndFeelConfig = () => { @@ -33,11 +35,13 @@ const LookAndFeelConfig = () => { const playlistCols: any = settings.getSync('playlistListColumns'); const artistCols: any = settings.getSync('artistListColumns'); const miniCols: any = settings.getSync('miniListColumns'); + const genreCols: any = settings.getSync('genreListColumns'); const currentSongColumns = songCols?.map((column: any) => column.label) || []; const currentAlbumColumns = albumCols?.map((column: any) => column.label) || []; const currentPlaylistColumns = playlistCols?.map((column: any) => column.label) || []; const currentArtistColumns = artistCols?.map((column: any) => column.label) || []; const currentMiniColumns = miniCols?.map((column: any) => column.label) || []; + const currentGenreColumns = genreCols?.map((column: any) => column.label) || []; return ( @@ -88,11 +92,12 @@ const LookAndFeelConfig = () => { activeKey={currentLAFTab} onSelect={(e) => setCurrentLAFTab(e)} > - Song List - Album List - Playlist List - Artist List - Miniplayer List + Songs + Albums + Playlists + Artists + Genres + Miniplayer {currentLAFTab === 'songList' && ( { /> )} + {currentLAFTab === 'genreList' && ( + + )} + {currentLAFTab === 'miniList' && ( { }, ]); } + + if (force || !settings.hasSync('genreListFontSize')) { + settings.setSync('genreListFontSize', '14'); + } + + if (force || !settings.hasSync('genreListRowHeight')) { + settings.setSync('genreListRowHeight', '50'); + } + + if (force || !settings.hasSync('genreListColumns')) { + settings.setSync('genreListColumns', [ + { + id: '#', + dataKey: 'index', + alignment: 'center', + resizable: true, + width: 50, + label: '#', + }, + { + id: 'Name', + dataKey: 'name', + alignment: 'left', + resizable: true, + width: 300, + label: 'Name', + }, + { + id: 'Album Count', + dataKey: 'albumCount', + alignment: 'left', + resizable: true, + width: 100, + label: 'Album Count', + }, + { + id: 'Song Count', + dataKey: 'songCount', + alignment: 'left', + resizable: true, + width: 100, + label: 'Song Count', + }, + ]); + } }; export default setDefaultSettings;