From 7fb1181034313b499ef39aa34126c9f1a293d860 Mon Sep 17 00:00:00 2001 From: Jakub Marcowski Date: Mon, 14 Jul 2025 23:16:38 +0200 Subject: Optimise PNGs across the repo --- apps/web/public/icons/android-icon-144x144.png | Bin 1076 -> 467 bytes apps/web/public/icons/android-icon-192x192.png | Bin 741 -> 564 bytes apps/web/public/icons/android-icon-36x36.png | Bin 768 -> 222 bytes apps/web/public/icons/android-icon-48x48.png | Bin 802 -> 253 bytes apps/web/public/icons/android-icon-72x72.png | Bin 826 -> 274 bytes apps/web/public/icons/android-icon-96x96.png | Bin 906 -> 334 bytes apps/web/public/icons/apple-icon-114x114.png | Bin 985 -> 399 bytes apps/web/public/icons/apple-icon-120x120.png | Bin 998 -> 419 bytes apps/web/public/icons/apple-icon-144x144.png | Bin 1076 -> 467 bytes apps/web/public/icons/apple-icon-152x152.png | Bin 1163 -> 546 bytes apps/web/public/icons/apple-icon-180x180.png | Bin 1360 -> 724 bytes apps/web/public/icons/apple-icon-57x57.png | Bin 809 -> 257 bytes apps/web/public/icons/apple-icon-60x60.png | Bin 843 -> 287 bytes apps/web/public/icons/apple-icon-72x72.png | Bin 826 -> 274 bytes apps/web/public/icons/apple-icon-76x76.png | Bin 820 -> 263 bytes apps/web/public/icons/favicon-16x16.png | Bin 670 -> 131 bytes apps/web/public/icons/favicon-32x32.png | Bin 747 -> 206 bytes apps/web/public/icons/favicon-96x96.png | Bin 906 -> 334 bytes apps/web/public/icons/ms-icon-144x144.png | Bin 1076 -> 467 bytes apps/web/public/icons/ms-icon-150x150.png | Bin 1159 -> 539 bytes apps/web/public/icons/ms-icon-310x310.png | Bin 2749 -> 1831 bytes apps/web/public/icons/ms-icon-70x70.png | Bin 814 -> 259 bytes apps/web/public/landing-page-bg.png | Bin 230853 -> 89374 bytes apps/web/public/logo.png | Bin 1789 -> 1021 bytes 24 files changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/web/public/icons/android-icon-144x144.png b/apps/web/public/icons/android-icon-144x144.png index 7320ece7..07988e24 100644 Binary files a/apps/web/public/icons/android-icon-144x144.png and b/apps/web/public/icons/android-icon-144x144.png differ diff --git a/apps/web/public/icons/android-icon-192x192.png b/apps/web/public/icons/android-icon-192x192.png index f534f429..6d8ad26b 100644 Binary files a/apps/web/public/icons/android-icon-192x192.png and b/apps/web/public/icons/android-icon-192x192.png differ diff --git a/apps/web/public/icons/android-icon-36x36.png b/apps/web/public/icons/android-icon-36x36.png index 6b8db020..043e1b68 100644 Binary files a/apps/web/public/icons/android-icon-36x36.png and b/apps/web/public/icons/android-icon-36x36.png differ diff --git a/apps/web/public/icons/android-icon-48x48.png b/apps/web/public/icons/android-icon-48x48.png index 30a68fa5..9f9203a6 100644 Binary files a/apps/web/public/icons/android-icon-48x48.png and b/apps/web/public/icons/android-icon-48x48.png differ diff --git a/apps/web/public/icons/android-icon-72x72.png b/apps/web/public/icons/android-icon-72x72.png index e4019c84..b7941733 100644 Binary files a/apps/web/public/icons/android-icon-72x72.png and b/apps/web/public/icons/android-icon-72x72.png differ diff --git a/apps/web/public/icons/android-icon-96x96.png b/apps/web/public/icons/android-icon-96x96.png index 85591ab4..a6b38795 100644 Binary files a/apps/web/public/icons/android-icon-96x96.png and b/apps/web/public/icons/android-icon-96x96.png differ diff --git a/apps/web/public/icons/apple-icon-114x114.png b/apps/web/public/icons/apple-icon-114x114.png index c776e3f9..7fee87bd 100644 Binary files a/apps/web/public/icons/apple-icon-114x114.png and b/apps/web/public/icons/apple-icon-114x114.png differ diff --git a/apps/web/public/icons/apple-icon-120x120.png b/apps/web/public/icons/apple-icon-120x120.png index 76604df8..b60076a7 100644 Binary files a/apps/web/public/icons/apple-icon-120x120.png and b/apps/web/public/icons/apple-icon-120x120.png differ diff --git a/apps/web/public/icons/apple-icon-144x144.png b/apps/web/public/icons/apple-icon-144x144.png index 7320ece7..07988e24 100644 Binary files a/apps/web/public/icons/apple-icon-144x144.png and b/apps/web/public/icons/apple-icon-144x144.png differ diff --git a/apps/web/public/icons/apple-icon-152x152.png b/apps/web/public/icons/apple-icon-152x152.png index eb5ef0df..18e27f66 100644 Binary files a/apps/web/public/icons/apple-icon-152x152.png and b/apps/web/public/icons/apple-icon-152x152.png differ diff --git a/apps/web/public/icons/apple-icon-180x180.png b/apps/web/public/icons/apple-icon-180x180.png index b90e62fb..39da6f02 100644 Binary files a/apps/web/public/icons/apple-icon-180x180.png and b/apps/web/public/icons/apple-icon-180x180.png differ diff --git a/apps/web/public/icons/apple-icon-57x57.png b/apps/web/public/icons/apple-icon-57x57.png index 7cfb2e56..9aeec9a7 100644 Binary files a/apps/web/public/icons/apple-icon-57x57.png and b/apps/web/public/icons/apple-icon-57x57.png differ diff --git a/apps/web/public/icons/apple-icon-60x60.png b/apps/web/public/icons/apple-icon-60x60.png index 1d90a370..923107b3 100644 Binary files a/apps/web/public/icons/apple-icon-60x60.png and b/apps/web/public/icons/apple-icon-60x60.png differ diff --git a/apps/web/public/icons/apple-icon-72x72.png b/apps/web/public/icons/apple-icon-72x72.png index e4019c84..b7941733 100644 Binary files a/apps/web/public/icons/apple-icon-72x72.png and b/apps/web/public/icons/apple-icon-72x72.png differ diff --git a/apps/web/public/icons/apple-icon-76x76.png b/apps/web/public/icons/apple-icon-76x76.png index 34575f42..6500cc33 100644 Binary files a/apps/web/public/icons/apple-icon-76x76.png and b/apps/web/public/icons/apple-icon-76x76.png differ diff --git a/apps/web/public/icons/favicon-16x16.png b/apps/web/public/icons/favicon-16x16.png index 96db3a2f..de473f33 100644 Binary files a/apps/web/public/icons/favicon-16x16.png and b/apps/web/public/icons/favicon-16x16.png differ diff --git a/apps/web/public/icons/favicon-32x32.png b/apps/web/public/icons/favicon-32x32.png index f8f589dc..5f829950 100644 Binary files a/apps/web/public/icons/favicon-32x32.png and b/apps/web/public/icons/favicon-32x32.png differ diff --git a/apps/web/public/icons/favicon-96x96.png b/apps/web/public/icons/favicon-96x96.png index 85591ab4..a6b38795 100644 Binary files a/apps/web/public/icons/favicon-96x96.png and b/apps/web/public/icons/favicon-96x96.png differ diff --git a/apps/web/public/icons/ms-icon-144x144.png b/apps/web/public/icons/ms-icon-144x144.png index 7320ece7..07988e24 100644 Binary files a/apps/web/public/icons/ms-icon-144x144.png and b/apps/web/public/icons/ms-icon-144x144.png differ diff --git a/apps/web/public/icons/ms-icon-150x150.png b/apps/web/public/icons/ms-icon-150x150.png index 248ef48a..21d11ff1 100644 Binary files a/apps/web/public/icons/ms-icon-150x150.png and b/apps/web/public/icons/ms-icon-150x150.png differ diff --git a/apps/web/public/icons/ms-icon-310x310.png b/apps/web/public/icons/ms-icon-310x310.png index 950a3d6b..8d6cef7b 100644 Binary files a/apps/web/public/icons/ms-icon-310x310.png and b/apps/web/public/icons/ms-icon-310x310.png differ diff --git a/apps/web/public/icons/ms-icon-70x70.png b/apps/web/public/icons/ms-icon-70x70.png index 7b9c2694..98852e10 100644 Binary files a/apps/web/public/icons/ms-icon-70x70.png and b/apps/web/public/icons/ms-icon-70x70.png differ diff --git a/apps/web/public/landing-page-bg.png b/apps/web/public/landing-page-bg.png index 4f873945..8f97ca22 100644 Binary files a/apps/web/public/landing-page-bg.png and b/apps/web/public/landing-page-bg.png differ diff --git a/apps/web/public/logo.png b/apps/web/public/logo.png index fc9e7902..d3c15141 100644 Binary files a/apps/web/public/logo.png and b/apps/web/public/logo.png differ -- cgit v1.3.1 From 7732f0f158a1d2bc6b0b66bac64fef8f94e39ef3 Mon Sep 17 00:00:00 2001 From: Pham Tan Minh Tien Date: Tue, 15 Jul 2025 20:37:06 +0700 Subject: chore: add .DS_Store to .gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4869b9bd..779c956b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ node_modules # cursor .cursor/ -bun.lockb \ No newline at end of file +bun.lockb + +# mac +.DS_Store \ No newline at end of file -- cgit v1.3.1 From a2c21dc3c474b5aa00e83d8894f74230a12f6a54 Mon Sep 17 00:00:00 2001 From: WOGGER7 Date: Mon, 21 Jul 2025 13:56:52 +0530 Subject: Add fullscreen preview mode with keyboard controls --- apps/web/src/components/editor/preview-panel.tsx | 261 ++++++++++++++++------- 1 file changed, 184 insertions(+), 77 deletions(-) diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 2ecee391..b20b0eed 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -17,7 +17,7 @@ import { DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; import { Play, Pause, Expand } from "lucide-react"; -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useCallback } from "react"; import { cn } from "@/lib/utils"; import { formatTimeCode } from "@/lib/time"; import { FONT_CLASS_MAP } from "@/lib/font-config"; @@ -41,6 +41,7 @@ export function PreviewPanel() { width: 0, height: 0, }); + const [isExpanded, setIsExpanded] = useState(false); const { activeProject } = useProjectStore(); // Calculate optimal preview size that fits in container while maintaining aspect ratio @@ -48,32 +49,41 @@ export function PreviewPanel() { 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); + let container; + let availableWidth, availableHeight; + + if (isExpanded) { + // Use full viewport when expanded + availableWidth = window.innerWidth; + availableHeight = window.innerHeight; + } else { + 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 + availableWidth = container.width - paddingLeft - paddingRight; + availableHeight = + container.height - + paddingTop - + paddingBottom - + toolbarHeight - + (toolbarHeight > 0 ? gap : 0); + } const targetRatio = canvasSize.width / canvasSize.height; const containerRatio = availableWidth / availableHeight; @@ -82,11 +92,11 @@ export function PreviewPanel() { if (containerRatio > targetRatio) { // Container is wider - constrain by height - height = availableHeight; + height = availableHeight * (isExpanded ? 0.9 : 1); // Leave some margin when expanded width = height * targetRatio; } else { // Container is taller - constrain by width - width = availableWidth; + width = availableWidth * (isExpanded ? 0.9 : 1); // Leave some margin when expanded height = width / targetRatio; } @@ -100,8 +110,44 @@ export function PreviewPanel() { resizeObserver.observe(containerRef.current); } - return () => resizeObserver.disconnect(); - }, [canvasSize.width, canvasSize.height]); + // Listen for window resize when expanded + if (isExpanded) { + window.addEventListener("resize", updatePreviewSize); + } + + return () => { + resizeObserver.disconnect(); + if (isExpanded) { + window.removeEventListener("resize", updatePreviewSize); + } + }; + }, [canvasSize.width, canvasSize.height, isExpanded]); + + // Handle escape key to exit expanded mode + useEffect(() => { + const handleEscapeKey = (event: KeyboardEvent) => { + if (event.key === "Escape" && isExpanded) { + setIsExpanded(false); + } + }; + + if (isExpanded) { + document.addEventListener("keydown", handleEscapeKey); + // Prevent body scroll when expanded + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + + return () => { + document.removeEventListener("keydown", handleEscapeKey); + document.body.style.overflow = ""; + }; + }, [isExpanded]); + + const toggleExpanded = useCallback(() => { + setIsExpanded((prev) => !prev); + }, []); // Get active elements at current time const getActiveElements = (): ActiveElement[] => { @@ -146,7 +192,7 @@ export function PreviewPanel() { element.type === "media" && mediaItem && (mediaItem.type === "video" || mediaItem.type === "image") && - element.mediaId !== "test" // Exclude test elements + element.mediaId !== "test", // Exclude test elements ); }; @@ -338,55 +384,114 @@ export function PreviewPanel() { }; 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 + <> +
+
+
+ {hasAnyElements ? ( +
+ {renderBlurBackground()} + {activeElements.length === 0 ? ( +
+ No elements at current time
+ ) : ( + activeElements.map((elementData, index) => + renderElement(elementData, index), + ) )} -
- ) : null} + {/* 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} -
+
- + +
-
+ + {/* Expanded/Fullscreen Mode */} + {isExpanded && ( +
+
+
+ {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 +
+ )} +
+
+
+ +
+
+ )} + ); } -function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { +function PreviewToolbar({ + hasAnyElements, + onToggleExpanded, + isExpanded, +}: { + hasAnyElements: boolean; + onToggleExpanded: () => void; + isExpanded: boolean; +}) { const { isPlaying, toggle, currentTime } = usePlaybackStore(); const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); const { getTotalDuration } = useTimelineStore(); @@ -417,14 +522,14 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) {

{formatTimeCode( currentTime, "HH:MM:SS:FF", - activeProject?.fps || 30 + activeProject?.fps || 30, )} / @@ -432,7 +537,7 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { {formatTimeCode( getTotalDuration(), "HH:MM:SS:FF", - activeProject?.fps || 30 + activeProject?.fps || 30, )}

@@ -476,7 +581,7 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { onClick={() => handlePresetSelect(preset)} className={cn( "text-xs", - currentPreset?.name === preset.name && "font-semibold" + currentPreset?.name === preset.name && "font-semibold", )} > {preset.name} @@ -488,6 +593,8 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { variant="text" size="icon" className="!size-4 text-muted-foreground" + onClick={onToggleExpanded} + title={isExpanded ? "Exit fullscreen (Esc)" : "Enter fullscreen"} > -- cgit v1.3.1 From 5915989669e43ddc51313cfe7f77514bff935837 Mon Sep 17 00:00:00 2001 From: WOGGER7 Date: Mon, 21 Jul 2025 14:04:45 +0530 Subject: Add video player timeline controls and keyboard shortcuts --- apps/web/src/components/editor/preview-panel.tsx | 238 +++++++++++++++++++---- 1 file changed, 201 insertions(+), 37 deletions(-) diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index b20b0eed..8f594d6c 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -16,7 +16,7 @@ import { DropdownMenuTrigger, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; -import { Play, Pause, Expand } from "lucide-react"; +import { Play, Pause, Expand, SkipBack, SkipForward } from "lucide-react"; import { useState, useRef, useEffect, useCallback } from "react"; import { cn } from "@/lib/utils"; import { formatTimeCode } from "@/lib/time"; @@ -31,9 +31,9 @@ interface ActiveElement { } export function PreviewPanel() { - const { tracks } = useTimelineStore(); + const { tracks, getTotalDuration } = useTimelineStore(); const { mediaItems } = useMediaStore(); - const { currentTime } = usePlaybackStore(); + const { currentTime, toggle, setCurrentTime } = usePlaybackStore(); const { canvasSize } = useEditorStore(); const previewRef = useRef(null); const containerRef = useRef(null); @@ -149,6 +149,78 @@ export function PreviewPanel() { setIsExpanded((prev) => !prev); }, []); + // Check if there are any elements in the timeline at all + const hasAnyElements = tracks.some((track) => track.elements.length > 0); + + // Handle keyboard shortcuts for timeline navigation + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + // Only handle shortcuts when preview panel is focused or no input is focused + const activeElement = document.activeElement; + const isInputFocused = + activeElement?.tagName === "INPUT" || + activeElement?.tagName === "TEXTAREA" || + activeElement?.contentEditable === "true"; + + if (isInputFocused) return; + + switch (event.key) { + case " ": + event.preventDefault(); + if (hasAnyElements) { + toggle(); + } + break; + case "ArrowLeft": + event.preventDefault(); + if (hasAnyElements) { + const frameTime = 1 / (activeProject?.fps || 30); + const newTime = Math.max( + 0, + currentTime - (event.shiftKey ? frameTime : 1), + ); + setCurrentTime(newTime); + } + break; + case "ArrowRight": + event.preventDefault(); + if (hasAnyElements) { + const frameTime = 1 / (activeProject?.fps || 30); + const totalDuration = getTotalDuration(); + const newTime = Math.min( + totalDuration, + currentTime + (event.shiftKey ? frameTime : 1), + ); + setCurrentTime(newTime); + } + break; + case "Home": + event.preventDefault(); + if (hasAnyElements) { + setCurrentTime(0); + } + break; + case "End": + event.preventDefault(); + if (hasAnyElements) { + setCurrentTime(getTotalDuration()); + } + break; + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [ + hasAnyElements, + toggle, + currentTime, + setCurrentTime, + activeProject?.fps, + getTotalDuration, + toggle, + ]); + // Get active elements at current time const getActiveElements = (): ActiveElement[] => { const activeElements: ActiveElement[] = []; @@ -182,9 +254,6 @@ export function PreviewPanel() { 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( @@ -431,6 +500,10 @@ export function PreviewPanel() { hasAnyElements={hasAnyElements} onToggleExpanded={toggleExpanded} isExpanded={isExpanded} + currentTime={currentTime} + setCurrentTime={setCurrentTime} + toggle={toggle} + getTotalDuration={getTotalDuration} />
@@ -475,6 +548,10 @@ export function PreviewPanel() { hasAnyElements={hasAnyElements} onToggleExpanded={toggleExpanded} isExpanded={isExpanded} + currentTime={currentTime} + setCurrentTime={setCurrentTime} + toggle={toggle} + getTotalDuration={getTotalDuration} /> @@ -487,14 +564,21 @@ function PreviewToolbar({ hasAnyElements, onToggleExpanded, isExpanded, + currentTime, + setCurrentTime, + toggle, + getTotalDuration, }: { hasAnyElements: boolean; onToggleExpanded: () => void; isExpanded: boolean; + currentTime: number; + setCurrentTime: (time: number) => void; + toggle: () => void; + getTotalDuration: () => number; }) { - const { isPlaying, toggle, currentTime } = usePlaybackStore(); + const { isPlaying } = usePlaybackStore(); const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); - const { getTotalDuration } = useTimelineStore(); const { activeProject } = useProjectStore(); const { currentPreset, @@ -513,19 +597,52 @@ function PreviewToolbar({ setCanvasSizeToOriginal(aspectRatio); }; + const totalDuration = getTotalDuration(); + const progress = totalDuration > 0 ? (currentTime / totalDuration) * 100 : 0; + + const handleTimelineClick = (e: React.MouseEvent) => { + const rect = e.currentTarget.getBoundingClientRect(); + const clickX = e.clientX - rect.left; + const percentage = clickX / rect.width; + const newTime = percentage * totalDuration; + setCurrentTime(Math.max(0, Math.min(newTime, totalDuration))); + }; + + const handleTimelineDrag = (e: React.MouseEvent) => { + const handleMouseMove = (moveEvent: MouseEvent) => { + const rect = e.currentTarget.getBoundingClientRect(); + const dragX = moveEvent.clientX - rect.left; + const percentage = Math.max(0, Math.min(1, dragX / rect.width)); + const newTime = percentage * totalDuration; + setCurrentTime(Math.max(0, Math.min(newTime, totalDuration))); + }; + + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }; + + const skipBackward = () => { + const newTime = Math.max(0, currentTime - 1); // Skip 1 second back + setCurrentTime(newTime); + }; + + const skipForward = () => { + const newTime = Math.min(totalDuration, currentTime + 1); // Skip 1 second forward + setCurrentTime(newTime); + }; + return ( -
-
-

- +

+ {/* Timeline Controls */} +
+ {/* Time Display */} +
+ {formatTimeCode( currentTime, "HH:MM:SS:FF", @@ -533,29 +650,76 @@ function PreviewToolbar({ )} / - + {formatTimeCode( - getTotalDuration(), + totalDuration, "HH:MM:SS:FF", activeProject?.fps || 30, )} -

+
+ + {/* Transport Controls */} +
+ + + +
+ + {/* Timeline Scrubber */} +
+
+
+
+
+
- -
+ + {/* Bottom Row - Aspect Ratio and Fullscreen */} +
-- cgit v1.3.1 From 6f8c864016ca53d331ced26de3524a857924c39d Mon Sep 17 00:00:00 2001 From: WOGGER7 Date: Mon, 21 Jul 2025 14:06:43 +0530 Subject: Hide aspect ratio controls when preview is expanded --- apps/web/src/components/editor/preview-panel.tsx | 70 +++++++++++++----------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 8f594d6c..375fc0e0 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -720,39 +720,43 @@ function PreviewToolbar({ {/* Bottom Row - Aspect Ratio and Fullscreen */}
- - - - - - - - Original - - - {canvasPresets.map((preset) => ( - handlePresetSelect(preset)} - className={cn( - "text-xs", - currentPreset?.name === preset.name && "font-semibold", - )} - > - {preset.name} - - ))} - - + {!isExpanded && ( + <> + + + + + + + + Original + + + {canvasPresets.map((preset) => ( + handlePresetSelect(preset)} + className={cn( + "text-xs", + currentPreset?.name === preset.name && "font-semibold", + )} + > + {preset.name} + + ))} + + + + )} - - -
+ {/* Transport Controls */} +
+ + + +
- {/* Timeline Scrubber */} -
+ {/* Timeline Scrubber */} +
+
-
-
-
+ className="absolute top-0 left-0 h-full bg-primary rounded-full transition-all duration-100" + style={{ width: `${progress}%` }} + /> +
- {/* Bottom Row - Aspect Ratio and Fullscreen */} -
+ {/* Aspect Ratio and Fullscreen Controls */} +
{!isExpanded && ( <> -- cgit v1.3.1 From 3cd968d423035ca0270ff568c5e8bcd00e457420 Mon Sep 17 00:00:00 2001 From: WOGGER7 Date: Mon, 21 Jul 2025 14:13:11 +0530 Subject: Pause playback when scrubbing or seeking video --- apps/web/src/components/editor/preview-panel.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index db2da954..6b1b7480 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -33,7 +33,7 @@ interface ActiveElement { export function PreviewPanel() { const { tracks, getTotalDuration } = useTimelineStore(); const { mediaItems } = useMediaStore(); - const { currentTime, toggle, setCurrentTime } = usePlaybackStore(); + const { currentTime, toggle, setCurrentTime, pause } = usePlaybackStore(); const { canvasSize } = useEditorStore(); const previewRef = useRef(null); const containerRef = useRef(null); @@ -174,6 +174,7 @@ export function PreviewPanel() { case "ArrowLeft": event.preventDefault(); if (hasAnyElements) { + pause(); const frameTime = 1 / (activeProject?.fps || 30); const newTime = Math.max( 0, @@ -185,6 +186,7 @@ export function PreviewPanel() { case "ArrowRight": event.preventDefault(); if (hasAnyElements) { + pause(); const frameTime = 1 / (activeProject?.fps || 30); const totalDuration = getTotalDuration(); const newTime = Math.min( @@ -504,6 +506,7 @@ export function PreviewPanel() { setCurrentTime={setCurrentTime} toggle={toggle} getTotalDuration={getTotalDuration} + pause={pause} />
@@ -552,6 +555,7 @@ export function PreviewPanel() { setCurrentTime={setCurrentTime} toggle={toggle} getTotalDuration={getTotalDuration} + pause={pause} />
@@ -568,6 +572,7 @@ function PreviewToolbar({ setCurrentTime, toggle, getTotalDuration, + pause, }: { hasAnyElements: boolean; onToggleExpanded: () => void; @@ -576,6 +581,7 @@ function PreviewToolbar({ setCurrentTime: (time: number) => void; toggle: () => void; getTotalDuration: () => number; + pause: () => void; }) { const { isPlaying } = usePlaybackStore(); const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); @@ -601,6 +607,7 @@ function PreviewToolbar({ const progress = totalDuration > 0 ? (currentTime / totalDuration) * 100 : 0; const handleTimelineClick = (e: React.MouseEvent) => { + pause(); const rect = e.currentTarget.getBoundingClientRect(); const clickX = e.clientX - rect.left; const percentage = clickX / rect.width; @@ -609,6 +616,7 @@ function PreviewToolbar({ }; const handleTimelineDrag = (e: React.MouseEvent) => { + pause(); const handleMouseMove = (moveEvent: MouseEvent) => { const rect = e.currentTarget.getBoundingClientRect(); const dragX = moveEvent.clientX - rect.left; @@ -627,11 +635,13 @@ function PreviewToolbar({ }; const skipBackward = () => { + pause(); const newTime = Math.max(0, currentTime - 1); // Skip 1 second back setCurrentTime(newTime); }; const skipForward = () => { + pause(); const newTime = Math.min(totalDuration, currentTime + 1); // Skip 1 second forward setCurrentTime(newTime); }; -- cgit v1.3.1 From 2f5dd8e12c099ad285b06cd4dd74cccb90c75961 Mon Sep 17 00:00:00 2001 From: WOGGER7 Date: Mon, 21 Jul 2025 14:18:48 +0530 Subject: Remove pause helper and improve timeline dragging This handles player state through isPlaying instead of a pause helper, and improves timeline seek interaction with better event handling and drag UX. --- apps/web/src/components/editor/preview-panel.tsx | 53 +++++++++++++----------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 6b1b7480..63384e62 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -33,7 +33,7 @@ interface ActiveElement { export function PreviewPanel() { const { tracks, getTotalDuration } = useTimelineStore(); const { mediaItems } = useMediaStore(); - const { currentTime, toggle, setCurrentTime, pause } = usePlaybackStore(); + const { currentTime, toggle, setCurrentTime, isPlaying } = usePlaybackStore(); const { canvasSize } = useEditorStore(); const previewRef = useRef(null); const containerRef = useRef(null); @@ -174,8 +174,8 @@ export function PreviewPanel() { case "ArrowLeft": event.preventDefault(); if (hasAnyElements) { - pause(); const frameTime = 1 / (activeProject?.fps || 30); + const totalDuration = getTotalDuration(); const newTime = Math.max( 0, currentTime - (event.shiftKey ? frameTime : 1), @@ -186,7 +186,6 @@ export function PreviewPanel() { case "ArrowRight": event.preventDefault(); if (hasAnyElements) { - pause(); const frameTime = 1 / (activeProject?.fps || 30); const totalDuration = getTotalDuration(); const newTime = Math.min( @@ -205,7 +204,8 @@ export function PreviewPanel() { case "End": event.preventDefault(); if (hasAnyElements) { - setCurrentTime(getTotalDuration()); + const totalDuration = getTotalDuration(); + setCurrentTime(totalDuration); } break; } @@ -213,15 +213,7 @@ export function PreviewPanel() { document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); - }, [ - hasAnyElements, - toggle, - currentTime, - setCurrentTime, - activeProject?.fps, - getTotalDuration, - toggle, - ]); + }, [hasAnyElements, toggle, currentTime, setCurrentTime, activeProject?.fps]); // Get active elements at current time const getActiveElements = (): ActiveElement[] => { @@ -506,7 +498,6 @@ export function PreviewPanel() { setCurrentTime={setCurrentTime} toggle={toggle} getTotalDuration={getTotalDuration} - pause={pause} />
@@ -555,7 +546,6 @@ export function PreviewPanel() { setCurrentTime={setCurrentTime} toggle={toggle} getTotalDuration={getTotalDuration} - pause={pause} />
@@ -572,7 +562,6 @@ function PreviewToolbar({ setCurrentTime, toggle, getTotalDuration, - pause, }: { hasAnyElements: boolean; onToggleExpanded: () => void; @@ -581,7 +570,6 @@ function PreviewToolbar({ setCurrentTime: (time: number) => void; toggle: () => void; getTotalDuration: () => number; - pause: () => void; }) { const { isPlaying } = usePlaybackStore(); const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); @@ -607,41 +595,57 @@ function PreviewToolbar({ const progress = totalDuration > 0 ? (currentTime / totalDuration) * 100 : 0; const handleTimelineClick = (e: React.MouseEvent) => { - pause(); + if (!hasAnyElements) return; + const rect = e.currentTarget.getBoundingClientRect(); const clickX = e.clientX - rect.left; - const percentage = clickX / rect.width; + const percentage = Math.max(0, Math.min(1, clickX / rect.width)); const newTime = percentage * totalDuration; setCurrentTime(Math.max(0, Math.min(newTime, totalDuration))); }; const handleTimelineDrag = (e: React.MouseEvent) => { - pause(); + if (!hasAnyElements) return; + + e.preventDefault(); + e.stopPropagation(); + + const rect = e.currentTarget.getBoundingClientRect(); + let isDragging = true; + const handleMouseMove = (moveEvent: MouseEvent) => { - const rect = e.currentTarget.getBoundingClientRect(); + if (!isDragging) return; + + moveEvent.preventDefault(); const dragX = moveEvent.clientX - rect.left; const percentage = Math.max(0, Math.min(1, dragX / rect.width)); const newTime = percentage * totalDuration; setCurrentTime(Math.max(0, Math.min(newTime, totalDuration))); }; - const handleMouseUp = () => { + const handleMouseUp = (moveEvent: MouseEvent) => { + isDragging = false; document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); + document.body.style.userSelect = ""; }; + // Prevent text selection during drag + document.body.style.userSelect = "none"; + document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); + + // Also handle the initial click position + handleMouseMove(e.nativeEvent); }; const skipBackward = () => { - pause(); const newTime = Math.max(0, currentTime - 1); // Skip 1 second back setCurrentTime(newTime); }; const skipForward = () => { - pause(); const newTime = Math.min(totalDuration, currentTime + 1); // Skip 1 second forward setCurrentTime(newTime); }; @@ -709,6 +713,7 @@ function PreviewToolbar({ )} onClick={hasAnyElements ? handleTimelineClick : undefined} onMouseDown={hasAnyElements ? handleTimelineDrag : undefined} + style={{ userSelect: "none" }} >
Date: Mon, 21 Jul 2025 14:23:29 +0530 Subject: Add fullscreen preview UI styling improvements --- apps/web/src/components/editor/preview-panel.tsx | 60 +++++++++++++++++++----- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 63384e62..4ffee341 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -507,19 +507,19 @@ export function PreviewPanel() {
{renderBlurBackground()} {activeElements.length === 0 ? ( -
+
No elements at current time
) : ( @@ -651,9 +651,20 @@ function PreviewToolbar({ }; return ( -
+
{/* Time Display */} -
+
{formatTimeCode(currentTime, "HH:MM:SS:FF", activeProject?.fps || 30)} @@ -674,7 +685,10 @@ function PreviewToolbar({ size="icon" onClick={skipBackward} disabled={!hasAnyElements} - className="h-auto p-0" + className={cn( + "h-auto p-0", + isExpanded && "text-white hover:text-white/80", + )} title="Skip backward 1s" > @@ -684,7 +698,10 @@ function PreviewToolbar({ size="icon" onClick={toggle} disabled={!hasAnyElements} - className="h-auto p-0" + className={cn( + "h-auto p-0", + isExpanded && "text-white hover:text-white/80", + )} > {isPlaying ? ( @@ -697,7 +714,10 @@ function PreviewToolbar({ size="icon" onClick={skipForward} disabled={!hasAnyElements} - className="h-auto p-0" + className={cn( + "h-auto p-0", + isExpanded && "text-white hover:text-white/80", + )} title="Skip forward 1s" > @@ -708,7 +728,8 @@ function PreviewToolbar({
@@ -768,7 +797,12 @@ function PreviewToolbar({