"use client"; import React, { useEffect, useState, memo, useCallback, useRef, } from "react"; import { useDispatch, useSelector } from "react-redux"; import { addNewSlide } from "@/store/slices/presentationGeneration"; import { Loader2, Plus, X } from "lucide-react"; import { v4 as uuidv4 } from "uuid"; import { notify } from "@/components/ui/sonner"; import { usePathname } from "next/navigation"; import { trackEvent, MixpanelEvent } from "@/utils/mixpanel"; import { RootState } from "@/store/store"; import { TemplateV2HtmlSlidePreview } from "../../components/TemplateV2HtmlSlidePreview"; import { extractTemplateV2Layouts, type TemplateV2Layout, } from "@/components/slide-editor/importing/template-v2-import"; import { BLANK_SLIDE_LAYOUT_ID, BLANK_TEMPLATE_V2_LAYOUT, } from "../../_shared/blank-slide"; import { MAX_NUMBER_OF_SLIDES } from "@/utils/presentationLimits"; interface LayoutItemProps { layout: any; onSelect: (sampleData: any, layoutId: string) => void; } const PREVIEW_WIDTH = 1280; const PREVIEW_HEIGHT = 720; const EMPTY_SLIDE_LAYOUT = { layoutId: BLANK_SLIDE_LAYOUT_ID, layoutName: "Empty Slide", sampleData: BLANK_TEMPLATE_V2_LAYOUT, isEmptySlide: true, }; function createTemplateV2LayoutItem(layout: TemplateV2Layout, layoutIndex: number) { const layoutId = typeof layout.id === "string" && layout.id.trim() ? layout.id : `layout_${layoutIndex + 1}`; const description = typeof layout.description === "string" && layout.description.trim() ? layout.description : null; return { layoutId, layoutName: description ?? layoutId, sampleData: layout, v2Layout: layout, }; } function createTemplateV2PreviewSlide(layout: TemplateV2Layout, layoutId: string) { return { id: `template-v2-preview-${layoutId}`, content: {}, ui: layout, layout: layoutId, layout_group: "template-v2", }; } const LayoutItem = memo(({ layout, onSelect }: LayoutItemProps) => { const previewRef = useRef(null); const [scale, setScale] = useState(0.2); const { component: LayoutComponent, sampleData, layoutId, layoutName, v2Layout, isEmptySlide, } = layout; useEffect(() => { if (!previewRef.current) return; const previewElement = previewRef.current; const updateScale = () => { const nextScale = Math.min( previewElement.clientWidth / PREVIEW_WIDTH, previewElement.clientHeight / PREVIEW_HEIGHT ); setScale(nextScale || 0.2); }; const resizeObserver = new ResizeObserver(updateScale); resizeObserver.observe(previewElement); updateScale(); return () => resizeObserver.disconnect(); }, []); const selectLayout = () => onSelect(sampleData, layoutId); return (
{ if (event.key !== "Enter" && event.key !== " ") return; event.preventDefault(); selectLayout(); }} className="relative aspect-video cursor-pointer overflow-hidden rounded-md border border-[#E4E4EA] bg-white shadow-[0_1px_2px_rgba(16,24,40,0.04)] outline-none transition duration-200 hover:border-[#7C51F8] hover:shadow-[0_0_0_2px_rgba(124,81,248,0.18)] focus-visible:ring-2 focus-visible:ring-[#7C51F8]" >
{isEmptySlide ? (
Empty Slide
) : v2Layout ? ( ) : LayoutComponent ? ( ) : null}
); }); LayoutItem.displayName = "LayoutItem"; interface NewSlideV1Props { setShowNewSlideSelection: (show: boolean) => void; templateID: string; index: number; presentationId: string; onSlideAdded?: ( index: number, options?: { promptOverlaySlideId?: string; promptOverlayKind?: "blank" | "layout"; }, ) => void; } const NewSlideV1 = ({ setShowNewSlideSelection, templateID, index, presentationId, onSlideAdded, }: NewSlideV1Props) => { const dispatch = useDispatch(); const pathname = usePathname(); const presentationLayout = useSelector( (state: RootState) => state.presentationGeneration.presentationData?.layout ); const slideCount = useSelector((state: RootState) => { const slides = state.presentationGeneration.presentationData?.slides; return Array.isArray(slides) ? slides.length : 0; }); const [layouts, setLayouts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const handleEscape = (event: KeyboardEvent) => { if (event.key === "Escape") setShowNewSlideSelection(false); }; window.addEventListener("keydown", handleEscape); return () => window.removeEventListener("keydown", handleEscape); }, [setShowNewSlideSelection]); const handleNewSlide = useCallback( (sampleData: any, id: string) => { if (slideCount >= MAX_NUMBER_OF_SLIDES) { notify.warning( "Slide limit reached", `You can have up to ${MAX_NUMBER_OF_SLIDES} slides.` ); return; } try { const slideId = uuidv4(); const newSlide = { id: slideId, index, content: {}, ui: sampleData, layout_group: templateID, layout: id, presentation: presentationId, }; dispatch(addNewSlide({ slideData: newSlide, index })); onSlideAdded?.( index + 1, { promptOverlaySlideId: slideId, promptOverlayKind: id === BLANK_SLIDE_LAYOUT_ID ? "blank" : "layout", }, ); trackEvent(MixpanelEvent.Presentation_Slide_Added, { pathname, presentation_id: presentationId, inserted_after_index: index, template_id: templateID, layout_id: id, }); setShowNewSlideSelection(false); } catch (error: any) { console.error(error); notify.error("Could not add slide", "Something went wrong while adding the new slide."); } }, [ index, templateID, presentationId, dispatch, setShowNewSlideSelection, pathname, onSlideAdded, slideCount, ] ); useEffect(() => { let isMounted = true; const fetchLayouts = async () => { try { setLoading(true); const templateV2Layouts = extractTemplateV2Layouts(presentationLayout); const layoutItems = templateV2Layouts.map((layout, layoutIndex) => createTemplateV2LayoutItem(layout, layoutIndex) ); if (isMounted) setLayouts(layoutItems); } catch (error) { console.error("Error loading slide layouts:", error); if (isMounted) setLayouts([]); } finally { if (isMounted) setLoading(false); } }; fetchLayouts(); return () => { isMounted = false; }; }, [presentationLayout]); const showEmptySlideLayout = true; const selectableLayouts = showEmptySlideLayout ? [EMPTY_SLIDE_LAYOUT, ...layouts] : layouts; const layoutCountText = showEmptySlideLayout ? `${selectableLayouts.length} Option${selectableLayouts.length === 1 ? "" : "s"}` : `${layouts.length} Layout${layouts.length === 1 ? "" : "s"}`; return (

Choose Slide Layout

{loading ? "Loading layouts" : layoutCountText}

{loading && ( )}
{loading ? (
) : selectableLayouts.length > 0 ? (
{selectableLayouts.map((layout: any) => ( ))}
) : (
No layouts available.
)}
); }; export default NewSlideV1;