返回 presentation-ai
usePresentationSlides.tsx
根目录 / src / hooks / presentation / usePresentationSlides.tsx
1 "use client";
2
3 import {
4 KeyboardSensor,
5 PointerSensor,
6 useSensor,
7 useSensors,
8 type DragEndEvent,
9 } from "@dnd-kit/core";
10 import { arrayMove, sortableKeyboardCoordinates } from "@dnd-kit/sortable";
11 import { useCallback, useMemo } from "react";
12
13 import { type PlateSlide } from "@/components/notebook/presentation/utils/parser";
14 import { usePresentationState } from "@/states/presentation-state";
15
16 interface SlideWithId extends PlateSlide {
17 id: string;
18 }
19
20 export function usePresentationSlides() {
21 // Subscribe to slide IDs only for rendering - prevents re-render when content changes
22 // shallow ensures array comparison is shallow (same values = no re-render)
23 const slideIds = usePresentationState((s) =>
24 s.slides.map((slide) => slide.id),
25 );
26 // Keep full slides reference for drag operations only
27 const slides = usePresentationState((s) => s.slides);
28 const setSlides = usePresentationState((s) => s.setSlides);
29 const setCurrentSlideId = usePresentationState((s) => s.setCurrentSlideId);
30 const isPresenting = usePresentationState((s) => s.isPresenting);
31 const setIsReorderingSlides = usePresentationState(
32 (s) => s.setIsReorderingSlides,
33 );
34
35 // Configure DnD sensors
36 const sensors = useSensors(
37 useSensor(PointerSensor, {
38 activationConstraint: {
39 distance: 8, // 8px
40 },
41 }),
42 useSensor(KeyboardSensor, {
43 coordinateGetter: sortableKeyboardCoordinates,
44 }),
45 );
46
47 // Memoize slide IDs for stable reference
48 const items = useMemo(() => slideIds, [slideIds]);
49
50 // Handle drag end - needs full slides for reordering
51 const handleDragEnd = useCallback(
52 (event: DragEndEvent) => {
53 if (isPresenting) return; // Prevent drag when presenting
54
55 const { active, over } = event;
56
57 if (over && active.id !== over.id) {
58 const oldIndex = slides.findIndex(
59 (item: SlideWithId) => item.id === active.id,
60 );
61 const newIndex = slides.findIndex(
62 (item: SlideWithId) => item.id === over.id,
63 );
64 const newArray = arrayMove(slides, oldIndex, newIndex);
65 setSlides([...newArray]);
66 // Update current slide to the dragged slide's ID (not new position index)
67 setCurrentSlideId(active.id as string);
68 }
69 // Clear reordering flag at end
70 setIsReorderingSlides(false);
71 },
72 [slides, isPresenting, setSlides, setCurrentSlideId, setIsReorderingSlides],
73 );
74
75 // Expose a start handler to set reordering flag (to be wired in DndContext)
76 const handleDragStart = useCallback(() => {
77 if (isPresenting) return;
78 setIsReorderingSlides(true);
79 }, [isPresenting, setIsReorderingSlides]);
80
81 // Scroll to a slide by index
82 const scrollToSlide = useCallback((id: string) => {
83 // Target the slide wrapper instead of slide container
84 const slideElement = document.querySelector(`.slide-wrapper-${id}`);
85
86 if (slideElement) {
87 // Find the scrollable container
88 const scrollContainer = document.querySelector(".presentation-slides");
89
90 if (scrollContainer) {
91 // Calculate the scroll position
92 scrollContainer.scrollTo({
93 top: (slideElement as HTMLElement).offsetTop - 30, // Add a small offset for better visibility
94 behavior: "smooth",
95 });
96 }
97 }
98 }, []);
99
100 return {
101 items,
102 slideIds,
103 slides, // Still exposed for components that need full slide data
104 sensors,
105 isPresenting,
106 handleDragStart,
107 handleDragEnd,
108 scrollToSlide,
109 };
110 }
111
111 lines Plain Text