/** * useTransitionPreview Hook * * Manages transition video preview state in the constructor. * Used to preview forward and reverse transitions before navigation. */ import { useState, useCallback, useMemo } from 'react'; import type { TransitionPreviewState } from '../types/presentation'; export type { TransitionPreviewState }; /** * Navigation element with transition configuration */ interface TransitionElement { type: string; label?: string; navLabel?: string; transitionVideoUrl?: string; transitionReverseMode?: 'auto_reverse' | 'separate_video'; reverseVideoUrl?: string; transitionDurationSec?: number; } interface UseTransitionPreviewOptions { /** Function to check if element type is a navigation type */ isNavigationElementType: (type: string) => boolean; /** Callback when error occurs (no video configured, etc.) */ onError?: (message: string) => void; } interface UseTransitionPreviewResult { /** Current preview state (null if not previewing) */ preview: TransitionPreviewState | null; /** Pending navigation page ID (used for actual navigation after preview) */ pendingPageId: string; /** Open transition preview for an element */ openPreview: ( element: TransitionElement, direction: 'forward' | 'back', ) => void; /** Open transition preview with a specific target page */ openPreviewWithTarget: ( element: TransitionElement, direction: 'forward' | 'back', targetPageId: string, ) => void; /** Close the preview */ closePreview: () => void; /** Whether a preview is currently active */ isActive: boolean; } /** * Hook for managing transition preview state. * Handles opening previews for forward and back navigation. * * @example * const { * preview, * pendingPageId, * openPreview, * closePreview, * isActive, * } = useTransitionPreview({ * isNavigationElementType, * onError: setErrorMessage, * }); * * // Open preview when clicking element * const handleClick = () => { * openPreviewWithTarget(element, 'forward', targetPage.id); * }; * * // Use with useTransitionPlayback hook * useTransitionPlayback({ * transition: preview, * onComplete: (targetId) => { * switchToPage(targetId); * closePreview(); * }, * }); */ export function useTransitionPreview({ isNavigationElementType, onError, }: UseTransitionPreviewOptions): UseTransitionPreviewResult { const [preview, setPreview] = useState(null); const [pendingPageId, setPendingPageId] = useState(''); const openPreview = useCallback( (element: TransitionElement, direction: 'forward' | 'back') => { if (!isNavigationElementType(element.type)) { return; } // Check if transition video is configured if (!element.transitionVideoUrl) { onError?.( 'Select transition video asset to preview transition playback.', ); return; } // For back navigation, check if reversed video is available // (either manually uploaded or backend-generated) if (direction === 'back' && !element.reverseVideoUrl) { onError?.( 'Reversed video not available. Save the page to generate it.', ); return; } // Determine reverse mode: // - 'none' for forward navigation // - 'separate' for back navigation (uses pre-reversed video) const reverseMode = direction === 'back' ? 'separate' : 'none'; const previewState: TransitionPreviewState = { videoUrl: element.transitionVideoUrl, storageKey: element.transitionVideoUrl, // Raw storage path for cache lookup reverseMode, reverseVideoUrl: element.reverseVideoUrl, reverseStorageKey: element.reverseVideoUrl, durationSec: element.transitionDurationSec, title: `${element.navLabel || element.label || 'Transition'} ยท ${direction}`, isBack: direction === 'back', // Track for history management }; setPreview(previewState); }, [isNavigationElementType, onError], ); const openPreviewWithTarget = useCallback( ( element: TransitionElement, direction: 'forward' | 'back', targetPageId: string, ) => { setPendingPageId(targetPageId); openPreview(element, direction); }, [openPreview], ); const closePreview = useCallback(() => { setPreview(null); setPendingPageId(''); }, []); const isActive = useMemo(() => preview !== null, [preview]); return { preview, pendingPageId, openPreview, openPreviewWithTarget, closePreview, isActive, }; } /** * Get navigation direction from element configuration. * Maps navType and element type to forward/back. */ export function getTransitionDirection(element: { type: string; navType?: 'forward' | 'back'; }): 'forward' | 'back' { if (element.navType === 'back') return 'back'; if (element.navType === 'forward') return 'forward'; // Infer from element type if (element.type === 'navigation_prev') return 'back'; return 'forward'; } export default useTransitionPreview;