From be1ae20d9089e81cb84a8294be76fe680a24b60f Mon Sep 17 00:00:00 2001 From: Hyteq Date: Sun, 20 Jul 2025 11:00:22 +0300 Subject: format --- apps/web/src/components/editor/preview-panel.tsx | 936 +++++++++++------------ 1 file changed, 468 insertions(+), 468 deletions(-) (limited to 'apps/web/src/components/editor/preview-panel.tsx') diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 2ecee391..ea66de74 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -10,11 +10,11 @@ import { VideoPlayer } from "@/components/ui/video-player"; import { AudioPlayer } from "@/components/ui/audio-player"; import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - DropdownMenuSeparator, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; import { Play, Pause, Expand } from "lucide-react"; import { useState, useRef, useEffect } from "react"; @@ -25,473 +25,473 @@ import { BackgroundSettings } from "../background-settings"; import { useProjectStore } from "@/stores/project-store"; interface ActiveElement { - element: TimelineElement; - track: TimelineTrack; - mediaItem: MediaItem | null; + element: TimelineElement; + track: TimelineTrack; + mediaItem: MediaItem | null; } export function PreviewPanel() { - const { tracks } = useTimelineStore(); - const { mediaItems } = useMediaStore(); - const { currentTime } = usePlaybackStore(); - const { canvasSize } = useEditorStore(); - const previewRef = useRef(null); - const containerRef = useRef(null); - const [previewDimensions, setPreviewDimensions] = useState({ - width: 0, - height: 0, - }); - const { activeProject } = useProjectStore(); - - // Calculate optimal preview size that fits in container while maintaining aspect ratio - useEffect(() => { - const updatePreviewSize = () => { - if (!containerRef.current) return; - - const container = containerRef.current.getBoundingClientRect(); - const computedStyle = getComputedStyle(containerRef.current); - - // Get padding values - const paddingTop = parseFloat(computedStyle.paddingTop); - const paddingBottom = parseFloat(computedStyle.paddingBottom); - const paddingLeft = parseFloat(computedStyle.paddingLeft); - const paddingRight = parseFloat(computedStyle.paddingRight); - - // Get gap value (gap-4 = 1rem = 16px) - const gap = parseFloat(computedStyle.gap) || 16; - - // Get toolbar height if it exists - const toolbar = containerRef.current.querySelector("[data-toolbar]"); - const toolbarHeight = toolbar - ? toolbar.getBoundingClientRect().height - : 0; - - // Calculate available space after accounting for padding, gap, and toolbar - const availableWidth = container.width - paddingLeft - paddingRight; - const availableHeight = - container.height - - paddingTop - - paddingBottom - - toolbarHeight - - (toolbarHeight > 0 ? gap : 0); - - const targetRatio = canvasSize.width / canvasSize.height; - const containerRatio = availableWidth / availableHeight; - - let width, height; - - if (containerRatio > targetRatio) { - // Container is wider - constrain by height - height = availableHeight; - width = height * targetRatio; - } else { - // Container is taller - constrain by width - width = availableWidth; - height = width / targetRatio; - } - - setPreviewDimensions({ width, height }); - }; - - updatePreviewSize(); - - const resizeObserver = new ResizeObserver(updatePreviewSize); - if (containerRef.current) { - resizeObserver.observe(containerRef.current); - } - - return () => resizeObserver.disconnect(); - }, [canvasSize.width, canvasSize.height]); - - // Get active elements at current time - const getActiveElements = (): ActiveElement[] => { - const activeElements: ActiveElement[] = []; - - tracks.forEach((track) => { - track.elements.forEach((element) => { - const elementStart = element.startTime; - const elementEnd = - element.startTime + - (element.duration - element.trimStart - element.trimEnd); - - if (currentTime >= elementStart && currentTime < elementEnd) { - let mediaItem = null; - - // Only get media item for media elements - if (element.type === "media") { - mediaItem = - element.mediaId === "test" - ? null // Test elements don't have a real media item - : mediaItems.find((item) => item.id === element.mediaId) || - null; - } - - activeElements.push({ element, track, mediaItem }); - } - }); - }); - - return activeElements; - }; - - const activeElements = getActiveElements(); - - // Check if there are any elements in the timeline at all - const hasAnyElements = tracks.some((track) => track.elements.length > 0); - - // Get media elements for blur background (video/image only) - const getBlurBackgroundElements = (): ActiveElement[] => { - return activeElements.filter( - ({ element, mediaItem }) => - element.type === "media" && - mediaItem && - (mediaItem.type === "video" || mediaItem.type === "image") && - element.mediaId !== "test" // Exclude test elements - ); - }; - - const blurBackgroundElements = getBlurBackgroundElements(); - - // Render blur background layer - const renderBlurBackground = () => { - if ( - !activeProject?.backgroundType || - activeProject.backgroundType !== "blur" || - blurBackgroundElements.length === 0 - ) { - return null; - } - - // Use the first media element for background (could be enhanced to use primary/focused element) - const backgroundElement = blurBackgroundElements[0]; - const { element, mediaItem } = backgroundElement; - - if (!mediaItem) return null; - - const blurIntensity = activeProject.blurIntensity || 8; - - if (mediaItem.type === "video") { - return ( -
- -
- ); - } - - if (mediaItem.type === "image") { - return ( -
- {mediaItem.name} -
- ); - } - - return null; - }; - - // Render an element - const renderElement = (elementData: ActiveElement, index: number) => { - const { element, mediaItem } = elementData; - - // Text elements - if (element.type === "text") { - const fontClassName = - FONT_CLASS_MAP[element.fontFamily as keyof typeof FONT_CLASS_MAP] || ""; - - const scaleRatio = previewDimensions.width / canvasSize.width; - - return ( -
-
- {element.content} -
-
- ); - } - - // Media elements - if (element.type === "media") { - // Test elements - if (!mediaItem || element.mediaId === "test") { - return ( -
-
-
🎬
-

