Browse Source

Set all page endpoints to use controller

master
jeffvli 3 years ago
committed by Jeff
parent
commit
780bf4d026
  1. 44
      src/components/card/Card.tsx
  2. 55
      src/components/dashboard/Dashboard.tsx
  3. 37
      src/components/library/AlbumList.tsx
  4. 51
      src/components/library/AlbumView.tsx
  5. 22
      src/components/library/ArtistList.tsx
  6. 91
      src/components/library/ArtistView.tsx
  7. 47
      src/components/library/FolderList.tsx
  8. 4
      src/components/library/GenreList.tsx
  9. 32
      src/components/player/NowPlayingMiniView.tsx
  10. 36
      src/components/player/NowPlayingView.tsx
  11. 43
      src/components/player/Player.tsx
  12. 22
      src/components/player/PlayerBar.tsx
  13. 7
      src/components/playlist/PlaylistList.tsx
  14. 65
      src/components/playlist/PlaylistView.tsx
  15. 48
      src/components/search/SearchView.tsx
  16. 12
      src/components/settings/Config.tsx
  17. 7
      src/components/settings/ConfigPanels/ServerConfig.tsx
  18. 149
      src/components/shared/ContextMenu.tsx
  19. 33
      src/components/starred/StarredView.tsx

44
src/components/card/Card.tsx

