Add always-visible player, play button on sidebar thumbnail, and GitHub link
- Player: Show minimal placeholder when no song is playing instead of hiding - RightSidebar: Add play button overlay on cover art thumbnail - Hover to reveal play/pause button - Click anywhere on cover to play - Shows pause icon when song is currently playing - SettingsModal: Add GitHub repo link for issues and feature requests
This commit is contained in:
@@ -959,6 +959,9 @@ export default function App() {
|
|||||||
onNavigateToSong={handleNavigateToSong}
|
onNavigateToSong={handleNavigateToSong}
|
||||||
isLiked={selectedSong ? likedSongIds.has(selectedSong.id) : false}
|
isLiked={selectedSong ? likedSongIds.has(selectedSong.id) : false}
|
||||||
onToggleLike={toggleLike}
|
onToggleLike={toggleLike}
|
||||||
|
onPlay={playSong}
|
||||||
|
isPlaying={isPlaying}
|
||||||
|
currentSong={currentSong}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1086,6 +1089,9 @@ export default function App() {
|
|||||||
onNavigateToSong={handleNavigateToSong}
|
onNavigateToSong={handleNavigateToSong}
|
||||||
isLiked={selectedSong ? likedSongIds.has(selectedSong.id) : false}
|
isLiked={selectedSong ? likedSongIds.has(selectedSong.id) : false}
|
||||||
onToggleLike={toggleLike}
|
onToggleLike={toggleLike}
|
||||||
|
onPlay={playSong}
|
||||||
|
isPlaying={isPlaying}
|
||||||
|
currentSong={currentSong}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+13
-1
@@ -75,7 +75,19 @@ export const Player: React.FC<PlayerProps> = ({
|
|||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [isFullscreen]);
|
}, [isFullscreen]);
|
||||||
|
|
||||||
if (!currentSong) return null;
|
// Show minimal player when no song is playing
|
||||||
|
if (!currentSong) {
|
||||||
|
return (
|
||||||
|
<div className="h-20 lg:h-24 bg-white dark:bg-black/95 backdrop-blur border-t border-zinc-200 dark:border-white/10 flex items-center justify-center z-50 transition-colors duration-300 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)] dark:shadow-none">
|
||||||
|
<div className="flex items-center gap-3 text-zinc-400 dark:text-zinc-600">
|
||||||
|
<div className="w-10 h-10 lg:w-12 lg:h-12 rounded bg-zinc-200 dark:bg-zinc-800 flex items-center justify-center">
|
||||||
|
<Play size={20} className="text-zinc-400 dark:text-zinc-600" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium">Select a song to play</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const formatTime = (time: number) => {
|
const formatTime = (time: number) => {
|
||||||
if (isNaN(time)) return "0:00";
|
if (isNaN(time)) return "0:00";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Song } from '../types';
|
import { Song } from '../types';
|
||||||
import { Heart, Share2, Play, MoreHorizontal, X, Copy, Wand2, MoreVertical, Download, Repeat, Video, Music, Link as LinkIcon, Sparkles, Globe, Lock, Trash2, Edit3, Layers } from 'lucide-react';
|
import { Heart, Share2, Play, Pause, MoreHorizontal, X, Copy, Wand2, MoreVertical, Download, Repeat, Video, Music, Link as LinkIcon, Sparkles, Globe, Lock, Trash2, Edit3, Layers } from 'lucide-react';
|
||||||
import { songsApi } from '../services/api';
|
import { songsApi } from '../services/api';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import { SongDropdownMenu } from './SongDropdownMenu';
|
import { SongDropdownMenu } from './SongDropdownMenu';
|
||||||
@@ -19,9 +19,12 @@ interface RightSidebarProps {
|
|||||||
onToggleLike?: (song: Song) => void;
|
onToggleLike?: (song: Song) => void;
|
||||||
onDelete?: (song: Song) => void;
|
onDelete?: (song: Song) => void;
|
||||||
onAddToPlaylist?: (song: Song) => void;
|
onAddToPlaylist?: (song: Song) => void;
|
||||||
|
onPlay?: (song: Song) => void;
|
||||||
|
isPlaying?: boolean;
|
||||||
|
currentSong?: Song | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RightSidebar: React.FC<RightSidebarProps> = ({ song, onClose, onOpenVideo, onReuse, onSongUpdate, onNavigateToProfile, onNavigateToSong, isLiked, onToggleLike, onDelete, onAddToPlaylist }) => {
|
export const RightSidebar: React.FC<RightSidebarProps> = ({ song, onClose, onOpenVideo, onReuse, onSongUpdate, onNavigateToProfile, onNavigateToSong, isLiked, onToggleLike, onDelete, onAddToPlaylist, onPlay, isPlaying, currentSong }) => {
|
||||||
const { token, user } = useAuth();
|
const { token, user } = useAuth();
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const [isOwner, setIsOwner] = useState(false);
|
const [isOwner, setIsOwner] = useState(false);
|
||||||
@@ -64,7 +67,10 @@ export const RightSidebar: React.FC<RightSidebarProps> = ({ song, onClose, onOpe
|
|||||||
<div className="p-5 space-y-6">
|
<div className="p-5 space-y-6">
|
||||||
|
|
||||||
{/* Cover Art */}
|
{/* Cover Art */}
|
||||||
<div className="group relative aspect-square w-full rounded-xl overflow-hidden shadow-2xl bg-zinc-200 dark:bg-zinc-800 ring-1 ring-black/5 dark:ring-white/10">
|
<div
|
||||||
|
className="group relative aspect-square w-full rounded-xl overflow-hidden shadow-2xl bg-zinc-200 dark:bg-zinc-800 ring-1 ring-black/5 dark:ring-white/10 cursor-pointer"
|
||||||
|
onClick={() => onPlay?.(song)}
|
||||||
|
>
|
||||||
{song.coverUrl ? (
|
{song.coverUrl ? (
|
||||||
<img src={song.coverUrl} alt={song.title} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" onError={(e) => { e.currentTarget.style.display = 'none'; }} />
|
<img src={song.coverUrl} alt={song.title} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" onError={(e) => { e.currentTarget.style.display = 'none'; }} />
|
||||||
) : null}
|
) : null}
|
||||||
@@ -73,6 +79,23 @@ export const RightSidebar: React.FC<RightSidebarProps> = ({ song, onClose, onOpe
|
|||||||
{/* Overlay Gradient */}
|
{/* Overlay Gradient */}
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-60"></div>
|
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-60"></div>
|
||||||
|
|
||||||
|
{/* Play Button Overlay */}
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onPlay?.(song);
|
||||||
|
}}
|
||||||
|
className="w-16 h-16 rounded-full bg-white/95 dark:bg-white text-black flex items-center justify-center shadow-2xl hover:scale-110 transition-transform"
|
||||||
|
>
|
||||||
|
{isPlaying && currentSong?.id === song.id ? (
|
||||||
|
<Pause size={28} fill="currentColor" />
|
||||||
|
) : (
|
||||||
|
<Play size={28} fill="currentColor" className="ml-1" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="absolute bottom-4 left-4 right-4 flex items-center justify-between">
|
<div className="absolute bottom-4 left-4 right-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2 text-white">
|
<div className="flex items-center gap-2 text-white">
|
||||||
<Play size={16} fill="currentColor" />
|
<Play size={16} fill="currentColor" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { X, User as UserIcon, Palette, Info, Edit3, ExternalLink } from 'lucide-react';
|
import { X, User as UserIcon, Palette, Info, Edit3, ExternalLink, Github } from 'lucide-react';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import { EditProfileModal } from './EditProfileModal';
|
import { EditProfileModal } from './EditProfileModal';
|
||||||
|
|
||||||
@@ -144,7 +144,8 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
|||||||
Powered by ACE-Step 1.5. Open source and free to use.
|
Powered by ACE-Step 1.5. Open source and free to use.
|
||||||
</p>
|
</p>
|
||||||
<div className="pt-3 border-t border-zinc-200 dark:border-zinc-700/50 mt-4">
|
<div className="pt-3 border-t border-zinc-200 dark:border-zinc-700/50 mt-4">
|
||||||
<p className="text-zinc-900 dark:text-white font-medium mb-2">Created by Ambsd</p>
|
<p className="text-zinc-900 dark:text-white font-medium mb-3">Created by Ambsd</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
<a
|
<a
|
||||||
href="https://x.com/AmbsdOP"
|
href="https://x.com/AmbsdOP"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -156,6 +157,19 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
|||||||
</svg>
|
</svg>
|
||||||
Follow @AmbsdOP
|
Follow @AmbsdOP
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/fspecii/ace-step-ui"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-800 dark:bg-zinc-700 text-white rounded-lg text-sm font-medium hover:bg-zinc-700 dark:hover:bg-zinc-600 transition-colors"
|
||||||
|
>
|
||||||
|
<Github size={16} />
|
||||||
|
GitHub Repo
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-zinc-400 dark:text-zinc-500 mt-3">
|
||||||
|
Report issues or request features on GitHub
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user