Add slide number to presentation mode route

- Update route to /presentations/:presentationId/present/:slideNumber
- Add URL-synced navigation with goToSlide function
- Update keyboard navigation to maintain URL state
- Enable deep linking and browser navigation for slides
- Start presentation from slide 1 in presentations list
- Present button in viewer uses current slide number

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-08-21 09:29:24 -05:00
parent 3864de28e7
commit 7c7c8235f3
4 changed files with 37 additions and 15 deletions

View File

@ -26,7 +26,7 @@ function App() {
<Route path="/presentations/new" element={<NewPresentationPage />} /> <Route path="/presentations/new" element={<NewPresentationPage />} />
<Route path="/presentations/:presentationId/edit/slides/:slideNumber" element={<PresentationEditor />} /> <Route path="/presentations/:presentationId/edit/slides/:slideNumber" element={<PresentationEditor />} />
<Route path="/presentations/:presentationId/view/slides/:slideNumber" element={<PresentationViewer />} /> <Route path="/presentations/:presentationId/view/slides/:slideNumber" element={<PresentationViewer />} />
<Route path="/presentations/:presentationId/present" element={<PresentationMode />} /> <Route path="/presentations/:presentationId/present/:slideNumber" element={<PresentationMode />} />
<Route path="/presentations/:presentationId/slide/:slideId/edit" element={<SlideEditor />} /> <Route path="/presentations/:presentationId/slide/:slideId/edit" element={<SlideEditor />} />
<Route path="/themes" element={<ThemeBrowser />} /> <Route path="/themes" element={<ThemeBrowser />} />
<Route path="/themes/:themeId" element={<ThemeDetailPage />} /> <Route path="/themes/:themeId" element={<ThemeDetailPage />} />

View File

@ -10,15 +10,29 @@ import { loggers } from '../../utils/logger.ts';
import './PresentationMode.css'; import './PresentationMode.css';
export const PresentationMode: React.FC = () => { export const PresentationMode: React.FC = () => {
const { presentationId } = useParams<{ presentationId: string }>(); const { presentationId, slideNumber } = useParams<{
presentationId: string;
slideNumber: string;
}>();
const navigate = useNavigate(); const navigate = useNavigate();
const [presentation, setPresentation] = useState<Presentation | null>(null); const [presentation, setPresentation] = useState<Presentation | null>(null);
const [theme, setTheme] = useState<Theme | null>(null); const [theme, setTheme] = useState<Theme | null>(null);
const [currentSlideIndex, setCurrentSlideIndex] = useState(0); const [currentSlideIndex, setCurrentSlideIndex] = useState(
slideNumber ? parseInt(slideNumber, 10) - 1 : 0
);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// Navigate to specific slide and update URL
const goToSlide = (slideIndex: number) => {
if (!presentation) return;
const clampedIndex = Math.max(0, Math.min(slideIndex, presentation.slides.length - 1));
setCurrentSlideIndex(clampedIndex);
navigate(`/presentations/${presentationId}/present/${clampedIndex + 1}`, { replace: true });
};
// Keyboard navigation handler // Keyboard navigation handler
const handleKeyPress = useCallback((event: KeyboardEvent) => { const handleKeyPress = useCallback((event: KeyboardEvent) => {
if (!presentation || presentation.slides.length === 0) return; if (!presentation || presentation.slides.length === 0) return;
@ -28,24 +42,22 @@ export const PresentationMode: React.FC = () => {
case 'Space': case 'Space':
case 'Enter': case 'Enter':
event.preventDefault(); event.preventDefault();
setCurrentSlideIndex(prev => goToSlide(currentSlideIndex + 1);
Math.min(prev + 1, presentation.slides.length - 1)
);
break; break;
case 'ArrowLeft': case 'ArrowLeft':
event.preventDefault(); event.preventDefault();
setCurrentSlideIndex(prev => Math.max(prev - 1, 0)); goToSlide(currentSlideIndex - 1);
break; break;
case 'Home': case 'Home':
event.preventDefault(); event.preventDefault();
setCurrentSlideIndex(0); goToSlide(0);
break; break;
case 'End': case 'End':
event.preventDefault(); event.preventDefault();
setCurrentSlideIndex(presentation.slides.length - 1); goToSlide(presentation.slides.length - 1);
break; break;
case 'Escape': case 'Escape':
@ -55,14 +67,24 @@ export const PresentationMode: React.FC = () => {
default: default:
// Handle number keys for direct slide navigation // Handle number keys for direct slide navigation
const slideNumber = parseInt(event.key); const slideNum = parseInt(event.key);
if (slideNumber >= 1 && slideNumber <= presentation.slides.length) { if (slideNum >= 1 && slideNum <= presentation.slides.length) {
event.preventDefault(); event.preventDefault();
setCurrentSlideIndex(slideNumber - 1); goToSlide(slideNum - 1);
} }
break; break;
} }
}, [presentation, navigate, presentationId]); }, [presentation, currentSlideIndex, goToSlide]);
// Sync current slide index with URL parameter
useEffect(() => {
if (slideNumber) {
const newIndex = parseInt(slideNumber, 10) - 1;
if (newIndex >= 0 && newIndex !== currentSlideIndex) {
setCurrentSlideIndex(newIndex);
}
}
}, [slideNumber]);
// Set up keyboard listeners // Set up keyboard listeners
useEffect(() => { useEffect(() => {

View File

@ -86,7 +86,7 @@ export const PresentationViewer: React.FC = () => {
const enterPresentationMode = () => { const enterPresentationMode = () => {
if (!presentation) return; if (!presentation) return;
navigate(`/presentations/${presentationId}/present`); navigate(`/presentations/${presentationId}/present/${currentSlideIndex + 1}`);
}; };
if (loading) { if (loading) {

View File

@ -213,7 +213,7 @@ export const PresentationsList: React.FC = () => {
<button <button
type="button" type="button"
className="card-action secondary" className="card-action secondary"
onClick={() => navigate(`/presentations/${presentation.metadata.id}/present`)} onClick={() => navigate(`/presentations/${presentation.metadata.id}/present/1`)}
> >
Present Present
</button> </button>