- Add reorderSlides function to useSlideOperations hook - Update SlidesSidebar to handle drag-and-drop events - Make SlideThumbnail components draggable with visual feedback - Add CSS styles for drag states (dragged, drag-over, drag handle) - Implement proper slide order management and persistence - Add visual indicators for drag operations (opacity, transform, borders) - Disable drag operations when saving to prevent conflicts This completes the slide reordering feature from the user flows document.
183 lines
6.0 KiB
TypeScript
183 lines
6.0 KiB
TypeScript
import { useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import type { Presentation, Slide } from '../types/presentation.ts';
|
|
import { updatePresentation } from '../utils/presentationStorage.ts';
|
|
import { loggers } from '../utils/logger.ts';
|
|
|
|
interface UseSlideOperationsProps {
|
|
presentation: Presentation | null;
|
|
presentationId: string;
|
|
onPresentationUpdate: (presentation: Presentation) => void;
|
|
onError: (error: string) => void;
|
|
confirmDelete: (message: string) => Promise<boolean>;
|
|
}
|
|
|
|
export const useSlideOperations = ({
|
|
presentation,
|
|
presentationId,
|
|
onPresentationUpdate,
|
|
onError,
|
|
confirmDelete
|
|
}: UseSlideOperationsProps) => {
|
|
const navigate = useNavigate();
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const duplicateSlide = async (slideIndex: number) => {
|
|
if (!presentation) return;
|
|
|
|
const slideToDuplicate = presentation.slides[slideIndex];
|
|
if (!slideToDuplicate) return;
|
|
|
|
try {
|
|
setSaving(true);
|
|
onError('');
|
|
|
|
// Create a duplicate slide with new ID
|
|
const duplicatedSlide: Slide = {
|
|
...slideToDuplicate,
|
|
id: `slide_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
order: slideIndex + 1 // Insert right after the original
|
|
};
|
|
|
|
// Create updated presentation with the duplicated slide
|
|
const updatedPresentation = { ...presentation };
|
|
const newSlides = [...presentation.slides];
|
|
|
|
// Insert the duplicated slide after the original
|
|
newSlides.splice(slideIndex + 1, 0, duplicatedSlide);
|
|
|
|
// Update slide order for all slides after the insertion point
|
|
newSlides.forEach((slide, index) => {
|
|
slide.order = index;
|
|
});
|
|
|
|
updatedPresentation.slides = newSlides;
|
|
|
|
// Save the updated presentation
|
|
await updatePresentation(updatedPresentation);
|
|
|
|
// Update local state
|
|
onPresentationUpdate(updatedPresentation);
|
|
|
|
// Navigate to the duplicated slide
|
|
const newSlideNumber = slideIndex + 2; // +2 because we inserted after and slide numbers are 1-based
|
|
navigate(`/presentations/${presentationId}/edit/slides/${newSlideNumber}`);
|
|
|
|
} catch (err) {
|
|
loggers.presentation.error('Failed to duplicate slide', err instanceof Error ? err : new Error(String(err)));
|
|
onError(err instanceof Error ? err.message : 'Failed to duplicate slide');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const deleteSlide = async (slideIndex: number) => {
|
|
if (!presentation) return;
|
|
|
|
const slideToDelete = presentation.slides[slideIndex];
|
|
if (!slideToDelete) return;
|
|
|
|
const slideNumber = slideIndex + 1;
|
|
const totalSlides = presentation.slides.length;
|
|
let confirmMessage = `Are you sure you want to delete slide ${slideNumber}?`;
|
|
|
|
if (totalSlides === 1) {
|
|
confirmMessage = `Are you sure you want to delete the only slide in this presentation? The presentation will be empty after deletion.`;
|
|
} else {
|
|
confirmMessage += ` This will remove the slide and renumber all subsequent slides. This action cannot be undone.`;
|
|
}
|
|
|
|
const confirmed = await confirmDelete(confirmMessage);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setSaving(true);
|
|
onError('');
|
|
|
|
// Create updated presentation with the slide removed
|
|
const updatedPresentation = { ...presentation };
|
|
updatedPresentation.slides = presentation.slides.filter((_, index) => index !== slideIndex);
|
|
|
|
// Update slide order for remaining slides
|
|
updatedPresentation.slides.forEach((slide, index) => {
|
|
slide.order = index;
|
|
});
|
|
|
|
// Save the updated presentation
|
|
await updatePresentation(updatedPresentation);
|
|
|
|
// Update local state
|
|
onPresentationUpdate(updatedPresentation);
|
|
|
|
// Handle navigation after deletion
|
|
const totalSlides = updatedPresentation.slides.length;
|
|
if (totalSlides === 0) {
|
|
// No slides left, stay on editor main view
|
|
navigate(`/presentations/${presentationId}/edit`);
|
|
} else {
|
|
// Navigate to appropriate slide
|
|
let newSlideIndex = slideIndex;
|
|
if (slideIndex >= totalSlides) {
|
|
// If we deleted the last slide, go to the new last slide
|
|
newSlideIndex = totalSlides - 1;
|
|
}
|
|
// Navigate to the adjusted slide number
|
|
const slideNumber = newSlideIndex + 1;
|
|
navigate(`/presentations/${presentationId}/edit/slides/${slideNumber}`);
|
|
}
|
|
|
|
} catch (err) {
|
|
loggers.presentation.error('Failed to delete slide', err instanceof Error ? err : new Error(String(err)));
|
|
onError(err instanceof Error ? err.message : 'Failed to delete slide');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const reorderSlides = async (fromIndex: number, toIndex: number) => {
|
|
if (!presentation || fromIndex === toIndex) return;
|
|
|
|
try {
|
|
setSaving(true);
|
|
onError('');
|
|
|
|
// Create updated presentation with reordered slides
|
|
const updatedPresentation = { ...presentation };
|
|
const newSlides = [...presentation.slides];
|
|
|
|
// Remove the slide from its current position
|
|
const [movedSlide] = newSlides.splice(fromIndex, 1);
|
|
|
|
// Insert it at the new position
|
|
newSlides.splice(toIndex, 0, movedSlide);
|
|
|
|
// Update slide order for all slides
|
|
newSlides.forEach((slide, index) => {
|
|
slide.order = index;
|
|
});
|
|
|
|
updatedPresentation.slides = newSlides;
|
|
|
|
// Save the updated presentation
|
|
await updatePresentation(updatedPresentation);
|
|
|
|
// Update local state
|
|
onPresentationUpdate(updatedPresentation);
|
|
|
|
} catch (err) {
|
|
loggers.presentation.error('Failed to reorder slides', err instanceof Error ? err : new Error(String(err)));
|
|
onError(err instanceof Error ? err.message : 'Failed to reorder slides');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
duplicateSlide,
|
|
deleteSlide,
|
|
reorderSlides,
|
|
saving
|
|
};
|
|
}; |