@ -2,7 +2,6 @@ import React from 'react';
import { Icon } from 'rsuite';
import { useHistory } from 'react-router-dom';
import cacheImage from '../shared/cacheImage';
import { getAlbum, getPlaylist, getArtistSongs } from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import {
appendPlayQueue,
@ -32,6 +31,7 @@ import { setStatus } from '../../redux/playerSlice';
import { addModalPage } from '../../redux/miscSlice';
import { notifyToast } from '../shared/toast';
import CustomTooltip from '../shared/CustomTooltip';
import { apiController } from '../../api/controller';
const Card = ({
onClick,
@ -60,7 +60,12 @@ const Card = ({
const handlePlayClick = async () => {
if (playClick.type === 'playlist') {
const res = await getPlaylist(playClick.id);
const res = await apiController({
serverType: config.serverType,
endpoint: 'getPlaylist',
args: { id: playClick.id },
});
const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) {
@ -76,7 +81,12 @@ const Card = ({
}
if (playClick.type === 'album') {
const res = await getAlbum({ id: playClick.id });
const res = await apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: playClick.id },
});
const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) {
@ -92,7 +102,12 @@ const Card = ({
}
if (playClick.type === 'artist') {
const res = await getArtistSongs({ id: playClick.id });
const res = await apiController({
serverType: config.serverType,
endpoint: 'getArtistSongs',
args: { id: playClick.id },
});
const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) {
@ -110,7 +125,12 @@ const Card = ({
const handlePlayAppend = async (type: 'next' | 'later') => {
if (playClick.type === 'playlist') {
const res = await getPlaylist(playClick.id);
const res = await apiController({
serverType: config.serverType,
endpoint: 'getPlaylist',
args: { id: playClick.id },
});
const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) {
@ -122,7 +142,12 @@ const Card = ({
}
if (playClick.type === 'album') {
const res = await getAlbum({ id: playClick.id });
const res = await apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: playClick.id },
});
const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) {
@ -134,7 +159,12 @@ const Card = ({
}
if (playClick.type === 'artist') {
const res = await getArtistSongs({ id: playClick.id });
const res = await apiController({
serverType: config.serverType,
endpoint: 'getArtistSongs',
args: { id: playClick.id },
});
const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) {

55
src/components/dashboard/Dashboard.tsx

@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import _ from 'lodash';
import { useHistory } from 'react-router-dom';
import { useQuery, useQueryClient } from 'react-query';
import { getAlbums, star, unstar } from '../../api/api';
import PageLoader from '../loader/PageLoader';
import GenericPage from '../layout/GenericPage';
import GenericPageHeader from '../layout/GenericPageHeader';
@ -10,6 +9,8 @@ import ScrollingMenu from '../scrollingmenu/ScrollingMenu';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { setStar } from '../../redux/playQueueSlice';
import { setActive } from '../../redux/albumSlice';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const Dashboard = () => {
const history = useHistory();
@ -28,27 +29,63 @@ const Dashboard = () => {
const { isLoading: isLoadingRecent, data: recentAlbums }: any = useQuery(
['recentAlbums', musicFolder],
() => getAlbums({ type: 'recent', size: 20, offset: 0, musicFolderId: musicFolder })
() =>
apiController({
serverType: config.serverType,
endpoint: 'getAlbums',
args:
config.serverType === Server.Subsonic
? { type: 'recent', size: 20, offset: 0, musicFolderId: musicFolder }
: null,
})
);
const { isLoading: isLoadingNewest, data: newestAlbums }: any = useQuery(
['newestAlbums', musicFolder],
() => getAlbums({ type: 'newest', size: 20, offset: 0, musicFolderId: musicFolder })
() =>
apiController({
serverType: config.serverType,
endpoint: 'getAlbums',
args:
config.serverType === Server.Subsonic
? { type: 'newest', size: 20, offset: 0, musicFolderId: musicFolder }
: null,
})
);
const { isLoading: isLoadingRandom, data: randomAlbums }: any = useQuery(
['randomAlbums', musicFolder],
() => getAlbums({ type: 'random', size: 20, offset: 0, musicFolderId: musicFolder })
() =>
apiController({
serverType: config.serverType,
endpoint: 'getAlbums',
args:
config.serverType === Server.Subsonic
? { type: 'random', size: 20, offset: 0, musicFolderId: musicFolder }
: null,
})
);
const { isLoading: isLoadingFrequent, data: frequentAlbums }: any = useQuery(
['frequentAlbums', musicFolder],
() => getAlbums({ type: 'frequent', size: 20, offset: 0, musicFolderId: musicFolder })
() =>
apiController({
serverType: config.serverType,
endpoint: 'getAlbums',
args:
config.serverType === Server.Subsonic
? { type: 'frequent', size: 20, offset: 0, musicFolderId: musicFolder }
: null,
})
);
const handleFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'star' }));
queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
@ -83,7 +120,11 @@ const Dashboard = () => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));

37
src/components/library/AlbumList.tsx

@ -9,7 +9,6 @@ import ListViewType from '../viewtypes/ListViewType';
import useSearchQuery from '../../hooks/useSearchQuery';
import GenericPageHeader from '../layout/GenericPageHeader';
import GenericPage from '../layout/GenericPage';
import { getAlbums, getGenres, star, unstar } from '../../api/api';
import PageLoader from '../loader/PageLoader';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import {
@ -22,6 +21,8 @@ import { StyledInputPicker, StyledInputPickerContainer } from '../shared/styled'
import { RefreshButton } from '../shared/ToolbarButtons';
import { setActive } from '../../redux/albumSlice';
import { setSearchQuery } from '../../redux/miscSlice';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const ALBUM_SORT_TYPES = [
{ label: 'A-Z (Name)', value: 'alphabeticalByName', role: 'Default' },
@ -56,18 +57,32 @@ const AlbumList = () => {
['albumList', album.active.filter, musicFolder],
() =>
album.active.filter === 'random'
? getAlbums({
? apiController({
serverType: config.serverType,
endpoint: 'getAlbums',
args:
config.serverType === Server.Subsonic
? {
type: 'random',
size: config.lookAndFeel.gridView.cardSize,
size: 100,
offset: 0,
musicFolderId: musicFolder,
}
: null,
})
: getAlbums({
: apiController({
serverType: config.serverType,
endpoint: 'getAlbums',
args:
config.serverType === Server.Subsonic
? {
type: album.active.filter,
size: 500,
offset: 0,
musicFolderId: musicFolder,
recursive: true,
}
: null,
}),
{
cacheTime: 3600000, // Stay in cache for 1 hour
@ -75,7 +90,7 @@ const AlbumList = () => {
}
);
const { data: genres }: any = useQuery(['genreList'], async () => {
const res = await getGenres();
const res = await apiController({ serverType: config.serverType, endpoint: 'getGenres' });
return res.map((genre: any) => {
if (genre.albumCount !== 0) {
return {
@ -130,7 +145,11 @@ const AlbumList = () => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['albumList', album.active.filter, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -140,7 +159,11 @@ const AlbumList = () => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['albumList', album.active.filter, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => {

51
src/components/library/AlbumView.tsx

@ -12,7 +12,6 @@ import {
PlayAppendNextButton,
PlayButton,
} from '../shared/ToolbarButtons';
import { getAlbum, getDownloadUrl, star, unstar } from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import {
appendPlayQueue,
@ -51,6 +50,8 @@ import {
BlurredBackgroundWrapper,
PageHeaderSubtitleDataLine,
} from '../layout/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
interface AlbumParams {
id: string;
@ -68,7 +69,11 @@ const AlbumView = ({ ...rest }: any) => {
const albumId = rest.id ? rest.id : id;
const { isLoading, isError, data, error }: any = useQuery(['album', albumId], () =>
getAlbum({ id: albumId })
apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: albumId },
})
);
const filteredData = useSearchQuery(misc.searchQuery, data?.song, [
'title',
@ -141,17 +146,29 @@ const AlbumView = ({ ...rest }: any) => {
const handleFavorite = async () => {
if (!data.starred) {
await star({ id: data.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: data.id, type: 'album' } : null,
});
queryClient.setQueryData(['album', id], { ...data, starred: Date.now() });
} else {
await unstar({ id: data.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: data.id, type: 'album' } : null,
});
queryClient.setQueryData(['album', id], { ...data, starred: undefined });
}
};
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'star' }));
queryClient.setQueryData(['album', id], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.song, { id: rowData.id }));
@ -162,7 +179,11 @@ const AlbumView = ({ ...rest }: any) => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
queryClient.setQueryData(['album', id], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.song, { id: rowData.id }));
@ -175,16 +196,28 @@ const AlbumView = ({ ...rest }: any) => {
}
};
const handleDownload = (type: 'copy' | 'download') => {
const handleDownload = async (type: 'copy' | 'download') => {
// If not Navidrome (this assumes Airsonic), then we need to use a song's parent
// to download. This is because Airsonic does not support downloading via album ids
// that are provided by /getAlbum or /getAlbumList2
if (data.song[0]?.parent) {
if (type === 'download') {
shell.openExternal(getDownloadUrl(data.song[0].parent));
shell.openExternal(
await apiController({
serverType: config.serverType,
endpoint: 'getDownloadUrl',
args: { id: data.song[0].parent },
})
);
} else {
clipboard.writeText(getDownloadUrl(data.song[0].parent));
clipboard.writeText(
await apiController({
serverType: config.serverType,
endpoint: 'getDownloadUrl',
args: { id: data.song[0].parent },
})
);
notifyToast('info', 'Download links copied!');
}
} else {

22
src/components/library/ArtistList.tsx

@ -4,7 +4,6 @@ import settings from 'electron-settings';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router';
import { ButtonToolbar } from 'rsuite';
import { getArtists, star, unstar } from '../../api/api';
import useSearchQuery from '../../hooks/useSearchQuery';
import GenericPage from '../layout/GenericPage';
import GenericPageHeader from '../layout/GenericPageHeader';
@ -19,6 +18,8 @@ import {
} from '../../redux/multiSelectSlice';
import GridViewType from '../viewtypes/GridViewType';
import { RefreshButton } from '../shared/ToolbarButtons';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const ArtistList = () => {
const dispatch = useAppDispatch();
@ -39,7 +40,12 @@ const ArtistList = () => {
const { isLoading, isError, data: artists, error }: any = useQuery(
['artistList', musicFolder],
() => getArtists({ musicFolderId: musicFolder }),
() =>
apiController({
serverType: config.serverType,
endpoint: 'getArtists',
args: config.serverType === Server.Subsonic ? { musicFolderId: musicFolder } : null,
}),
{
cacheTime: 3600000, // Stay in cache for 1 hour
staleTime: Infinity, // Only allow manual refresh
@ -79,7 +85,11 @@ const ArtistList = () => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'artist' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'artist' } : null,
});
queryClient.setQueryData(['artistList', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -89,7 +99,11 @@ const ArtistList = () => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'artist' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'artist' } : null,
});
queryClient.setQueryData(['artistList', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
starredIndices.forEach((index) => {

91
src/components/library/ArtistView.tsx

@ -14,15 +14,6 @@ import {
PlayAppendNextButton,
PlayButton,
} from '../shared/ToolbarButtons';
import {
getAlbum,
getArtistSongs,
getArtist,
getArtistInfo,
getDownloadUrl,
star,
unstar,
} from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import {
toggleSelected,
@ -54,6 +45,8 @@ import {
import { StyledButton, StyledPopover, StyledTag } from '../shared/styled';
import { setStatus } from '../../redux/playerSlice';
import { GradientBackground, PageHeaderSubtitleDataLine } from '../layout/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const fac = new FastAverageColor();
@ -75,14 +68,20 @@ const ArtistView = ({ ...rest }: any) => {
const { id } = useParams<ArtistParams>();
const artistId = rest.id ? rest.id : id;
const { isLoading, isError, data, error }: any = useQuery(['artist', artistId], () =>
getArtist({ id: artistId })
apiController({ serverType: config.serverType, endpoint: 'getArtist', args: { id: artistId } })
);
const {
isLoading: isLoadingAI,
isError: isErrorAI,
data: artistInfo,
error: errorAI,
}: any = useQuery(['artistInfo', artistId], () => getArtistInfo({ id: artistId, count: 8 }));
}: any = useQuery(['artistInfo', artistId], () =>
apiController({
serverType: config.serverType,
endpoint: 'getArtistInfo',
args: config.serverType === Server.Subsonic ? { id: artistId, count: 8 } : null,
})
);
const filteredData = useSearchQuery(misc.searchQuery, data?.album, [
'title',
@ -116,16 +115,29 @@ const ArtistView = ({ ...rest }: any) => {
const handleFavorite = async () => {
if (!data.starred) {
await star({ id: data.id, type: 'artist' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: data.id, type: 'artist' } : null,
});
queryClient.setQueryData(['artist', artistId], { ...data, starred: Date.now() });
} else {
await unstar({ id: data.id, type: 'artist' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: data.id, type: 'artist' } : null,
});
queryClient.setQueryData(['artist', artistId], { ...data, starred: undefined });
}
};
const handlePlay = async () => {
const res = await getArtistSongs({ id: data.id });
const res = await apiController({
serverType: config.serverType,
endpoint: 'getArtistSongs',
args: { id: data.id },
});
const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) {
@ -141,7 +153,12 @@ const ArtistView = ({ ...rest }: any) => {
};
const handlePlayAppend = async (type: 'next' | 'later') => {
const res = await getArtistSongs({ id: data.id });
const res = await await apiController({
serverType: config.serverType,
endpoint: 'getArtistSongs',
args: { id: data.id },
});
const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) {
@ -154,7 +171,11 @@ const ArtistView = ({ ...rest }: any) => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['artist', artistId], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -164,7 +185,11 @@ const ArtistView = ({ ...rest }: any) => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['artist', artistId], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -179,9 +204,21 @@ const ArtistView = ({ ...rest }: any) => {
const handleDownload = async (type: 'copy' | 'download') => {
if (data.album[0]?.parent) {
if (type === 'download') {
shell.openExternal(getDownloadUrl(data.album[0].parent));
shell.openExternal(
await apiController({
serverType: config.serverType,
endpoint: 'getDownloadUrl',
args: { id: data.album[0].parent },
})
);
} else {
clipboard.writeText(getDownloadUrl(data.album[0].parent));
clipboard.writeText(
await apiController({
serverType: config.serverType,
endpoint: 'getDownloadUrl',
args: { id: data.album[0].parent },
})
);
notifyToast('info', 'Download links copied!');
}
} else {
@ -189,9 +226,21 @@ const ArtistView = ({ ...rest }: any) => {
for (let i = 0; i < data.album.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
const albumRes = await getAlbum({ id: data.album[i].id });
const albumRes = await apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: data.album[i].id },
});
if (albumRes.song[0]?.parent) {
downloadUrls.push(getDownloadUrl(albumRes.song[0].parent));
downloadUrls.push(
// eslint-disable-next-line no-await-in-loop
await apiController({
serverType: config.serverType,
endpoint: 'getDownloadUrl',
args: { id: albumRes.song[0].parent },
})
);
} else {
notifyToast('warning', `[${albumRes.title}] No parent album found`);
}

47
src/components/library/FolderList.tsx

@ -4,14 +4,6 @@ import _ from 'lodash';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom';
import { ButtonToolbar, Icon } from 'rsuite';
import {
getIndexes,
getMusicDirectory,
getMusicFolders,
setRating,
star,
unstar,
} from '../../api/api';
import PageLoader from '../loader/PageLoader';
import ListViewType from '../viewtypes/ListViewType';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
@ -29,6 +21,8 @@ import { setStatus } from '../../redux/playerSlice';
import useSearchQuery from '../../hooks/useSearchQuery';
import { setCurrentViewedFolder } from '../../redux/folderSlice';
import useRouterQuery from '../../hooks/useRouterQuery';
import { Server } from '../../types';
import { apiController } from '../../api/controller';
const FolderList = () => {
const dispatch = useAppDispatch();
@ -43,19 +37,28 @@ const FolderList = () => {
const { isLoading, isError, data: indexData, error }: any = useQuery(
['indexes', musicFolder],
() => getIndexes({ musicFolderId: musicFolder })
() =>
apiController({
serverType: config.serverType,
endpoint: 'getIndexes',
args: config.serverType === Server.Subsonic ? { musicFolderId: musicFolder } : null,
})
);
const { isLoading: isLoadingFolderData, data: folderData }: any = useQuery(
['folder', folder.currentViewedFolder],
() => getMusicDirectory({ id: folder.currentViewedFolder }),
() =>
apiController({
serverType: config.serverType,
endpoint: 'getMusicDirectory',
args: { id: folder.currentViewedFolder },
}),
{
enabled: folder.currentViewedFolder !== '',
}
);
const { isLoading: isLoadingMusicFolders, data: musicFolders } = useQuery(
['musicFolders'],
getMusicFolders
const { isLoading: isLoadingMusicFolders, data: musicFolders } = useQuery(['musicFolders'], () =>
apiController({ serverType: config.serverType, endpoint: 'getMusicFolders' })
);
const filteredData = useSearchQuery(
@ -112,7 +115,11 @@ const FolderList = () => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['folder', folder.currentViewedFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.child, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -122,7 +129,11 @@ const FolderList = () => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['folder', folder.currentViewedFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.child, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -135,7 +146,11 @@ const FolderList = () => {
};
const handleRowRating = (rowData: any, e: number) => {
setRating({ id: rowData.id, rating: e });
apiController({
serverType: config.serverType,
endpoint: 'setRating',
args: { id: rowData.id, rating: e },
});
dispatch(setRate({ id: [rowData.id], rating: e }));
};

4
src/components/library/GenreList.tsx

@ -14,8 +14,8 @@ import {
toggleRangeSelected,
toggleSelected,
} from '../../redux/multiSelectSlice';
import { getGenres } from '../../api/api';
import { setActive } from '../../redux/albumSlice';
import { apiController } from '../../api/controller';
const GenreList = () => {
const dispatch = useAppDispatch();
@ -24,7 +24,7 @@ const GenreList = () => {
const album = useAppSelector((state) => state.album);
const misc = useAppSelector((state) => state.misc);
const { isLoading, isError, data: genres, error }: any = useQuery(['genrePageList'], async () => {
const res = await getGenres();
const res = await apiController({ serverType: config.serverType, endpoint: 'getGenres' });
return _.orderBy(res, 'songCount', 'desc');
});
const filteredData = useSearchQuery(misc.searchQuery, genres, ['title']);

32
src/components/player/NowPlayingMiniView.tsx

@ -47,7 +47,6 @@ import {
getPlayedSongsNotification,
isFailedResponse,
} from '../../shared/utils';
import { getGenres, getMusicFolders, getRandomSongs, star, unstar } from '../../api/api';
import {
AutoPlaylistButton,
ClearQueueButton,
@ -57,6 +56,8 @@ import {
ShuffleButton,
} from '../shared/ToolbarButtons';
import { notifyToast } from '../shared/toast';
import { apiController } from '../../api/controller';
import { Server, Song } from '../../types';
const NowPlayingMiniView = () => {
const tableRef = useRef<any>();
@ -78,7 +79,7 @@ const NowPlayingMiniView = () => {
const [musicFolder, setMusicFolder] = useState(folder.musicFolder);
const { isLoading: isLoadingGenres, data: genres }: any = useQuery(['genreList'], async () => {
const res = await getGenres();
const res = await apiController({ serverType: config.serverType, endpoint: 'getGenres' });
const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc');
return genresOrderedBySongCount.map((genre: any) => {
return {
@ -89,9 +90,8 @@ const NowPlayingMiniView = () => {
});
});
const { isLoading: isLoadingMusicFolders, data: musicFolders } = useQuery(
['musicFolders'],
getMusicFolders
const { isLoading: isLoadingMusicFolders, data: musicFolders } = useQuery(['musicFolders'], () =>
apiController({ serverType: config.serverType, endpoint: 'getMusicFolders' })
);
useHotkeys(
@ -175,12 +175,20 @@ const NowPlayingMiniView = () => {
const handlePlayRandom = async (action: 'play' | 'addNext' | 'addLater') => {
setIsLoadingRandom(true);
const res = await getRandomSongs({
const res: Song[] = await apiController({
serverType: config.serverType,
endpoint: 'getRandomSongs',
args:
config.serverType === Server.Subsonic
? {
size: autoPlaylistTrackCount,
fromYear: autoPlaylistFromYear !== 0 ? autoPlaylistFromYear : undefined,
toYear: autoPlaylistToYear !== 0 ? autoPlaylistToYear : undefined,
genre: randomPlaylistGenre,
musicFolderId: musicFolder,
}
: null,
});
if (isFailedResponse(res)) {
@ -254,10 +262,18 @@ const NowPlayingMiniView = () => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'star' }));
} else {
await unstar({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
}
};

36
src/components/player/NowPlayingView.tsx

@ -58,8 +58,9 @@ import {
getPlayedSongsNotification,
isFailedResponse,
} from '../../shared/utils';
import { getGenres, getMusicFolders, getRandomSongs, setRating, star, unstar } from '../../api/api';
import { notifyToast } from '../shared/toast';
import { apiController } from '../../api/controller';
import { Server, Song } from '../../types';
const NowPlayingView = () => {
const tableRef = useRef<any>();
@ -81,7 +82,9 @@ const NowPlayingView = () => {
const [isLoadingRandom, setIsLoadingRandom] = useState(false);
const [musicFolder, setMusicFolder] = useState(folder.musicFolder);
const { data: musicFolders } = useQuery(['musicFolders'], getMusicFolders);
const { data: musicFolders } = useQuery(['musicFolders'], () =>
apiController({ serverType: config.serverType, endpoint: 'getMusicFolders' })
);
const filteredData = useSearchQuery(misc.searchQuery, playQueue.entry, [
'title',
@ -93,7 +96,7 @@ const NowPlayingView = () => {
]);
const { data: genres }: any = useQuery(['genreList'], async () => {
const res = await getGenres();
const res = await apiController({ serverType: config.serverType, endpoint: 'getGenres' });
const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc');
return genresOrderedBySongCount.map((genre: any) => {
return {
@ -186,12 +189,19 @@ const NowPlayingView = () => {
const handlePlayRandom = async (action: 'play' | 'addNext' | 'addLater') => {
setIsLoadingRandom(true);
const res = await getRandomSongs({
const res: Song[] = await apiController({
serverType: config.serverType,
endpoint: 'getRandomSongs',
args:
config.serverType === Server.Subsonic
? {
size: autoPlaylistTrackCount,
fromYear: autoPlaylistFromYear !== 0 ? autoPlaylistFromYear : undefined,
toYear: autoPlaylistToYear !== 0 ? autoPlaylistToYear : undefined,
genre: randomPlaylistGenre,
musicFolderId: musicFolder,
}
: null,
});
if (isFailedResponse(res)) {
@ -265,16 +275,28 @@ const NowPlayingView = () => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'star' }));
} else {
await unstar({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
}
};
const handleRowRating = (rowData: any, e: number) => {
setRating({ id: rowData.id, rating: e });
apiController({
serverType: config.serverType,
endpoint: 'setRating',
args: { id: rowData.id, rating: e },
});
dispatch(setRate({ id: [rowData.id], rating: e }));
};

43
src/components/player/Player.tsx

@ -28,6 +28,8 @@ import { setCurrentSeek } from '../../redux/playerSlice';
import cacheSong from '../shared/cacheSong';
import { isCached } from '../../shared/utils';
import { scrobble } from '../../api/api';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const gaplessListenHandler = (
currentPlayerRef: any,
@ -38,7 +40,8 @@ const gaplessListenHandler = (
pollingInterval: number,
shouldScrobble: boolean,
scrobbled: boolean,
setScrobbled: any
setScrobbled: any,
serverType: Server
) => {
const currentSeek = currentPlayerRef.current?.audioEl.current?.currentTime || 0;
const duration = currentPlayerRef.current?.audioEl.current?.duration;
@ -67,7 +70,12 @@ const gaplessListenHandler = (
currentSeek <= duration - 2
) {
setScrobbled(true);
scrobble({ id: playQueue.currentSongId, submission: true });
apiController({
serverType,
endpoint: 'scrobble',
args:
serverType === Server.Subsonic ? { id: playQueue.currentSongId, submission: true } : null,
});
}
};
@ -84,7 +92,8 @@ const listenHandler = (
debug: boolean,
shouldScrobble: boolean,
scrobbled: boolean,
setScrobbled: any
setScrobbled: any,
serverType: Server
) => {
const currentSeek = currentPlayerRef.current?.audioEl.current?.currentTime || 0;
const duration = currentPlayerRef.current?.audioEl.current?.duration;
@ -227,7 +236,12 @@ const listenHandler = (
currentSeek <= fadeAtTime
) {
setScrobbled(true);
scrobble({ id: playQueue.currentSongId, submission: true });
apiController({
serverType,
endpoint: 'scrobble',
args:
serverType === Server.Subsonic ? { id: playQueue.currentSongId, submission: true } : null,
});
}
};
@ -238,6 +252,7 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
const playQueue = useAppSelector((state) => state.playQueue);
const player = useAppSelector((state) => state.player);
const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const cacheSongs = settings.getSync('cacheSongs');
const [title] = useState('');
const [scrobbled, setScrobbled] = useState(false);
@ -348,9 +363,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.showDebugWindow,
playQueue.scrobble,
scrobbled,
setScrobbled
setScrobbled,
config.serverType
);
}, [currentEntryList, dispatch, playQueue, scrobbled]);
}, [config.serverType, currentEntryList, dispatch, playQueue, scrobbled]);
const handleListenPlayer2 = useCallback(() => {
listenHandler(
@ -366,9 +382,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.showDebugWindow,
playQueue.scrobble,
scrobbled,
setScrobbled
setScrobbled,
config.serverType
);
}, [currentEntryList, dispatch, playQueue, scrobbled]);
}, [config.serverType, currentEntryList, dispatch, playQueue, scrobbled]);
const handleOnEndedPlayer1 = useCallback(() => {
player1Ref.current.audioEl.current.currentTime = 0;
@ -470,9 +487,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.pollingInterval,
playQueue.scrobble,
scrobbled,
setScrobbled
setScrobbled,
config.serverType
);
}, [dispatch, playQueue, scrobbled]);
}, [config.serverType, dispatch, playQueue, scrobbled]);
const handleGaplessPlayer2 = useCallback(() => {
gaplessListenHandler(
@ -484,9 +502,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.pollingInterval,
playQueue.scrobble,
scrobbled,
setScrobbled
setScrobbled,
config.serverType
);
}, [dispatch, playQueue, scrobbled]);
}, [config.serverType, dispatch, playQueue, scrobbled]);
const handleOnPlay = useCallback(
(playerNumber: 1 | 2) => {

22
src/components/player/PlayerBar.tsx

@ -29,18 +29,20 @@ import { setStatus, resetPlayer } from '../../redux/playerSlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import Player from './Player';
import CustomTooltip from '../shared/CustomTooltip';
import { star, unstar } from '../../api/api';
import placeholderImg from '../../img/placeholder.jpg';
import DebugWindow from '../debug/DebugWindow';
import { CoverArtWrapper } from '../layout/styled';
import { getCurrentEntryList, isCached } from '../../shared/utils';
import { StyledPopover } from '../shared/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const PlayerBar = () => {
const queryClient = useQueryClient();
const playQueue = useAppSelector((state) => state.playQueue);
const player = useAppSelector((state) => state.player);
const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const dispatch = useAppDispatch();
const [seek, setSeek] = useState(0);
const [isDragging, setIsDragging] = useState(false);
@ -273,7 +275,14 @@ const PlayerBar = () => {
const handleFavorite = async () => {
if (!playQueue[currentEntryList][playQueue.currentIndex].starred) {
await star({ id: playQueue[currentEntryList][playQueue.currentIndex].id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args:
config.serverType === Server.Subsonic
? { id: playQueue[currentEntryList][playQueue.currentIndex].id, type: 'music' }
: null,
});
dispatch(
setStar({
id: [playQueue[currentEntryList][playQueue.currentIndex].id],
@ -281,7 +290,14 @@ const PlayerBar = () => {
})
);
} else {
await unstar({ id: playQueue[currentEntryList][playQueue.currentIndex].id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args:
config.serverType === Server.Subsonic
? { id: playQueue[currentEntryList][playQueue.currentIndex].id, type: 'music' }
: null,
});
dispatch(
setStar({
id: [playQueue[currentEntryList][playQueue.currentIndex].id],

7
src/components/playlist/PlaylistList.tsx

@ -4,7 +4,6 @@ import { useHistory } from 'react-router-dom';
import { Form, Whisper } from 'rsuite';
import settings from 'electron-settings';
import useSearchQuery from '../../hooks/useSearchQuery';
import { createPlaylist } from '../../api/api';
import ListViewType from '../viewtypes/ListViewType';
import PageLoader from '../loader/PageLoader';
import GenericPage from '../layout/GenericPage';
@ -39,7 +38,11 @@ const PlaylistList = () => {
const handleCreatePlaylist = async (name: string) => {
try {
const res = await createPlaylist({ name });
const res = await apiController({
serverType: config.serverType,
endpoint: 'createPlaylist',
args: { name },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);

65
src/components/playlist/PlaylistView.tsx

@ -16,16 +16,6 @@ import {
SaveButton,
UndoButton,
} from '../shared/ToolbarButtons';
import {
clearPlaylist,
deletePlaylist,
updatePlaylistSongsLg,
updatePlaylistSongs,
updatePlaylist,
star,
unstar,
setRating,
} from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import {
fixPlayer2Index,
@ -76,6 +66,7 @@ import {
import { PageHeaderSubtitleDataLine } from '../layout/styled';
import CustomTooltip from '../shared/CustomTooltip';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
interface PlaylistParams {
id: string;
@ -224,7 +215,12 @@ const PlaylistView = ({ ...rest }) => {
// Smaller playlists can use the safe /createPlaylist method of saving
if (playlistData.length <= 400 && !recovery) {
res = await updatePlaylistSongs({ id: data.id, entry: playlistData });
res = await apiController({
serverType: config.serverType,
endpoint: 'updatePlaylistSongs',
args: { id: data.id, entry: playlistData },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);
} else {
@ -236,17 +232,25 @@ const PlaylistView = ({ ...rest }) => {
} else {
// For larger playlists, we'll need to first clear out the playlist and then re-populate it
// Tested on Airsonic instances, /createPlaylist fails with around ~350+ songId params
res = await clearPlaylist(data.id);
res = await apiController({
serverType: config.serverType,
endpoint: 'clearPlaylist',
args: { id: data.id },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);
return dispatch(removeProcessingPlaylist(data.id));
}
res = await updatePlaylistSongsLg({ id: data.id, entry: playlistData });
res = await apiController({
serverType: config.serverType,
endpoint: 'updatePlaylistSongsLg',
args: { id: data.id, entry: playlistData },
});
if (isFailedResponse(res)) {
res.forEach((response) => {
res.forEach((response: any) => {
if (isFailedResponse(response)) {
notifyToast('error', errorMessages(response)[0]);
}
@ -287,11 +291,18 @@ const PlaylistView = ({ ...rest }) => {
const handleEdit = async () => {
setIsSubmittingEdit(true);
const res = await updatePlaylist({
const res = await apiController({
serverType: config.serverType,
endpoint: 'updatePlaylist',
args:
config.serverType === Server.Subsonic
? {
id: data.id,
name: editName,
comment: editDescription,
isPublic: editPublic,
}
: null,
});
if (isFailedResponse(res)) {
@ -308,7 +319,11 @@ const PlaylistView = ({ ...rest }) => {
const handleDelete = async () => {
try {
const res = await deletePlaylist({ id: data.id });
const res = await apiController({
serverType: config.serverType,
endpoint: 'deletePlaylist',
args: { id: data.id },
});
if (isFailedResponse(res)) {
notifyToast('error', res.error.message);
@ -334,7 +349,11 @@ const PlaylistView = ({ ...rest }) => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'star' }));
dispatch(setPlaylistStar({ id: [rowData.id], type: 'star' }));
@ -347,7 +366,11 @@ const PlaylistView = ({ ...rest }) => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
dispatch(setPlaylistStar({ id: [rowData.id], type: 'unstar' }));
@ -363,7 +386,11 @@ const PlaylistView = ({ ...rest }) => {
};
const handleRowRating = (rowData: any, e: number) => {
setRating({ id: rowData.id, rating: e });
apiController({
serverType: config.serverType,
endpoint: 'setRating',
args: { id: rowData.id, rating: e },
});
dispatch(setRate({ id: [rowData.id], rating: e }));
dispatch(setPlaylistRate({ id: [rowData.id], rating: e }));
};

48
src/components/search/SearchView.tsx

@ -3,7 +3,6 @@ import _ from 'lodash';
import settings from 'electron-settings';
import { useHistory } from 'react-router-dom';
import { useQuery, useQueryClient } from 'react-query';
import { getSearch, star, unstar } from '../../api/api';
import useRouterQuery from '../../hooks/useRouterQuery';
import GenericPage from '../layout/GenericPage';
import GenericPageHeader from '../layout/GenericPageHeader';
@ -20,6 +19,8 @@ import { fixPlayer2Index, setPlayQueueByRowClick } from '../../redux/playQueueSl
import { setStatus } from '../../redux/playerSlice';
import ListViewTable from '../viewtypes/ListViewTable';
import { SectionTitle, SectionTitleWrapper, StyledPanel } from '../shared/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const SearchView = () => {
const dispatch = useAppDispatch();
@ -40,7 +41,14 @@ const SearchView = () => {
}, [folder]);
const { isLoading, isError, data, error }: any = useQuery(['search', urlQuery, musicFolder], () =>
getSearch({ query: urlQuery, songCount: 100, musicFolderId: musicFolder })
apiController({
serverType: config.serverType,
endpoint: 'getSearch',
args:
config.serverType === Server.Subsonic
? { query: urlQuery, songCount: 100, musicFolderId: musicFolder }
: null,
})
);
let timeout: any = null;
@ -83,7 +91,11 @@ const SearchView = () => {
const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.song, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -93,7 +105,11 @@ const SearchView = () => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.song, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -107,7 +123,11 @@ const SearchView = () => {
const handleArtistFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'artist' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'artist' } : null,
});
queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.artist, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -117,7 +137,11 @@ const SearchView = () => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.artist, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -131,7 +155,11 @@ const SearchView = () => {
const handleAlbumFavorite = async (rowData: any) => {
if (!rowData.starred) {
await star({ id: rowData.id, type: 'artist' });
await apiController({
serverType: config.serverType,
endpoint: 'star',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'artist' } : null,
});
queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.album, { id: rowData.id }));
starredIndices.forEach((index) => {
@ -141,7 +169,11 @@ const SearchView = () => {
return oldData;
});
} else {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.album, { id: rowData.id }));
starredIndices.forEach((index) => {

12
src/components/settings/Config.tsx

@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { shell } from 'electron';
import { Whisper, Nav, ButtonToolbar } from 'rsuite';
import { startScan, getScanStatus } from '../../api/api';
import GenericPage from '../layout/GenericPage';
import DisconnectButton from './DisconnectButton';
import GenericPageHeader from '../layout/GenericPageHeader';
@ -18,6 +17,7 @@ import packageJson from '../../package.json';
import ServerConfig from './ConfigPanels/ServerConfig';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { setActive } from '../../redux/configSlice';
import { apiController } from '../../api/controller';
const GITHUB_RELEASE_URL = 'https://api.github.com/repos/jeffvli/sonixd/releases?per_page=3';
@ -40,7 +40,7 @@ const Config = () => {
useEffect(() => {
// Check scan status on render
getScanStatus()
apiController({ serverType: config.serverType, endpoint: 'getScanStatus' })
.then((status) => {
if (status.scanning) {
return setIsScanning(true);
@ -49,13 +49,13 @@ const Config = () => {
return setScanProgress(0);
})
.catch((err) => console.log(err));
}, []);
}, [config.serverType]);
useEffect(() => {
// Reload scan status on interval during scan
if (isScanning) {
const interval = setInterval(() => {
getScanStatus()
apiController({ serverType: config.serverType, endpoint: 'getScanStatus' })
.then((status) => {
if (status.scanning) {
return setScanProgress(status.count);
@ -69,7 +69,7 @@ const Config = () => {
return () => clearInterval(interval);
}
return () => clearInterval();
}, [isScanning]);
}, [config.serverType, isScanning]);
return (
<GenericPage
@ -125,7 +125,7 @@ const Config = () => {
<StyledButton
size="sm"
onClick={async () => {
startScan();
apiController({ serverType: config.serverType, endpoint: 'startScan' });
setIsScanning(true);
}}
disabled={isScanning}

7
src/components/settings/ConfigPanels/ServerConfig.tsx

@ -5,13 +5,16 @@ import { CheckboxGroup } from 'rsuite';
import { ConfigPanel } from '../styled';
import { StyledCheckbox, StyledInputPicker, StyledInputPickerContainer } from '../../shared/styled';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { getMusicFolders } from '../../../api/api';
import { setAppliedFolderViews, setMusicFolder } from '../../../redux/folderSlice';
import { apiController } from '../../../api/controller';
const ServerConfig = () => {
const dispatch = useAppDispatch();
const folder = useAppSelector((state) => state.folder);
const { isLoading, data: musicFolders } = useQuery(['musicFolders'], getMusicFolders);
const config = useAppSelector((state) => state.config);
const { isLoading, data: musicFolders } = useQuery(['musicFolders'], () =>
apiController({ serverType: config.serverType, endpoint: 'getMusicFolders' })
);
const musicFolderPickerContainerRef = useRef(null);
return (

149
src/components/shared/ContextMenu.tsx

@ -5,18 +5,6 @@ import { nanoid } from 'nanoid/non-secure';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router';
import { Col, FlexboxGrid, Form, Grid, Icon, Row, Whisper } from 'rsuite';
import {
getPlaylists,
updatePlaylistSongsLg,
createPlaylist,
batchStar,
batchUnstar,
getAlbum,
getPlaylist,
deletePlaylist,
getArtistSongs,
getMusicDirectorySongs,
} from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import {
addModalPage,
@ -67,6 +55,7 @@ import {
isFailedResponse,
} from '../../shared/utils';
import { setStatus } from '../../redux/playerSlice';
import { apiController } from '../../api/controller';
export const ContextMenuButton = ({ text, hotkey, children, ...rest }: any) => {
return (
@ -120,7 +109,9 @@ export const GlobalContextMenu = () => {
const [indexToMoveTo, setIndexToMoveTo] = useState(0);
const playlistPickerContainerRef = useRef(null);
const { data: playlists }: any = useQuery(['playlists'], () => getPlaylists());
const { data: playlists }: any = useQuery(['playlists'], () =>
apiController({ serverType: config.serverType, endpoint: 'getPlaylists' })
);
const handlePlay = async () => {
dispatch(setContextMenu({ show: false }));
@ -135,7 +126,13 @@ export const GlobalContextMenu = () => {
});
for (let i = 0; i < folders.length; i += 1) {
promises.push(getMusicDirectorySongs({ id: folders[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getMusicDirectorySongs',
args: { id: folders[i].id },
})
);
}
const res = await Promise.all(promises);
@ -154,7 +151,13 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'play' }));
} else if (misc.contextMenu.type === 'playlist') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getPlaylist(multiSelect.selected[i].id));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getPlaylist',
args: { id: multiSelect.selected[i].id },
})
);
}
const res = await Promise.all(promises);
@ -172,7 +175,13 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'play' }));
} else if (misc.contextMenu.type === 'album') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAlbum({ id: multiSelect.selected[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: multiSelect.selected[i].id },
})
);
}
const res = await Promise.all(promises);
@ -191,7 +200,13 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'play' }));
} else if (misc.contextMenu.type === 'artist') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getArtistSongs({ id: multiSelect.selected[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getArtistSongs',
args: { id: multiSelect.selected[i].id },
})
);
}
const res = await Promise.all(promises);
@ -223,7 +238,13 @@ export const GlobalContextMenu = () => {
});
for (let i = 0; i < folders.length; i += 1) {
promises.push(getMusicDirectorySongs({ id: multiSelect.selected[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getMusicDirectorySongs',
args: { id: folders[i].id },
})
);
}
const res = await Promise.all(promises);
@ -238,7 +259,13 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'add' }));
} else if (misc.contextMenu.type === 'playlist') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getPlaylist(multiSelect.selected[i].id));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getPlaylist',
args: { id: multiSelect.selected[i].id },
})
);
}
const res = await Promise.all(promises);
@ -252,7 +279,13 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'add' }));
} else if (misc.contextMenu.type === 'album') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAlbum({ id: multiSelect.selected[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: multiSelect.selected[i].id },
})
);
}
const res = await Promise.all(promises);
@ -266,7 +299,13 @@ export const GlobalContextMenu = () => {
notifyToast('info', getPlayedSongsNotification({ ...songs.count, type: 'add' }));
} else if (misc.contextMenu.type === 'artist') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getArtistSongs({ id: multiSelect.selected[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getArtistSongs',
args: { id: multiSelect.selected[i].id },
})
);
}
const res = await Promise.all(promises);
@ -332,7 +371,13 @@ export const GlobalContextMenu = () => {
});
for (let i = 0; i < folders.length; i += 1) {
promises.push(getMusicDirectorySongs({ id: multiSelect.selected[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getMusicDirectorySongs',
args: { id: folders[i].id },
})
);
}
const folderSongs = await Promise.all(promises);
@ -340,7 +385,11 @@ export const GlobalContextMenu = () => {
folderSongs.push(_.orderBy(music, 'rowIndex', 'asc'));
songs = _.flatten(folderSongs);
res = await updatePlaylistSongsLg({ id: localSelectedPlaylistId, entry: songs });
res = await apiController({
serverType: config.serverType,
endpoint: 'updatePlaylistSongsLg',
args: { id: localSelectedPlaylistId, entry: songs },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);
@ -349,12 +398,22 @@ export const GlobalContextMenu = () => {
}
} else if (misc.contextMenu.type === 'playlist') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getPlaylist(multiSelect.selected[i].id));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getPlaylist',
args: { id: multiSelect.selected[i].id },
})
);
}
res = await Promise.all(promises);
songs = _.flatten(_.map(res, 'song'));
res = await updatePlaylistSongsLg({ id: localSelectedPlaylistId, entry: songs });
res = await apiController({
serverType: config.serverType,
endpoint: 'updatePlaylistSongsLg',
args: { id: localSelectedPlaylistId, entry: songs },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);
@ -363,12 +422,22 @@ export const GlobalContextMenu = () => {
}
} else if (misc.contextMenu.type === 'album') {
for (let i = 0; i < multiSelect.selected.length; i += 1) {
promises.push(getAlbum({ id: multiSelect.selected[i].id }));
promises.push(
apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: multiSelect.selected[i].id },
})
);
}
res = await Promise.all(promises);
songs = _.flatten(_.map(res, 'song'));
res = await updatePlaylistSongsLg({ id: localSelectedPlaylistId, entry: songs });
res = await apiController({
serverType: config.serverType,
endpoint: 'updatePlaylistSongsLg',
args: { id: localSelectedPlaylistId, entry: songs },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);
@ -394,7 +463,13 @@ export const GlobalContextMenu = () => {
const res = [];
for (let i = 0; i < multiSelect.selected.length; i += 1) {
try {
res.push(await deletePlaylist({ id: multiSelect.selected[i].id }));
res.push(
await apiController({
serverType: config.serverType,
endpoint: 'deletePlaylist',
args: { id: multiSelect.selected[i].id },
})
);
} catch (err) {
notifyToast('error', err);
}
@ -413,7 +488,11 @@ export const GlobalContextMenu = () => {
const handleCreatePlaylist = async () => {
try {
const res = await createPlaylist({ name: newPlaylistName });
const res = await apiController({
serverType: config.serverType,
endpoint: 'createPlaylist',
args: { name: newPlaylistName },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);
@ -453,7 +532,11 @@ export const GlobalContextMenu = () => {
const ids = _.map(sortedEntries, 'id');
try {
const res = await batchStar({ ids, type: sortedEntries[0].type });
const res = await apiController({
serverType: config.serverType,
endpoint: 'batchStar',
args: { ids, type: sortedEntries[0].type },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);
@ -476,7 +559,11 @@ export const GlobalContextMenu = () => {
try {
// Infer the type from the first selected entry
const res = await batchUnstar({ ids, type: multiSelect.selected[0].type });
const res = await apiController({
serverType: config.serverType,
endpoint: 'batchUnstar',
args: { ids, type: multiSelect.selected[0].type },
});
if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]);

33
src/components/starred/StarredView.tsx

@ -12,7 +12,6 @@ import {
toggleRangeSelected,
setRangeSelected,
} from '../../redux/multiSelectSlice';
import { getStarred, unstar } from '../../api/api';
import GenericPage from '../layout/GenericPage';
import GenericPageHeader from '../layout/GenericPageHeader';
import PageLoader from '../loader/PageLoader';
@ -21,6 +20,8 @@ import GridViewType from '../viewtypes/GridViewType';
import { setStatus } from '../../redux/playerSlice';
import { StyledNavItem } from '../shared/styled';
import { setActive } from '../../redux/favoriteSlice';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const StarredView = () => {
const history = useHistory();
@ -40,8 +41,13 @@ const StarredView = () => {
}, [folder]);
const { isLoading, isError, data, error }: any = useQuery(['starred', musicFolder], () =>
getStarred({ musicFolderId: musicFolder })
apiController({
serverType: config.serverType,
endpoint: 'getStarred',
args: config.serverType === Server.Subsonic ? { musicFolderId: musicFolder } : null,
})
);
const filteredData = useSearchQuery(
misc.searchQuery,
favorite.active.tab === 'tracks'
@ -97,7 +103,11 @@ const StarredView = () => {
};
const handleRowFavorite = async (rowData: any) => {
await unstar({ id: rowData.id, type: 'music' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'music' } : null,
});
dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
await queryClient.refetchQueries(['starred', musicFolder], {
active: true,
@ -105,14 +115,22 @@ const StarredView = () => {
};
const handleRowFavoriteAlbum = async (rowData: any) => {
await unstar({ id: rowData.id, type: 'album' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'album' } : null,
});
await queryClient.refetchQueries(['starred', musicFolder], {
active: true,
});
};
const handleRowFavoriteArtist = async (rowData: any) => {
await unstar({ id: rowData.id, type: 'artist' });
await apiController({
serverType: config.serverType,
endpoint: 'unstar',
args: config.serverType === Server.Subsonic ? { id: rowData.id, type: 'artist' } : null,
});
await queryClient.refetchQueries(['starred', musicFolder], {
active: true,
});
@ -170,9 +188,8 @@ const StarredView = () => {
/>
}
>
{isLoading ? (
<PageLoader />
) : (
{(isLoading || !data) && <PageLoader />}
{data && (
<>
{favorite.active.tab === 'tracks' && (
<ListViewType

Loading…
Cancel
Save