import { useRef, useState, useCallback, useEffect } from "react"; import { useParams, Link } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { api } from "../api/client"; import { listAnnotations, addReaction } from "../api/annotations"; import { useVersionWebSocket } from "../hooks/useWebSocket"; import { useWaveform } from "../hooks/useWaveform"; import type { Annotation } from "../api/annotations"; interface SongComment { id: string; song_id: string; body: string; author_id: string; author_name: string; author_avatar_url: string | null; created_at: string; timestamp: number | null; // Timestamp in seconds } export function SongPage() { const { bandId, songId } = useParams<{ bandId: string; songId: string }>(); const qc = useQueryClient(); const waveformRef = useRef(null); const [selectedVersionId, setSelectedVersionId] = useState(null); const [commentBody, setCommentBody] = useState(""); const { data: versions } = useQuery({ queryKey: ["versions", songId], queryFn: () => api.get<{ id: string; version_number: number; label: string | null; analysis_status: string }[]>(`/songs/${songId}/versions`), enabled: !!songId, }); const activeVersion = selectedVersionId ?? versions?.[0]?.id ?? null; const { data: annotations } = useQuery({ queryKey: ["annotations", activeVersion], queryFn: () => listAnnotations(activeVersion!), enabled: !!activeVersion, }); const { isPlaying, currentTime, play, pause, seekTo, addMarker, clearMarkers } = useWaveform(waveformRef, { url: activeVersion ? `/api/v1/versions/${activeVersion}/stream` : null, peaksUrl: activeVersion ? `/api/v1/versions/${activeVersion}/waveform` : null, }); // Add space key shortcut for play/pause useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.code === "Space") { e.preventDefault(); if (isPlaying) { pause(); } else { play(); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isPlaying, play, pause]); const { data: comments } = useQuery({ queryKey: ["comments", songId], queryFn: () => api.get(`/songs/${songId}/comments`), enabled: !!songId, }); // Scroll to comment when a marker is clicked const scrollToComment = (commentId: string) => { const commentElement = document.getElementById(`comment-${commentId}`); if (commentElement) { commentElement.scrollIntoView({ behavior: "smooth", block: "center" }); commentElement.style.backgroundColor = "var(--accent-bg)"; setTimeout(() => { commentElement.style.backgroundColor = "var(--bg-subtle)"; }, 2000); } }; useEffect(() => { if (comments) { console.log('Comments data:', comments); clearMarkers(); comments.forEach((comment) => { console.log('Processing comment:', comment.id, 'timestamp:', comment.timestamp, 'avatar:', comment.author_avatar_url); if (comment.timestamp !== undefined && comment.timestamp !== null) { console.log('Adding marker at time:', comment.timestamp); addMarker({ id: comment.id, time: comment.timestamp, onClick: () => scrollToComment(comment.id), icon: comment.author_avatar_url || "https://via.placeholder.com/20", }); } else { console.log('Skipping comment without timestamp:', comment.id); } }); } }, [comments, addMarker, clearMarkers]); const addCommentMutation = useMutation({ mutationFn: ({ body, timestamp }: { body: string; timestamp: number }) => { console.log('Creating comment with timestamp:', timestamp); return api.post(`/songs/${songId}/comments`, { body, timestamp }); }, onSuccess: () => { console.log('Comment created successfully'); qc.invalidateQueries({ queryKey: ["comments", songId] }); setCommentBody(""); }, onError: (error) => { console.error('Error creating comment:', error); } }); const deleteCommentMutation = useMutation({ mutationFn: (commentId: string) => api.delete(`/comments/${commentId}`), onSuccess: () => qc.invalidateQueries({ queryKey: ["comments", songId] }), }); const invalidateAnnotations = useCallback( () => qc.invalidateQueries({ queryKey: ["annotations", activeVersion] }), [qc, activeVersion] ); useVersionWebSocket(activeVersion, { "annotation.created": invalidateAnnotations, "annotation.updated": invalidateAnnotations, "annotation.deleted": invalidateAnnotations, "reaction.added": invalidateAnnotations, }); return (
← Back to Band {/* Version selector */}
{versions?.map((v) => ( ))}
{/* Waveform */}
{formatTime(currentTime)}
{/* Current Play Time Display */}
Current Time: {formatTime(currentTime)}
{/* Annotations */}
{annotations?.map((a) => ( ))}
{/* Comments */}

COMMENTS

{comments?.map((c) => (
{c.author_name}
{new Date(c.created_at).toLocaleString()}
{c.timestamp !== undefined && c.timestamp !== null && (
)}

{c.body}

))} {comments?.length === 0 && (

No comments yet. Be the first.

)}