Browse Source

Add types, normalize API returns

master
jeffvli 3 years ago
committed by Jeff
parent
commit
4af81d4404
  1. 14
      src/__tests__/App.test.tsx
  2. 802
      src/api/api.ts
  3. 108
      src/api/types.ts
  4. 10
      src/components/card/Card.tsx
  5. 56
      src/components/dashboard/Dashboard.tsx
  6. 16
      src/components/library/AlbumList.tsx
  7. 4
      src/components/library/AlbumView.tsx
  8. 4
      src/components/library/ArtistList.tsx
  9. 22
      src/components/library/ArtistView.tsx
  10. 10
      src/components/library/FolderList.tsx
  11. 6
      src/components/library/GenreList.tsx
  12. 29
      src/components/player/NowPlayingMiniView.tsx
  13. 28
      src/components/player/NowPlayingView.tsx
  14. 9
      src/components/playlist/PlaylistList.tsx
  15. 4
      src/components/playlist/PlaylistView.tsx
  16. 4
      src/components/search/SearchView.tsx
  17. 46
      src/components/settings/ListViewColumns.ts
  18. 26
      src/components/shared/ContextMenu.tsx
  19. 10
      src/components/starred/StarredView.tsx

14
src/__tests__/App.test.tsx

@ -220,7 +220,7 @@ const configState: ConfigPage = {
}, },
{ {
id: 'Title', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
flexGrow: 5, flexGrow: 5,
label: 'Title', label: 'Title',
@ -276,11 +276,11 @@ const configState: ConfigPage = {
label: 'CoverArt', label: 'CoverArt',
}, },
{ {
id: 'Name', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
flexGrow: 5, flexGrow: 5,
label: 'Name', label: 'Title',
}, },
{ {
id: 'Albums', id: 'Albums',
@ -311,11 +311,11 @@ const configState: ConfigPage = {
label: '#', label: '#',
}, },
{ {
id: 'Name', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
flexGrow: 5, flexGrow: 5,
label: 'Name', label: 'Tame',
}, },
{ {
id: 'Albums', id: 'Albums',

802
src/api/api.ts

File diff suppressed because it is too large

108
src/api/types.ts

@ -3,6 +3,112 @@ export enum Server {
Jellyfin = 'jellyfin', Jellyfin = 'jellyfin',
} }
export type ServerType = Server.Subsonic | Server.Jellyfin; export enum Item {
Album = 'album',
Artist = 'artist',
Folder = 'folder',
Genre = 'genre',
Music = 'music',
Playlist = 'playlist',
}
export type ServerType = Server.Subsonic | Server.Jellyfin;
export type APIEndpoints = 'getPlaylist' | 'getPlaylists'; export type APIEndpoints = 'getPlaylist' | 'getPlaylists';
export interface Album {
id: string;
title: string;
isDir?: boolean;
albumId: string;
artist?: string;
artistId?: string;
songCount: number;
duration: number;
created: string;
year?: number;
genre?: string;
image: string;
starred?: string;
type: Item.Album;
uniqueId: string;
song?: Song[];
}
export interface Artist {
id: string;
title: string;
albumCount: number;
image: string;
starred?: string;
type: Item.Artist;
uniqueId: string;
album: Album[];
}
export interface ArtistInfo {
biography?: string;
lastFmUrl?: string;
imageUrl?: string;
similarArtist?: Artist[];
}
export interface Folder {
id: string;
title: string;
isDir?: boolean;
image: string;
type: Item.Folder;
uniqueId: string;
}
export interface Genre {
id: string;
title: string;
songCount?: number;
albumCount?: number;
type: Item.Genre;
uniqueId: string;
}
export interface Playlist {
id: string;
title: string;
comment?: string;
owner: string;
public?: boolean;
songCount: number;
duration: number;
created: string;
changed: string;
image: string;
type: Item.Playlist;
uniqueId: string;
song?: Song[];
}
export interface Song {
id: string;
parent?: string;
title: string;
isDir?: boolean;
album: string;
albumId?: string;
artist: string;
artistId?: string;
track?: number;
year?: number;
genre?: string;
size: number;
contentType?: string;
suffix?: string;
duration?: number;
bitRate?: number;
path?: string;
playCount?: number;
discNumber?: number;
created: string;
streamUrl: string;
image: string;
starred?: string;
type: Item.Music;
uniqueId: string;
}

10
src/components/card/Card.tsx

@ -2,7 +2,7 @@ import React from 'react';
import { Icon } from 'rsuite'; import { Icon } from 'rsuite';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import cacheImage from '../shared/cacheImage'; import cacheImage from '../shared/cacheImage';
import { getAlbum, getPlaylist, getAllArtistSongs } from '../../api/api'; import { getAlbum, getPlaylist, getArtistSongs } from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
appendPlayQueue, appendPlayQueue,
@ -76,7 +76,7 @@ const Card = ({
} }
if (playClick.type === 'album') { if (playClick.type === 'album') {
const res = await getAlbum(playClick.id); const res = await getAlbum({ id: playClick.id });
const songs = filterPlayQueue(config.playback.filters, res.song); const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -92,7 +92,7 @@ const Card = ({
} }
if (playClick.type === 'artist') { if (playClick.type === 'artist') {
const res = await getAllArtistSongs(playClick.id); const res = await getArtistSongs({ id: playClick.id });
const songs = filterPlayQueue(config.playback.filters, res); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -122,7 +122,7 @@ const Card = ({
} }
if (playClick.type === 'album') { if (playClick.type === 'album') {
const res = await getAlbum(playClick.id); const res = await getAlbum({ id: playClick.id });
const songs = filterPlayQueue(config.playback.filters, res.song); const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -134,7 +134,7 @@ const Card = ({
} }
if (playClick.type === 'artist') { if (playClick.type === 'artist') {
const res = await getAllArtistSongs(playClick.id); const res = await getArtistSongs({ id: playClick.id });
const songs = filterPlayQueue(config.playback.filters, res); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {

56
src/components/dashboard/Dashboard.tsx

@ -28,22 +28,22 @@ const Dashboard = () => {
const { isLoading: isLoadingRecent, data: recentAlbums }: any = useQuery( const { isLoading: isLoadingRecent, data: recentAlbums }: any = useQuery(
['recentAlbums', musicFolder], ['recentAlbums', musicFolder],
() => getAlbums({ type: 'recent', size: 20, musicFolderId: musicFolder }) () => getAlbums({ type: 'recent', size: 20, offset: 0, musicFolderId: musicFolder })
); );
const { isLoading: isLoadingNewest, data: newestAlbums }: any = useQuery( const { isLoading: isLoadingNewest, data: newestAlbums }: any = useQuery(
['newestAlbums', musicFolder], ['newestAlbums', musicFolder],
() => getAlbums({ type: 'newest', size: 20, musicFolderId: musicFolder }) () => getAlbums({ type: 'newest', size: 20, offset: 0, musicFolderId: musicFolder })
); );
const { isLoading: isLoadingRandom, data: randomAlbums }: any = useQuery( const { isLoading: isLoadingRandom, data: randomAlbums }: any = useQuery(
['randomAlbums', musicFolder], ['randomAlbums', musicFolder],
() => getAlbums({ type: 'random', size: 20, musicFolderId: musicFolder }) () => getAlbums({ type: 'random', size: 20, offset: 0, musicFolderId: musicFolder })
); );
const { isLoading: isLoadingFrequent, data: frequentAlbums }: any = useQuery( const { isLoading: isLoadingFrequent, data: frequentAlbums }: any = useQuery(
['frequentAlbums', musicFolder], ['frequentAlbums', musicFolder],
() => getAlbums({ type: 'frequent', size: 20, musicFolderId: musicFolder }) () => getAlbums({ type: 'frequent', size: 20, offset: 0, musicFolderId: musicFolder })
); );
const handleFavorite = async (rowData: any) => { const handleFavorite = async (rowData: any) => {
@ -51,33 +51,33 @@ const Dashboard = () => {
await star(rowData.id, 'album'); await star(rowData.id, 'album');
dispatch(setStar({ id: [rowData.id], type: 'star' })); dispatch(setStar({ id: [rowData.id], type: 'star' }));
queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = Date.now(); oldData[index].starred = Date.now();
}); });
return oldData; return oldData;
}); });
queryClient.setQueryData(['newestAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['newestAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = Date.now(); oldData[index].starred = Date.now();
}); });
return oldData; return oldData;
}); });
queryClient.setQueryData(['randomAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['randomAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = Date.now(); oldData[index].starred = Date.now();
}); });
return oldData; return oldData;
}); });
queryClient.setQueryData(['frequentAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['frequentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = Date.now(); oldData[index].starred = Date.now();
}); });
return oldData; return oldData;
@ -86,33 +86,33 @@ const Dashboard = () => {
await unstar(rowData.id, 'album'); await unstar(rowData.id, 'album');
dispatch(setStar({ id: [rowData.id], type: 'unstar' })); dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = undefined; oldData[index].starred = undefined;
}); });
return oldData; return oldData;
}); });
queryClient.setQueryData(['newestAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['newestAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = undefined; oldData[index].starred = undefined;
}); });
return oldData; return oldData;
}); });
queryClient.setQueryData(['randomAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['randomAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = undefined; oldData[index].starred = undefined;
}); });
return oldData; return oldData;
}); });
queryClient.setQueryData(['frequentAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['frequentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
oldData.album[index].starred = undefined; oldData[index].starred = undefined;
}); });
return oldData; return oldData;
@ -134,10 +134,10 @@ const Dashboard = () => {
<> <>
<ScrollingMenu <ScrollingMenu
title="Recently Played" title="Recently Played"
data={recentAlbums.album} data={recentAlbums}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{
@ -158,10 +158,10 @@ const Dashboard = () => {
<ScrollingMenu <ScrollingMenu
title="Recently Added" title="Recently Added"
data={newestAlbums.album} data={newestAlbums}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{
@ -182,10 +182,10 @@ const Dashboard = () => {
<ScrollingMenu <ScrollingMenu
title="Random" title="Random"
data={randomAlbums.album} data={randomAlbums}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{
@ -206,10 +206,10 @@ const Dashboard = () => {
<ScrollingMenu <ScrollingMenu
title="Most Played" title="Most Played"
data={frequentAlbums.album} data={frequentAlbums}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{

16
src/components/library/AlbumList.tsx

@ -9,7 +9,7 @@ import ListViewType from '../viewtypes/ListViewType';
import useSearchQuery from '../../hooks/useSearchQuery'; import useSearchQuery from '../../hooks/useSearchQuery';
import GenericPageHeader from '../layout/GenericPageHeader'; import GenericPageHeader from '../layout/GenericPageHeader';
import GenericPage from '../layout/GenericPage'; import GenericPage from '../layout/GenericPage';
import { getAlbumsDirect, getAllAlbums, getGenres, star, unstar } from '../../api/api'; import { getAlbums, getGenres, star, unstar } from '../../api/api';
import PageLoader from '../loader/PageLoader'; import PageLoader from '../loader/PageLoader';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
@ -56,16 +56,18 @@ const AlbumList = () => {
['albumList', album.active.filter, musicFolder], ['albumList', album.active.filter, musicFolder],
() => () =>
album.active.filter === 'random' album.active.filter === 'random'
? getAlbumsDirect({ ? getAlbums({
type: 'random', type: 'random',
size: config.lookAndFeel.gridView.cardSize, size: config.lookAndFeel.gridView.cardSize,
offset: 0,
musicFolderId: musicFolder, musicFolderId: musicFolder,
}) })
: getAllAlbums({ : getAlbums({
type: album.active.filter, type: album.active.filter,
size: 500, size: 500,
offset: 0, offset: 0,
musicFolderId: musicFolder, musicFolderId: musicFolder,
recursive: true,
}), }),
{ {
cacheTime: 3600000, // Stay in cache for 1 hour cacheTime: 3600000, // Stay in cache for 1 hour
@ -77,8 +79,8 @@ const AlbumList = () => {
return res.map((genre: any) => { return res.map((genre: any) => {
if (genre.albumCount !== 0) { if (genre.albumCount !== 0) {
return { return {
label: `${genre.value} (${genre.albumCount})`, label: `${genre.title} (${genre.albumCount})`,
value: genre.value, value: genre.title,
role: 'Genre', role: 'Genre',
}; };
} }
@ -86,7 +88,7 @@ const AlbumList = () => {
}); });
}); });
const filteredData = useSearchQuery(misc.searchQuery, albums, [ const filteredData = useSearchQuery(misc.searchQuery, albums, [
'name', 'title',
'artist', 'artist',
'genre', 'genre',
'year', 'year',
@ -228,7 +230,7 @@ const AlbumList = () => {
data={misc.searchQuery !== '' ? filteredData : albums} data={misc.searchQuery !== '' ? filteredData : albums}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{

4
src/components/library/AlbumView.tsx

@ -68,7 +68,7 @@ const AlbumView = ({ ...rest }: any) => {
const albumId = rest.id ? rest.id : id; const albumId = rest.id ? rest.id : id;
const { isLoading, isError, data, error }: any = useQuery(['album', albumId], () => const { isLoading, isError, data, error }: any = useQuery(['album', albumId], () =>
getAlbum(albumId) getAlbum({ id: albumId })
); );
const filteredData = useSearchQuery(misc.searchQuery, data?.song, [ const filteredData = useSearchQuery(misc.searchQuery, data?.song, [
'title', 'title',
@ -240,7 +240,7 @@ const AlbumView = ({ ...rest }: any) => {
id: data.albumId, id: data.albumId,
}} }}
imageHeight={200} imageHeight={200}
title={data.name} title={data.title}
showTitleTooltip showTitleTooltip
subtitle={ subtitle={
<div> <div>

4
src/components/library/ArtistList.tsx

@ -45,7 +45,7 @@ const ArtistList = () => {
staleTime: Infinity, // Only allow manual refresh staleTime: Infinity, // Only allow manual refresh
} }
); );
const filteredData = useSearchQuery(misc.searchQuery, artists, ['name']); const filteredData = useSearchQuery(misc.searchQuery, artists, ['title']);
let timeout: any = null; let timeout: any = null;
const handleRowClick = (e: any, rowData: any, tableData: any) => { const handleRowClick = (e: any, rowData: any, tableData: any) => {
@ -151,7 +151,7 @@ const ArtistList = () => {
data={misc.searchQuery !== '' ? filteredData : artists} data={misc.searchQuery !== '' ? filteredData : artists}
cardTitle={{ cardTitle={{
prefix: '/library/artist', prefix: '/library/artist',
property: 'name', property: 'title',
urlProperty: 'id', urlProperty: 'id',
}} }}
cardSubtitle={{ cardSubtitle={{

22
src/components/library/ArtistView.tsx

@ -16,7 +16,7 @@ import {
} from '../shared/ToolbarButtons'; } from '../shared/ToolbarButtons';
import { import {
getAlbum, getAlbum,
getAllArtistSongs, getArtistSongs,
getArtist, getArtist,
getArtistInfo, getArtistInfo,
getDownloadUrl, getDownloadUrl,
@ -75,17 +75,17 @@ const ArtistView = ({ ...rest }: any) => {
const { id } = useParams<ArtistParams>(); const { id } = useParams<ArtistParams>();
const artistId = rest.id ? rest.id : id; const artistId = rest.id ? rest.id : id;
const { isLoading, isError, data, error }: any = useQuery(['artist', artistId], () => const { isLoading, isError, data, error }: any = useQuery(['artist', artistId], () =>
getArtist(artistId) getArtist({ id: artistId })
); );
const { const {
isLoading: isLoadingAI, isLoading: isLoadingAI,
isError: isErrorAI, isError: isErrorAI,
data: artistInfo, data: artistInfo,
error: errorAI, error: errorAI,
}: any = useQuery(['artistInfo', artistId], () => getArtistInfo(artistId, 8)); }: any = useQuery(['artistInfo', artistId], () => getArtistInfo({ id: artistId, count: 8 }));
const filteredData = useSearchQuery(misc.searchQuery, data?.album, [ const filteredData = useSearchQuery(misc.searchQuery, data?.album, [
'name', 'title',
'artist', 'artist',
'genre', 'genre',
'year', 'year',
@ -125,7 +125,7 @@ const ArtistView = ({ ...rest }: any) => {
}; };
const handlePlay = async () => { const handlePlay = async () => {
const res = await getAllArtistSongs(data.id); const res = await getArtistSongs({ id: data.id });
const songs = filterPlayQueue(config.playback.filters, res); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -141,7 +141,7 @@ const ArtistView = ({ ...rest }: any) => {
}; };
const handlePlayAppend = async (type: 'next' | 'later') => { const handlePlayAppend = async (type: 'next' | 'later') => {
const res = await getAllArtistSongs(data.id); const res = await getArtistSongs({ id: data.id });
const songs = filterPlayQueue(config.playback.filters, res); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -189,11 +189,11 @@ const ArtistView = ({ ...rest }: any) => {
for (let i = 0; i < data.album.length; i += 1) { for (let i = 0; i < data.album.length; i += 1) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const albumRes = await getAlbum(data.album[i].id); const albumRes = await getAlbum({ id: data.album[i].id });
if (albumRes.song[0]?.parent) { if (albumRes.song[0]?.parent) {
downloadUrls.push(getDownloadUrl(albumRes.song[0].parent)); downloadUrls.push(getDownloadUrl(albumRes.song[0].parent));
} else { } else {
notifyToast('warning', `[${albumRes.name}] No parent album found`); notifyToast('warning', `[${albumRes.title}] No parent album found`);
} }
} }
@ -295,7 +295,7 @@ const ArtistView = ({ ...rest }: any) => {
id: data.id, id: data.id,
}} }}
imageHeight={185} imageHeight={185}
title={data.name} title={data.title}
showTitleTooltip showTitleTooltip
subtitle={ subtitle={
<> <>
@ -393,7 +393,7 @@ const ArtistView = ({ ...rest }: any) => {
} }
}} }}
> >
{artist.name} {artist.title}
</StyledTag> </StyledTag>
))} ))}
</TagGroup> </TagGroup>
@ -455,7 +455,7 @@ const ArtistView = ({ ...rest }: any) => {
data={misc.searchQuery !== '' ? filteredData : data.album} data={misc.searchQuery !== '' ? filteredData : data.album}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{

10
src/components/library/FolderList.tsx

@ -61,7 +61,7 @@ const FolderList = () => {
const filteredData = useSearchQuery( const filteredData = useSearchQuery(
misc.searchQuery, misc.searchQuery,
folderData?.id ? folderData?.child : indexData, folderData?.id ? folderData?.child : indexData,
['name', 'title', 'artist', 'album', 'year', 'genre', 'path'] ['title', 'artist', 'album', 'year', 'genre', 'path']
); );
useEffect(() => { useEffect(() => {
@ -98,7 +98,7 @@ const FolderList = () => {
const selected = folderData?.id ? folderData?.child : indexData?.child; const selected = folderData?.id ? folderData?.child : indexData?.child;
dispatch( dispatch(
setPlayQueueByRowClick({ setPlayQueueByRowClick({
entries: selected.filter((entry: any) => entry.isDir === false), entries: selected.filter((entry: any) => entry?.isDir === false),
currentIndex: rowData.index, currentIndex: rowData.index,
currentSongId: rowData.id, currentSongId: rowData.id,
uniqueSongId: rowData.uniqueId, uniqueSongId: rowData.uniqueId,
@ -149,8 +149,8 @@ const FolderList = () => {
header={ header={
<GenericPageHeader <GenericPageHeader
title={`${ title={`${
folderData?.name folderData?.title
? folderData.name ? folderData.title
: isLoadingFolderData : isLoadingFolderData
? 'Loading...' ? 'Loading...'
: 'Select a folder' : 'Select a folder'
@ -167,7 +167,7 @@ const FolderList = () => {
data={musicFolders} data={musicFolders}
defaultValue={musicFolder} defaultValue={musicFolder}
valueKey="id" valueKey="id"
labelKey="name" labelKey="title"
onChange={(e: any) => { onChange={(e: any) => {
setMusicFolder(e); setMusicFolder(e);
}} }}

6
src/components/library/GenreList.tsx

@ -27,7 +27,7 @@ const GenreList = () => {
const res = await getGenres(); const res = await getGenres();
return _.orderBy(res, 'songCount', 'desc'); return _.orderBy(res, 'songCount', 'desc');
}); });
const filteredData = useSearchQuery(misc.searchQuery, genres, ['value']); const filteredData = useSearchQuery(misc.searchQuery, genres, ['title']);
let timeout: any = null; let timeout: any = null;
const handleRowClick = (e: any, rowData: any, tableData: any) => { const handleRowClick = (e: any, rowData: any, tableData: any) => {
@ -48,12 +48,12 @@ const GenreList = () => {
const handleRowDoubleClick = (rowData: any) => { const handleRowDoubleClick = (rowData: any) => {
window.clearTimeout(timeout); window.clearTimeout(timeout);
timeout = null; timeout = null;
dispatch(setActive({ ...album.active, filter: rowData.value })); dispatch(setActive({ ...album.active, filter: rowData.title }));
dispatch(clearSelected()); dispatch(clearSelected());
// Needs a small delay or the filter won't set properly when navigating to the album list // Needs a small delay or the filter won't set properly when navigating to the album list
setTimeout(() => { setTimeout(() => {
history.push(`/library/album?sortType=${rowData.value}`); history.push(`/library/album?sortType=${rowData.title}`);
}, 50); }, 50);
}; };

29
src/components/player/NowPlayingMiniView.tsx

@ -82,8 +82,8 @@ const NowPlayingMiniView = () => {
const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc'); const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc');
return genresOrderedBySongCount.map((genre: any) => { return genresOrderedBySongCount.map((genre: any) => {
return { return {
label: `${genre.value} (${genre.songCount})`, label: `${genre.title} (${genre.songCount})`,
value: genre.value, title: genre.title,
role: 'Genre', role: 'Genre',
}; };
}); });
@ -190,7 +190,7 @@ const NowPlayingMiniView = () => {
const cleanedSongs = filterPlayQueue( const cleanedSongs = filterPlayQueue(
config.playback.filters, config.playback.filters,
res.song.filter((song: any) => { res.filter((song: any) => {
// Remove invalid songs that may break the player // Remove invalid songs that may break the player
return song.bitRate && song.duration; return song.bitRate && song.duration;
}) })
@ -210,7 +210,7 @@ const NowPlayingMiniView = () => {
notifyToast( notifyToast(
'info', 'info',
getPlayedSongsNotification({ getPlayedSongsNotification({
original: res.song.length, original: res.length,
filtered: cleanedSongs.count.filtered, filtered: cleanedSongs.count.filtered,
type: 'play', type: 'play',
}) })
@ -224,7 +224,7 @@ const NowPlayingMiniView = () => {
notifyToast( notifyToast(
'info', 'info',
getPlayedSongsNotification({ getPlayedSongsNotification({
original: res.song.length, original: res.length,
filtered: cleanedSongs.count.filtered, filtered: cleanedSongs.count.filtered,
type: 'add', type: 'add',
}) })
@ -238,7 +238,7 @@ const NowPlayingMiniView = () => {
notifyToast( notifyToast(
'info', 'info',
getPlayedSongsNotification({ getPlayedSongsNotification({
original: res.song.length, original: res.length,
filtered: cleanedSongs.count.filtered, filtered: cleanedSongs.count.filtered,
type: 'add', type: 'add',
}) })
@ -300,7 +300,8 @@ const NowPlayingMiniView = () => {
<Whisper <Whisper
ref={autoPlaylistTriggerRef} ref={autoPlaylistTriggerRef}
placement="autoVertical" placement="autoVertical"
trigger="none" trigger="click"
enterable
speaker={ speaker={
<StyledPopover> <StyledPopover>
<ControlLabel>How many tracks? (1-500)*</ControlLabel> <ControlLabel>How many tracks? (1-500)*</ControlLabel>
@ -356,6 +357,8 @@ const NowPlayingMiniView = () => {
container={() => genrePickerContainerRef.current} container={() => genrePickerContainerRef.current}
data={!isLoadingGenres ? genres : []} data={!isLoadingGenres ? genres : []}
value={randomPlaylistGenre} value={randomPlaylistGenre}
valueKey="title"
labelKey="label"
virtualized virtualized
onChange={(e: string) => setRandomPlaylistGenre(e)} onChange={(e: string) => setRandomPlaylistGenre(e)}
/> />
@ -370,7 +373,7 @@ const NowPlayingMiniView = () => {
data={!isLoadingMusicFolders ? musicFolders : []} data={!isLoadingMusicFolders ? musicFolders : []}
defaultValue={musicFolder} defaultValue={musicFolder}
valueKey="id" valueKey="id"
labelKey="name" labelKey="title"
onChange={(e: any) => { onChange={(e: any) => {
setMusicFolder(e); setMusicFolder(e);
}} }}
@ -411,15 +414,7 @@ const NowPlayingMiniView = () => {
</StyledPopover> </StyledPopover>
} }
> >
<AutoPlaylistButton <AutoPlaylistButton size="xs" noText />
size="xs"
noText
onClick={() =>
autoPlaylistTriggerRef.current.state.isOverlayShown
? autoPlaylistTriggerRef.current.close()
: autoPlaylistTriggerRef.current.open()
}
/>
</Whisper> </Whisper>
<MoveTopButton <MoveTopButton
size="xs" size="xs"

28
src/components/player/NowPlayingView.tsx

@ -97,8 +97,8 @@ const NowPlayingView = () => {
const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc'); const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc');
return genresOrderedBySongCount.map((genre: any) => { return genresOrderedBySongCount.map((genre: any) => {
return { return {
label: `${genre.value} (${genre.songCount})`, label: `${genre.title} (${genre.songCount})`,
value: genre.value, title: genre.title,
role: 'Genre', role: 'Genre',
}; };
}); });
@ -201,7 +201,7 @@ const NowPlayingView = () => {
const cleanedSongs = filterPlayQueue( const cleanedSongs = filterPlayQueue(
config.playback.filters, config.playback.filters,
res.song.filter((song: any) => { res.filter((song: any) => {
// Remove invalid songs that may break the player // Remove invalid songs that may break the player
return song.bitRate && song.duration; return song.bitRate && song.duration;
}) })
@ -221,7 +221,7 @@ const NowPlayingView = () => {
notifyToast( notifyToast(
'info', 'info',
getPlayedSongsNotification({ getPlayedSongsNotification({
original: res.song.length, original: res.length,
filtered: cleanedSongs.count.filtered, filtered: cleanedSongs.count.filtered,
type: 'play', type: 'play',
}) })
@ -235,7 +235,7 @@ const NowPlayingView = () => {
notifyToast( notifyToast(
'info', 'info',
getPlayedSongsNotification({ getPlayedSongsNotification({
original: res.song.length, original: res.length,
filtered: cleanedSongs.count.filtered, filtered: cleanedSongs.count.filtered,
type: 'add', type: 'add',
}) })
@ -249,7 +249,7 @@ const NowPlayingView = () => {
notifyToast( notifyToast(
'info', 'info',
getPlayedSongsNotification({ getPlayedSongsNotification({
original: res.song.length, original: res.length,
filtered: cleanedSongs.count.filtered, filtered: cleanedSongs.count.filtered,
type: 'add', type: 'add',
}) })
@ -310,7 +310,8 @@ const NowPlayingView = () => {
<Whisper <Whisper
ref={autoPlaylistTriggerRef} ref={autoPlaylistTriggerRef}
placement="autoVertical" placement="autoVertical"
trigger="none" trigger="click"
enterable
speaker={ speaker={
<StyledPopover> <StyledPopover>
<ControlLabel>How many tracks? (1-500)*</ControlLabel> <ControlLabel>How many tracks? (1-500)*</ControlLabel>
@ -367,6 +368,8 @@ const NowPlayingView = () => {
container={() => genrePickerContainerRef.current} container={() => genrePickerContainerRef.current}
data={genres} data={genres}
value={randomPlaylistGenre} value={randomPlaylistGenre}
valueKey="title"
labelKey="label"
virtualized virtualized
onChange={(e: string) => setRandomPlaylistGenre(e)} onChange={(e: string) => setRandomPlaylistGenre(e)}
/> />
@ -381,7 +384,7 @@ const NowPlayingView = () => {
data={musicFolders} data={musicFolders}
defaultValue={musicFolder} defaultValue={musicFolder}
valueKey="id" valueKey="id"
labelKey="name" labelKey="title"
onChange={(e: any) => { onChange={(e: any) => {
setMusicFolder(e); setMusicFolder(e);
}} }}
@ -421,14 +424,7 @@ const NowPlayingView = () => {
</StyledPopover> </StyledPopover>
} }
> >
<AutoPlaylistButton <AutoPlaylistButton size="sm" />
size="sm"
onClick={() =>
autoPlaylistTriggerRef.current.state.isOverlayShown
? autoPlaylistTriggerRef.current.close()
: autoPlaylistTriggerRef.current.open()
}
/>
</Whisper> </Whisper>
<ButtonGroup> <ButtonGroup>
<MoveTopButton <MoveTopButton

9
src/components/playlist/PlaylistList.tsx

@ -29,13 +29,12 @@ const PlaylistList = () => {
const config = useAppSelector((state) => state.config); const config = useAppSelector((state) => state.config);
const misc = useAppSelector((state) => state.misc); const misc = useAppSelector((state) => state.misc);
const playlistTriggerRef = useRef<any>(); const playlistTriggerRef = useRef<any>();
const [sortBy] = useState('name');
const [newPlaylistName, setNewPlaylistName] = useState(''); const [newPlaylistName, setNewPlaylistName] = useState('');
const [viewType, setViewType] = useState(settings.getSync('playlistViewType') || 'list'); const [viewType, setViewType] = useState(settings.getSync('playlistViewType') || 'list');
const { isLoading, isError, data: playlists, error }: any = useQuery(['playlists', sortBy], () => const { isLoading, isError, data: playlists, error }: any = useQuery(['playlists'], () =>
getPlaylists(sortBy) getPlaylists()
); );
const filteredData = useSearchQuery(misc.searchQuery, playlists, ['name', 'comment', 'owner']); const filteredData = useSearchQuery(misc.searchQuery, playlists, ['title', 'comment', 'owner']);
const handleCreatePlaylist = async (name: string) => { const handleCreatePlaylist = async (name: string) => {
try { try {
@ -174,7 +173,7 @@ const PlaylistList = () => {
data={misc.searchQuery === '' ? playlists : filteredData} data={misc.searchQuery === '' ? playlists : filteredData}
cardTitle={{ cardTitle={{
prefix: 'playlist', prefix: 'playlist',
property: 'name', property: 'title',
urlProperty: 'id', urlProperty: 'id',
}} }}
cardSubtitle={{ cardSubtitle={{

4
src/components/playlist/PlaylistView.tsx

@ -135,7 +135,7 @@ const PlaylistView = ({ ...rest }) => {
useEffect(() => { useEffect(() => {
// Set the local playlist data on any changes // Set the local playlist data on any changes
dispatch(setPlaylistData(data?.song)); dispatch(setPlaylistData(data?.song));
setEditName(data?.name); setEditName(data?.title);
setEditDescription(data?.comment); setEditDescription(data?.comment);
setEditPublic(data?.public); setEditPublic(data?.public);
}, [data, dispatch]); }, [data, dispatch]);
@ -402,7 +402,7 @@ const PlaylistView = ({ ...rest }) => {
id: data.id, id: data.id,
}} }}
imageHeight={184} imageHeight={184}
title={data.name} title={data.title}
subtitle={ subtitle={
<div> <div>
<PageHeaderSubtitleDataLine $top> <PageHeaderSubtitleDataLine $top>

4
src/components/search/SearchView.tsx

@ -164,7 +164,7 @@ const SearchView = () => {
data={data.artist} data={data.artist}
cardTitle={{ cardTitle={{
prefix: '/library/artist', prefix: '/library/artist',
property: 'name', property: 'title',
urlProperty: 'id', urlProperty: 'id',
}} }}
cardSubtitle={{ cardSubtitle={{
@ -181,7 +181,7 @@ const SearchView = () => {
data={data.album} data={data.album}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{

46
src/components/settings/ListViewColumns.ts

@ -476,7 +476,7 @@ export const albumColumnList = [
label: 'Title', label: 'Title',
value: { value: {
id: 'Title', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
resizable: true, resizable: true,
width: 300, width: 300,
@ -594,7 +594,7 @@ export const albumColumnListAuto = [
label: 'Title', label: 'Title',
value: { value: {
id: 'Title', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
flexGrow: 5, flexGrow: 5,
label: 'Title', label: 'Title',
@ -752,7 +752,7 @@ export const playlistColumnList = [
label: 'Title', label: 'Title',
value: { value: {
id: 'Title', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
resizable: true, resizable: true,
width: 300, width: 300,
@ -879,7 +879,7 @@ export const playlistColumnListAuto = [
label: 'Title', label: 'Title',
value: { value: {
id: 'Title', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
flexGrow: 5, flexGrow: 5,
label: 'Title', label: 'Title',
@ -956,14 +956,14 @@ export const artistColumnList = [
}, },
}, },
{ {
label: 'Name', label: 'Title',
value: { value: {
id: 'Name', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
resizable: true, resizable: true,
width: 300, width: 300,
label: 'Name', label: 'Title',
}, },
}, },
]; ];
@ -1012,13 +1012,13 @@ export const artistColumnListAuto = [
}, },
}, },
{ {
label: 'Name', label: 'Title',
value: { value: {
id: 'Name', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
flexGrow: 5, flexGrow: 5,
label: 'Name', label: 'Title',
}, },
}, },
]; ];
@ -1028,13 +1028,13 @@ export const artistColumnPicker = [
{ label: 'Album Count' }, { label: 'Album Count' },
{ label: 'CoverArt' }, { label: 'CoverArt' },
{ label: 'Favorite' }, { label: 'Favorite' },
{ label: 'Name' }, { label: 'Title' },
]; ];
export const genreColumnPicker = [ export const genreColumnPicker = [
{ label: '#' }, { label: '#' },
{ label: 'Album Count' }, { label: 'Album Count' },
{ label: 'Name' }, { label: 'Title' },
{ label: 'Track Count' }, { label: 'Track Count' },
]; ];
@ -1062,14 +1062,14 @@ export const genreColumnList = [
}, },
}, },
{ {
label: 'Name', label: 'Title',
value: { value: {
id: 'Name', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
resizable: true, resizable: true,
width: 300, width: 300,
label: 'Name', label: 'Title',
}, },
}, },
{ {
@ -1108,17 +1108,17 @@ export const genreColumnListAuto = [
}, },
}, },
{ {
label: 'Name', label: 'Title',
value: { value: {
id: 'Name', id: 'Title',
dataKey: 'name', dataKey: 'title',
alignment: 'left', alignment: 'left',
flexGrow: 5, flexGrow: 5,
label: 'Name', label: 'Title',
}, },
}, },
{ {
label: 'Tracks', label: 'Track Count',
value: { value: {
id: 'Tracks', id: 'Tracks',
dataKey: 'songCount', dataKey: 'songCount',

26
src/components/shared/ContextMenu.tsx

@ -14,8 +14,8 @@ import {
getAlbum, getAlbum,
getPlaylist, getPlaylist,
deletePlaylist, deletePlaylist,
getAllArtistSongs, getArtistSongs,
getAllDirectorySongs, getDirectorySongs,
} from '../../api/api'; } from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
@ -120,7 +120,7 @@ export const GlobalContextMenu = () => {
const [indexToMoveTo, setIndexToMoveTo] = useState(0); const [indexToMoveTo, setIndexToMoveTo] = useState(0);
const playlistPickerContainerRef = useRef(null); const playlistPickerContainerRef = useRef(null);
const { data: playlists }: any = useQuery(['playlists', 'name'], () => getPlaylists('name')); const { data: playlists }: any = useQuery(['playlists'], () => getPlaylists());
const handlePlay = async () => { const handlePlay = async () => {
dispatch(setContextMenu({ show: false })); dispatch(setContextMenu({ show: false }));
@ -135,7 +135,7 @@ export const GlobalContextMenu = () => {
}); });
for (let i = 0; i < folders.length; i += 1) { for (let i = 0; i < folders.length; i += 1) {
promises.push(getAllDirectorySongs({ id: folders[i].id })); promises.push(getDirectorySongs({ id: folders[i].id }));
} }
const res = await Promise.all(promises); const res = await Promise.all(promises);
@ -172,7 +172,7 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'play' })); notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'play' }));
} else if (misc.contextMenu.type === 'album') { } else if (misc.contextMenu.type === 'album') {
for (let i = 0; i < multiSelect.selected.length; i += 1) { for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAlbum(multiSelect.selected[i].id)); promises.push(getAlbum({ id: multiSelect.selected[i].id }));
} }
const res = await Promise.all(promises); const res = await Promise.all(promises);
@ -191,7 +191,7 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'play' })); notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'play' }));
} else if (misc.contextMenu.type === 'artist') { } else if (misc.contextMenu.type === 'artist') {
for (let i = 0; i < multiSelect.selected.length; i += 1) { for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAllArtistSongs(multiSelect.selected[i].id)); promises.push(getArtistSongs({ id: multiSelect.selected[i].id }));
} }
const res = await Promise.all(promises); const res = await Promise.all(promises);
@ -223,7 +223,7 @@ export const GlobalContextMenu = () => {
}); });
for (let i = 0; i < folders.length; i += 1) { for (let i = 0; i < folders.length; i += 1) {
promises.push(getAllDirectorySongs({ id: multiSelect.selected[i].id })); promises.push(getDirectorySongs({ id: multiSelect.selected[i].id }));
} }
const res = await Promise.all(promises); const res = await Promise.all(promises);
@ -252,7 +252,7 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'add' })); notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'add' }));
} else if (misc.contextMenu.type === 'album') { } else if (misc.contextMenu.type === 'album') {
for (let i = 0; i < multiSelect.selected.length; i += 1) { for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAlbum(multiSelect.selected[i].id)); promises.push(getAlbum({ id: multiSelect.selected[i].id }));
} }
const res = await Promise.all(promises); const res = await Promise.all(promises);
@ -266,7 +266,7 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'add' })); notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'add' }));
} else if (misc.contextMenu.type === 'artist') { } else if (misc.contextMenu.type === 'artist') {
for (let i = 0; i < multiSelect.selected.length; i += 1) { for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAllArtistSongs(multiSelect.selected[i].id)); promises.push(getArtistSongs({ id: multiSelect.selected[i].id }));
} }
const res = await Promise.all(promises); const res = await Promise.all(promises);
@ -298,7 +298,7 @@ export const GlobalContextMenu = () => {
notifyToast( notifyToast(
'success', 'success',
`Added ${songCount} song(s) to playlist ${ `Added ${songCount} song(s) to playlist ${
playlists.find((pl: any) => pl.id === playlistId)?.name playlists.find((pl: any) => pl.id === playlistId)?.title
}`, }`,
<> <>
<StyledButton <StyledButton
@ -332,7 +332,7 @@ export const GlobalContextMenu = () => {
}); });
for (let i = 0; i < folders.length; i += 1) { for (let i = 0; i < folders.length; i += 1) {
promises.push(getAllDirectorySongs({ id: multiSelect.selected[i].id })); promises.push(getDirectorySongs({ id: multiSelect.selected[i].id }));
} }
const folderSongs = await Promise.all(promises); const folderSongs = await Promise.all(promises);
@ -363,7 +363,7 @@ export const GlobalContextMenu = () => {
} }
} else if (misc.contextMenu.type === 'album') { } else if (misc.contextMenu.type === 'album') {
for (let i = 0; i < multiSelect.selected.length; i += 1) { for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAlbum(multiSelect.selected[i].id)); promises.push(getAlbum({ id: multiSelect.selected[i].id }));
} }
res = await Promise.all(promises); res = await Promise.all(promises);
@ -700,7 +700,7 @@ export const GlobalContextMenu = () => {
data={playlists} data={playlists}
placement="autoVerticalStart" placement="autoVerticalStart"
virtualized virtualized
labelKey="name" labelKey="title"
valueKey="id" valueKey="id"
width={200} width={200}
onChange={(e: any) => setSelectedPlaylistId(e)} onChange={(e: any) => setSelectedPlaylistId(e)}

10
src/components/starred/StarredView.tsx

@ -50,10 +50,10 @@ const StarredView = () => {
? data?.album ? data?.album
: data?.artist, : data?.artist,
favorite.active.tab === 'tracks' favorite.active.tab === 'tracks'
? ['title', 'artist', 'album', 'name', 'genre'] ? ['title', 'artist', 'album', 'genre']
: favorite.active.tab === 'albums' : favorite.active.tab === 'albums'
? ['name', 'artist', 'genre', 'year'] ? ['title', 'artist', 'genre', 'year']
: ['name'] : ['title']
); );
let timeout: any = null; let timeout: any = null;
@ -230,7 +230,7 @@ const StarredView = () => {
data={misc.searchQuery !== '' ? filteredData : data.album} data={misc.searchQuery !== '' ? filteredData : data.album}
cardTitle={{ cardTitle={{
prefix: '/library/album', prefix: '/library/album',
property: 'name', property: 'title',
urlProperty: 'albumId', urlProperty: 'albumId',
}} }}
cardSubtitle={{ cardSubtitle={{
@ -279,7 +279,7 @@ const StarredView = () => {
data={misc.searchQuery !== '' ? filteredData : data.artist} data={misc.searchQuery !== '' ? filteredData : data.artist}
cardTitle={{ cardTitle={{
prefix: '/library/artist', prefix: '/library/artist',
property: 'name', property: 'title',
urlProperty: 'id', urlProperty: 'id',
}} }}
cardSubtitle={{ cardSubtitle={{

Loading…
Cancel
Save