From a25fb9b5eb5af94a097ec0af63345a46fa346c60 Mon Sep 17 00:00:00 2001 From: JSC Date: Mon, 22 Sep 2025 20:28:27 +0200 Subject: [PATCH] feat: implement new compact player components for controls, progress, and track info --- src/components/player/CompactPlayer.tsx | 200 ++++-------------- .../player/CompactPlayerControls.tsx | 128 +++++++++++ .../player/CompactPlayerProgress.tsx | 31 +++ .../player/CompactPlayerTrackInfo.tsx | 62 ++++++ 4 files changed, 259 insertions(+), 162 deletions(-) create mode 100644 src/components/player/CompactPlayerControls.tsx create mode 100644 src/components/player/CompactPlayerProgress.tsx create mode 100644 src/components/player/CompactPlayerTrackInfo.tsx diff --git a/src/components/player/CompactPlayer.tsx b/src/components/player/CompactPlayer.tsx index 437a5ef..c9888ce 100644 --- a/src/components/player/CompactPlayer.tsx +++ b/src/components/player/CompactPlayer.tsx @@ -1,12 +1,3 @@ -import { Button } from '@/components/ui/button' -import { Progress } from '@/components/ui/progress' -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@/components/ui/tooltip' -import { useSidebar } from '@/components/ui/sidebar' -import { filesService } from '@/lib/api/services/files' import { type MessageResponse, type PlayerState, @@ -14,26 +5,17 @@ import { } from '@/lib/api/services/player' import { PLAYER_EVENTS, playerEvents } from '@/lib/events' import { cn } from '@/lib/utils' -import { - Maximize2, - Music, - Pause, - Play, - SkipBack, - SkipForward, - Volume2, - VolumeX, -} from 'lucide-react' import { useCallback, useEffect, useState } from 'react' import { toast } from 'sonner' -import { NumberFlowDuration } from '../ui/number-flow-duration' +import { CompactPlayerControls } from './CompactPlayerControls' +import { CompactPlayerProgress } from './CompactPlayerProgress' +import { CompactPlayerTrackInfo } from './CompactPlayerTrackInfo' interface CompactPlayerProps { className?: string } export function CompactPlayer({ className }: CompactPlayerProps) { - const { isMobile, state: sidebarState } = useSidebar() const [state, setState] = useState({ status: 'stopped', mode: 'continuous', @@ -114,156 +96,50 @@ export function CompactPlayer({ className }: CompactPlayerProps) { } }, [state.volume, executeAction]) - // // Don't show if no current sound - // if (!state.current_sound) { - // return null - // } + const handleExpand = useCallback(() => { + const expandFn = ( + window as unknown as { __expandPlayerFromSidebar?: () => void } + ).__expandPlayerFromSidebar + if (expandFn) expandFn() + }, []) return (
{/* Collapsed state - only play/pause button */} -
- - - - - - -
+ {/* Expanded state - full player */}
- {/* Track Info */} -
-
- {state.current_sound?.thumbnail ? ( - {state.current_sound.name} { - // Hide image and show music icon if thumbnail fails to load - const target = e.target as HTMLImageElement - target.style.display = 'none' - const musicIcon = target.nextElementSibling as HTMLElement - if (musicIcon) musicIcon.style.display = 'block' - }} - /> - ) : null} - -
-
-
- {state.current_sound?.name || 'No track selected'} -
-
- {state.playlist?.name} -
-
- -
+ - {/* Progress Bar */} -
- -
- - -
-
+ - {/* Controls */} -
- - - - - - - -
+
) diff --git a/src/components/player/CompactPlayerControls.tsx b/src/components/player/CompactPlayerControls.tsx new file mode 100644 index 0000000..08d9669 --- /dev/null +++ b/src/components/player/CompactPlayerControls.tsx @@ -0,0 +1,128 @@ +import { Button } from '@/components/ui/button' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { useSidebar } from '@/components/ui/sidebar' +import type { PlayerState } from '@/lib/api/services/player' +import { + Pause, + Play, + SkipBack, + SkipForward, + Volume2, + VolumeX, +} from 'lucide-react' +import { memo } from 'react' + +interface CompactPlayerControlsProps { + status: PlayerState['status'] + volume: number + isLoading: boolean + onPlayPause: () => void + onPrevious: () => void + onNext: () => void + onVolumeToggle: () => void + variant?: 'collapsed' | 'expanded' +} + +export const CompactPlayerControls = memo(function CompactPlayerControls({ + status, + volume, + isLoading, + onPlayPause, + onPrevious, + onNext, + onVolumeToggle, + variant = 'expanded', +}: CompactPlayerControlsProps) { + const { isMobile, state: sidebarState } = useSidebar() + + if (variant === 'collapsed') { + return ( +
+ + + + + + +
+ ) + } + + // Expanded variant + return ( +
+ + + + + + + +
+ ) +}) \ No newline at end of file diff --git a/src/components/player/CompactPlayerProgress.tsx b/src/components/player/CompactPlayerProgress.tsx new file mode 100644 index 0000000..54a13b8 --- /dev/null +++ b/src/components/player/CompactPlayerProgress.tsx @@ -0,0 +1,31 @@ +import { Progress } from '@/components/ui/progress' +import { memo, useMemo } from 'react' +import { NumberFlowDuration } from '../ui/number-flow-duration' + +interface CompactPlayerProgressProps { + position: number + duration: number +} + +export const CompactPlayerProgress = memo(function CompactPlayerProgress({ + position, + duration, +}: CompactPlayerProgressProps) { + const progressPercentage = useMemo(() => + (position / (duration || 1)) * 100, + [position, duration] + ) + + return ( +
+ +
+ + +
+
+ ) +}) \ No newline at end of file diff --git a/src/components/player/CompactPlayerTrackInfo.tsx b/src/components/player/CompactPlayerTrackInfo.tsx new file mode 100644 index 0000000..7ab77d8 --- /dev/null +++ b/src/components/player/CompactPlayerTrackInfo.tsx @@ -0,0 +1,62 @@ +import { Button } from '@/components/ui/button' +import { filesService } from '@/lib/api/services/files' +import type { PlayerState } from '@/lib/api/services/player' +import { cn } from '@/lib/utils' +import { Maximize2, Music } from 'lucide-react' +import { memo } from 'react' + +interface CompactPlayerTrackInfoProps { + currentSound: PlayerState['current_sound'] + playlistName?: string + onExpand: () => void +} + +export const CompactPlayerTrackInfo = memo(function CompactPlayerTrackInfo({ + currentSound, + playlistName, + onExpand, +}: CompactPlayerTrackInfoProps) { + return ( +
+
+ {currentSound?.thumbnail ? ( + {currentSound.name} { + // Hide image and show music icon if thumbnail fails to load + const target = e.target as HTMLImageElement + target.style.display = 'none' + const musicIcon = target.nextElementSibling as HTMLElement + if (musicIcon) musicIcon.style.display = 'block' + }} + /> + ) : null} + +
+
+
+ {currentSound?.name || 'No track selected'} +
+
+ {playlistName} +
+
+ +
+ ) +}) \ No newline at end of file