From e29d364fd33bef9843e9f8d284f78027a5563520 Mon Sep 17 00:00:00 2001 From: Yiannis Christodoulou Date: Fri, 19 Sep 2025 12:56:45 +0300 Subject: [PATCH] feat: Fix chapters (rename text/name to chapterTitle) and fetch/post the correct object --- .../chapters-editor/client/src/App.tsx | 2 - .../client/src/components/ClipSegments.tsx | 8 +- .../src/components/TimelineControls.tsx | 428 +++--------------- .../client/src/hooks/useVideoChapters.tsx | 52 +-- .../chapters-editor/client/src/index.css | 9 - .../client/src/lib/videoUtils.ts | 29 +- .../client/src/services/videoApi.ts | 40 +- .../client/src/styles/ClipSegments.css | 10 - 8 files changed, 104 insertions(+), 474 deletions(-) diff --git a/frontend-tools/chapters-editor/client/src/App.tsx b/frontend-tools/chapters-editor/client/src/App.tsx index 905e697c..0cecab7e 100644 --- a/frontend-tools/chapters-editor/client/src/App.tsx +++ b/frontend-tools/chapters-editor/client/src/App.tsx @@ -15,7 +15,6 @@ const App = () => { isPlaying, setIsPlaying, isMuted, - thumbnails, trimStart, trimEnd, splitPoints, @@ -244,7 +243,6 @@ const App = () => { { className={`segment-item ${getSegmentColorClass(index)} ${selectedSegmentId === segment.id ? 'selected' : ''}`} >
-
{segment.chapterTitle ? ( diff --git a/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx b/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx index b90e0ac3..7236f7d7 100644 --- a/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx +++ b/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx @@ -1,6 +1,6 @@ import { useRef, useEffect, useState, useCallback } from 'react'; import { formatTime, formatDetailedTime } from '../lib/timeUtils'; -import { generateThumbnail, generateSolidColor } from '../lib/videoUtils'; +import { generateSolidColor } from '../lib/videoUtils'; import { Segment } from './ClipSegments'; import Modal from './Modal'; import { autoSaveVideo } from '../services/videoApi'; @@ -38,7 +38,7 @@ interface TimelineControlsProps { selectedSegmentId?: number | null; onSelectedSegmentChange?: (segmentId: number | null) => void; onSegmentUpdate?: (segmentId: number, updates: Partial) => void; - onChapterSave?: (chapters: { name: string; from: string; to: string }[]) => void; + onChapterSave?: (chapters: { chapterTitle: string; from: string; to: string }[]) => void; onTrimStartChange: (time: number) => void; onTrimEndChange: (time: number) => void; onZoomChange: (level: number) => void; @@ -104,7 +104,6 @@ const constrainTooltipPosition = (positionPercent: number) => { const TimelineControls = ({ currentTime, duration, - thumbnails, trimStart, trimEnd, splitPoints, @@ -167,27 +166,42 @@ const TimelineControls = ({ const [isAutoSaving, setIsAutoSaving] = useState(false); const autoSaveTimerRef = useRef(null); const clipSegmentsRef = useRef(clipSegments); + + // Redirect timer refs + const countdownIntervalRef = useRef(null); + const redirectTimeoutRef = useRef(null); // Keep clipSegmentsRef updated useEffect(() => { clipSegmentsRef.current = clipSegments; }, [clipSegments]); + // Function to cancel redirect timers + const cancelRedirect = useCallback(() => { + if (countdownIntervalRef.current) { + clearInterval(countdownIntervalRef.current); + countdownIntervalRef.current = null; + } + if (redirectTimeoutRef.current) { + clearTimeout(redirectTimeoutRef.current); + redirectTimeoutRef.current = null; + } + logger.debug('Redirect cancelled by user'); + }, []); + // Auto-save function const performAutoSave = useCallback(async () => { try { setIsAutoSaving(true); // Format segments data for API request - use ref to get latest segments - const segments = clipSegmentsRef.current.map((segment) => ({ - startTime: formatDetailedTime(segment.startTime), - endTime: formatDetailedTime(segment.endTime), - name: segment.name, - chapterTitle: segment.chapterTitle, - text: segment.chapterTitle, + const chapters = clipSegmentsRef.current.map((chapter) => ({ + startTime: formatDetailedTime(chapter.startTime), + endTime: formatDetailedTime(chapter.endTime), + chapterTitle: chapter.chapterTitle, })); - logger.debug('segments', segments); + logger.debug('chapters', chapters); const mediaId = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.mediaId) || null; // For testing, use '1234' if no mediaId is available @@ -195,20 +209,22 @@ const TimelineControls = ({ logger.debug('mediaId', finalMediaId); - if (!finalMediaId || segments.length === 0) { + if (!finalMediaId || chapters.length === 0) { logger.debug('No mediaId or segments, skipping auto-save'); setIsAutoSaving(false); return; } - logger.debug('Auto-saving segments:', { mediaId: finalMediaId, segments }); + logger.debug('Auto-saving segments:', { mediaId: finalMediaId, chapters }); - const response = await autoSaveVideo(finalMediaId, { segments }); + const response = await autoSaveVideo(finalMediaId, { chapters }); - if (response.success) { + console.log('response autoSaveVideo edw', response); + + if (response.success === true) { logger.debug('Auto-save successful'); // Format the timestamp for display - const date = new Date(response.timestamp); + const date = new Date(response.updated_at || new Date().toISOString()); const formattedTime = date .toLocaleString('en-US', { year: 'numeric', @@ -224,10 +240,10 @@ const TimelineControls = ({ setLastAutoSaveTime(formattedTime); logger.debug('Auto-save successful:', formattedTime); } else { - logger.error('Auto-save failed:', response.error); + logger.error('Auto-save failed: (TimelineControls.tsx)'); } } catch (error) { - logger.error('Auto-save error:', error); + logger.error('Auto-save error: (TimelineControls.tsx)', error); } finally { setIsAutoSaving(false); } @@ -255,6 +271,7 @@ const TimelineControls = ({ // Update editing title when selected segment changes useEffect(() => { + console.log('edw selectedSegment', selectedSegment); if (selectedSegment) { setEditingChapterTitle(selectedSegment.chapterTitle || ''); } else { @@ -274,7 +291,7 @@ const TimelineControls = ({ }; // Handle save chapters - const handleSaveChapters = () => { + /* const handleSaveChapters = () => { if (!onChapterSave) return; // Convert segments to chapter format @@ -286,10 +303,10 @@ const TimelineControls = ({ onChapterSave(chapters); setChapterHasUnsavedChanges(false); - }; + }; */ // Helper function for time adjustment buttons to maintain playback state - const handleTimeAdjustment = (offsetSeconds: number) => (e: React.MouseEvent) => { + /* const handleTimeAdjustment = (offsetSeconds: number) => (e: React.MouseEvent) => { e.stopPropagation(); // Calculate new time based on offset (positive or negative) @@ -313,7 +330,7 @@ const TimelineControls = ({ videoRef.current.play(); setIsPlayingSegment(true); } - }; + }; */ // Enhanced helper for continuous time adjustment when button is held down const handleContinuousTimeAdjustment = (offsetSeconds: number) => { @@ -484,7 +501,7 @@ const TimelineControls = ({ const chapters = clipSegments .filter((segment) => segment.chapterTitle && segment.chapterTitle.trim()) .map((segment) => ({ - name: segment.chapterTitle || `Chapter ${segment.id}`, + chapterTitle: segment.chapterTitle || `Chapter ${segment.id}`, from: formatDetailedTime(segment.startTime), to: formatDetailedTime(segment.endTime), })); @@ -969,12 +986,14 @@ const TimelineControls = ({ const loadSavedSegments = () => { // Get savedSegments directly from window.MEDIA_DATA let savedData = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.chapters) || null; + console.log('MEDIA_DATA edw1', (window as any).MEDIA_DATA); + console.log('savedData edw1', savedData); // If no saved segments, use default segments - if (!savedData) { + /* if (!savedData) { logger.debug('No saved segments found in MEDIA_DATA, using default segments'); savedData = { - segments: [ + chapters: [ { startTime: '00:00:00.000', endTime: '00:00:10.000', @@ -993,22 +1012,19 @@ const TimelineControls = ({ ], updated_at: '2025-06-24 14:59:14', }; - } - - logger.debug('Loading saved segments:', savedData); + } */ try { - if (savedData && savedData.segments && savedData.segments.length > 0) { + if (savedData && savedData.chapters && savedData.chapters.length > 0) { logger.debug('Found saved segments:', savedData); + console.log('savedData edw', savedData); // Convert the saved segments to the format expected by the component - const convertedSegments: Segment[] = savedData.segments.map((seg: any, index: number) => ({ + const convertedSegments: Segment[] = savedData.chapters.map((seg: any , index: number) => ({ id: Date.now() + index, // Generate unique IDs - name: seg.name || `Segment ${index + 1}`, + chapterTitle: seg.chapterTitle || `Chapter ${index + 1}`, startTime: parseTimeString(seg.startTime), endTime: parseTimeString(seg.endTime), - thumbnail: '', - chapterTitle: seg.chapterTitle || '', // Preserve chapter title from saved data })); // Dispatch event to update segments @@ -1188,34 +1204,6 @@ const TimelineControls = ({ }; }, [duration, trimStart, trimEnd, onTrimStartChange, onTrimEndChange]); - // Render solid color backgrounds evenly spread across timeline - const renderThumbnails = () => { - // Create thumbnail sections even if we don't have actual thumbnail data - const numSections = thumbnails.length || 10; // Default to 10 sections if no thumbnails - - return Array.from({ length: numSections }).map((_, index) => { - const segmentDuration = duration / numSections; - const segmentStartTime = index * segmentDuration; - const segmentEndTime = segmentStartTime + segmentDuration; - const midpointTime = (segmentStartTime + segmentEndTime) / 2; - - // Get a solid color based on the segment position - const backgroundColor = generateSolidColor(midpointTime, duration); - - return ( -
- ); - }); - }; - // Render split points const renderSplitPoints = () => { return splitPoints.map((point, index) => { @@ -1443,7 +1431,7 @@ const TimelineControls = ({ } // Only process tooltip display if clicked on the timeline background or thumbnails, not on other UI elements - if (e.target === timelineRef.current || (e.target as HTMLElement).classList.contains('timeline-thumbnail')) { + if (e.target === timelineRef.current) { // Check if there's a segment at the clicked position if (segmentAtClickedTime) { setSelectedSegmentId(segmentAtClickedTime.id); @@ -1549,15 +1537,6 @@ const TimelineControls = ({ const position = Math.max(0, Math.min(1, (clientX - updatedTimelineRect.left) / updatedTimelineRect.width)); const newTime = position * duration; - // Create a temporary segment with the current drag position to check against - const draggedSegment = { - id: segmentId, - startTime: isLeft ? newTime : originalStartTime, - endTime: isLeft ? originalEndTime : newTime, - name: '', - thumbnail: '', - }; - // Check if the current marker position intersects with where the segment will be const currentSegmentStart = isLeft ? newTime : originalStartTime; const currentSegmentEnd = isLeft ? originalEndTime : newTime; @@ -2121,10 +2100,9 @@ const TimelineControls = ({ // Create a full video segment const fullVideoSegment: Segment = { id: Date.now(), - name: 'Full Video', + chapterTitle: 'Full Video', startTime: 0, endTime: duration, - thumbnail: '', }; // Create and dispatch the update event to replace all segments with the full video segment @@ -2522,15 +2500,15 @@ const TimelineControls = ({ // Add a useEffect for auto-redirection useEffect(() => { - let countdownInterval: NodeJS.Timeout; - let redirectTimeout: NodeJS.Timeout; + // Clear any existing timers first + cancelRedirect(); if (showSuccessModal && redirectUrl) { // Start countdown timer let secondsLeft = 10; // Update the countdown every second - countdownInterval = setInterval(() => { + countdownIntervalRef.current = setInterval(() => { secondsLeft--; const countdownElement = document.querySelector('.countdown'); if (countdownElement) { @@ -2538,32 +2516,28 @@ const TimelineControls = ({ } if (secondsLeft <= 0) { - clearInterval(countdownInterval); + if (countdownIntervalRef.current) { + clearInterval(countdownIntervalRef.current); + countdownIntervalRef.current = null; + } } }, 1000); // Set redirect timeout - redirectTimeout = setTimeout(() => { + redirectTimeoutRef.current = setTimeout(() => { // Redirect to the URL logger.debug('Automatically redirecting to:', redirectUrl); window.location.href = redirectUrl; }, 10000); // 10 seconds } - // Cleanup on unmount or when success modal closes + // Cleanup on unmount return () => { - if (countdownInterval) clearInterval(countdownInterval); - if (redirectTimeout) clearTimeout(redirectTimeout); + cancelRedirect(); }; - }, [showSuccessModal, redirectUrl]); + }, [showSuccessModal, redirectUrl, cancelRedirect]); - // Effect to handle redirect after success modal is closed - useEffect(() => { - if (!showSuccessModal && redirectUrl) { - logger.debug('Redirecting to:', redirectUrl); - window.location.href = redirectUrl; - } - }, [redirectUrl, saveType, showSuccessModal]); + // Note: Removed the conflicting redirect effect - redirect is now handled by cancelRedirect function return (
@@ -2665,9 +2639,6 @@ const TimelineControls = ({ {/* Split Points */} {renderSplitPoints()} - {/* Thumbnails */} - {renderThumbnails()} - {/* Segment Tooltip */} {selectedSegmentId !== null && (
- {/* Play/Pause button for empty space */} - {/* */} - {/* Play/Pause button for empty space - Same as main play/pause button */}