add qobuz and listenbrainz external links

This commit is contained in:
jeffvli
2026-03-17 21:10:24 -07:00
parent f996b111b9
commit e40a175e12
10 changed files with 352 additions and 134 deletions
+2
View File
@@ -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 09 (number). | | `general.primaryShade` | `6` | `FS_GENERAL_PRIMARY_SHADE` | Mantine primary shade 09 (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. |
+2
View File
@@ -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}";
+6
View File
@@ -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' },
+6
View File
@@ -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,