| 1 | "use client"; |
| 2 | |
| 3 | import { useMemo, type ReactNode } from "react"; |
| 4 | |
| 5 | import { FontLoader } from "@/components/plate/utils/font-loader"; |
| 6 | import { cn } from "@/lib/utils"; |
| 7 | import { type PlateSlide } from "../../utils/parser"; |
| 8 | import { PresentationRenderProvider } from "../context/PresentationRenderContext"; |
| 9 | import ImageSlide from "../custom-elements/image-slide/ImageSlide"; |
| 10 | import ImageSlideStatic from "../custom-elements/image-slide/ImageSlideStatic"; |
| 11 | import { |
| 12 | getLayoutClasses, |
| 13 | getPresentingClasses, |
| 14 | getSlideCustomStyles, |
| 15 | getSlideFormatStyles, |
| 16 | } from "../utils/slide-format-styles"; |
| 17 | |
| 18 | interface PresentationRootProps { |
| 19 | className?: string; |
| 20 | initialContent?: PlateSlide; |
| 21 | fontsToLoad: string[]; |
| 22 | isPresenting: boolean; |
| 23 | readOnly: boolean; |
| 24 | isStatic?: boolean; |
| 25 | onActivateSlide?: (slideId: string) => void; |
| 26 | children: ReactNode; |
| 27 | } |
| 28 | |
| 29 | export function PresentationRoot({ |
| 30 | className, |
| 31 | initialContent, |
| 32 | fontsToLoad, |
| 33 | isPresenting, |
| 34 | readOnly, |
| 35 | isStatic = false, |
| 36 | onActivateSlide, |
| 37 | children, |
| 38 | }: PresentationRootProps) { |
| 39 | // Calculate format styles using extracted utility |
| 40 | const styleByFormat = useMemo( |
| 41 | () => |
| 42 | getSlideFormatStyles( |
| 43 | initialContent?.formatCategory, |
| 44 | initialContent?.width, |
| 45 | initialContent?.aspectRatio, |
| 46 | ), |
| 47 | [ |
| 48 | initialContent?.formatCategory, |
| 49 | initialContent?.aspectRatio, |
| 50 | initialContent?.width, |
| 51 | ], |
| 52 | ); |
| 53 | |
| 54 | // Calculate custom inline styles |
| 55 | const customStyles = useMemo( |
| 56 | () => getSlideCustomStyles(initialContent, isPresenting, styleByFormat), |
| 57 | [initialContent, isPresenting, styleByFormat], |
| 58 | ); |
| 59 | |
| 60 | return ( |
| 61 | <div |
| 62 | id={!isStatic ? `presentation-root-${initialContent?.id}` : undefined} |
| 63 | className={cn( |
| 64 | "group/overflow-border flex", |
| 65 | "scrollbar-thin p-0 scrollbar-thumb-muted-foreground/20 scrollbar-track-transparent hover:scrollbar-thumb-muted-foreground/30", |
| 66 | "relative", |
| 67 | "bg-(--presentation-background)", |
| 68 | "text-(--presentation-text)", |
| 69 | "focus-within:ring-opacity-50 focus-within:ring-2 focus-within:ring-primary", |
| 70 | getPresentingClasses(isPresenting, initialContent?.formatCategory), |
| 71 | className, |
| 72 | getLayoutClasses(initialContent?.layoutType), |
| 73 | )} |
| 74 | style={customStyles} |
| 75 | data-is-presenting={readOnly && isPresenting ? "true" : "false"} |
| 76 | data-slide-content="true" |
| 77 | onClickCapture={() => { |
| 78 | if (readOnly && initialContent?.id) |
| 79 | onActivateSlide?.(initialContent.id); |
| 80 | }} |
| 81 | > |
| 82 | <PresentationRenderProvider |
| 83 | layoutType={initialContent?.layoutType} |
| 84 | isStatic={isStatic} |
| 85 | > |
| 86 | <FontLoader fontsToLoad={fontsToLoad} /> |
| 87 | {initialContent?.isImageSlide && initialContent.rootImage ? ( |
| 88 | readOnly ? ( |
| 89 | <ImageSlideStatic |
| 90 | image={initialContent.rootImage} |
| 91 | slideId={initialContent.id} |
| 92 | /> |
| 93 | ) : ( |
| 94 | <ImageSlide |
| 95 | image={initialContent.rootImage} |
| 96 | slideId={initialContent.id} |
| 97 | /> |
| 98 | ) |
| 99 | ) : ( |
| 100 | children |
| 101 | )} |
| 102 | </PresentationRenderProvider> |
| 103 | </div> |
| 104 | ); |
| 105 | } |
| 106 |