diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 135d292a4..59369115c 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -79,6 +79,7 @@ "dismiss": "dismiss", "doNotShowAgain": "do not show this again", "duration": "duration", + "external": "external", "view": "view", "edit": "edit", "enable": "enable", @@ -901,7 +902,7 @@ "musicbrainzPrioritizeCountries": "prioritize MusicBrainz countries", "musicbrainzPrioritizeCountries_description": "country codes to prioritize when ordering MusicBrainz releases, comma separated and non case-sensitive (e.g. us, gb, de)", "youtube": "enable youtube integration", - "youtube_description": "external songs will attempt to use YouTube to resolve stream URLs for playback", + "youtube_description": "external songs will attempt to use YouTube to resolve stream URLs for playback (desktop only)", "neteaseTranslation_description": "When enabled, fetches and displays translated lyrics from NetEase if available", "neteaseTranslation": "Enable NetEase translations", "notify": "enable song notifications", diff --git a/src/renderer/components/item-card/item-card.module.css b/src/renderer/components/item-card/item-card.module.css index ea170c795..1a93ca7e4 100644 --- a/src/renderer/components/item-card/item-card.module.css +++ b/src/renderer/components/item-card/item-card.module.css @@ -61,7 +61,7 @@ .image-container.external { img { - opacity: 0.3; + opacity: 0.5; filter: grayscale(0.5) saturate(0.7); transition: all 0.2s ease-in-out; } diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx index ea0770132..7e53a24a7 100644 --- a/src/renderer/components/item-card/item-card.tsx +++ b/src/renderer/components/item-card/item-card.tsx @@ -19,7 +19,7 @@ import { ItemControls } from '/@/renderer/components/item-list/types'; import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; import { AppRoute } from '/@/renderer/router/routes'; -import { useShowRatings } from '/@/renderer/store'; +import { useIntegrationsSettings, useShowRatings } from '/@/renderer/store'; import { formatDateAbsolute, formatDateAbsoluteUTC, @@ -179,6 +179,7 @@ const CompactItemCard = ({ showRating, withControls, }: ItemCardDerivativeProps) => { + const { youtube: youtubeIntegrationEnabled } = useIntegrationsSettings(); const [showControls, setShowControls] = useState(false); const itemRowId = data && internalState && typeof data === 'object' && 'id' in data @@ -342,9 +343,12 @@ const CompactItemCard = ({ const hasRating = showRating && userRating !== null && userRating > 0; const isExternal = data._serverType === ServerType.EXTERNAL; + const showItemCardControls = + withControls && showControls && data && (!isExternal || youtubeIntegrationEnabled); + const imageContainerClassName = clsx(styles.imageContainer, { [styles.isRound]: isRound, - [styles.noHoverOverlay]: isExternal, + [styles.noHoverOverlay]: isExternal && !showItemCardControls, }); const imageContainerContent = ( @@ -377,7 +381,7 @@ const CompactItemCard = ({ {isFavorite &&
} {hasRating &&
{userRating}
} - {withControls && showControls && data && !isExternal && ( + {showItemCardControls && ( { + const { youtube: youtubeIntegrationEnabled } = useIntegrationsSettings(); const [showControls, setShowControls] = useState(false); const itemRowId = data && internalState && typeof data === 'object' && 'id' in data @@ -584,10 +589,13 @@ const DefaultItemCard = ({ const hasRating = showRating && userRating !== null && userRating > 0; const isExternal = data._serverType === ServerType.EXTERNAL; + const showItemCardControls = + withControls && showControls && data && (!isExternal || youtubeIntegrationEnabled); + const imageContainerClassName = clsx(styles.imageContainer, { [styles.external]: isExternal, [styles.isRound]: isRound, - [styles.noHoverOverlay]: isExternal, + [styles.noHoverOverlay]: isExternal && !showItemCardControls, }); const imageContainerContent = ( @@ -618,7 +626,7 @@ const DefaultItemCard = ({ {isFavorite &&
} {hasRating &&
{userRating}
} - {withControls && showControls && !isExternal && ( + {showItemCardControls && ( { + const { youtube: youtubeIntegrationEnabled } = useIntegrationsSettings(); const [showControls, setShowControls] = useState(false); const itemRowId = data && internalState && typeof data === 'object' && 'id' in data @@ -888,10 +897,13 @@ const PosterItemCard = ({ const hasRating = showRating && userRating !== null && userRating > 0; const isExternal = data._serverType === ServerType.EXTERNAL; + const showItemCardControls = + withControls && showControls && data && (!isExternal || youtubeIntegrationEnabled); + const imageContainerClassName = clsx(styles.imageContainer, { [styles.external]: isExternal, [styles.isRound]: isRound, - [styles.noHoverOverlay]: isExternal, + [styles.noHoverOverlay]: isExternal && !showItemCardControls, }); const imageContainerContent = ( @@ -922,7 +934,7 @@ const PosterItemCard = ({ {isFavorite &&
} {hasRating &&
{userRating}
} - {withControls && showControls && data && !isExternal && ( + {showItemCardControls && ( { const player = usePlayer(); + const queryClient = useQueryClient(); const navigate = useNavigate(); const navigateRef = useRef(navigate); const setFavorite = useSetFavorite(); @@ -384,6 +387,40 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs return; } + const isExternal = + (item as Song & { _serverType?: ServerType })._serverType === + ServerType.EXTERNAL; + + if (isExternal) { + if ( + itemType === LibraryItem.SONG || + itemType === LibraryItem.PLAYLIST_SONG || + (item as { _itemType?: LibraryItem })._itemType === LibraryItem.SONG + ) { + player.addToQueueByData([item as Song], playType, item.id); + return; + } + if (itemType === LibraryItem.ALBUM) { + (async () => { + try { + const album = await queryClient.fetchQuery( + albumQueries.detail({ + query: { id: item.id }, + serverId: 'musicbrainz', + }), + ); + const songs = album?.songs ?? []; + if (songs.length > 0) { + player.addToQueueByData(songs, playType); + } + } catch { + console.error('Error fetching album songs for item', item); + } + })(); + return; + } + } + player.addToQueueByFetch(item._serverId, [item.id], itemType, playType); }, @@ -417,10 +454,11 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs }; }, [ enableMultiSelect, - overrides, onColumnReordered, onColumnResized, + overrides, player, + queryClient, setFavorite, setRating, ]); diff --git a/src/renderer/features/settings/components/integrations/integrations-tab.tsx b/src/renderer/features/settings/components/integrations/integrations-tab.tsx index 55dd3162e..ad4fffed3 100644 --- a/src/renderer/features/settings/components/integrations/integrations-tab.tsx +++ b/src/renderer/features/settings/components/integrations/integrations-tab.tsx @@ -1,3 +1,4 @@ +import isElectron from 'is-electron'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -116,6 +117,7 @@ export const IntegrationsTab = memo(() => { updateIntegrations({ youtube: e.currentTarget.checked })} /> ),