mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
handle playback from ItemCard
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && data && !isExternal && (
|
||||
{showItemCardControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
@@ -486,6 +490,7 @@ const DefaultItemCard = ({
|
||||
showRating,
|
||||
withControls,
|
||||
}: ItemCardDerivativeProps) => {
|
||||
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 && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && !isExternal && (
|
||||
{showItemCardControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
@@ -725,6 +733,7 @@ const PosterItemCard = ({
|
||||
showRating,
|
||||
withControls,
|
||||
}: ItemCardDerivativeProps) => {
|
||||
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 && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && data && !isExternal && (
|
||||
{showItemCardControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
@@ -949,7 +961,7 @@ const PosterItemCard = ({
|
||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||
<Link
|
||||
className={imageContainerClassName}
|
||||
data-unavailable-text={i18n.t('common.unavailable', {
|
||||
data-unavailable-text={i18n.t('common.external', {
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
draggable={false}
|
||||
@@ -966,7 +978,7 @@ const PosterItemCard = ({
|
||||
) : (
|
||||
<div
|
||||
className={imageContainerClassName}
|
||||
data-unavailable-text={i18n.t('common.unavailable', {
|
||||
data-unavailable-text={i18n.t('common.external', {
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
onClick={handleImageClick}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
||||
import { ItemListStateItemWithRequiredProperties } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||
import { DefaultItemControlProps, ItemControls } from '/@/renderer/components/item-list/types';
|
||||
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite';
|
||||
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
|
||||
import { LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
|
||||
import { LibraryItem, QueueSong, ServerType, Song } from '/@/shared/types/domain-types';
|
||||
import { Play, TableColumn } from '/@/shared/types/types';
|
||||
|
||||
interface UseDefaultItemListControlsArgs {
|
||||
@@ -34,6 +36,7 @@ const itemTypeMapping = {
|
||||
|
||||
export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs) => {
|
||||
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,
|
||||
]);
|
||||
|
||||
@@ -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(() => {
|
||||
<Switch
|
||||
aria-label={t('setting.youtube', { postProcess: 'sentenceCase' })}
|
||||
defaultChecked={settings.youtube}
|
||||
disabled={!isElectron()}
|
||||
onChange={(e) => updateIntegrations({ youtube: e.currentTarget.checked })}
|
||||
/>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user