Major categories fixed: - TS6133 (188): Remove unused imports (React, icons, types) and variables - TS2322 (222): Fix type mismatches in stories (satisfies Meta -> const meta: Meta), add nullish coalescing for optional values, fix component prop types - TS2345 (43): Fix argument type mismatches with proper null checks and type narrowing - TS2741 (21): Add missing required properties to mock/story data - TS2339 (19): Fix property access on incorrect types, add type guards - TS2353 (13): Remove extra properties from object literals or extend interfaces - TS2352 (11): Fix type conversion chains - TS2307 (9): Fix import paths and module references - Other (42): Fix implicit any, possibly undefined, export declarations Vite build and tsc --noEmit both pass cleanly. Co-authored-by: Cursor <cursoragent@cursor.com>
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import { useRef, useState, useCallback } from 'react';
|
|
import { usePlayerStore } from '@/features/player/store/playerStore';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Tooltip } from '@/components/ui/tooltip';
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
import { List } from 'lucide-react';
|
|
import { QueuePanel } from '../QueuePanel';
|
|
import { useAudioPlayerEffects } from './useAudioPlayerEffects';
|
|
import { AudioPlayerTrackInfo } from './AudioPlayerTrackInfo';
|
|
import { AudioPlayerControls } from './AudioPlayerControls';
|
|
import { AudioPlayerProgress } from './AudioPlayerProgress';
|
|
import { AudioPlayerVolume } from './AudioPlayerVolume';
|
|
import type { AudioPlayerProps as Props } from './types';
|
|
|
|
export function AudioPlayer(_props: Props = {}) {
|
|
const audioRef = useRef<HTMLAudioElement>(null);
|
|
const { handlePlayPause } = useAudioPlayerEffects(audioRef);
|
|
|
|
const {
|
|
currentTrack,
|
|
isPlaying,
|
|
currentTime,
|
|
duration,
|
|
volume,
|
|
muted,
|
|
repeat,
|
|
shuffle,
|
|
setCurrentTime,
|
|
setVolume,
|
|
toggleMute,
|
|
toggleShuffle,
|
|
setRepeat,
|
|
next,
|
|
previous,
|
|
} = usePlayerStore();
|
|
|
|
const { t } = useTranslation();
|
|
const [showQueue, setShowQueue] = useState(false);
|
|
|
|
const handleSeek = useCallback(
|
|
(value: number[]) => {
|
|
const audio = audioRef.current;
|
|
const seekTime = value[0] ?? 0;
|
|
if (audio) {
|
|
audio.currentTime = seekTime;
|
|
setCurrentTime(seekTime);
|
|
}
|
|
},
|
|
[setCurrentTime],
|
|
);
|
|
|
|
const handleVolumeChange = useCallback((value: number[]) => setVolume(value[0] ?? 0), [setVolume]);
|
|
|
|
const handleRepeatCycle = useCallback(() => {
|
|
const modes: Array<'off' | 'track' | 'playlist'> = ['off', 'track', 'playlist'];
|
|
const currentIndex = modes.indexOf(repeat);
|
|
setRepeat(modes[(currentIndex + 1) % modes.length] ?? 'off');
|
|
}, [repeat, setRepeat]);
|
|
|
|
if (!currentTrack) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<audio ref={audioRef} preload="metadata" />
|
|
<div className="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur-md border-t border-border z-50">
|
|
{/* Full-width progress bar at the very top of the player */}
|
|
<AudioPlayerProgress
|
|
currentTime={currentTime}
|
|
duration={duration}
|
|
onSeek={handleSeek}
|
|
variant="minimal"
|
|
/>
|
|
<div className="container mx-auto px-4 py-2">
|
|
<div className="grid grid-cols-3 items-center gap-4">
|
|
{/* Left: Track info */}
|
|
<AudioPlayerTrackInfo track={currentTrack} />
|
|
|
|
{/* Center: Playback controls */}
|
|
<AudioPlayerControls
|
|
isPlaying={isPlaying}
|
|
shuffle={shuffle}
|
|
repeat={repeat}
|
|
onPlayPause={handlePlayPause}
|
|
onPrevious={previous}
|
|
onNext={next}
|
|
onToggleShuffle={toggleShuffle}
|
|
onRepeatCycle={handleRepeatCycle}
|
|
/>
|
|
|
|
{/* Right: Volume + Queue */}
|
|
<div className="flex items-center justify-end gap-2">
|
|
<AudioPlayerVolume
|
|
volume={volume}
|
|
muted={muted}
|
|
onVolumeChange={handleVolumeChange}
|
|
onToggleMute={toggleMute}
|
|
/>
|
|
<Tooltip
|
|
content={
|
|
showQueue
|
|
? t('player.hideQueue', 'Hide queue')
|
|
: t('player.showQueue', 'Show queue')
|
|
}
|
|
>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => setShowQueue(!showQueue)}
|
|
className={showQueue ? 'text-primary' : ''}
|
|
aria-label={
|
|
showQueue
|
|
? t('player.hideQueue', 'Hide queue')
|
|
: t('player.showQueue', 'Show queue')
|
|
}
|
|
>
|
|
<List className="h-4 w-4" />
|
|
</Button>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{showQueue && <QueuePanel onClose={() => setShowQueue(false)} />}
|
|
</>
|
|
);
|
|
}
|