Refactor annotation tools and implement quality choice annotation feature

- Updated the annotation label from "bewertet" to "annotiert" in the HomePage component.
- Removed the SingleChoiceView component and replaced it with a new QualityChoiceEntryView component for handling quality choice annotations.
- Introduced QualityChoiceView component to manage the flow of quality choice annotations, including navigation and progress tracking.
- Updated EditableExperimentHeader to default to 'quality-choice' annotation tool.
- Added unique index for annotations in the database schema to prevent duplicate entries.
- Enhanced WaveformPlayer to support audio playback state management.
This commit is contained in:
averel10
2026-03-20 10:01:20 +01:00
parent ea7c75c523
commit f26a0a7906
12 changed files with 1070 additions and 224 deletions

View File

@@ -1,13 +1,12 @@
'use client';
import { useEffect, useRef, useState, useCallback } from 'react';
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import { useAudio } from './AudioProvider';
interface WaveformPlayerProps {
src: string;
durationMs?: number | null;
/** When false the player should stop if it was playing */
isActive: boolean;
onPlay: () => void;
onPlay?: () => void;
onFullyPlayed: () => void;
}
@@ -16,10 +15,15 @@ const BAR_COUNT = 120;
export default function WaveformPlayer({
src,
durationMs,
isActive,
onPlay,
onFullyPlayed,
}: WaveformPlayerProps) {
const { currentAudioId, setCurrentAudio } = useAudio();
// Generate unique ID for this player instance
const playerId = useMemo(() => Math.random().toString(36).slice(2), []);
const isActive = currentAudioId === playerId;
const audioRef = useRef<HTMLAudioElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const rafRef = useRef<number>(0);
@@ -39,6 +43,21 @@ export default function WaveformPlayer({
}
}, [isActive, isPlaying]);
// Reset state when src changes and cancel playback
useEffect(() => {
setPeaks([]);
setCurrentTime(0);
setDuration(durationMs ? durationMs / 1000 : 0);
fullyPlayedRef.current = false;
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.currentTime = 0;
}
cancelAnimationFrame(rafRef.current);
setIsPlaying(false);
}, [src, durationMs]);
// Decode audio and generate waveform peaks
useEffect(() => {
let cancelled = false;
@@ -103,7 +122,8 @@ export default function WaveformPlayer({
const handlePlay = () => {
const audio = audioRef.current;
if (!audio) return;
onPlay();
setCurrentAudio(playerId);
onPlay?.();
audio.play();
setIsPlaying(true);
rafRef.current = requestAnimationFrame(tick);
@@ -122,7 +142,7 @@ export default function WaveformPlayer({
cancelAnimationFrame(rafRef.current);
if (!fullyPlayedRef.current) {
fullyPlayedRef.current = true;
onFullyPlayed();
onFullyPlayed?.();
}
};