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. 53
      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. 42
      src/components/player/NowPlayingMiniView.tsx
  10. 46
      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. 73
      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 { 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, getArtistSongs } from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
appendPlayQueue, appendPlayQueue,
@ -32,6 +31,7 @@ import { setStatus } from '../../redux/playerSlice';
import { addModalPage } from '../../redux/miscSlice'; import { addModalPage } from '../../redux/miscSlice';
import { notifyToast } from '../shared/toast'; import { notifyToast } from '../shared/toast';
import CustomTooltip from '../shared/CustomTooltip'; import CustomTooltip from '../shared/CustomTooltip';
import { apiController } from '../../api/controller';
const Card = ({ const Card = ({
onClick, onClick,
@ -60,7 +60,12 @@ const Card = ({
const handlePlayClick = async () => { const handlePlayClick = async () => {
if (playClick.type === 'playlist') { 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); const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -76,7 +81,12 @@ const Card = ({
} }
if (playClick.type === 'album') { 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); const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -92,7 +102,12 @@ const Card = ({
} }
if (playClick.type === 'artist') { 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); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -110,7 +125,12 @@ const Card = ({
const handlePlayAppend = async (type: 'next' | 'later') => { const handlePlayAppend = async (type: 'next' | 'later') => {
if (playClick.type === 'playlist') { 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); const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -122,7 +142,12 @@ const Card = ({
} }
if (playClick.type === 'album') { 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); const songs = filterPlayQueue(config.playback.filters, res.song);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -134,7 +159,12 @@ const Card = ({
} }
if (playClick.type === 'artist') { 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); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { 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 _ from 'lodash';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useQuery, useQueryClient } from 'react-query'; import { useQuery, useQueryClient } from 'react-query';
import { getAlbums, star, unstar } from '../../api/api';
import PageLoader from '../loader/PageLoader'; import PageLoader from '../loader/PageLoader';
import GenericPage from '../layout/GenericPage'; import GenericPage from '../layout/GenericPage';
import GenericPageHeader from '../layout/GenericPageHeader'; import GenericPageHeader from '../layout/GenericPageHeader';
@ -10,6 +9,8 @@ import ScrollingMenu from '../scrollingmenu/ScrollingMenu';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { setStar } from '../../redux/playQueueSlice'; import { setStar } from '../../redux/playQueueSlice';
import { setActive } from '../../redux/albumSlice'; import { setActive } from '../../redux/albumSlice';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const Dashboard = () => { const Dashboard = () => {
const history = useHistory(); const history = useHistory();
@ -28,27 +29,63 @@ 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, 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( const { isLoading: isLoadingNewest, data: newestAlbums }: any = useQuery(
['newestAlbums', musicFolder], ['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( const { isLoading: isLoadingRandom, data: randomAlbums }: any = useQuery(
['randomAlbums', musicFolder], ['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( const { isLoading: isLoadingFrequent, data: frequentAlbums }: any = useQuery(
['frequentAlbums', musicFolder], ['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) => { const handleFavorite = async (rowData: any) => {
if (!rowData.starred) { 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' })); dispatch(setStar({ id: [rowData.id], type: 'star' }));
queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));
@ -83,7 +120,11 @@ const Dashboard = () => {
return oldData; return oldData;
}); });
} else { } 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' })); dispatch(setStar({ id: [rowData.id], type: 'unstar' }));
queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => { queryClient.setQueryData(['recentAlbums', musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData, { id: rowData.id }));

53
src/components/library/AlbumList.tsx

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

51
src/components/library/AlbumView.tsx

@ -12,7 +12,6 @@ import {
PlayAppendNextButton, PlayAppendNextButton,
PlayButton, PlayButton,
} from '../shared/ToolbarButtons'; } from '../shared/ToolbarButtons';
import { getAlbum, getDownloadUrl, star, unstar } from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
appendPlayQueue, appendPlayQueue,
@ -51,6 +50,8 @@ import {
BlurredBackgroundWrapper, BlurredBackgroundWrapper,
PageHeaderSubtitleDataLine, PageHeaderSubtitleDataLine,
} from '../layout/styled'; } from '../layout/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
interface AlbumParams { interface AlbumParams {
id: string; id: string;
@ -68,7 +69,11 @@ 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({ id: albumId }) apiController({
serverType: config.serverType,
endpoint: 'getAlbum',
args: { id: albumId },
})
); );
const filteredData = useSearchQuery(misc.searchQuery, data?.song, [ const filteredData = useSearchQuery(misc.searchQuery, data?.song, [
'title', 'title',
@ -141,17 +146,29 @@ const AlbumView = ({ ...rest }: any) => {
const handleFavorite = async () => { const handleFavorite = async () => {
if (!data.starred) { 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() }); queryClient.setQueryData(['album', id], { ...data, starred: Date.now() });
} else { } 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 }); queryClient.setQueryData(['album', id], { ...data, starred: undefined });
} }
}; };
const handleRowFavorite = async (rowData: any) => { const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) { 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(setStar({ id: [rowData.id], type: 'star' }));
queryClient.setQueryData(['album', id], (oldData: any) => { queryClient.setQueryData(['album', id], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.song, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData?.song, { id: rowData.id }));
@ -162,7 +179,11 @@ const AlbumView = ({ ...rest }: any) => {
return oldData; return oldData;
}); });
} else { } 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(setStar({ id: [rowData.id], type: 'unstar' }));
queryClient.setQueryData(['album', id], (oldData: any) => { queryClient.setQueryData(['album', id], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.song, { id: rowData.id })); 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 // 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 // to download. This is because Airsonic does not support downloading via album ids
// that are provided by /getAlbum or /getAlbumList2 // that are provided by /getAlbum or /getAlbumList2
if (data.song[0]?.parent) { if (data.song[0]?.parent) {
if (type === 'download') { 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 { } 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!'); notifyToast('info', 'Download links copied!');
} }
} else { } else {

22
src/components/library/ArtistList.tsx

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

91
src/components/library/ArtistView.tsx

@ -14,15 +14,6 @@ import {
PlayAppendNextButton, PlayAppendNextButton,
PlayButton, PlayButton,
} from '../shared/ToolbarButtons'; } from '../shared/ToolbarButtons';
import {
getAlbum,
getArtistSongs,
getArtist,
getArtistInfo,
getDownloadUrl,
star,
unstar,
} from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
toggleSelected, toggleSelected,
@ -54,6 +45,8 @@ import {
import { StyledButton, StyledPopover, StyledTag } from '../shared/styled'; import { StyledButton, StyledPopover, StyledTag } from '../shared/styled';
import { setStatus } from '../../redux/playerSlice'; import { setStatus } from '../../redux/playerSlice';
import { GradientBackground, PageHeaderSubtitleDataLine } from '../layout/styled'; import { GradientBackground, PageHeaderSubtitleDataLine } from '../layout/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const fac = new FastAverageColor(); const fac = new FastAverageColor();
@ -75,14 +68,20 @@ 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({ id: artistId }) apiController({ serverType: config.serverType, endpoint: 'getArtist', args: { 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({ 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, [ const filteredData = useSearchQuery(misc.searchQuery, data?.album, [
'title', 'title',
@ -116,16 +115,29 @@ const ArtistView = ({ ...rest }: any) => {
const handleFavorite = async () => { const handleFavorite = async () => {
if (!data.starred) { 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() }); queryClient.setQueryData(['artist', artistId], { ...data, starred: Date.now() });
} else { } 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 }); queryClient.setQueryData(['artist', artistId], { ...data, starred: undefined });
} }
}; };
const handlePlay = async () => { 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); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -141,7 +153,12 @@ const ArtistView = ({ ...rest }: any) => {
}; };
const handlePlayAppend = async (type: 'next' | 'later') => { 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); const songs = filterPlayQueue(config.playback.filters, res);
if (songs.entries.length > 0) { if (songs.entries.length > 0) {
@ -154,7 +171,11 @@ const ArtistView = ({ ...rest }: any) => {
const handleRowFavorite = async (rowData: any) => { const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) { 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) => { queryClient.setQueryData(['artist', artistId], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -164,7 +185,11 @@ const ArtistView = ({ ...rest }: any) => {
return oldData; return oldData;
}); });
} else { } 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) => { queryClient.setQueryData(['artist', artistId], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData?.album, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -179,9 +204,21 @@ const ArtistView = ({ ...rest }: any) => {
const handleDownload = async (type: 'copy' | 'download') => { const handleDownload = async (type: 'copy' | 'download') => {
if (data.album[0]?.parent) { if (data.album[0]?.parent) {
if (type === 'download') { 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 { } 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!'); notifyToast('info', 'Download links copied!');
} }
} else { } else {
@ -189,9 +226,21 @@ 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({ 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) { 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 { } else {
notifyToast('warning', `[${albumRes.title}] No parent album found`); 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 { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { ButtonToolbar, Icon } from 'rsuite'; import { ButtonToolbar, Icon } from 'rsuite';
import {
getIndexes,
getMusicDirectory,
getMusicFolders,
setRating,
star,
unstar,
} from '../../api/api';
import PageLoader from '../loader/PageLoader'; import PageLoader from '../loader/PageLoader';
import ListViewType from '../viewtypes/ListViewType'; import ListViewType from '../viewtypes/ListViewType';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
@ -29,6 +21,8 @@ import { setStatus } from '../../redux/playerSlice';
import useSearchQuery from '../../hooks/useSearchQuery'; import useSearchQuery from '../../hooks/useSearchQuery';
import { setCurrentViewedFolder } from '../../redux/folderSlice'; import { setCurrentViewedFolder } from '../../redux/folderSlice';
import useRouterQuery from '../../hooks/useRouterQuery'; import useRouterQuery from '../../hooks/useRouterQuery';
import { Server } from '../../types';
import { apiController } from '../../api/controller';
const FolderList = () => { const FolderList = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -43,19 +37,28 @@ const FolderList = () => {
const { isLoading, isError, data: indexData, error }: any = useQuery( const { isLoading, isError, data: indexData, error }: any = useQuery(
['indexes', musicFolder], ['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( const { isLoading: isLoadingFolderData, data: folderData }: any = useQuery(
['folder', folder.currentViewedFolder], ['folder', folder.currentViewedFolder],
() => getMusicDirectory({ id: folder.currentViewedFolder }), () =>
apiController({
serverType: config.serverType,
endpoint: 'getMusicDirectory',
args: { id: folder.currentViewedFolder },
}),
{ {
enabled: folder.currentViewedFolder !== '', enabled: folder.currentViewedFolder !== '',
} }
); );
const { isLoading: isLoadingMusicFolders, data: musicFolders } = useQuery( const { isLoading: isLoadingMusicFolders, data: musicFolders } = useQuery(['musicFolders'], () =>
['musicFolders'], apiController({ serverType: config.serverType, endpoint: 'getMusicFolders' })
getMusicFolders
); );
const filteredData = useSearchQuery( const filteredData = useSearchQuery(
@ -112,7 +115,11 @@ const FolderList = () => {
const handleRowFavorite = async (rowData: any) => { const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) { 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) => { queryClient.setQueryData(['folder', folder.currentViewedFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.child, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.child, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -122,7 +129,11 @@ const FolderList = () => {
return oldData; return oldData;
}); });
} else { } 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) => { queryClient.setQueryData(['folder', folder.currentViewedFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.child, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.child, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -135,7 +146,11 @@ const FolderList = () => {
}; };
const handleRowRating = (rowData: any, e: number) => { 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(setRate({ id: [rowData.id], rating: e }));
}; };

4
src/components/library/GenreList.tsx

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

42
src/components/player/NowPlayingMiniView.tsx

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

46
src/components/player/NowPlayingView.tsx

@ -58,8 +58,9 @@ import {
getPlayedSongsNotification, getPlayedSongsNotification,
isFailedResponse, isFailedResponse,
} from '../../shared/utils'; } from '../../shared/utils';
import { getGenres, getMusicFolders, getRandomSongs, setRating, star, unstar } from '../../api/api';
import { notifyToast } from '../shared/toast'; import { notifyToast } from '../shared/toast';
import { apiController } from '../../api/controller';
import { Server, Song } from '../../types';
const NowPlayingView = () => { const NowPlayingView = () => {
const tableRef = useRef<any>(); const tableRef = useRef<any>();
@ -81,7 +82,9 @@ const NowPlayingView = () => {
const [isLoadingRandom, setIsLoadingRandom] = useState(false); const [isLoadingRandom, setIsLoadingRandom] = useState(false);
const [musicFolder, setMusicFolder] = useState(folder.musicFolder); 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, [ const filteredData = useSearchQuery(misc.searchQuery, playQueue.entry, [
'title', 'title',
@ -93,7 +96,7 @@ const NowPlayingView = () => {
]); ]);
const { data: genres }: any = useQuery(['genreList'], async () => { 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'); const genresOrderedBySongCount = _.orderBy(res, 'songCount', 'desc');
return genresOrderedBySongCount.map((genre: any) => { return genresOrderedBySongCount.map((genre: any) => {
return { return {
@ -186,12 +189,19 @@ const NowPlayingView = () => {
const handlePlayRandom = async (action: 'play' | 'addNext' | 'addLater') => { const handlePlayRandom = async (action: 'play' | 'addNext' | 'addLater') => {
setIsLoadingRandom(true); setIsLoadingRandom(true);
const res = await getRandomSongs({ const res: Song[] = await apiController({
size: autoPlaylistTrackCount, serverType: config.serverType,
fromYear: autoPlaylistFromYear !== 0 ? autoPlaylistFromYear : undefined, endpoint: 'getRandomSongs',
toYear: autoPlaylistToYear !== 0 ? autoPlaylistToYear : undefined, args:
genre: randomPlaylistGenre, config.serverType === Server.Subsonic
musicFolderId: musicFolder, ? {
size: autoPlaylistTrackCount,
fromYear: autoPlaylistFromYear !== 0 ? autoPlaylistFromYear : undefined,
toYear: autoPlaylistToYear !== 0 ? autoPlaylistToYear : undefined,
genre: randomPlaylistGenre,
musicFolderId: musicFolder,
}
: null,
}); });
if (isFailedResponse(res)) { if (isFailedResponse(res)) {
@ -265,16 +275,28 @@ const NowPlayingView = () => {
const handleRowFavorite = async (rowData: any) => { const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) { 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(setStar({ id: [rowData.id], type: 'star' }));
} else { } 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(setStar({ id: [rowData.id], type: 'unstar' }));
} }
}; };
const handleRowRating = (rowData: any, e: number) => { 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(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 cacheSong from '../shared/cacheSong';
import { isCached } from '../../shared/utils'; import { isCached } from '../../shared/utils';
import { scrobble } from '../../api/api'; import { scrobble } from '../../api/api';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const gaplessListenHandler = ( const gaplessListenHandler = (
currentPlayerRef: any, currentPlayerRef: any,
@ -38,7 +40,8 @@ const gaplessListenHandler = (
pollingInterval: number, pollingInterval: number,
shouldScrobble: boolean, shouldScrobble: boolean,
scrobbled: boolean, scrobbled: boolean,
setScrobbled: any setScrobbled: any,
serverType: Server
) => { ) => {
const currentSeek = currentPlayerRef.current?.audioEl.current?.currentTime || 0; const currentSeek = currentPlayerRef.current?.audioEl.current?.currentTime || 0;
const duration = currentPlayerRef.current?.audioEl.current?.duration; const duration = currentPlayerRef.current?.audioEl.current?.duration;
@ -67,7 +70,12 @@ const gaplessListenHandler = (
currentSeek <= duration - 2 currentSeek <= duration - 2
) { ) {
setScrobbled(true); 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, debug: boolean,
shouldScrobble: boolean, shouldScrobble: boolean,
scrobbled: boolean, scrobbled: boolean,
setScrobbled: any setScrobbled: any,
serverType: Server
) => { ) => {
const currentSeek = currentPlayerRef.current?.audioEl.current?.currentTime || 0; const currentSeek = currentPlayerRef.current?.audioEl.current?.currentTime || 0;
const duration = currentPlayerRef.current?.audioEl.current?.duration; const duration = currentPlayerRef.current?.audioEl.current?.duration;
@ -227,7 +236,12 @@ const listenHandler = (
currentSeek <= fadeAtTime currentSeek <= fadeAtTime
) { ) {
setScrobbled(true); 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 playQueue = useAppSelector((state) => state.playQueue);
const player = useAppSelector((state) => state.player); const player = useAppSelector((state) => state.player);
const misc = useAppSelector((state) => state.misc); const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const cacheSongs = settings.getSync('cacheSongs'); const cacheSongs = settings.getSync('cacheSongs');
const [title] = useState(''); const [title] = useState('');
const [scrobbled, setScrobbled] = useState(false); const [scrobbled, setScrobbled] = useState(false);
@ -348,9 +363,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.showDebugWindow, playQueue.showDebugWindow,
playQueue.scrobble, playQueue.scrobble,
scrobbled, scrobbled,
setScrobbled setScrobbled,
config.serverType
); );
}, [currentEntryList, dispatch, playQueue, scrobbled]); }, [config.serverType, currentEntryList, dispatch, playQueue, scrobbled]);
const handleListenPlayer2 = useCallback(() => { const handleListenPlayer2 = useCallback(() => {
listenHandler( listenHandler(
@ -366,9 +382,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.showDebugWindow, playQueue.showDebugWindow,
playQueue.scrobble, playQueue.scrobble,
scrobbled, scrobbled,
setScrobbled setScrobbled,
config.serverType
); );
}, [currentEntryList, dispatch, playQueue, scrobbled]); }, [config.serverType, currentEntryList, dispatch, playQueue, scrobbled]);
const handleOnEndedPlayer1 = useCallback(() => { const handleOnEndedPlayer1 = useCallback(() => {
player1Ref.current.audioEl.current.currentTime = 0; player1Ref.current.audioEl.current.currentTime = 0;
@ -470,9 +487,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.pollingInterval, playQueue.pollingInterval,
playQueue.scrobble, playQueue.scrobble,
scrobbled, scrobbled,
setScrobbled setScrobbled,
config.serverType
); );
}, [dispatch, playQueue, scrobbled]); }, [config.serverType, dispatch, playQueue, scrobbled]);
const handleGaplessPlayer2 = useCallback(() => { const handleGaplessPlayer2 = useCallback(() => {
gaplessListenHandler( gaplessListenHandler(
@ -484,9 +502,10 @@ const Player = ({ currentEntryList, children }: any, ref: any) => {
playQueue.pollingInterval, playQueue.pollingInterval,
playQueue.scrobble, playQueue.scrobble,
scrobbled, scrobbled,
setScrobbled setScrobbled,
config.serverType
); );
}, [dispatch, playQueue, scrobbled]); }, [config.serverType, dispatch, playQueue, scrobbled]);
const handleOnPlay = useCallback( const handleOnPlay = useCallback(
(playerNumber: 1 | 2) => { (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 { useAppDispatch, useAppSelector } from '../../redux/hooks';
import Player from './Player'; import Player from './Player';
import CustomTooltip from '../shared/CustomTooltip'; import CustomTooltip from '../shared/CustomTooltip';
import { star, unstar } from '../../api/api';
import placeholderImg from '../../img/placeholder.jpg'; import placeholderImg from '../../img/placeholder.jpg';
import DebugWindow from '../debug/DebugWindow'; import DebugWindow from '../debug/DebugWindow';
import { CoverArtWrapper } from '../layout/styled'; import { CoverArtWrapper } from '../layout/styled';
import { getCurrentEntryList, isCached } from '../../shared/utils'; import { getCurrentEntryList, isCached } from '../../shared/utils';
import { StyledPopover } from '../shared/styled'; import { StyledPopover } from '../shared/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const PlayerBar = () => { const PlayerBar = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const playQueue = useAppSelector((state) => state.playQueue); const playQueue = useAppSelector((state) => state.playQueue);
const player = useAppSelector((state) => state.player); const player = useAppSelector((state) => state.player);
const misc = useAppSelector((state) => state.misc); const misc = useAppSelector((state) => state.misc);
const config = useAppSelector((state) => state.config);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [seek, setSeek] = useState(0); const [seek, setSeek] = useState(0);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
@ -273,7 +275,14 @@ const PlayerBar = () => {
const handleFavorite = async () => { const handleFavorite = async () => {
if (!playQueue[currentEntryList][playQueue.currentIndex].starred) { 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( dispatch(
setStar({ setStar({
id: [playQueue[currentEntryList][playQueue.currentIndex].id], id: [playQueue[currentEntryList][playQueue.currentIndex].id],
@ -281,7 +290,14 @@ const PlayerBar = () => {
}) })
); );
} else { } 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( dispatch(
setStar({ setStar({
id: [playQueue[currentEntryList][playQueue.currentIndex].id], 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 { Form, Whisper } from 'rsuite';
import settings from 'electron-settings'; import settings from 'electron-settings';
import useSearchQuery from '../../hooks/useSearchQuery'; import useSearchQuery from '../../hooks/useSearchQuery';
import { createPlaylist } from '../../api/api';
import ListViewType from '../viewtypes/ListViewType'; import ListViewType from '../viewtypes/ListViewType';
import PageLoader from '../loader/PageLoader'; import PageLoader from '../loader/PageLoader';
import GenericPage from '../layout/GenericPage'; import GenericPage from '../layout/GenericPage';
@ -39,7 +38,11 @@ const PlaylistList = () => {
const handleCreatePlaylist = async (name: string) => { const handleCreatePlaylist = async (name: string) => {
try { try {
const res = await createPlaylist({ name }); const res = await apiController({
serverType: config.serverType,
endpoint: 'createPlaylist',
args: { name },
});
if (isFailedResponse(res)) { if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]); notifyToast('error', errorMessages(res)[0]);

73
src/components/playlist/PlaylistView.tsx

@ -16,16 +16,6 @@ import {
SaveButton, SaveButton,
UndoButton, UndoButton,
} from '../shared/ToolbarButtons'; } from '../shared/ToolbarButtons';
import {
clearPlaylist,
deletePlaylist,
updatePlaylistSongsLg,
updatePlaylistSongs,
updatePlaylist,
star,
unstar,
setRating,
} from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
fixPlayer2Index, fixPlayer2Index,
@ -76,6 +66,7 @@ import {
import { PageHeaderSubtitleDataLine } from '../layout/styled'; import { PageHeaderSubtitleDataLine } from '../layout/styled';
import CustomTooltip from '../shared/CustomTooltip'; import CustomTooltip from '../shared/CustomTooltip';
import { apiController } from '../../api/controller'; import { apiController } from '../../api/controller';
import { Server } from '../../types';
interface PlaylistParams { interface PlaylistParams {
id: string; id: string;
@ -224,7 +215,12 @@ const PlaylistView = ({ ...rest }) => {
// Smaller playlists can use the safe /createPlaylist method of saving // Smaller playlists can use the safe /createPlaylist method of saving
if (playlistData.length <= 400 && !recovery) { 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)) { if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]); notifyToast('error', errorMessages(res)[0]);
} else { } else {
@ -236,17 +232,25 @@ const PlaylistView = ({ ...rest }) => {
} else { } else {
// For larger playlists, we'll need to first clear out the playlist and then re-populate it // 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 // 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)) { if (isFailedResponse(res)) {
notifyToast('error', errorMessages(res)[0]); notifyToast('error', errorMessages(res)[0]);
return dispatch(removeProcessingPlaylist(data.id)); 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)) { if (isFailedResponse(res)) {
res.forEach((response) => { res.forEach((response: any) => {
if (isFailedResponse(response)) { if (isFailedResponse(response)) {
notifyToast('error', errorMessages(response)[0]); notifyToast('error', errorMessages(response)[0]);
} }
@ -287,11 +291,18 @@ const PlaylistView = ({ ...rest }) => {
const handleEdit = async () => { const handleEdit = async () => {
setIsSubmittingEdit(true); setIsSubmittingEdit(true);
const res = await updatePlaylist({ const res = await apiController({
id: data.id, serverType: config.serverType,
name: editName, endpoint: 'updatePlaylist',
comment: editDescription, args:
isPublic: editPublic, config.serverType === Server.Subsonic
? {
id: data.id,
name: editName,
comment: editDescription,
isPublic: editPublic,
}
: null,
}); });
if (isFailedResponse(res)) { if (isFailedResponse(res)) {
@ -308,7 +319,11 @@ const PlaylistView = ({ ...rest }) => {
const handleDelete = async () => { const handleDelete = async () => {
try { try {
const res = await deletePlaylist({ id: data.id }); const res = await apiController({
serverType: config.serverType,
endpoint: 'deletePlaylist',
args: { id: data.id },
});
if (isFailedResponse(res)) { if (isFailedResponse(res)) {
notifyToast('error', res.error.message); notifyToast('error', res.error.message);
@ -334,7 +349,11 @@ const PlaylistView = ({ ...rest }) => {
const handleRowFavorite = async (rowData: any) => { const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) { 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(setStar({ id: [rowData.id], type: 'star' }));
dispatch(setPlaylistStar({ id: [rowData.id], type: 'star' })); dispatch(setPlaylistStar({ id: [rowData.id], type: 'star' }));
@ -347,7 +366,11 @@ const PlaylistView = ({ ...rest }) => {
return oldData; return oldData;
}); });
} else { } 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(setStar({ id: [rowData.id], type: 'unstar' }));
dispatch(setPlaylistStar({ 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) => { 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(setRate({ id: [rowData.id], rating: e }));
dispatch(setPlaylistRate({ 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 settings from 'electron-settings';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useQuery, useQueryClient } from 'react-query'; import { useQuery, useQueryClient } from 'react-query';
import { getSearch, star, unstar } from '../../api/api';
import useRouterQuery from '../../hooks/useRouterQuery'; import useRouterQuery from '../../hooks/useRouterQuery';
import GenericPage from '../layout/GenericPage'; import GenericPage from '../layout/GenericPage';
import GenericPageHeader from '../layout/GenericPageHeader'; import GenericPageHeader from '../layout/GenericPageHeader';
@ -20,6 +19,8 @@ import { fixPlayer2Index, setPlayQueueByRowClick } from '../../redux/playQueueSl
import { setStatus } from '../../redux/playerSlice'; import { setStatus } from '../../redux/playerSlice';
import ListViewTable from '../viewtypes/ListViewTable'; import ListViewTable from '../viewtypes/ListViewTable';
import { SectionTitle, SectionTitleWrapper, StyledPanel } from '../shared/styled'; import { SectionTitle, SectionTitleWrapper, StyledPanel } from '../shared/styled';
import { apiController } from '../../api/controller';
import { Server } from '../../types';
const SearchView = () => { const SearchView = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -40,7 +41,14 @@ const SearchView = () => {
}, [folder]); }, [folder]);
const { isLoading, isError, data, error }: any = useQuery(['search', urlQuery, musicFolder], () => 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; let timeout: any = null;
@ -83,7 +91,11 @@ const SearchView = () => {
const handleRowFavorite = async (rowData: any) => { const handleRowFavorite = async (rowData: any) => {
if (!rowData.starred) { 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) => { queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.song, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.song, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -93,7 +105,11 @@ const SearchView = () => {
return oldData; return oldData;
}); });
} else { } 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) => { queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.song, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.song, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -107,7 +123,11 @@ const SearchView = () => {
const handleArtistFavorite = async (rowData: any) => { const handleArtistFavorite = async (rowData: any) => {
if (!rowData.starred) { 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) => { queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.artist, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.artist, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -117,7 +137,11 @@ const SearchView = () => {
return oldData; return oldData;
}); });
} else { } 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) => { queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.artist, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.artist, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -131,7 +155,11 @@ const SearchView = () => {
const handleAlbumFavorite = async (rowData: any) => { const handleAlbumFavorite = async (rowData: any) => {
if (!rowData.starred) { 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) => { queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.album, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {
@ -141,7 +169,11 @@ const SearchView = () => {
return oldData; return oldData;
}); });
} else { } 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) => { queryClient.setQueryData(['search', urlQuery, musicFolder], (oldData: any) => {
const starredIndices = _.keys(_.pickBy(oldData.album, { id: rowData.id })); const starredIndices = _.keys(_.pickBy(oldData.album, { id: rowData.id }));
starredIndices.forEach((index) => { starredIndices.forEach((index) => {

12
src/components/settings/Config.tsx

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

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

@ -5,13 +5,16 @@ import { CheckboxGroup } from 'rsuite';
import { ConfigPanel } from '../styled'; import { ConfigPanel } from '../styled';
import { StyledCheckbox, StyledInputPicker, StyledInputPickerContainer } from '../../shared/styled'; import { StyledCheckbox, StyledInputPicker, StyledInputPickerContainer } from '../../shared/styled';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { getMusicFolders } from '../../../api/api';
import { setAppliedFolderViews, setMusicFolder } from '../../../redux/folderSlice'; import { setAppliedFolderViews, setMusicFolder } from '../../../redux/folderSlice';
import { apiController } from '../../../api/controller';
const ServerConfig = () => { const ServerConfig = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const folder = useAppSelector((state) => state.folder); 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); const musicFolderPickerContainerRef = useRef(null);
return ( return (

149
src/components/shared/ContextMenu.tsx

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

33
src/components/starred/StarredView.tsx

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

Loading…
Cancel
Save