'use client'; import { createPortal } from 'react-dom'; import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { BringToFront, ImagePlus, SendToBack, Trash2 } from 'lucide-react'; import type { MouseEvent } from 'react'; import type { PPTVideoElement } from '@openmaic/dsl'; import { computeToolbarPosition } from '../text/TextToolbarOverlay'; import { useToolbarAnchor } from '../text/useToolbarAnchor'; import type { VideoEditorLabels, VideoPosterPickerRenderer } from '../types'; interface ToolbarSize { readonly width: number; readonly height: number; } export interface VideoToolbarOverlayProps { readonly element: PPTVideoElement; readonly elementIdPrefix?: string; readonly labels: VideoEditorLabels; readonly renderPosterPicker?: VideoPosterPickerRenderer; readonly onPosterChange: (poster: string) => void; readonly onBringToFront?: () => void; readonly onSendToBack?: () => void; readonly onDelete?: () => void; } export function VideoToolbarOverlay({ element, elementIdPrefix = 'slide-element-', labels, renderPosterPicker, onPosterChange, onBringToFront, onSendToBack, onDelete, }: VideoToolbarOverlayProps) { const anchor = useToolbarAnchor(element.id, elementIdPrefix); const rootRef = useRef(null); const toolbarRef = useRef(null); const [toolbarSize, setToolbarSize] = useState(null); const [posterPickerOpen, setPosterPickerOpen] = useState(false); const preventFocusLoss = (event: MouseEvent) => event.preventDefault(); useLayoutEffect(() => { const toolbar = toolbarRef.current; if (!anchor || !toolbar) return; const measure = () => { const rect = toolbar.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { setToolbarSize((current) => current?.width === rect.width && current.height === rect.height ? current : { width: rect.width, height: rect.height }, ); } }; measure(); const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure); observer?.observe(toolbar); return () => observer?.disconnect(); }, [anchor]); useEffect(() => { if (!posterPickerOpen) return; const closeWhenOutside = (event: PointerEvent) => { if (!rootRef.current?.contains(event.target as Node)) setPosterPickerOpen(false); }; const closeWhenEscaped = (event: KeyboardEvent) => { if (event.key === 'Escape') setPosterPickerOpen(false); }; document.addEventListener('pointerdown', closeWhenOutside); document.addEventListener('keydown', closeWhenEscaped); return () => { document.removeEventListener('pointerdown', closeWhenOutside); document.removeEventListener('keydown', closeWhenEscaped); }; }, [posterPickerOpen]); if (!anchor || typeof document === 'undefined') return null; const position = toolbarSize ? computeToolbarPosition( anchor, toolbarSize, { width: window.innerWidth, height: window.innerHeight }, 'top', ) : null; const showPosterPicker = posterPickerOpen && renderPosterPicker; return createPortal(
{renderPosterPicker ? ( ) : null} {onBringToFront || onSendToBack || onDelete ? (
{showPosterPicker ? (
{renderPosterPicker({ element, onPick: (poster) => { onPosterChange(poster); setPosterPickerOpen(false); }, close: () => setPosterPickerOpen(false), })}
) : null}
, document.body, ); }