feat(track): play-on-hover cover art, replacing double-click
Add a play overlay button shown on cover art hover that inserts the track into the queue right after the current track and jumps to it, so it takes priority over what's already queued. Replaces the double-click-to-play interaction with a new playNow queue action.
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { Row } from '@olly/modern-sk';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TrackContextMenu } from './TrackContextMenu';
|
||||
import { AvailabilityBadge } from './AvailabilityBadge';
|
||||
import { MetadataStatusBadge } from './MetadataStatusBadge';
|
||||
import { Icon } from '../common/Icon';
|
||||
import { formatDuration } from '../../lib/format';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
|
||||
import { play } from '../../store/slices/player';
|
||||
import { playNow } from '../../store/slices/queue';
|
||||
import type { Track } from '../../api/types';
|
||||
import { getCoverUrl, getTrackCoverUrl } from '../../api/endpoints/streaming';
|
||||
|
||||
@@ -25,6 +27,7 @@ export function TrackRow({
|
||||
onEditMetadata,
|
||||
onDelete,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const currentTrackId = useAppSelector((s) => s.player.currentTrackId);
|
||||
const isPlaying = useAppSelector((s) => s.player.isPlaying);
|
||||
@@ -36,10 +39,22 @@ export function TrackRow({
|
||||
getCoverUrl(track.albumArtUrl) ??
|
||||
(token ? getTrackCoverUrl(track.id, token, track.hasCover) : undefined);
|
||||
|
||||
const handlePlayNow = () => {
|
||||
dispatch(
|
||||
playNow({
|
||||
trackId: track.id,
|
||||
title: track.title,
|
||||
artistName: track.artistName,
|
||||
albumTitle: track.albumTitle,
|
||||
durationMs: track.durationMs,
|
||||
albumArtUrl: track.albumArtUrl,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row
|
||||
selected={isActive}
|
||||
onDoubleClick={() => dispatch(play(track.id))}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '2rem 2.5rem 1fr auto auto',
|
||||
@@ -58,6 +73,10 @@ export function TrackRow({
|
||||
>
|
||||
{isActive && isPlaying ? '▶' : index !== undefined ? index + 1 : ''}
|
||||
</span>
|
||||
<div
|
||||
className="track-art"
|
||||
style={{ position: 'relative', width: 36, height: 36 }}
|
||||
>
|
||||
{artUrl ? (
|
||||
<img
|
||||
src={artUrl}
|
||||
@@ -76,6 +95,16 @@ export function TrackRow({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="track-art-play"
|
||||
onClick={handlePlayNow}
|
||||
aria-label={t('track.menu.playNow')}
|
||||
title={t('track.menu.playNow')}
|
||||
>
|
||||
<Icon name="play" fill />
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -59,6 +59,11 @@ export const queueSlice = createSlice({
|
||||
addNextInQueue(state, action: PayloadAction<QueueEntry>) {
|
||||
state.entries.splice(state.currentIndex + 1, 0, action.payload);
|
||||
},
|
||||
playNow(state, action: PayloadAction<QueueEntry>) {
|
||||
const insertAt = state.currentIndex + 1;
|
||||
state.entries.splice(insertAt, 0, action.payload);
|
||||
state.currentIndex = insertAt;
|
||||
},
|
||||
removeFromQueue(state, action: PayloadAction<number>) {
|
||||
state.entries.splice(action.payload, 1);
|
||||
if (action.payload < state.currentIndex) state.currentIndex--;
|
||||
@@ -93,6 +98,7 @@ export const {
|
||||
setQueue,
|
||||
addToQueue,
|
||||
addNextInQueue,
|
||||
playNow,
|
||||
removeFromQueue,
|
||||
moveInQueue,
|
||||
goToIndex,
|
||||
|
||||
@@ -951,3 +951,28 @@
|
||||
.sb-sec-link.active {
|
||||
color: var(--fg-1);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
TRACK ROW — cover art play overlay
|
||||
============================================================ */
|
||||
.track-art-play {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: var(--fg-1);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity var(--dur-quick);
|
||||
}
|
||||
.track-art:hover .track-art-play {
|
||||
opacity: 1;
|
||||
}
|
||||
.track-art-play:hover {
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user