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:
parent
3864de28e7
commit
7c7c8235f3
@ -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 />} />
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user