{element.name}

-
-
- ); - } - - // Video elements - if (mediaItem.type === "video") { - return ( -
- -
- ); - } - - // Image elements - if (mediaItem.type === "image") { - return ( -
- {mediaItem.name} -
- ); - } - - // Audio elements (no visual representation) - if (mediaItem.type === "audio") { - return ( -
- -
- ); - } - } - - return null; - }; - - return ( -
-
-
- {hasAnyElements ? ( -
- {renderBlurBackground()} - {activeElements.length === 0 ? ( -
- No elements at current time -
- ) : ( - activeElements.map((elementData, index) => - renderElement(elementData, index) - ) - )} - {/* Show message when blur is selected but no media available */} - {activeProject?.backgroundType === "blur" && - blurBackgroundElements.length === 0 && - activeElements.length > 0 && ( -
- Add a video or image to use blur background -
- )} -
- ) : null} - -
- - -
-
- ); + const { tracks } = useTimelineStore(); + const { mediaItems } = useMediaStore(); + const { currentTime } = usePlaybackStore(); + const { canvasSize } = useEditorStore(); + const previewRef = useRef(null); + const containerRef = useRef(null); + const [previewDimensions, setPreviewDimensions] = useState({ + width: 0, + height: 0, + }); + const { activeProject } = useProjectStore(); + + // Calculate optimal preview size that fits in container while maintaining aspect ratio + useEffect(() => { + const updatePreviewSize = () => { + if (!containerRef.current) return; + + const container = containerRef.current.getBoundingClientRect(); + const computedStyle = getComputedStyle(containerRef.current); + + // Get padding values + const paddingTop = parseFloat(computedStyle.paddingTop); + const paddingBottom = parseFloat(computedStyle.paddingBottom); + const paddingLeft = parseFloat(computedStyle.paddingLeft); + const paddingRight = parseFloat(computedStyle.paddingRight); + + // Get gap value (gap-4 = 1rem = 16px) + const gap = parseFloat(computedStyle.gap) || 16; + + // Get toolbar height if it exists + const toolbar = containerRef.current.querySelector("[data-toolbar]"); + const toolbarHeight = toolbar + ? toolbar.getBoundingClientRect().height + : 0; + + // Calculate available space after accounting for padding, gap, and toolbar + const availableWidth = container.width - paddingLeft - paddingRight; + const availableHeight = + container.height - + paddingTop - + paddingBottom - + toolbarHeight - + (toolbarHeight > 0 ? gap : 0); + + const targetRatio = canvasSize.width / canvasSize.height; + const containerRatio = availableWidth / availableHeight; + + let width, height; + + if (containerRatio > targetRatio) { + // Container is wider - constrain by height + height = availableHeight; + width = height * targetRatio; + } else { + // Container is taller - constrain by width + width = availableWidth; + height = width / targetRatio; + } + + setPreviewDimensions({ width, height }); + }; + + updatePreviewSize(); + + const resizeObserver = new ResizeObserver(updatePreviewSize); + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => resizeObserver.disconnect(); + }, [canvasSize.width, canvasSize.height]); + + // Get active elements at current time + const getActiveElements = (): ActiveElement[] => { + const activeElements: ActiveElement[] = []; + + tracks.forEach((track) => { + track.elements.forEach((element) => { + const elementStart = element.startTime; + const elementEnd = + element.startTime + + (element.duration - element.trimStart - element.trimEnd); + + if (currentTime >= elementStart && currentTime < elementEnd) { + let mediaItem = null; + + // Only get media item for media elements + if (element.type === "media") { + mediaItem = + element.mediaId === "test" + ? null // Test elements don't have a real media item + : mediaItems.find((item) => item.id === element.mediaId) || + null; + } + + activeElements.push({ element, track, mediaItem }); + } + }); + }); + + return activeElements; + }; + + const activeElements = getActiveElements(); + + // Check if there are any elements in the timeline at all + const hasAnyElements = tracks.some((track) => track.elements.length > 0); + + // Get media elements for blur background (video/image only) + const getBlurBackgroundElements = (): ActiveElement[] => { + return activeElements.filter( + ({ element, mediaItem }) => + element.type === "media" && + mediaItem && + (mediaItem.type === "video" || mediaItem.type === "image") && + element.mediaId !== "test", // Exclude test elements + ); + }; + + const blurBackgroundElements = getBlurBackgroundElements(); + + // Render blur background layer + const renderBlurBackground = () => { + if ( + !activeProject?.backgroundType || + activeProject.backgroundType !== "blur" || + blurBackgroundElements.length === 0 + ) { + return null; + } + + // Use the first media element for background (could be enhanced to use primary/focused element) + const backgroundElement = blurBackgroundElements[0]; + const { element, mediaItem } = backgroundElement; + + if (!mediaItem) return null; + + const blurIntensity = activeProject.blurIntensity || 8; + + if (mediaItem.type === "video") { + return ( +
+ +
+ ); + } + + if (mediaItem.type === "image") { + return ( +
+ {mediaItem.name} +
+ ); + } + + return null; + }; + + // Render an element + const renderElement = (elementData: ActiveElement, index: number) => { + const { element, mediaItem } = elementData; + + // Text elements + if (element.type === "text") { + const fontClassName = + FONT_CLASS_MAP[element.fontFamily as keyof typeof FONT_CLASS_MAP] || ""; + + const scaleRatio = previewDimensions.width / canvasSize.width; + + return ( +
+
+ {element.content} +
+
+ ); + } + + // Media elements + if (element.type === "media") { + // Test elements + if (!mediaItem || element.mediaId === "test") { + return ( +
+
+
🎬
+

