mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-05 23:18:53 -05:00
Support empty chapters state in editor
Allows users to clear all chapters, sending an empty array to the backend. Removes default segment creation when no chapters exist, updates UI and modal messaging for empty state, and ensures backend receives empty chapters when appropriate.
This commit is contained in:
parent
c071524cb9
commit
03872d0b25
@ -508,18 +508,17 @@ const TimelineControls = ({
|
||||
to: formatDetailedTime(segment.endTime),
|
||||
}));
|
||||
|
||||
if (chapters.length === 0) {
|
||||
setErrorMessage('No chapters with titles found');
|
||||
setShowErrorModal(true);
|
||||
setShowProcessingModal(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow saving even when no chapters exist (will send empty array)
|
||||
// Call the onChapterSave function if provided
|
||||
if (onChapterSave) {
|
||||
await onChapterSave(chapters);
|
||||
setShowProcessingModal(false);
|
||||
setSuccessMessage('Chapters saved successfully!');
|
||||
|
||||
if (chapters.length === 0) {
|
||||
setSuccessMessage('All chapters cleared successfully!');
|
||||
} else {
|
||||
setSuccessMessage('Chapters saved successfully!');
|
||||
}
|
||||
|
||||
// Set redirect URL to media page
|
||||
const mediaId = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.mediaId) || null;
|
||||
@ -1975,48 +1974,12 @@ const TimelineControls = ({
|
||||
// Check if this was the last segment before deletion
|
||||
const remainingSegments = clipSegments.filter((seg) => seg.id !== segmentId);
|
||||
if (remainingSegments.length === 0) {
|
||||
// Create a full video segment
|
||||
const fullVideoSegment: Segment = {
|
||||
id: Date.now(),
|
||||
chapterTitle: 'Full Video',
|
||||
startTime: 0,
|
||||
endTime: duration,
|
||||
};
|
||||
|
||||
// Create and dispatch the update event to replace all segments with the full video segment
|
||||
const updateEvent = new CustomEvent('update-segments', {
|
||||
detail: {
|
||||
segments: [fullVideoSegment],
|
||||
recordHistory: true,
|
||||
action: 'create_full_video_segment',
|
||||
},
|
||||
});
|
||||
document.dispatchEvent(updateEvent);
|
||||
|
||||
// Update UI to show the segment tooltip
|
||||
setSelectedSegmentId(fullVideoSegment.id);
|
||||
// Allow empty state - clear all UI state
|
||||
setSelectedSegmentId(null);
|
||||
setShowEmptySpaceTooltip(false);
|
||||
setClickedTime(currentTime);
|
||||
setDisplayTime(currentTime);
|
||||
setActiveSegment(fullVideoSegment);
|
||||
|
||||
// Calculate tooltip position at current time
|
||||
if (timelineRef.current) {
|
||||
const rect = timelineRef.current.getBoundingClientRect();
|
||||
const posPercent = (currentTime / duration) * 100;
|
||||
const xPosition = rect.left + rect.width * (posPercent / 100);
|
||||
|
||||
setTooltipPosition({
|
||||
x: xPosition,
|
||||
y: rect.top - 10,
|
||||
});
|
||||
|
||||
logger.debug('Created full video segment:', {
|
||||
id: fullVideoSegment.id,
|
||||
duration: formatDetailedTime(duration),
|
||||
currentPosition: formatDetailedTime(currentTime),
|
||||
});
|
||||
}
|
||||
setActiveSegment(null);
|
||||
|
||||
logger.debug('All segments deleted - entering empty state');
|
||||
} else if (selectedSegmentId === segmentId) {
|
||||
// Handle normal segment deletion
|
||||
const deletedSegment = clipSegments.find((seg) => seg.id === segmentId);
|
||||
@ -3984,10 +3947,13 @@ const TimelineControls = ({
|
||||
<button
|
||||
onClick={() => setShowSaveChaptersModal(true)}
|
||||
className="save-chapters-button"
|
||||
data-tooltip="Save chapters"
|
||||
disabled={clipSegments.filter((s) => s.chapterTitle && s.chapterTitle.trim()).length === 0}
|
||||
data-tooltip={clipSegments.filter((s) => s.chapterTitle && s.chapterTitle.trim()).length === 0
|
||||
? "Clear all chapters"
|
||||
: "Save chapters"}
|
||||
>
|
||||
Save Chapters
|
||||
{clipSegments.filter((s) => s.chapterTitle && s.chapterTitle.trim()).length === 0
|
||||
? 'Clear Chapters'
|
||||
: 'Save Chapters'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -4008,15 +3974,22 @@ const TimelineControls = ({
|
||||
className="modal-button modal-button-primary"
|
||||
onClick={handleSaveChaptersConfirm}
|
||||
>
|
||||
Save Chapters
|
||||
{clipSegments.filter((s) => s.chapterTitle && s.chapterTitle.trim()).length === 0
|
||||
? 'Clear Chapters'
|
||||
: 'Save Chapters'}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<p className="modal-message">
|
||||
Are you sure you want to save the chapters? This will save{' '}
|
||||
{clipSegments.filter((s) => s.chapterTitle && s.chapterTitle.trim()).length} chapters to the
|
||||
database.
|
||||
{(() => {
|
||||
const chaptersWithTitles = clipSegments.filter((s) => s.chapterTitle && s.chapterTitle.trim()).length;
|
||||
if (chaptersWithTitles === 0) {
|
||||
return "Are you sure you want to clear all chapters? This will remove all existing chapters from the database.";
|
||||
} else {
|
||||
return `Are you sure you want to save the chapters? This will save ${chaptersWithTitles} chapters to the database.`;
|
||||
}
|
||||
})()}
|
||||
</p>
|
||||
</Modal>
|
||||
|
||||
|
||||
@ -147,15 +147,8 @@ const useVideoChapters = () => {
|
||||
initialSegments.push(segment);
|
||||
}
|
||||
} else {
|
||||
|
||||
const initialSegment: Segment = {
|
||||
id: 1,
|
||||
chapterTitle: '',
|
||||
startTime: 0,
|
||||
endTime: video.duration,
|
||||
};
|
||||
|
||||
initialSegments = [initialSegment];
|
||||
// Start with empty state - no default segment
|
||||
initialSegments = [];
|
||||
}
|
||||
|
||||
// Initialize history state with the segments
|
||||
@ -274,24 +267,17 @@ const useVideoChapters = () => {
|
||||
|
||||
// Check if we now have duration and initialize if needed
|
||||
if (video.duration > 0 && clipSegments.length === 0) {
|
||||
logger.debug('Safari: Successfully initialized metadata, creating default segment');
|
||||
logger.debug('Safari: Successfully initialized metadata with empty state');
|
||||
|
||||
const defaultSegment: Segment = {
|
||||
id: 1,
|
||||
chapterTitle: '',
|
||||
startTime: 0,
|
||||
endTime: video.duration,
|
||||
};
|
||||
|
||||
setDuration(video.duration);
|
||||
setTrimEnd(video.duration);
|
||||
setClipSegments([defaultSegment]);
|
||||
setClipSegments([]);
|
||||
|
||||
const initialState: EditorState = {
|
||||
trimStart: 0,
|
||||
trimEnd: video.duration,
|
||||
splitPoints: [],
|
||||
clipSegments: [defaultSegment],
|
||||
clipSegments: [],
|
||||
};
|
||||
|
||||
setHistory([initialState]);
|
||||
@ -680,21 +666,13 @@ const useVideoChapters = () => {
|
||||
const newSegments = clipSegments.filter((segment) => segment.id !== segmentId);
|
||||
|
||||
if (newSegments.length !== clipSegments.length) {
|
||||
// If all segments are deleted, create a new full video segment
|
||||
if (newSegments.length === 0 && videoRef.current) {
|
||||
// Create a new default segment that spans the entire video
|
||||
const defaultSegment: Segment = {
|
||||
id: Date.now(),
|
||||
chapterTitle: 'Chapter 1',
|
||||
startTime: 0,
|
||||
endTime: videoRef.current.duration,
|
||||
};
|
||||
|
||||
if (newSegments.length === 0) {
|
||||
// Allow empty state - no segments
|
||||
setClipSegments([]);
|
||||
// Reset the trim points as well
|
||||
setTrimStart(0);
|
||||
setTrimEnd(videoRef.current.duration);
|
||||
setTrimEnd(videoRef.current?.duration || 0);
|
||||
setSplitPoints([]);
|
||||
setClipSegments([defaultSegment]);
|
||||
} else {
|
||||
// Renumber remaining segments to ensure proper chronological naming
|
||||
const renumberedSegments = renumberAllSegments(newSegments);
|
||||
@ -767,17 +745,8 @@ const useVideoChapters = () => {
|
||||
setTrimEnd(duration);
|
||||
setSplitPoints([]);
|
||||
|
||||
// Create a new default segment that spans the entire video
|
||||
if (!videoRef.current) return;
|
||||
|
||||
const defaultSegment: Segment = {
|
||||
id: Date.now(),
|
||||
chapterTitle: 'Chapter 1',
|
||||
startTime: 0,
|
||||
endTime: duration,
|
||||
};
|
||||
|
||||
setClipSegments([defaultSegment]);
|
||||
// Reset to empty state - no default segment
|
||||
setClipSegments([]);
|
||||
saveState('reset_all');
|
||||
};
|
||||
|
||||
@ -918,7 +887,7 @@ const useVideoChapters = () => {
|
||||
}
|
||||
|
||||
// Convert chapters to backend expected format and sort by start time
|
||||
const backendChapters = chapters
|
||||
let backendChapters = chapters
|
||||
.map((chapter) => ({
|
||||
startTime: chapter.from,
|
||||
endTime: chapter.to,
|
||||
@ -931,6 +900,21 @@ const useVideoChapters = () => {
|
||||
return aStartSeconds - bStartSeconds;
|
||||
});
|
||||
|
||||
// If there's only one chapter that spans the full video duration, send empty array
|
||||
if (backendChapters.length === 1) {
|
||||
const singleChapter = backendChapters[0];
|
||||
const startSeconds = parseTimeToSeconds(singleChapter.startTime);
|
||||
const endSeconds = parseTimeToSeconds(singleChapter.endTime);
|
||||
|
||||
// Check if this single chapter spans the entire video (within 0.1 second tolerance)
|
||||
const isFullVideoChapter = startSeconds <= 0.1 && Math.abs(endSeconds - duration) <= 0.1;
|
||||
|
||||
if (isFullVideoChapter) {
|
||||
logger.debug('Manual save: Single chapter spans full video - sending empty array');
|
||||
backendChapters = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the API request body
|
||||
const requestData = {
|
||||
chapters: backendChapters,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user