mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add qobuz and listenbrainz external links
This commit is contained in:
@@ -29,12 +29,14 @@ These variables override app settings **on first run** when no persisted setting
|
|||||||
| `general.themeLight` | `defaultLight` | `FS_GENERAL_THEME_LIGHT` | Same as theme (used when system is light). |
|
| `general.themeLight` | `defaultLight` | `FS_GENERAL_THEME_LIGHT` | Same as theme (used when system is light). |
|
||||||
| `general.lastfmApiKey` | *(empty)* | `FS_GENERAL_LASTFM_API_KEY` | Last.fm API key. |
|
| `general.lastfmApiKey` | *(empty)* | `FS_GENERAL_LASTFM_API_KEY` | Last.fm API key. |
|
||||||
| `general.lastFM` | `true` | `FS_GENERAL_LAST_FM` | `true` / `false` — Enable Last.fm. |
|
| `general.lastFM` | `true` | `FS_GENERAL_LAST_FM` | `true` / `false` — Enable Last.fm. |
|
||||||
|
| `general.listenBrainz` | `true` | `FS_GENERAL_LISTEN_BRAINZ` | `true` / `false` — ListenBrainz links. |
|
||||||
| `general.musicBrainz` | `true` | `FS_GENERAL_MUSIC_BRAINZ` | `true` / `false` — MusicBrainz links. |
|
| `general.musicBrainz` | `true` | `FS_GENERAL_MUSIC_BRAINZ` | `true` / `false` — MusicBrainz links. |
|
||||||
| `general.nativeAspectRatio` | `false` | `FS_GENERAL_NATIVE_ASPECT_RATIO` | `true` / `false` — Use native cover art aspect ratio. |
|
| `general.nativeAspectRatio` | `false` | `FS_GENERAL_NATIVE_ASPECT_RATIO` | `true` / `false` — Use native cover art aspect ratio. |
|
||||||
| `general.pathReplace` | *(empty)* | `FS_GENERAL_PATH_REPLACE` | Path pattern to replace (e.g. server path in Docker). |
|
| `general.pathReplace` | *(empty)* | `FS_GENERAL_PATH_REPLACE` | Path pattern to replace (e.g. server path in Docker). |
|
||||||
| `general.pathReplaceWith` | *(empty)* | `FS_GENERAL_PATH_REPLACE_WITH` | Replacement path. |
|
| `general.pathReplaceWith` | *(empty)* | `FS_GENERAL_PATH_REPLACE_WITH` | Replacement path. |
|
||||||
| `general.playerbarOpenDrawer` | `false` | `FS_GENERAL_PLAYERBAR_OPEN_DRAWER` | `true` / `false` — Open queue/lyrics as drawer from player bar. |
|
| `general.playerbarOpenDrawer` | `false` | `FS_GENERAL_PLAYERBAR_OPEN_DRAWER` | `true` / `false` — Open queue/lyrics as drawer from player bar. |
|
||||||
| `general.primaryShade` | `6` | `FS_GENERAL_PRIMARY_SHADE` | Mantine primary shade 0–9 (number). |
|
| `general.primaryShade` | `6` | `FS_GENERAL_PRIMARY_SHADE` | Mantine primary shade 0–9 (number). |
|
||||||
|
| `general.qobuz` | `true` | `FS_GENERAL_QOBUZ` | `true` / `false` — Qobuz links. |
|
||||||
| `general.resume` | `true` | `FS_GENERAL_RESUME` | `true` / `false` — Resume playback on load. |
|
| `general.resume` | `true` | `FS_GENERAL_RESUME` | `true` / `false` — Resume playback on load. |
|
||||||
| `general.showLyricsInSidebar` | `true` | `FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR` | `true` / `false` — Show lyrics in sidebar. |
|
| `general.showLyricsInSidebar` | `true` | `FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR` | `true` / `false` — Show lyrics in sidebar. |
|
||||||
| `general.showRatings` | `true` | `FS_GENERAL_SHOW_RATINGS` | `true` / `false` — Show star ratings. |
|
| `general.showRatings` | `true` | `FS_GENERAL_SHOW_RATINGS` | `true` / `false` — Show star ratings. |
|
||||||
|
|||||||
@@ -24,12 +24,14 @@ window.FS_GENERAL_HOME_FEATURE_STYLE = "${FS_GENERAL_HOME_FEATURE_STYLE}";
|
|||||||
window.FS_GENERAL_LANGUAGE = "${FS_GENERAL_LANGUAGE}";
|
window.FS_GENERAL_LANGUAGE = "${FS_GENERAL_LANGUAGE}";
|
||||||
window.FS_GENERAL_LAST_FM = "${FS_GENERAL_LAST_FM}";
|
window.FS_GENERAL_LAST_FM = "${FS_GENERAL_LAST_FM}";
|
||||||
window.FS_GENERAL_LASTFM_API_KEY = "${FS_GENERAL_LASTFM_API_KEY}";
|
window.FS_GENERAL_LASTFM_API_KEY = "${FS_GENERAL_LASTFM_API_KEY}";
|
||||||
|
window.FS_GENERAL_LISTEN_BRAINZ = "${FS_GENERAL_LISTEN_BRAINZ}";
|
||||||
window.FS_GENERAL_MUSIC_BRAINZ = "${FS_GENERAL_MUSIC_BRAINZ}";
|
window.FS_GENERAL_MUSIC_BRAINZ = "${FS_GENERAL_MUSIC_BRAINZ}";
|
||||||
window.FS_GENERAL_NATIVE_ASPECT_RATIO = "${FS_GENERAL_NATIVE_ASPECT_RATIO}";
|
window.FS_GENERAL_NATIVE_ASPECT_RATIO = "${FS_GENERAL_NATIVE_ASPECT_RATIO}";
|
||||||
window.FS_GENERAL_PATH_REPLACE = "${FS_GENERAL_PATH_REPLACE}";
|
window.FS_GENERAL_PATH_REPLACE = "${FS_GENERAL_PATH_REPLACE}";
|
||||||
window.FS_GENERAL_PATH_REPLACE_WITH = "${FS_GENERAL_PATH_REPLACE_WITH}";
|
window.FS_GENERAL_PATH_REPLACE_WITH = "${FS_GENERAL_PATH_REPLACE_WITH}";
|
||||||
window.FS_GENERAL_PLAYERBAR_OPEN_DRAWER = "${FS_GENERAL_PLAYERBAR_OPEN_DRAWER}";
|
window.FS_GENERAL_PLAYERBAR_OPEN_DRAWER = "${FS_GENERAL_PLAYERBAR_OPEN_DRAWER}";
|
||||||
window.FS_GENERAL_PRIMARY_SHADE = "${FS_GENERAL_PRIMARY_SHADE}";
|
window.FS_GENERAL_PRIMARY_SHADE = "${FS_GENERAL_PRIMARY_SHADE}";
|
||||||
|
window.FS_GENERAL_QOBUZ = "${FS_GENERAL_QOBUZ}";
|
||||||
window.FS_GENERAL_RESUME = "${FS_GENERAL_RESUME}";
|
window.FS_GENERAL_RESUME = "${FS_GENERAL_RESUME}";
|
||||||
window.FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR = "${FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR}";
|
window.FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR = "${FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR}";
|
||||||
window.FS_GENERAL_SHOW_RATINGS = "${FS_GENERAL_SHOW_RATINGS}";
|
window.FS_GENERAL_SHOW_RATINGS = "${FS_GENERAL_SHOW_RATINGS}";
|
||||||
|
|||||||
@@ -37,7 +37,9 @@
|
|||||||
"openApplicationDirectory": "open application directory",
|
"openApplicationDirectory": "open application directory",
|
||||||
"openIn": {
|
"openIn": {
|
||||||
"lastfm": "Open in Last.fm",
|
"lastfm": "Open in Last.fm",
|
||||||
|
"listenbrainz": "Open in ListenBrainz",
|
||||||
"musicbrainz": "Open in MusicBrainz",
|
"musicbrainz": "Open in MusicBrainz",
|
||||||
|
"qobuz": "Open in Qobuz",
|
||||||
"spotify": "Open in Spotify"
|
"spotify": "Open in Spotify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -898,6 +900,8 @@
|
|||||||
"language_description": "sets the language for the application ($t(common.restartRequired))",
|
"language_description": "sets the language for the application ($t(common.restartRequired))",
|
||||||
"lastfm_description": "show links to Last.fm on artist/album pages",
|
"lastfm_description": "show links to Last.fm on artist/album pages",
|
||||||
"lastfm": "show last.fm links",
|
"lastfm": "show last.fm links",
|
||||||
|
"listenbrainz_description": "show links to ListenBrainz on artist/album pages",
|
||||||
|
"listenbrainz": "show ListenBrainz links",
|
||||||
"lastfmApiKey_description": "the API key for {{lastfm}}. required for cover art",
|
"lastfmApiKey_description": "the API key for {{lastfm}}. required for cover art",
|
||||||
"lastfmApiKey": "{{lastfm}} API key",
|
"lastfmApiKey": "{{lastfm}} API key",
|
||||||
"lyricFetch_description": "fetch lyrics from various internet sources",
|
"lyricFetch_description": "fetch lyrics from various internet sources",
|
||||||
@@ -925,6 +929,8 @@
|
|||||||
"mpvExtraParameters_help": "one per line",
|
"mpvExtraParameters_help": "one per line",
|
||||||
"musicbrainz_description": "show links to MusicBrainz on artist/album pages, where MusicBrainz ID exists",
|
"musicbrainz_description": "show links to MusicBrainz on artist/album pages, where MusicBrainz ID exists",
|
||||||
"musicbrainz": "show MusicBrainz links",
|
"musicbrainz": "show MusicBrainz links",
|
||||||
|
"qobuz_description": "show links to Qobuz on artist/album pages",
|
||||||
|
"qobuz": "show Qobuz links",
|
||||||
"spotify_description": "show links to Spotify on artist/album pages",
|
"spotify_description": "show links to Spotify on artist/album pages",
|
||||||
"spotify": "show Spotify links",
|
"spotify": "show Spotify links",
|
||||||
"nativeSpotify_description": "open in the Spotify app instead of your browser",
|
"nativeSpotify_description": "open in the Spotify app instead of your browser",
|
||||||
|
|||||||
@@ -296,25 +296,60 @@ interface AlbumMetadataExternalLinksProps {
|
|||||||
albumName?: string;
|
albumName?: string;
|
||||||
externalLinks: boolean;
|
externalLinks: boolean;
|
||||||
lastFM: boolean;
|
lastFM: boolean;
|
||||||
|
listenBrainz: boolean;
|
||||||
mbzId?: null | string;
|
mbzId?: null | string;
|
||||||
|
mbzReleaseGroupId?: null | string;
|
||||||
musicBrainz: boolean;
|
musicBrainz: boolean;
|
||||||
nativeSpotify: boolean;
|
nativeSpotify: boolean;
|
||||||
|
qobuz: boolean;
|
||||||
spotify: boolean;
|
spotify: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getListenBrainzUrl = (
|
||||||
|
mbzReleaseGroupId: null | string,
|
||||||
|
albumArtist?: string,
|
||||||
|
albumName?: string,
|
||||||
|
) => {
|
||||||
|
if (mbzReleaseGroupId) {
|
||||||
|
return `https://listenbrainz.org/album/${mbzReleaseGroupId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (albumArtist || albumName) {
|
||||||
|
return `https://listenbrainz.org/search/?search_term=${encodeURIComponent([albumArtist, albumName].filter(Boolean).join(' ').trim())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQobuzUrl = (albumArtist?: string, albumName?: string) => {
|
||||||
|
if (albumArtist || albumName) {
|
||||||
|
return `https://www.qobuz.com/us-en/search/albums/${encodeURIComponent([albumArtist, albumName].filter(Boolean).join(' ').trim())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const AlbumMetadataExternalLinks = ({
|
const AlbumMetadataExternalLinks = ({
|
||||||
albumArtist,
|
albumArtist,
|
||||||
albumName,
|
albumName,
|
||||||
externalLinks,
|
externalLinks,
|
||||||
lastFM,
|
lastFM,
|
||||||
|
listenBrainz,
|
||||||
mbzId,
|
mbzId,
|
||||||
|
mbzReleaseGroupId,
|
||||||
musicBrainz,
|
musicBrainz,
|
||||||
nativeSpotify,
|
nativeSpotify,
|
||||||
|
qobuz,
|
||||||
spotify,
|
spotify,
|
||||||
}: AlbumMetadataExternalLinksProps) => {
|
}: AlbumMetadataExternalLinksProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!externalLinks || (!lastFM && !musicBrainz && !spotify)) return null;
|
const listenBrainzUrl = getListenBrainzUrl(mbzReleaseGroupId || null, albumArtist, albumName);
|
||||||
|
const qobuzUrl = getQobuzUrl(albumArtist, albumName);
|
||||||
|
|
||||||
|
if (!externalLinks || (!lastFM && !listenBrainz && !musicBrainz && !qobuz && !spotify)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
@@ -323,7 +358,7 @@ const AlbumMetadataExternalLinks = ({
|
|||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Group className={styles.externalLinksGroup} gap="sm">
|
<Group className={styles.externalLinksGroup} gap="xs">
|
||||||
{lastFM && (
|
{lastFM && (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
component="a"
|
component="a"
|
||||||
@@ -332,8 +367,7 @@ const AlbumMetadataExternalLinks = ({
|
|||||||
)}/${encodeURIComponent(albumName || '')}`}
|
)}/${encodeURIComponent(albumName || '')}`}
|
||||||
icon="brandLastfm"
|
icon="brandLastfm"
|
||||||
iconProps={{
|
iconProps={{
|
||||||
fill: 'default',
|
size: '2xl',
|
||||||
size: 'xl',
|
|
||||||
}}
|
}}
|
||||||
radius="md"
|
radius="md"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -350,8 +384,7 @@ const AlbumMetadataExternalLinks = ({
|
|||||||
href={`https://musicbrainz.org/release/${mbzId}`}
|
href={`https://musicbrainz.org/release/${mbzId}`}
|
||||||
icon="brandMusicBrainz"
|
icon="brandMusicBrainz"
|
||||||
iconProps={{
|
iconProps={{
|
||||||
fill: 'default',
|
size: '2xl',
|
||||||
size: 'xl',
|
|
||||||
}}
|
}}
|
||||||
radius="md"
|
radius="md"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -362,6 +395,40 @@ const AlbumMetadataExternalLinks = ({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{listenBrainz && listenBrainzUrl && (
|
||||||
|
<ActionIcon
|
||||||
|
component="a"
|
||||||
|
href={listenBrainzUrl}
|
||||||
|
icon="brandListenBrainz"
|
||||||
|
iconProps={{
|
||||||
|
size: '2xl',
|
||||||
|
}}
|
||||||
|
radius="md"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
tooltip={{
|
||||||
|
label: t('action.openIn.listenbrainz'),
|
||||||
|
}}
|
||||||
|
variant="subtle"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{qobuz && qobuzUrl && (
|
||||||
|
<ActionIcon
|
||||||
|
component="a"
|
||||||
|
href={qobuzUrl}
|
||||||
|
icon="brandQobuz"
|
||||||
|
iconProps={{
|
||||||
|
size: '2xl',
|
||||||
|
}}
|
||||||
|
radius="md"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
tooltip={{
|
||||||
|
label: t('action.openIn.qobuz'),
|
||||||
|
}}
|
||||||
|
variant="subtle"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{spotify && (
|
{spotify && (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
component="a"
|
component="a"
|
||||||
@@ -372,8 +439,7 @@ const AlbumMetadataExternalLinks = ({
|
|||||||
}
|
}
|
||||||
icon="brandSpotify"
|
icon="brandSpotify"
|
||||||
iconProps={{
|
iconProps={{
|
||||||
fill: 'default',
|
size: '2xl',
|
||||||
size: 'xl',
|
|
||||||
}}
|
}}
|
||||||
radius="md"
|
radius="md"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -396,7 +462,8 @@ export const AlbumDetailContent = () => {
|
|||||||
albumQueries.detail({ query: { id: albumId }, serverId: server.id }),
|
albumQueries.detail({ query: { id: albumId }, serverId: server.id }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { externalLinks, lastFM, musicBrainz, nativeSpotify, spotify } = useExternalLinks();
|
const { externalLinks, lastFM, listenBrainz, musicBrainz, nativeSpotify, qobuz, spotify } =
|
||||||
|
useExternalLinks();
|
||||||
|
|
||||||
const comment = detailQuery?.data?.comment;
|
const comment = detailQuery?.data?.comment;
|
||||||
|
|
||||||
@@ -427,9 +494,12 @@ export const AlbumDetailContent = () => {
|
|||||||
albumName={detailQuery?.data?.name}
|
albumName={detailQuery?.data?.name}
|
||||||
externalLinks={externalLinks}
|
externalLinks={externalLinks}
|
||||||
lastFM={lastFM}
|
lastFM={lastFM}
|
||||||
|
listenBrainz={listenBrainz}
|
||||||
mbzId={mbzId || undefined}
|
mbzId={mbzId || undefined}
|
||||||
|
mbzReleaseGroupId={detailQuery?.data?.mbzReleaseGroupId}
|
||||||
musicBrainz={musicBrainz}
|
musicBrainz={musicBrainz}
|
||||||
nativeSpotify={nativeSpotify}
|
nativeSpotify={nativeSpotify}
|
||||||
|
qobuz={qobuz}
|
||||||
spotify={spotify}
|
spotify={spotify}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -888,26 +888,54 @@ interface AlbumArtistMetadataExternalLinksProps {
|
|||||||
artistName?: string;
|
artistName?: string;
|
||||||
externalLinks: boolean;
|
externalLinks: boolean;
|
||||||
lastFM: boolean;
|
lastFM: boolean;
|
||||||
|
listenBrainz: boolean;
|
||||||
mbzId?: null | string;
|
mbzId?: null | string;
|
||||||
musicBrainz: boolean;
|
musicBrainz: boolean;
|
||||||
nativeSpotify: boolean;
|
nativeSpotify: boolean;
|
||||||
order?: number;
|
order?: number;
|
||||||
|
qobuz: boolean;
|
||||||
spotify: boolean;
|
spotify: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getListenBrainzUrl = (mbzId: null | string, artistName?: string) => {
|
||||||
|
if (mbzId) {
|
||||||
|
return `https://listenbrainz.org/artist/${mbzId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artistName) {
|
||||||
|
return `https://listenbrainz.org/search/?search_term=${encodeURIComponent(artistName)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQobuzUrl = (artistName?: string) => {
|
||||||
|
if (artistName) {
|
||||||
|
return `https://www.qobuz.com/us-en/search/artists/${encodeURIComponent(artistName)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const AlbumArtistMetadataExternalLinks = ({
|
const AlbumArtistMetadataExternalLinks = ({
|
||||||
artistName,
|
artistName,
|
||||||
externalLinks,
|
externalLinks,
|
||||||
lastFM,
|
lastFM,
|
||||||
|
listenBrainz,
|
||||||
mbzId,
|
mbzId,
|
||||||
musicBrainz,
|
musicBrainz,
|
||||||
nativeSpotify,
|
nativeSpotify,
|
||||||
order,
|
order,
|
||||||
|
qobuz,
|
||||||
spotify,
|
spotify,
|
||||||
}: AlbumArtistMetadataExternalLinksProps) => {
|
}: AlbumArtistMetadataExternalLinksProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const listenBrainzUrl = getListenBrainzUrl(mbzId || null, artistName);
|
||||||
|
const qobuzUrl = getQobuzUrl(artistName);
|
||||||
|
|
||||||
if (!externalLinks || (!lastFM && !musicBrainz && !spotify)) return null;
|
if (!externalLinks || (!lastFM && !listenBrainz && !musicBrainz && !qobuz && !spotify)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid.Col order={order} span={12}>
|
<Grid.Col order={order} span={12}>
|
||||||
@@ -917,15 +945,14 @@ const AlbumArtistMetadataExternalLinks = ({
|
|||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Group gap="sm">
|
<Group gap="xs">
|
||||||
{lastFM && (
|
{lastFM && (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
component="a"
|
component="a"
|
||||||
href={`https://www.last.fm/music/${encodeURIComponent(artistName || '')}`}
|
href={`https://www.last.fm/music/${encodeURIComponent(artistName || '')}`}
|
||||||
icon="brandLastfm"
|
icon="brandLastfm"
|
||||||
iconProps={{
|
iconProps={{
|
||||||
fill: 'default',
|
size: '2xl',
|
||||||
size: 'xl',
|
|
||||||
}}
|
}}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -941,8 +968,7 @@ const AlbumArtistMetadataExternalLinks = ({
|
|||||||
href={`https://musicbrainz.org/artist/${mbzId}`}
|
href={`https://musicbrainz.org/artist/${mbzId}`}
|
||||||
icon="brandMusicBrainz"
|
icon="brandMusicBrainz"
|
||||||
iconProps={{
|
iconProps={{
|
||||||
fill: 'default',
|
size: '2xl',
|
||||||
size: 'xl',
|
|
||||||
}}
|
}}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -952,6 +978,38 @@ const AlbumArtistMetadataExternalLinks = ({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{listenBrainz && listenBrainzUrl && (
|
||||||
|
<ActionIcon
|
||||||
|
component="a"
|
||||||
|
href={listenBrainzUrl}
|
||||||
|
icon="brandListenBrainz"
|
||||||
|
iconProps={{
|
||||||
|
size: '2xl',
|
||||||
|
}}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
tooltip={{
|
||||||
|
label: t('action.openIn.listenbrainz'),
|
||||||
|
}}
|
||||||
|
variant="subtle"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{qobuz && qobuzUrl && (
|
||||||
|
<ActionIcon
|
||||||
|
component="a"
|
||||||
|
href={qobuzUrl}
|
||||||
|
icon="brandQobuz"
|
||||||
|
iconProps={{
|
||||||
|
size: '2xl',
|
||||||
|
}}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
tooltip={{
|
||||||
|
label: t('action.openIn.qobuz'),
|
||||||
|
}}
|
||||||
|
variant="subtle"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{spotify && (
|
{spotify && (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
component="a"
|
component="a"
|
||||||
@@ -962,8 +1020,7 @@ const AlbumArtistMetadataExternalLinks = ({
|
|||||||
}
|
}
|
||||||
icon="brandSpotify"
|
icon="brandSpotify"
|
||||||
iconProps={{
|
iconProps={{
|
||||||
fill: 'default',
|
size: '2xl',
|
||||||
size: 'xl',
|
|
||||||
}}
|
}}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target={nativeSpotify ? undefined : '_blank'}
|
target={nativeSpotify ? undefined : '_blank'}
|
||||||
@@ -1075,7 +1132,8 @@ export const AlbumArtistDetailContent = ({
|
|||||||
}: AlbumArtistDetailContentProps) => {
|
}: AlbumArtistDetailContentProps) => {
|
||||||
const artistItems = useArtistItems();
|
const artistItems = useArtistItems();
|
||||||
const artistRadioCount = useArtistRadioCount();
|
const artistRadioCount = useArtistRadioCount();
|
||||||
const { externalLinks, lastFM, musicBrainz, nativeSpotify, spotify } = useExternalLinks();
|
const { externalLinks, lastFM, listenBrainz, musicBrainz, nativeSpotify, qobuz, spotify } =
|
||||||
|
useExternalLinks();
|
||||||
const { albumArtistId, artistId } = useParams() as {
|
const { albumArtistId, artistId } = useParams() as {
|
||||||
albumArtistId?: string;
|
albumArtistId?: string;
|
||||||
artistId?: string;
|
artistId?: string;
|
||||||
@@ -1161,18 +1219,21 @@ export const AlbumArtistDetailContent = ({
|
|||||||
genres={detailQuery.data?.genres}
|
genres={detailQuery.data?.genres}
|
||||||
order={genresOrder}
|
order={genresOrder}
|
||||||
/>
|
/>
|
||||||
{externalLinks && (lastFM || musicBrainz || spotify) && (
|
{externalLinks &&
|
||||||
<AlbumArtistMetadataExternalLinks
|
(lastFM || listenBrainz || musicBrainz || qobuz || spotify) && (
|
||||||
artistName={detailQuery.data?.name}
|
<AlbumArtistMetadataExternalLinks
|
||||||
externalLinks={externalLinks}
|
artistName={detailQuery.data?.name}
|
||||||
lastFM={lastFM}
|
externalLinks={externalLinks}
|
||||||
mbzId={mbzId}
|
lastFM={lastFM}
|
||||||
musicBrainz={musicBrainz}
|
listenBrainz={listenBrainz}
|
||||||
nativeSpotify={nativeSpotify}
|
mbzId={mbzId}
|
||||||
order={externalLinksOrder}
|
musicBrainz={musicBrainz}
|
||||||
spotify={spotify}
|
nativeSpotify={nativeSpotify}
|
||||||
/>
|
order={externalLinksOrder}
|
||||||
)}
|
qobuz={qobuz}
|
||||||
|
spotify={spotify}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{enabledItem.biography && (
|
{enabledItem.biography && (
|
||||||
<AlbumArtistMetadataBiography
|
<AlbumArtistMetadataBiography
|
||||||
artistName={detailQuery.data?.name}
|
artistName={detailQuery.data?.name}
|
||||||
|
|||||||
@@ -580,110 +580,6 @@ export const ApplicationSettings = memo(() => {
|
|||||||
isHidden: settings.sideQueueType !== 'sideQueue',
|
isHidden: settings.sideQueueType !== 'sideQueue',
|
||||||
title: t('setting.sidePlayQueueLayout', { postProcess: 'sentenceCase' }),
|
title: t('setting.sidePlayQueueLayout', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
control: (
|
|
||||||
<Switch
|
|
||||||
defaultChecked={settings.externalLinks}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
general: {
|
|
||||||
...settings,
|
|
||||||
externalLinks: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
description: t('setting.externalLinks', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
title: t('setting.externalLinks', { postProcess: 'sentenceCase' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: (
|
|
||||||
<Switch
|
|
||||||
defaultChecked={settings.lastFM}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
general: {
|
|
||||||
...settings,
|
|
||||||
lastFM: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
description: t('setting.lastfm', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
isHidden: !settings.externalLinks,
|
|
||||||
title: t('setting.lastfm', { postProcess: 'sentenceCase' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: (
|
|
||||||
<Switch
|
|
||||||
defaultChecked={settings.musicBrainz}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
general: {
|
|
||||||
...settings,
|
|
||||||
musicBrainz: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
description: t('setting.musicbrainz', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
isHidden: !settings.externalLinks,
|
|
||||||
title: t('setting.musicbrainz', { postProcess: 'sentenceCase' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: (
|
|
||||||
<Switch
|
|
||||||
defaultChecked={settings.spotify}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
general: {
|
|
||||||
...settings,
|
|
||||||
spotify: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
description: t('setting.spotify', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
isHidden: !settings.externalLinks,
|
|
||||||
title: t('setting.spotify', { postProcess: 'sentenceCase' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: (
|
|
||||||
<Switch
|
|
||||||
defaultChecked={settings.nativeSpotify}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
general: {
|
|
||||||
...settings,
|
|
||||||
nativeSpotify: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
description: t('setting.nativeSpotify', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
isHidden: !settings.externalLinks || !settings.spotify,
|
|
||||||
title: t('setting.nativeSpotify', { postProcess: 'sentenceCase' }),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SettingOption,
|
||||||
|
SettingsSection,
|
||||||
|
} from '/@/renderer/features/settings/components/settings-section';
|
||||||
|
import { useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
|
||||||
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
|
|
||||||
|
export const ExternalLinksSettings = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const settings = useGeneralSettings();
|
||||||
|
const { setSettings } = useSettingsStoreActions();
|
||||||
|
|
||||||
|
const options: SettingOption[] = [
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.externalLinks}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
externalLinks: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.externalLinks', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
title: t('setting.externalLinks', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.lastFM}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
lastFM: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.lastfm', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !settings.externalLinks,
|
||||||
|
title: t('setting.lastfm', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.listenBrainz}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
listenBrainz: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.listenbrainz', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !settings.externalLinks,
|
||||||
|
title: t('setting.listenbrainz', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.musicBrainz}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
musicBrainz: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.musicbrainz', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !settings.externalLinks,
|
||||||
|
title: t('setting.musicbrainz', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.qobuz}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
qobuz: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.qobuz', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !settings.externalLinks,
|
||||||
|
title: t('setting.qobuz', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.spotify}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
spotify: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.spotify', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !settings.externalLinks,
|
||||||
|
title: t('setting.spotify', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.nativeSpotify}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
nativeSpotify: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.nativeSpotify', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !settings.externalLinks || !settings.spotify,
|
||||||
|
title: t('setting.nativeSpotify', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsSection
|
||||||
|
options={options}
|
||||||
|
title={t('common.externalLinks', { postProcess: 'sentenceCase' })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import { Fragment } from 'react/jsx-runtime';
|
|||||||
|
|
||||||
import { ApplicationSettings } from '/@/renderer/features/settings/components/general/application-settings';
|
import { ApplicationSettings } from '/@/renderer/features/settings/components/general/application-settings';
|
||||||
import { ControlSettings } from '/@/renderer/features/settings/components/general/control-settings';
|
import { ControlSettings } from '/@/renderer/features/settings/components/general/control-settings';
|
||||||
|
import { ExternalLinksSettings } from '/@/renderer/features/settings/components/general/external-links-settings';
|
||||||
import { LyricSettings } from '/@/renderer/features/settings/components/general/lyric-settings';
|
import { LyricSettings } from '/@/renderer/features/settings/components/general/lyric-settings';
|
||||||
import { QueryBuilderSettings } from '/@/renderer/features/settings/components/general/query-builder-settings';
|
import { QueryBuilderSettings } from '/@/renderer/features/settings/components/general/query-builder-settings';
|
||||||
import { ScrobbleSettings } from '/@/renderer/features/settings/components/general/scrobble-settings';
|
import { ScrobbleSettings } from '/@/renderer/features/settings/components/general/scrobble-settings';
|
||||||
@@ -22,6 +23,7 @@ export const GeneralTab = memo(() => {
|
|||||||
const baseSections = [
|
const baseSections = [
|
||||||
{ component: ThemeSettings, key: 'theme' },
|
{ component: ThemeSettings, key: 'theme' },
|
||||||
{ component: ApplicationSettings, key: 'application' },
|
{ component: ApplicationSettings, key: 'application' },
|
||||||
|
{ component: ExternalLinksSettings, key: 'externalLinks' },
|
||||||
{ component: ControlSettings, key: 'control' },
|
{ component: ControlSettings, key: 'control' },
|
||||||
{ component: SidebarSettings, key: 'sidebar' },
|
{ component: SidebarSettings, key: 'sidebar' },
|
||||||
{ component: ScrobbleSettings, key: 'scrobble' },
|
{ component: ScrobbleSettings, key: 'scrobble' },
|
||||||
|
|||||||
@@ -154,7 +154,9 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
|
|||||||
{ key: 'FS_GENERAL_PATH_REPLACE_WITH', path: ['general', 'pathReplaceWith'], type: 'string' },
|
{ key: 'FS_GENERAL_PATH_REPLACE_WITH', path: ['general', 'pathReplaceWith'], type: 'string' },
|
||||||
{ key: 'FS_GENERAL_LASTFM_API_KEY', path: ['general', 'lastfmApiKey'], type: 'string' },
|
{ key: 'FS_GENERAL_LASTFM_API_KEY', path: ['general', 'lastfmApiKey'], type: 'string' },
|
||||||
{ key: 'FS_GENERAL_LAST_FM', path: ['general', 'lastFM'], type: 'bool' },
|
{ key: 'FS_GENERAL_LAST_FM', path: ['general', 'lastFM'], type: 'bool' },
|
||||||
|
{ key: 'FS_GENERAL_LISTEN_BRAINZ', path: ['general', 'listenBrainz'], type: 'bool' },
|
||||||
{ key: 'FS_GENERAL_MUSIC_BRAINZ', path: ['general', 'musicBrainz'], type: 'bool' },
|
{ key: 'FS_GENERAL_MUSIC_BRAINZ', path: ['general', 'musicBrainz'], type: 'bool' },
|
||||||
|
{ key: 'FS_GENERAL_QOBUZ', path: ['general', 'qobuz'], type: 'bool' },
|
||||||
{ key: 'FS_GENERAL_SPOTIFY', path: ['general', 'spotify'], type: 'bool' },
|
{ key: 'FS_GENERAL_SPOTIFY', path: ['general', 'spotify'], type: 'bool' },
|
||||||
{ key: 'FS_GENERAL_SPOTIFY_NATIVE_APP', path: ['general', 'nativeSpotify'], type: 'bool' },
|
{ key: 'FS_GENERAL_SPOTIFY_NATIVE_APP', path: ['general', 'nativeSpotify'], type: 'bool' },
|
||||||
{ key: 'FS_GENERAL_NATIVE_ASPECT_RATIO', path: ['general', 'nativeAspectRatio'], type: 'bool' },
|
{ key: 'FS_GENERAL_NATIVE_ASPECT_RATIO', path: ['general', 'nativeAspectRatio'], type: 'bool' },
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
language: z.string(),
|
language: z.string(),
|
||||||
lastFM: z.boolean(),
|
lastFM: z.boolean(),
|
||||||
lastfmApiKey: z.string(),
|
lastfmApiKey: z.string(),
|
||||||
|
listenBrainz: z.boolean(),
|
||||||
musicBrainz: z.boolean(),
|
musicBrainz: z.boolean(),
|
||||||
nativeAspectRatio: z.boolean(),
|
nativeAspectRatio: z.boolean(),
|
||||||
nativeSpotify: z.boolean(),
|
nativeSpotify: z.boolean(),
|
||||||
@@ -488,6 +489,7 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
playerItems: z.array(SortableItemSchema(PlayerItemSchema)),
|
playerItems: z.array(SortableItemSchema(PlayerItemSchema)),
|
||||||
playlistTarget: PlaylistTargetSchema,
|
playlistTarget: PlaylistTargetSchema,
|
||||||
primaryShade: z.number().min(0).max(9),
|
primaryShade: z.number().min(0).max(9),
|
||||||
|
qobuz: z.boolean(),
|
||||||
resume: z.boolean(),
|
resume: z.boolean(),
|
||||||
showLyricsInSidebar: z.boolean(),
|
showLyricsInSidebar: z.boolean(),
|
||||||
showRatings: z.boolean(),
|
showRatings: z.boolean(),
|
||||||
@@ -1132,6 +1134,7 @@ const initialState: SettingsState = {
|
|||||||
language: 'en',
|
language: 'en',
|
||||||
lastFM: true,
|
lastFM: true,
|
||||||
lastfmApiKey: '',
|
lastfmApiKey: '',
|
||||||
|
listenBrainz: true,
|
||||||
musicBrainz: true,
|
musicBrainz: true,
|
||||||
nativeAspectRatio: false,
|
nativeAspectRatio: false,
|
||||||
nativeSpotify: false,
|
nativeSpotify: false,
|
||||||
@@ -1150,6 +1153,7 @@ const initialState: SettingsState = {
|
|||||||
playerItems,
|
playerItems,
|
||||||
playlistTarget: PlaylistTarget.TRACK,
|
playlistTarget: PlaylistTarget.TRACK,
|
||||||
primaryShade: 6,
|
primaryShade: 6,
|
||||||
|
qobuz: true,
|
||||||
resume: true,
|
resume: true,
|
||||||
showLyricsInSidebar: true,
|
showLyricsInSidebar: true,
|
||||||
showRatings: true,
|
showRatings: true,
|
||||||
@@ -2565,8 +2569,10 @@ export const useExternalLinks = () =>
|
|||||||
(state) => ({
|
(state) => ({
|
||||||
externalLinks: state.general.externalLinks,
|
externalLinks: state.general.externalLinks,
|
||||||
lastFM: state.general.lastFM,
|
lastFM: state.general.lastFM,
|
||||||
|
listenBrainz: state.general.listenBrainz,
|
||||||
musicBrainz: state.general.musicBrainz,
|
musicBrainz: state.general.musicBrainz,
|
||||||
nativeSpotify: state.general.nativeSpotify,
|
nativeSpotify: state.general.nativeSpotify,
|
||||||
|
qobuz: state.general.qobuz,
|
||||||
spotify: state.general.spotify,
|
spotify: state.general.spotify,
|
||||||
}),
|
}),
|
||||||
shallow,
|
shallow,
|
||||||
|
|||||||
Reference in New Issue
Block a user