{element.name}

+
+
+ ); + } + + // Video elements + if (mediaItem.type === "video") { + return ( +
+ +
+ ); + } + + // Image elements + if (mediaItem.type === "image") { + return ( +
+ {mediaItem.name} +
+ ); + } + + // Audio elements (no visual representation) + if (mediaItem.type === "audio") { + return ( +
+ +
+ ); + } + } + + return null; + }; + + return ( +
+
+
+ {hasAnyElements ? ( +
+ {renderBlurBackground()} + {activeElements.length === 0 ? ( +
+ No elements at current time +
+ ) : ( + activeElements.map((elementData, index) => + renderElement(elementData, index), + ) + )} + {/* Show message when blur is selected but no media available */} + {activeProject?.backgroundType === "blur" && + blurBackgroundElements.length === 0 && + activeElements.length > 0 && ( +
+ Add a video or image to use blur background +
+ )} +
+ ) : null} + +
+ + +
+
+ ); } function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { - const { isPlaying, toggle, currentTime } = usePlaybackStore(); - const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); - const { getTotalDuration } = useTimelineStore(); - const { activeProject } = useProjectStore(); - const { - currentPreset, - isOriginal, - getOriginalAspectRatio, - getDisplayName, - canvasPresets, - } = useAspectRatio(); - - const handlePresetSelect = (preset: { width: number; height: number }) => { - setCanvasSize({ width: preset.width, height: preset.height }); - }; - - const handleOriginalSelect = () => { - const aspectRatio = getOriginalAspectRatio(); - setCanvasSizeToOriginal(aspectRatio); - }; - - return ( -
-
-

- - {formatTimeCode( - currentTime, - "HH:MM:SS:FF", - activeProject?.fps || 30 - )} - - / - - {formatTimeCode( - getTotalDuration(), - "HH:MM:SS:FF", - activeProject?.fps || 30 - )} - -

-
- -
- - - - - - - - Original - - - {canvasPresets.map((preset) => ( - handlePresetSelect(preset)} - className={cn( - "text-xs", - currentPreset?.name === preset.name && "font-semibold" - )} - > - {preset.name} - - ))} - - - -
-
- ); + const { isPlaying, toggle, currentTime } = usePlaybackStore(); + const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); + const { getTotalDuration } = useTimelineStore(); + const { activeProject } = useProjectStore(); + const { + currentPreset, + isOriginal, + getOriginalAspectRatio, + getDisplayName, + canvasPresets, + } = useAspectRatio(); + + const handlePresetSelect = (preset: { width: number; height: number }) => { + setCanvasSize({ width: preset.width, height: preset.height }); + }; + + const handleOriginalSelect = () => { + const aspectRatio = getOriginalAspectRatio(); + setCanvasSizeToOriginal(aspectRatio); + }; + + return ( +
+
+

+ + {formatTimeCode( + currentTime, + "HH:MM:SS:FF", + activeProject?.fps || 30, + )} + + / + + {formatTimeCode( + getTotalDuration(), + "HH:MM:SS:FF", + activeProject?.fps || 30, + )} + +

+
+ +
+ + + + + + + + Original + + + {canvasPresets.map((preset) => ( + handlePresetSelect(preset)} + className={cn( + "text-xs", + currentPreset?.name === preset.name && "font-semibold", + )} + > + {preset.name} + + ))} + + + +
+
+ ); } -- cgit v1.3.1