Browse Source

Add download for album/artist pages

master
jeffvli 3 years ago
committed by Jeff
parent
commit
b66e171596
  1. 78
      src/api/api.ts
  2. 47
      src/components/library/AlbumView.tsx
  3. 70
      src/components/library/ArtistView.tsx

78
src/api/api.ts

@ -174,6 +174,29 @@ const getCoverArtUrl = (item: any, useLegacyAuth: boolean, size?: number) => {
); );
}; };
export const getDownloadUrl = (id: string, useLegacyAuth = legacyAuth) => {
if (useLegacyAuth) {
return (
`${API_BASE_URL}/download` +
`?id=${id}` +
`&u=${auth.username}` +
`&p=${auth.password}` +
`&v=1.15.0` +
`&c=sonixd`
);
}
return (
`${API_BASE_URL}/download` +
`?id=${id}` +
`&u=${auth.username}` +
`&s=${auth.salt}` +
`&t=${auth.hash}` +
`&v=1.15.0` +
`&c=sonixd`
);
};
const getStreamUrl = (id: string, useLegacyAuth: boolean) => { const getStreamUrl = (id: string, useLegacyAuth: boolean) => {
if (useLegacyAuth) { if (useLegacyAuth) {
return ( return (
@ -254,8 +277,14 @@ export const getStream = async (id: string) => {
return data; return data;
}; };
export const getDownload = async (id: string) => { export const getDownload = async (options: { id: string }) => {
const { data } = await api.get(`/download?id=${id}`); const { data } = await api.get(`/download?id=${options.id}`, {
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const percentCompleted = Math.floor((progressEvent.loaded / progressEvent.total) * 100);
console.log(`percentCompleted`, percentCompleted);
},
});
return data; return data;
}; };
@ -825,6 +854,51 @@ export const getGenres = async () => {
})); }));
}; };
export const search2 = async (options: {
query: string;
artistCount?: number;
artistOffset?: 0;
albumCount?: number;
albumOffset?: 0;
songCount?: number;
songOffset?: 0;
musicFolderId?: string | number;
}) => {
const { data } = await api.get(`/search2`, { params: options });
const results = data.searchResult3;
return {
artist: (results.artist || []).map((entry: any, index: any) => ({
...entry,
image: getCoverArtUrl(entry, legacyAuth, 350),
starred: entry.starred || undefined,
type: 'artist',
index,
uniqueId: nanoid(),
})),
album: (results.album || []).map((entry: any, index: any) => ({
...entry,
albumId: entry.id,
image: getCoverArtUrl(entry, legacyAuth, 350),
starred: entry.starred || undefined,
type: 'album',
isDir: false,
index,
uniqueId: nanoid(),
})),
song: (results.song || []).map((entry: any, index: any) => ({
...entry,
streamUrl: getStreamUrl(entry.id, legacyAuth),
image: getCoverArtUrl(entry, legacyAuth, 150),
type: 'music',
starred: entry.starred || undefined,
index,
uniqueId: nanoid(),
})),
};
};
export const search3 = async (options: { export const search3 = async (options: {
query: string; query: string;
artistCount?: number; artistCount?: number;

47
src/components/library/AlbumView.tsx

@ -1,16 +1,18 @@
import React from 'react'; import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import { clipboard, shell } from 'electron';
import settings from 'electron-settings'; import settings from 'electron-settings';
import { ButtonToolbar } from 'rsuite'; import { ButtonToolbar, Whisper } from 'rsuite';
import { useQuery, useQueryClient } from 'react-query'; import { useQuery, useQueryClient } from 'react-query';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import { import {
DownloadButton,
FavoriteButton, FavoriteButton,
PlayAppendButton, PlayAppendButton,
PlayAppendNextButton, PlayAppendNextButton,
PlayButton, PlayButton,
} from '../shared/ToolbarButtons'; } from '../shared/ToolbarButtons';
import { getAlbum, star, unstar } from '../../api/api'; import { getAlbum, getDownloadUrl, star, unstar } from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
appendPlayQueue, appendPlayQueue,
@ -38,10 +40,11 @@ import {
filterPlayQueue, filterPlayQueue,
formatDate, formatDate,
formatDuration, formatDuration,
getAlbumSize,
getPlayedSongsNotification, getPlayedSongsNotification,
isCached, isCached,
} from '../../shared/utils'; } from '../../shared/utils';
import { StyledTagLink } from '../shared/styled'; import { StyledButton, StyledPopover, StyledTagLink } from '../shared/styled';
import { setActive } from '../../redux/albumSlice'; import { setActive } from '../../redux/albumSlice';
import { import {
BlurredBackground, BlurredBackground,
@ -172,6 +175,23 @@ const AlbumView = ({ ...rest }: any) => {
} }
}; };
const handleDownload = (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));
} else {
clipboard.writeText(getDownloadUrl(data.song[0].parent));
notifyToast('info', 'Download links copied!');
}
} else {
notifyToast('warning', 'No parent album found');
}
};
if (isLoading) { if (isLoading) {
return <PageLoader />; return <PageLoader />;
} }
@ -329,6 +349,27 @@ const AlbumView = ({ ...rest }: any) => {
size="lg" size="lg"
onClick={() => handlePlayAppend('later')} onClick={() => handlePlayAppend('later')}
/> />
<Whisper
trigger="hover"
placement="bottom"
delay={250}
enterable
preventOverflow
speaker={
<StyledPopover>
<ButtonToolbar>
<StyledButton onClick={() => handleDownload('download')}>
Download
</StyledButton>
<StyledButton onClick={() => handleDownload('copy')}>
Copy to clipboard
</StyledButton>
</ButtonToolbar>
</StyledPopover>
}
>
<DownloadButton size="lg" downloadSize={getAlbumSize(data.song)} />
</Whisper>
<FavoriteButton size="lg" isFavorite={data.starred} onClick={handleFavorite} /> <FavoriteButton size="lg" isFavorite={data.starred} onClick={handleFavorite} />
</ButtonToolbar> </ButtonToolbar>
</div> </div>

70
src/components/library/ArtistView.tsx

@ -2,18 +2,27 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import FastAverageColor from 'fast-average-color'; import FastAverageColor from 'fast-average-color';
import { shell } from 'electron'; import { clipboard, shell } from 'electron';
import settings from 'electron-settings'; import settings from 'electron-settings';
import { ButtonToolbar, Whisper, TagGroup } from 'rsuite'; import { ButtonToolbar, Whisper, TagGroup } from 'rsuite';
import { useQuery, useQueryClient } from 'react-query'; import { useQuery, useQueryClient } from 'react-query';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import { import {
DownloadButton,
FavoriteButton, FavoriteButton,
PlayAppendButton, PlayAppendButton,
PlayAppendNextButton, PlayAppendNextButton,
PlayButton, PlayButton,
} from '../shared/ToolbarButtons'; } from '../shared/ToolbarButtons';
import { getAllArtistSongs, getArtist, getArtistInfo, star, unstar } from '../../api/api'; import {
getAlbum,
getAllArtistSongs,
getArtist,
getArtistInfo,
getDownloadUrl,
star,
unstar,
} from '../../api/api';
import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
toggleSelected, toggleSelected,
@ -167,6 +176,38 @@ 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));
} else {
clipboard.writeText(getDownloadUrl(data.album[0].parent));
notifyToast('info', 'Download links copied!');
}
} else {
const downloadUrls: string[] = [];
for (let i = 0; i < data.album.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
const albumRes = await getAlbum(data.album[i].id);
if (albumRes.song[0]?.parent) {
downloadUrls.push(getDownloadUrl(albumRes.song[0].parent));
} else {
notifyToast('warning', `[${albumRes.name}] No parent album found`);
}
}
if (type === 'download') {
downloadUrls.forEach((link) => shell.openExternal(link));
}
if (type === 'copy') {
clipboard.writeText(downloadUrls.join('\n'));
notifyToast('info', 'Download links copied!');
}
}
};
useEffect(() => { useEffect(() => {
if (!isLoading) { if (!isLoading) {
const img = isCached(`${misc.imageCachePath}artist_${data?.id}.jpg`) const img = isCached(`${misc.imageCachePath}artist_${data?.id}.jpg`)
@ -303,11 +344,34 @@ const ArtistView = ({ ...rest }: any) => {
size="lg" size="lg"
onClick={() => handlePlayAppend('later')} onClick={() => handlePlayAppend('later')}
/> />
<Whisper
trigger="hover"
placement="bottom"
delay={250}
enterable
preventOverflow
speaker={
<StyledPopover>
<ButtonToolbar>
<StyledButton onClick={() => handleDownload('download')}>
Download
</StyledButton>
<StyledButton onClick={() => handleDownload('copy')}>
Copy to clipboard
</StyledButton>
</ButtonToolbar>
</StyledPopover>
}
>
<DownloadButton size="lg" />
</Whisper>
<FavoriteButton size="lg" isFavorite={data.starred} onClick={handleFavorite} /> <FavoriteButton size="lg" isFavorite={data.starred} onClick={handleFavorite} />
<Whisper <Whisper
placement="auto"
trigger="hover" trigger="hover"
placement="bottom"
delay={250}
enterable enterable
preventOverflow
speaker={ speaker={
<StyledPopover style={{ width: '400px' }}> <StyledPopover style={{ width: '400px' }}>
<div> <div>

Loading…
Cancel
Save