mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
9058abd340
Track is the app's normalized song model and QueueItemRef its thin queue identity -- consumed app-wide (queue, cover, context menus, sharing, lucky-mix, library browse), not a playback-internal detail. They lived in features/playback/store/playerStoreTypes, forcing ~115 core/feature files to reach into the playback feature for the central media type. Move the two interfaces to lib/media/trackTypes; playerStoreTypes keeps PlayerState and now imports them from lib. 115 importers repointed (mixed 'PlayerState, Track' lines split so PlayerState stays). Pure type move (erased at runtime). tsc 0, lint 0.
82 lines
3.0 KiB
TypeScript
82 lines
3.0 KiB
TypeScript
import { memo, useMemo, useSyncExternalStore } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { X } from 'lucide-react';
|
|
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
|
import type { Track } from '@/lib/media/trackTypes';
|
|
import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
|
|
import {
|
|
getQueueResolverVersion,
|
|
subscribeQueueResolver,
|
|
} from '@/features/playback/store/queueTrackResolver';
|
|
import { formatTrackTime } from '@/lib/format/formatDuration';
|
|
|
|
interface Props {
|
|
onClose: () => void;
|
|
}
|
|
|
|
/**
|
|
* Semi-transparent "Up next" overlay for the fullscreen player — lists the
|
|
* upcoming queue (no blur, in keeping with the static player). Clicking a row
|
|
* jumps to that queue item (same-queue jump as the queue panel).
|
|
*/
|
|
export const FsQueueModal = memo(function FsQueueModal({ onClose }: Props) {
|
|
const { t } = useTranslation();
|
|
const queueItems = usePlayerStore(s => s.queueItems);
|
|
const queueIndex = usePlayerStore(s => s.queueIndex);
|
|
const playTrack = usePlayerStore(s => s.playTrack);
|
|
// Re-resolve as the resolver cache fills.
|
|
const version = useSyncExternalStore(subscribeQueueResolver, getQueueResolverVersion);
|
|
|
|
const upcoming = useMemo(() => {
|
|
const out: { track: Track; absIdx: number }[] = [];
|
|
for (let i = queueIndex + 1; i < queueItems.length; i++) {
|
|
const ref = queueItems[i];
|
|
if (ref) out.push({ track: resolveQueueTrack(ref), absIdx: i });
|
|
}
|
|
return out;
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [queueItems, queueIndex, version]);
|
|
|
|
return (
|
|
<div
|
|
className="fsq-backdrop"
|
|
onClick={onClose}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label={t('player.fsUpNext')}
|
|
>
|
|
<div className="fsq-panel" onClick={e => e.stopPropagation()}>
|
|
<div className="fsq-header">
|
|
<span className="fsq-title">{t('player.fsUpNext')}</span>
|
|
<button className="fsq-close" onClick={onClose} aria-label={t('common.close')}>
|
|
<X size={18} />
|
|
</button>
|
|
</div>
|
|
<div className="fsq-list">
|
|
{upcoming.length === 0 ? (
|
|
<div className="fsq-empty">{t('player.fsQueueEmpty')}</div>
|
|
) : (
|
|
upcoming.map(({ track, absIdx }) => (
|
|
<button
|
|
key={`${track.id}:${absIdx}`}
|
|
className="fsq-item"
|
|
onClick={() => {
|
|
playTrack(track, undefined, undefined, undefined, absIdx);
|
|
onClose();
|
|
}}
|
|
>
|
|
<span className="fsq-item-pos">{absIdx + 1}</span>
|
|
<span className="fsq-item-info">
|
|
<span className="fsq-item-title">{track.title}</span>
|
|
<span className="fsq-item-artist">{track.artist}</span>
|
|
</span>
|
|
<span className="fsq-item-dur">{formatTrackTime(track.duration ?? 0)}</span>
|
|
</button>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|