Compare commits

..

4 Commits

Author SHA1 Message Date
Hördle Bot
fa6f1097dd Bump version to 0.1.6.19 2025-12-05 21:48:00 +01:00
Hördle Bot
d2ec0119ce Fix waveform editor: show end marker for last segment and fix play full section stop functionality 2025-12-05 21:47:57 +01:00
Hördle Bot
8914c552cd Bump version to 0.1.6.18 2025-12-05 21:33:44 +01:00
Hördle Bot
d816422419 Update song list start time after saving changes in waveform editor 2025-12-05 21:33:41 +01:00
4 changed files with 97 additions and 56 deletions

View File

@@ -17,22 +17,27 @@ export default function SpecialEditorPage() {
const [special, setSpecial] = useState<CurateSpecial | null>(null); const [special, setSpecial] = useState<CurateSpecial | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { const fetchSpecial = async (showLoading = true) => {
const fetchSpecial = async () => { try {
try { if (showLoading) {
const res = await fetch(`/api/specials/${specialId}`); setLoading(true);
if (res.ok) { }
const data = await res.json(); const res = await fetch(`/api/specials/${specialId}`);
setSpecial(data); if (res.ok) {
} const data = await res.json();
} catch (error) { setSpecial(data);
console.error('Error fetching special:', error); }
} finally { } catch (error) {
console.error('Error fetching special:', error);
} finally {
if (showLoading) {
setLoading(false); setLoading(false);
} }
}; }
};
fetchSpecial(); useEffect(() => {
fetchSpecial(true);
}, [specialId]); }, [specialId]);
const handleSaveStartTime = async (songId: number, startTime: number) => { const handleSaveStartTime = async (songId: number, startTime: number) => {
@@ -46,6 +51,9 @@ export default function SpecialEditorPage() {
const errorText = await res.text().catch(() => res.statusText || 'Unknown error'); const errorText = await res.text().catch(() => res.statusText || 'Unknown error');
console.error('Error updating special song (admin):', res.status, errorText); console.error('Error updating special song (admin):', res.status, errorText);
throw new Error(`Failed to save start time: ${errorText}`); throw new Error(`Failed to save start time: ${errorText}`);
} else {
// Reload special data to update the start time in the song list
await fetchSpecial(false);
} }
}; };

View File

@@ -25,32 +25,36 @@ export default function CuratorSpecialEditorPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
useEffect(() => { const fetchSpecial = async (showLoading = true) => {
const fetchSpecial = async () => { try {
try { if (showLoading) {
setLoading(true); setLoading(true);
const res = await fetch(`/api/curator/specials/${specialId}`, { }
headers: getCuratorAuthHeaders(), const res = await fetch(`/api/curator/specials/${specialId}`, {
}); headers: getCuratorAuthHeaders(),
if (res.status === 403) { });
setError(t('specialForbidden')); if (res.status === 403) {
return; setError(t('specialForbidden'));
} return;
if (!res.ok) { }
setError('Failed to load special'); if (!res.ok) {
return;
}
const data = await res.json();
setSpecial(data);
} catch (e) {
setError('Failed to load special'); setError('Failed to load special');
} finally { return;
}
const data = await res.json();
setSpecial(data);
} catch (e) {
setError('Failed to load special');
} finally {
if (showLoading) {
setLoading(false); setLoading(false);
} }
}; }
};
useEffect(() => {
if (specialId) { if (specialId) {
fetchSpecial(); fetchSpecial(true);
} }
}, [specialId, t]); }, [specialId, t]);
@@ -67,6 +71,9 @@ export default function CuratorSpecialEditorPage() {
setError(t('specialForbidden')); setError(t('specialForbidden'));
} else if (!res.ok) { } else if (!res.ok) {
setError('Failed to save changes'); setError('Failed to save changes');
} else {
// Reload special data to update the start time in the song list
await fetchSpecial(false);
} }
}; };

View File

@@ -133,6 +133,24 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
cumulativeTime = step; cumulativeTime = step;
}); });
// Draw end marker for the last segment (at startTime + duration)
const endTime = startTime + duration;
const endPx = ((endTime - visibleStart) / visibleDuration) * width;
if (endPx >= 0 && endPx <= width) {
ctx.beginPath();
ctx.moveTo(endPx, 0);
ctx.lineTo(endPx, height);
ctx.stroke();
// Draw "End" label
ctx.setLineDash([]);
ctx.fillStyle = '#ef4444';
ctx.font = 'bold 12px sans-serif';
ctx.fillText('End', endPx + 3, 15);
ctx.setLineDash([5, 5]);
}
ctx.setLineDash([]); ctx.setLineDash([]);
// Draw hover preview (semi-transparent) // Draw hover preview (semi-transparent)
@@ -287,30 +305,38 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
const handlePlayFull = () => { const handlePlayFull = () => {
if (!audioBuffer || !audioContextRef.current) return; if (!audioBuffer || !audioContextRef.current) return;
if (isPlaying) { // If full playback is already playing, stop it
if (isPlaying && playingSegment === null) {
stopPlayback(); stopPlayback();
} else { return;
const source = audioContextRef.current.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContextRef.current.destination);
playbackStartTimeRef.current = audioContextRef.current.currentTime;
playbackOffsetRef.current = startTime;
source.start(0, startTime, duration);
sourceRef.current = source;
setIsPlaying(true);
setPlaybackPosition(startTime);
source.onended = () => {
setIsPlaying(false);
setPlaybackPosition(null);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
}
};
} }
// Stop any current playback (segment or full)
stopPlayback();
// Start full playback
const source = audioContextRef.current.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContextRef.current.destination);
playbackStartTimeRef.current = audioContextRef.current.currentTime;
playbackOffsetRef.current = startTime;
source.start(0, startTime, duration);
sourceRef.current = source;
setIsPlaying(true);
setPlayingSegment(null); // Ensure playingSegment is null for full playback
setPlaybackPosition(startTime);
source.onended = () => {
setIsPlaying(false);
setPlayingSegment(null);
setPlaybackPosition(null);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
}
};
}; };
const handleZoomIn = () => setZoom(prev => Math.min(prev * 1.5, 10)); const handleZoomIn = () => setZoom(prev => Math.min(prev * 1.5, 10));

View File

@@ -1,6 +1,6 @@
{ {
"name": "hoerdle", "name": "hoerdle",
"version": "0.1.6.17", "version": "0.1.6.19",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",