Advertisement

← vibesdk

src/features/presentation/hooks/use-presentation-sync.ts

4.2 KB Download file
import { useState, useEffect, useRef } from 'react';
import type { FileType } from '@/api-types';
import type { SlideInfo, PresentationTimestamps } from './types';

export function usePresentationSync(
    allFiles: FileType[] = [],
    slideFiles: SlideInfo[],
    slideDirectory: string,
    currentSlideIndex: number
) {
    const [timestamps, setTimestamps] = useState<PresentationTimestamps>({
        global: Date.now(),
        main: Date.now(),
        slides: {},
    });
    const [generatingSlides, setGeneratingSlides] = useState<Set<string>>(new Set());
    const fileHashes = useRef<Map<string, string>>(new Map());

    // Handle file system updates (new files, modified files)
    useEffect(() => {
        // Helper: Generate hash for file change detection
        const getFileHash = (file: FileType) =>
            `${file.filePath}-${file.fileContents?.length || 0}-${file.isGenerating ? 'gen' : 'ready'}`;

        // Helper: Categorize file types
        const isSlideFile = (path: string) => path.startsWith(`${slideDirectory}/`) && path.endsWith('.json');
        
        const isGlobalFile = (path: string) =>
            path.includes('slides-styles') ||
            path === 'public/_dev/Presentation.jsx' ||
            path.startsWith('public/_dev/runtime/') ||
            (path.endsWith('manifest.json') && generatingSlides.size === 0);

        // Find files that have actually changed (not just re-rendered)
        const changedFiles = allFiles.filter((file) => {
            if (!(isSlideFile(file.filePath) || isGlobalFile(file.filePath))) return false;
            
            // Skip files currently being generated/streamed
            // These are handled by the streaming event forwarding
            if (file.isGenerating || generatingSlides.has(file.filePath)) return false;

            const currentHash = getFileHash(file);
            const previousHash = fileHashes.current.get(file.filePath);

            if (currentHash !== previousHash) {
                fileHashes.current.set(file.filePath, currentHash);
                return true;
            }
            return false;
        });

        if (changedFiles.length === 0) return;

        // Categorize changes
        const hasGlobalChange = changedFiles.some((f) => isGlobalFile(f.filePath));
        const updatedSlideIndices = new Map<number, number>();

        changedFiles.forEach((file) => {
            if (isSlideFile(file.filePath)) {
                const slide = slideFiles.find((s) => s.filePath === file.filePath);
                if (slide) {
                    updatedSlideIndices.set(slide.index, Date.now());
                }
            }
        });

        // Apply updates
        const now = Date.now();

        if (hasGlobalChange) {
            setTimestamps((prev) => ({
                ...prev,
                global: now,
                main: now,
            }));
        } else if (updatedSlideIndices.size > 0) {
            setTimestamps((prev) => ({
                ...prev,
                slides: {
                    ...prev.slides,
                    ...Object.fromEntries(updatedSlideIndices),
                },
                // Refresh main iframe if current slide was updated
                main: updatedSlideIndices.has(currentSlideIndex) ? now : prev.main,
            }));
        }
    }, [allFiles, slideFiles, currentSlideIndex, slideDirectory, generatingSlides]);

    // Clean up file hashes for files that no longer exist in allFiles
    useEffect(() => {
        // Only clean up hashes for files that don't exist in allFiles anymore
        // This is more conservative than checking against slideFiles/manifest,
        // which may not include newly created slides that haven't been added to manifest yet
        const allFilePaths = new Set(allFiles.map((f) => f.filePath));
        Array.from(fileHashes.current.keys()).forEach((path) => {
            if (!allFilePaths.has(path)) {
                fileHashes.current.delete(path);
            }
        });
    }, [allFiles]);

    return {
        timestamps,
        setTimestamps,
        generatingSlides,
        setGeneratingSlides
    };
}