feat(queue): add per-track overflow menu in queue panel
Replace the bare "remove" cross on each queue row with a ghost three-dot menu offering Play now, Move next (reposition right after the current track), Track info, and Remove — consolidating the previously separate info button into the same menu.
This commit is contained in:
@@ -1,4 +1,12 @@
|
|||||||
import { Slider, Badge } from '@olly/modern-sk';
|
import {
|
||||||
|
Slider,
|
||||||
|
Badge,
|
||||||
|
Menu,
|
||||||
|
MenuTrigger,
|
||||||
|
MenuContent,
|
||||||
|
MenuItem,
|
||||||
|
IconButton,
|
||||||
|
} from '@olly/modern-sk';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon } from '../common/Icon';
|
import { Icon } from '../common/Icon';
|
||||||
import { ArtTile } from '../common/ArtTile';
|
import { ArtTile } from '../common/ArtTile';
|
||||||
@@ -7,6 +15,7 @@ import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
|
|||||||
import {
|
import {
|
||||||
goToIndex,
|
goToIndex,
|
||||||
removeFromQueue,
|
removeFromQueue,
|
||||||
|
moveInQueue,
|
||||||
clearQueue,
|
clearQueue,
|
||||||
type QueueEntry,
|
type QueueEntry,
|
||||||
} from '../../store/slices/queue';
|
} from '../../store/slices/queue';
|
||||||
@@ -112,6 +121,11 @@ export function QueuePanel() {
|
|||||||
isCurrent={index === queue.currentIndex}
|
isCurrent={index === queue.currentIndex}
|
||||||
isPlaying={isPlaying}
|
isPlaying={isPlaying}
|
||||||
onPlay={() => dispatch(goToIndex(index))}
|
onPlay={() => dispatch(goToIndex(index))}
|
||||||
|
onMoveNext={() =>
|
||||||
|
dispatch(
|
||||||
|
moveInQueue({ from: index, to: queue.currentIndex + 1 }),
|
||||||
|
)
|
||||||
|
}
|
||||||
onRemove={() => dispatch(removeFromQueue(index))}
|
onRemove={() => dispatch(removeFromQueue(index))}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -137,12 +151,14 @@ function QueueRow({
|
|||||||
isCurrent,
|
isCurrent,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
onPlay,
|
onPlay,
|
||||||
|
onMoveNext,
|
||||||
onRemove,
|
onRemove,
|
||||||
}: {
|
}: {
|
||||||
entry: QueueEntry;
|
entry: QueueEntry;
|
||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
onPlay: () => void;
|
onPlay: () => void;
|
||||||
|
onMoveNext: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -174,24 +190,29 @@ function QueueRow({
|
|||||||
<div className="t">{resolved?.title ?? entry.title}</div>
|
<div className="t">{resolved?.title ?? entry.title}</div>
|
||||||
<div className="r">{resolved?.artistName ?? entry.artistName}</div>
|
<div className="r">{resolved?.artistName ?? entry.artistName}</div>
|
||||||
</div>
|
</div>
|
||||||
{isCurrent && (
|
<Menu>
|
||||||
<button
|
<MenuTrigger asChild>
|
||||||
type="button"
|
<IconButton
|
||||||
className="iconbtn sm"
|
variant="ghost"
|
||||||
onClick={() => dispatch(openTrackInfo(entry.trackId))}
|
size="sm"
|
||||||
title={t('trackInfo.open')}
|
aria-label={t('queue.menu.options')}
|
||||||
>
|
>
|
||||||
<Icon name="info" />
|
⋯
|
||||||
</button>
|
</IconButton>
|
||||||
)}
|
</MenuTrigger>
|
||||||
<button
|
<MenuContent>
|
||||||
type="button"
|
<MenuItem onSelect={onPlay}>{t('queue.menu.playNow')}</MenuItem>
|
||||||
className="iconbtn sm"
|
{!isCurrent && (
|
||||||
onClick={onRemove}
|
<MenuItem onSelect={onMoveNext}>
|
||||||
title={t('queue.removeFromQueue')}
|
{t('queue.menu.moveNext')}
|
||||||
>
|
</MenuItem>
|
||||||
<Icon name="x" />
|
)}
|
||||||
</button>
|
<MenuItem onSelect={() => dispatch(openTrackInfo(entry.trackId))}>
|
||||||
|
{t('queue.menu.info')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onSelect={onRemove}>{t('queue.menu.remove')}</MenuItem>
|
||||||
|
</MenuContent>
|
||||||
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,13 @@ const en = {
|
|||||||
loadingMore: 'Loading more from radio…',
|
loadingMore: 'Loading more from radio…',
|
||||||
doubleClickPlay: 'Double-click to play',
|
doubleClickPlay: 'Double-click to play',
|
||||||
removeFromQueue: 'Remove from queue',
|
removeFromQueue: 'Remove from queue',
|
||||||
|
menu: {
|
||||||
|
options: 'Track options',
|
||||||
|
playNow: 'Play now',
|
||||||
|
moveNext: 'Move next',
|
||||||
|
info: 'Track info',
|
||||||
|
remove: 'Remove from queue',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
track: {
|
track: {
|
||||||
menu: {
|
menu: {
|
||||||
|
|||||||
@@ -149,6 +149,13 @@ const ru: Translations = {
|
|||||||
loadingMore: 'Загрузка радио…',
|
loadingMore: 'Загрузка радио…',
|
||||||
doubleClickPlay: 'Двойной клик для воспроизведения',
|
doubleClickPlay: 'Двойной клик для воспроизведения',
|
||||||
removeFromQueue: 'Убрать из очереди',
|
removeFromQueue: 'Убрать из очереди',
|
||||||
|
menu: {
|
||||||
|
options: 'Параметры трека',
|
||||||
|
playNow: 'Воспроизвести сейчас',
|
||||||
|
moveNext: 'Сделать следующим',
|
||||||
|
info: 'Информация о треке',
|
||||||
|
remove: 'Убрать из очереди',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
track: {
|
track: {
|
||||||
menu: {
|
menu: {
|
||||||
|
|||||||
Reference in New Issue
Block a user