fix: update previous_volume to 80 in CompactPlayer and Player components for consistency
This commit is contained in:
@@ -27,7 +27,7 @@ export function CompactPlayer({ className }: CompactPlayerProps) {
|
|||||||
status: 'stopped',
|
status: 'stopped',
|
||||||
mode: 'continuous',
|
mode: 'continuous',
|
||||||
volume: 80,
|
volume: 80,
|
||||||
previous_volume: 50,
|
previous_volume: 80,
|
||||||
position: 0
|
position: 0
|
||||||
})
|
})
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
|
|||||||
status: 'stopped',
|
status: 'stopped',
|
||||||
mode: 'continuous',
|
mode: 'continuous',
|
||||||
volume: 80,
|
volume: 80,
|
||||||
previous_volume: 50,
|
previous_volume: 80,
|
||||||
position: 0
|
position: 0
|
||||||
})
|
})
|
||||||
const [displayMode, setDisplayMode] = useState<PlayerDisplayMode>(() => {
|
const [displayMode, setDisplayMode] = useState<PlayerDisplayMode>(() => {
|
||||||
@@ -566,11 +566,6 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{state.playlist && (
|
|
||||||
<p className="text-lg text-muted-foreground">
|
|
||||||
{state.playlist.name}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Progress Bar */}
|
{/* Progress Bar */}
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ import { useEffect, useState, useCallback } from 'react'
|
|||||||
import { useParams, useNavigate } from 'react-router'
|
import { useParams, useNavigate } from 'react-router'
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragOverlay,
|
|
||||||
DragStartEvent,
|
|
||||||
DragEndEvent,
|
DragEndEvent,
|
||||||
closestCenter,
|
closestCenter,
|
||||||
PointerSensor,
|
PointerSensor,
|
||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
useDroppable,
|
|
||||||
} from '@dnd-kit/core'
|
} from '@dnd-kit/core'
|
||||||
import {
|
import {
|
||||||
SortableContext,
|
SortableContext,
|
||||||
@@ -19,7 +16,6 @@ import {
|
|||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import { AppLayout } from '@/components/AppLayout'
|
import { AppLayout } from '@/components/AppLayout'
|
||||||
import { playlistsService, type Playlist, type PlaylistSound } from '@/lib/api/services/playlists'
|
import { playlistsService, type Playlist, type PlaylistSound } from '@/lib/api/services/playlists'
|
||||||
import { soundsService, type Sound } from '@/lib/api/services/sounds'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -28,53 +24,10 @@ import { Label } from '@/components/ui/label'
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||||
import { AlertCircle, Save, Music, Clock, ChevronUp, ChevronDown, Trash2, RefreshCw, Edit, X, ArrowLeft, Plus } from 'lucide-react'
|
import { AlertCircle, Save, Music, Clock, ChevronUp, ChevronDown, Trash2, RefreshCw, Edit, X, ArrowLeft } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { formatDuration } from '@/utils/format-duration'
|
import { formatDuration } from '@/utils/format-duration'
|
||||||
|
|
||||||
// Sortable playlist item component for add sounds mode
|
|
||||||
interface SortablePlaylistItemProps {
|
|
||||||
sound: PlaylistSound
|
|
||||||
index: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function SortablePlaylistItem({ sound, index }: SortablePlaylistItemProps) {
|
|
||||||
const {
|
|
||||||
attributes,
|
|
||||||
listeners,
|
|
||||||
setNodeRef,
|
|
||||||
transform,
|
|
||||||
transition,
|
|
||||||
isDragging,
|
|
||||||
} = useSortable({ id: `playlist-sound-${sound.id}` })
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
transform: CSS.Transform.toString(transform),
|
|
||||||
transition,
|
|
||||||
opacity: isDragging ? 0.5 : 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={setNodeRef}
|
|
||||||
style={style}
|
|
||||||
{...attributes}
|
|
||||||
{...listeners}
|
|
||||||
className="flex items-center gap-3 p-3 border-b hover:bg-muted/50 cursor-grab active:cursor-grabbing"
|
|
||||||
>
|
|
||||||
<div className="text-sm text-muted-foreground font-mono w-8">
|
|
||||||
{index + 1}
|
|
||||||
</div>
|
|
||||||
<Music className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="font-medium truncate">{sound.name}</div>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{formatDuration(sound.duration || 0)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sortable table row component for normal table view
|
// Sortable table row component for normal table view
|
||||||
interface SortableTableRowProps {
|
interface SortableTableRowProps {
|
||||||
@@ -184,99 +137,6 @@ function SortableTableRow({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draggable available sound component
|
|
||||||
interface DraggableAvailableSoundProps {
|
|
||||||
sound: Sound
|
|
||||||
onAddSound: (soundId: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function DraggableAvailableSound({ sound, onAddSound }: DraggableAvailableSoundProps) {
|
|
||||||
const {
|
|
||||||
attributes,
|
|
||||||
listeners,
|
|
||||||
setNodeRef,
|
|
||||||
transform,
|
|
||||||
transition,
|
|
||||||
isDragging,
|
|
||||||
} = useSortable({ id: sound.id.toString() })
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
transform: CSS.Transform.toString(transform),
|
|
||||||
transition,
|
|
||||||
opacity: isDragging ? 0.5 : 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={setNodeRef}
|
|
||||||
style={style}
|
|
||||||
{...attributes}
|
|
||||||
{...listeners}
|
|
||||||
className="flex items-center gap-3 p-3 border-b hover:bg-muted/50 cursor-grab active:cursor-grabbing select-none"
|
|
||||||
onClick={() => onAddSound(sound.id)}
|
|
||||||
>
|
|
||||||
<Music className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="font-medium truncate">{sound.name}</div>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{formatDuration(sound.duration || 0)} • {sound.type}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Plus className="h-4 w-4 text-primary" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop zone component
|
|
||||||
interface DropZoneProps {
|
|
||||||
position: number
|
|
||||||
isActive?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropZone({ position, isActive }: DropZoneProps) {
|
|
||||||
const {
|
|
||||||
setNodeRef,
|
|
||||||
isOver,
|
|
||||||
} = useDroppable({
|
|
||||||
id: `playlist-position-${position}`,
|
|
||||||
data: { type: 'position', position }
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={setNodeRef}
|
|
||||||
className={`h-2 border-2 border-dashed transition-colors ${
|
|
||||||
isOver || isActive
|
|
||||||
? 'border-primary bg-primary/10'
|
|
||||||
: 'border-transparent hover:border-primary/50'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty playlist drop zone
|
|
||||||
function EmptyPlaylistDropZone() {
|
|
||||||
const {
|
|
||||||
setNodeRef,
|
|
||||||
isOver,
|
|
||||||
} = useDroppable({
|
|
||||||
id: 'playlist-position-0',
|
|
||||||
data: { type: 'position', position: 0 }
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={setNodeRef}
|
|
||||||
className={`flex items-center justify-center h-full text-muted-foreground border-2 border-dashed transition-colors ${
|
|
||||||
isOver
|
|
||||||
? 'border-primary bg-primary/10'
|
|
||||||
: 'border-muted hover:border-primary/50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<p>Drag sounds here to add to playlist</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PlaylistEditPage() {
|
export function PlaylistEditPage() {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
@@ -290,12 +150,7 @@ export function PlaylistEditPage() {
|
|||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [isEditMode, setIsEditMode] = useState(false)
|
const [isEditMode, setIsEditMode] = useState(false)
|
||||||
const [isAddSoundsMode, setIsAddSoundsMode] = useState(false)
|
|
||||||
const [availableSounds, setAvailableSounds] = useState<Sound[]>([])
|
|
||||||
const [loadingAvailableSounds, setLoadingAvailableSounds] = useState(false)
|
|
||||||
|
|
||||||
// dnd-kit state
|
|
||||||
const [draggedSound, setDraggedSound] = useState<Sound | PlaylistSound | null>(null)
|
|
||||||
|
|
||||||
// dnd-kit sensors
|
// dnd-kit sensors
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
@@ -481,135 +336,24 @@ export function PlaylistEditPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchAvailableSounds = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoadingAvailableSounds(true)
|
|
||||||
// Get all EXT sounds
|
|
||||||
const allExtSounds = await soundsService.getSoundsByType('EXT')
|
|
||||||
|
|
||||||
// Filter out sounds that are already in the current playlist
|
|
||||||
const currentSoundIds = sounds.map(sound => sound.id)
|
|
||||||
const available = allExtSounds.filter(sound => !currentSoundIds.includes(sound.id))
|
|
||||||
|
|
||||||
setAvailableSounds(available)
|
|
||||||
} catch (err) {
|
|
||||||
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch available sounds'
|
|
||||||
toast.error(errorMessage)
|
|
||||||
} finally {
|
|
||||||
setLoadingAvailableSounds(false)
|
|
||||||
}
|
|
||||||
}, [sounds])
|
|
||||||
|
|
||||||
const handleOpenAddSounds = async () => {
|
|
||||||
setIsAddSoundsMode(true)
|
|
||||||
await fetchAvailableSounds()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCloseAddSounds = () => {
|
|
||||||
setIsAddSoundsMode(false)
|
|
||||||
setAvailableSounds([])
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddSoundToPlaylist = async (soundId: number, position?: number) => {
|
|
||||||
// Find the sound being added for potential rollback
|
|
||||||
const soundToAdd = availableSounds.find(sound => sound.id === soundId)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Optimistically remove the sound from available sounds for instant feedback
|
|
||||||
setAvailableSounds(prev => prev.filter(sound => sound.id !== soundId))
|
|
||||||
|
|
||||||
await playlistsService.addSoundToPlaylist(playlistId, soundId, position)
|
|
||||||
toast.success('Sound added to playlist')
|
|
||||||
|
|
||||||
// Refresh playlist sounds to show the new addition
|
|
||||||
await fetchSounds()
|
|
||||||
} catch (err) {
|
|
||||||
// Rollback: add the sound back to available sounds if the API call failed
|
|
||||||
if (soundToAdd) {
|
|
||||||
setAvailableSounds(prev => [...prev, soundToAdd])
|
|
||||||
}
|
|
||||||
const errorMessage = err instanceof Error ? err.message : 'Failed to add sound to playlist'
|
|
||||||
toast.error(errorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dnd-kit drag handlers
|
|
||||||
const handleDragStart = (event: DragStartEvent) => {
|
|
||||||
const { active } = event
|
|
||||||
|
|
||||||
// Extract sound ID from different ID formats
|
|
||||||
const activeId = active.id as string
|
|
||||||
const getSoundId = (id: string) => {
|
|
||||||
if (id.startsWith('table-sound-')) {
|
|
||||||
return parseInt(id.replace('table-sound-', ''), 10)
|
|
||||||
} else if (id.startsWith('playlist-sound-')) {
|
|
||||||
return parseInt(id.replace('playlist-sound-', ''), 10)
|
|
||||||
} else {
|
|
||||||
return parseInt(id, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const soundId = getSoundId(activeId)
|
|
||||||
const availableSound = availableSounds.find(s => s.id === soundId)
|
|
||||||
const playlistSound = sounds.find(s => s.id === soundId)
|
|
||||||
|
|
||||||
setDraggedSound(availableSound || playlistSound || null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDragOver = () => {
|
|
||||||
// Handle drag over logic here if needed for visual feedback
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// dnd-kit drag handler
|
||||||
const handleDragEnd = async (event: DragEndEvent) => {
|
const handleDragEnd = async (event: DragEndEvent) => {
|
||||||
const { active, over } = event
|
const { active, over } = event
|
||||||
|
|
||||||
setDraggedSound(null)
|
|
||||||
|
|
||||||
if (!over) return
|
if (!over) return
|
||||||
|
|
||||||
const activeId = active.id as string
|
const activeId = active.id as string
|
||||||
const overId = over.id as string
|
const overId = over.id as string
|
||||||
|
|
||||||
// Extract sound ID from different ID formats
|
// Only handle table-sound reordering
|
||||||
const getDraggedSoundId = (id: string) => {
|
if (overId.startsWith('table-sound-')) {
|
||||||
if (id.startsWith('table-sound-')) {
|
const draggedSoundId = parseInt(activeId.replace('table-sound-', ''), 10)
|
||||||
return parseInt(id.replace('table-sound-', ''), 10)
|
const targetSoundId = parseInt(overId.replace('table-sound-', ''), 10)
|
||||||
} else if (id.startsWith('playlist-sound-')) {
|
|
||||||
return parseInt(id.replace('playlist-sound-', ''), 10)
|
|
||||||
} else {
|
|
||||||
return parseInt(id, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTargetSoundId = (id: string) => {
|
|
||||||
if (id.startsWith('table-sound-')) {
|
|
||||||
return parseInt(id.replace('table-sound-', ''), 10)
|
|
||||||
} else if (id.startsWith('playlist-sound-')) {
|
|
||||||
return parseInt(id.replace('playlist-sound-', ''), 10)
|
|
||||||
} else {
|
|
||||||
return parseInt(id, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const draggedSoundId = getDraggedSoundId(activeId)
|
|
||||||
|
|
||||||
// Check if dragging from available sounds (adding new sound)
|
|
||||||
const isFromAvailable = availableSounds.some(s => s.id === draggedSoundId)
|
|
||||||
|
|
||||||
// Handle dropping onto playlist positions (inserting new sound in add sounds mode)
|
|
||||||
if (overId.startsWith('playlist-position-') && isFromAvailable) {
|
|
||||||
const position = parseInt(overId.replace('playlist-position-', ''), 10)
|
|
||||||
await handleAddSoundToPlaylist(draggedSoundId, position)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle reordering within playlist (both add sounds mode and table mode)
|
|
||||||
if (overId.startsWith('playlist-sound-') || overId.startsWith('table-sound-')) {
|
|
||||||
const targetSoundId = getTargetSoundId(overId)
|
|
||||||
const draggedIndex = sounds.findIndex(s => s.id === draggedSoundId)
|
const draggedIndex = sounds.findIndex(s => s.id === draggedSoundId)
|
||||||
const targetIndex = sounds.findIndex(s => s.id === targetSoundId)
|
const targetIndex = sounds.findIndex(s => s.id === targetSoundId)
|
||||||
|
|
||||||
// Only allow reordering if both sounds are in the playlist
|
|
||||||
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
|
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
|
||||||
// Reorder sounds in playlist
|
// Reorder sounds in playlist
|
||||||
const newSounds = [...sounds]
|
const newSounds = [...sounds]
|
||||||
@@ -628,11 +372,6 @@ export function PlaylistEditPage() {
|
|||||||
toast.error(errorMessage)
|
toast.error(errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle dropping from available sounds onto existing playlist item (insert before)
|
|
||||||
else if (isFromAvailable && targetIndex !== -1) {
|
|
||||||
await handleAddSoundToPlaylist(draggedSoundId, targetIndex)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,8 +429,6 @@ export function PlaylistEditPage() {
|
|||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
onDragStart={handleDragStart}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
>
|
>
|
||||||
<AppLayout
|
<AppLayout
|
||||||
@@ -706,14 +443,6 @@ export function PlaylistEditPage() {
|
|||||||
<div className="flex-1 rounded-xl bg-muted/50 p-4">
|
<div className="flex-1 rounded-xl bg-muted/50 p-4">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => navigate('/playlists')}
|
|
||||||
>
|
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">{playlist.name}</h1>
|
<h1 className="text-2xl font-bold">{playlist.name}</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
@@ -892,28 +621,9 @@ export function PlaylistEditPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Music className="h-5 w-5" />
|
<Music className="h-5 w-5" />
|
||||||
{isAddSoundsMode ? 'Add Sounds to Playlist' : `Playlist Sounds (${sounds.length})`}
|
Playlist Sounds ({sounds.length})
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isAddSoundsMode ? (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleCloseAddSounds}
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4 mr-2" />
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleOpenAddSounds}
|
|
||||||
title="Add sounds to playlist"
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -922,87 +632,11 @@ export function PlaylistEditPage() {
|
|||||||
>
|
>
|
||||||
<RefreshCw className={`h-4 w-4 ${soundsLoading ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`h-4 w-4 ${soundsLoading ? 'animate-spin' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isAddSoundsMode && (
|
|
||||||
<p className="text-sm text-muted-foreground mt-2">
|
|
||||||
Drag sounds from the available list (right) to add them to your playlist (left) at specific positions, or click a sound to add it to the end.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{isAddSoundsMode ? (
|
{soundsLoading ? (
|
||||||
/* Add Sounds Mode - Two Column Layout */
|
|
||||||
<div className="grid grid-cols-2 gap-6 min-h-[600px]">
|
|
||||||
{/* Current Playlist Sounds - Left Column */}
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<h3 className="font-semibold mb-4 flex items-center gap-2">
|
|
||||||
<Music className="h-4 w-4" />
|
|
||||||
Current Playlist ({sounds.length} sounds)
|
|
||||||
</h3>
|
|
||||||
<div className="flex-1 border rounded-lg overflow-hidden">
|
|
||||||
<SortableContext
|
|
||||||
items={sounds.map(sound => `playlist-sound-${sound.id}`)}
|
|
||||||
strategy={verticalListSortingStrategy}
|
|
||||||
>
|
|
||||||
{sounds.length === 0 ? (
|
|
||||||
<EmptyPlaylistDropZone />
|
|
||||||
) : (
|
|
||||||
<div className="h-full overflow-y-auto">
|
|
||||||
{/* Drop zone at the top */}
|
|
||||||
<DropZone position={0} />
|
|
||||||
|
|
||||||
{sounds.map((sound, index) => (
|
|
||||||
<div key={sound.id}>
|
|
||||||
<SortablePlaylistItem sound={sound} index={index} />
|
|
||||||
<DropZone position={index + 1} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</SortableContext>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Available Sounds - Right Column */}
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<h3 className="font-semibold mb-4 flex items-center gap-2">
|
|
||||||
<Music className="h-4 w-4" />
|
|
||||||
Available EXT Sounds ({availableSounds.length} available)
|
|
||||||
</h3>
|
|
||||||
<div className="flex-1 border rounded-lg overflow-hidden">
|
|
||||||
{loadingAvailableSounds ? (
|
|
||||||
<div className="flex items-center justify-center h-full">
|
|
||||||
<RefreshCw className="h-6 w-6 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : availableSounds.length === 0 ? (
|
|
||||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
|
||||||
<p>No available EXT sounds</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<SortableContext
|
|
||||||
items={availableSounds.map(sound => sound.id.toString())}
|
|
||||||
strategy={verticalListSortingStrategy}
|
|
||||||
>
|
|
||||||
<div className="h-full overflow-y-auto">
|
|
||||||
{availableSounds.map((sound) => (
|
|
||||||
<DraggableAvailableSound
|
|
||||||
key={sound.id}
|
|
||||||
sound={sound}
|
|
||||||
onAddSound={handleAddSoundToPlaylist}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</SortableContext>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
/* Normal Mode - Table View */
|
|
||||||
soundsLoading ? (
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
<Skeleton key={i} className="h-12 w-full" />
|
<Skeleton key={i} className="h-12 w-full" />
|
||||||
@@ -1055,26 +689,11 @@ export function PlaylistEditPage() {
|
|||||||
</Table>
|
</Table>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Drag Overlay */}
|
|
||||||
<DragOverlay>
|
|
||||||
{draggedSound && (
|
|
||||||
<div className="flex items-center gap-3 p-3 bg-background border rounded-lg shadow-lg">
|
|
||||||
<Music className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="font-medium truncate">{draggedSound.name}</div>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{formatDuration(draggedSound.duration || 0)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</DragOverlay>
|
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user