From b0cfedbc5aacdaf4df165ef4b0a61c3128aff1f2 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 4 Aug 2025 10:59:55 +0200 Subject: refactor: remove unnecessary code from tab-bar --- .../src/components/editor/media-panel/tabbar.tsx | 99 +--------------------- 1 file changed, 4 insertions(+), 95 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/media-panel/tabbar.tsx b/apps/web/src/components/editor/media-panel/tabbar.tsx index fc4f677d..8d2e3850 100644 --- a/apps/web/src/components/editor/media-panel/tabbar.tsx +++ b/apps/web/src/components/editor/media-panel/tabbar.tsx @@ -2,82 +2,22 @@ import { cn } from "@/lib/utils"; import { Tab, tabs, useMediaPanelStore } from "./store"; -import { Button } from "@/components/ui/button"; -import { ChevronRight, ChevronLeft } from "lucide-react"; -import { useRef, useState, useEffect } from "react"; export function TabBar() { const { activeTab, setActiveTab } = useMediaPanelStore(); - const scrollContainerRef = useRef(null); - const [isAtEnd, setIsAtEnd] = useState(false); - const [isAtStart, setIsAtStart] = useState(true); - - const scrollToEnd = () => { - if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTo({ - left: scrollContainerRef.current.scrollWidth, - }); - setIsAtEnd(true); - setIsAtStart(false); - } - }; - - const scrollToStart = () => { - if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTo({ - left: 0, - }); - setIsAtStart(true); - setIsAtEnd(false); - } - }; - - const checkScrollPosition = () => { - if (scrollContainerRef.current) { - const { scrollLeft, scrollWidth, clientWidth } = - scrollContainerRef.current; - const isAtEndNow = scrollLeft + clientWidth >= scrollWidth - 1; - const isAtStartNow = scrollLeft <= 1; - setIsAtEnd(isAtEndNow); - setIsAtStart(isAtStartNow); - } - }; - - // We're using useEffect because we need to sync with external DOM scroll events - useEffect(() => { - const container = scrollContainerRef.current; - if (!container) return; - - checkScrollPosition(); - container.addEventListener("scroll", checkScrollPosition); - - const resizeObserver = new ResizeObserver(checkScrollPosition); - resizeObserver.observe(container); - - return () => { - container.removeEventListener("scroll", checkScrollPosition); - resizeObserver.disconnect(); - }; - }, []); return (
- -
+
{(Object.keys(tabs) as Tab[]).map((tabKey) => { const tab = tabs[tabKey]; return (
setActiveTab(tabKey)} key={tabKey} @@ -87,37 +27,6 @@ export function TabBar() { ); })}
- -
- ); -} - -function ScrollButton({ - direction, - onClick, - isVisible, -}: { - direction: "left" | "right"; - onClick: () => void; - isVisible: boolean; -}) { - if (!isVisible) return null; - - const Icon = direction === "left" ? ChevronLeft : ChevronRight; - - return ( -
-
); } -- cgit v1.3.1 From 0386624ff5fd6d82d64d6b103ddc1a7e5045f45b Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 4 Aug 2025 12:35:14 +0200 Subject: fix: click empty area on timeline to move playhead --- apps/web/src/components/editor/timeline/index.tsx | 117 +++++++++++++++++++--- 1 file changed, 102 insertions(+), 15 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx index 7a2cc7f2..dd81b7b3 100644 --- a/apps/web/src/components/editor/timeline/index.tsx +++ b/apps/web/src/components/editor/timeline/index.tsx @@ -167,11 +167,32 @@ export function Timeline() { const handleTimelineMouseDown = useCallback((e: React.MouseEvent) => { // Only track mouse down on timeline background areas (not elements) const target = e.target as HTMLElement; + console.log( + JSON.stringify({ + debug_mousedown: "START", + target_class: target.className, + target_parent_class: target.parentElement?.className, + clientX: e.clientX, + clientY: e.clientY, + timeStamp: e.timeStamp, + }) + ); + const isTimelineBackground = !target.closest(".timeline-element") && !playheadRef.current?.contains(target) && !target.closest("[data-track-labels]"); + console.log( + JSON.stringify({ + debug_mousedown: "CHECK", + isTimelineBackground, + hasTimelineElement: !!target.closest(".timeline-element"), + hasPlayhead: !!playheadRef.current?.contains(target), + hasTrackLabels: !!target.closest("[data-track-labels]"), + }) + ); + if (isTimelineBackground) { mouseTrackingRef.current = { isMouseDown: true, @@ -179,12 +200,38 @@ export function Timeline() { downY: e.clientY, downTime: e.timeStamp, }; + console.log( + JSON.stringify({ + debug_mousedown: "TRACKED", + mouseTracking: mouseTrackingRef.current, + }) + ); + } else { + console.log( + JSON.stringify({ + debug_mousedown: "IGNORED - not timeline background", + }) + ); } }, []); // Timeline content click to seek handler const handleTimelineContentClick = useCallback( (e: React.MouseEvent) => { + console.log( + JSON.stringify({ + debug_click: "START", + target: (e.target as HTMLElement).className, + target_parent: (e.target as HTMLElement).parentElement?.className, + mouseTracking: mouseTrackingRef.current, + isSelecting, + justFinishedSelecting, + clickX: e.clientX, + clickY: e.clientY, + timeStamp: e.timeStamp, + }) + ); + const { isMouseDown, downX, downY, downTime } = mouseTrackingRef.current; // Reset mouse tracking @@ -199,8 +246,8 @@ export function Timeline() { if (!isMouseDown) { console.log( JSON.stringify({ - ignoredClickWithoutMouseDown: true, - timeStamp: e.timeStamp, + debug_click: "REJECTED - no mousedown", + mouseTracking: mouseTrackingRef.current, }) ); return; @@ -214,11 +261,10 @@ export function Timeline() { if (deltaX > 5 || deltaY > 5 || deltaTime > 500) { console.log( JSON.stringify({ - ignoredDragNotClick: true, + debug_click: "REJECTED - movement too large", deltaX, deltaY, deltaTime, - timeStamp: e.timeStamp, }) ); return; @@ -226,27 +272,54 @@ export function Timeline() { // Don't seek if this was a selection box operation if (isSelecting || justFinishedSelecting) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - selection operation", + isSelecting, + justFinishedSelecting, + }) + ); return; } // Don't seek if clicking on timeline elements, but still deselect if ((e.target as HTMLElement).closest(".timeline-element")) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - clicked timeline element", + }) + ); return; } // Don't seek if clicking on playhead if (playheadRef.current?.contains(e.target as Node)) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - clicked playhead", + }) + ); return; } // Don't seek if clicking on track labels if ((e.target as HTMLElement).closest("[data-track-labels]")) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - clicked track labels", + }) + ); clearSelectedElements(); return; } // Clear selected elements when clicking empty timeline area - console.log(JSON.stringify({ clearingSelectedElements: true })); + console.log( + JSON.stringify({ + debug_click: "PROCEEDING - clearing elements", + clearingSelectedElements: true, + }) + ); clearSelectedElements(); // Determine if we're clicking in ruler or tracks area @@ -254,24 +327,39 @@ export function Timeline() { "[data-ruler-area]" ); + console.log( + JSON.stringify({ + debug_click: "CALCULATING POSITION", + isRulerClick, + clientX: e.clientX, + clientY: e.clientY, + target_element: (e.target as HTMLElement).tagName, + target_class: (e.target as HTMLElement).className, + }) + ); + let mouseX: number; let scrollLeft = 0; if (isRulerClick) { // Calculate based on ruler position - const rulerContent = rulerScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - if (!rulerContent) return; + const rulerContent = rulerScrollRef.current; + if (!rulerContent) { + console.log( + JSON.stringify({ + debug_click: "ERROR - no ruler container found", + }) + ); + return; + } const rect = rulerContent.getBoundingClientRect(); mouseX = e.clientX - rect.left; scrollLeft = rulerContent.scrollLeft; } else { - // Calculate based on tracks content position - const tracksContent = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - if (!tracksContent) return; + const tracksContent = tracksScrollRef.current; + if (!tracksContent) { + return; + } const rect = tracksContent.getBoundingClientRect(); mouseX = e.clientX - rect.left; scrollLeft = tracksContent.scrollLeft; @@ -289,7 +377,6 @@ export function Timeline() { // Use frame snapping for timeline clicking const projectFps = activeProject?.fps || 30; const time = snapTimeToFrame(rawTime, projectFps); - seek(time); }, [ -- cgit v1.3.1 From 2fcc5e0643a36c040343bc37249e7acbd6caf6de Mon Sep 17 00:00:00 2001 From: Pratiyank Kumar <159152851+Pratiyankkumar@users.noreply.github.com> Date: Mon, 4 Aug 2025 17:46:25 +0530 Subject: feat: add tooltip in TabBar (#515) --- .../src/components/editor/media-panel/tabbar.tsx | 21 ++++-- apps/web/src/components/ui/tooltip.tsx | 78 ++++++++++++++++------ 2 files changed, 75 insertions(+), 24 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/media-panel/tabbar.tsx b/apps/web/src/components/editor/media-panel/tabbar.tsx index 8d2e3850..4c3965c4 100644 --- a/apps/web/src/components/editor/media-panel/tabbar.tsx +++ b/apps/web/src/components/editor/media-panel/tabbar.tsx @@ -2,6 +2,12 @@ import { cn } from "@/lib/utils"; import { Tab, tabs, useMediaPanelStore } from "./store"; +import { Button } from "@/components/ui/button"; +import { ChevronRight, ChevronLeft } from "lucide-react"; +import { useRef, useState, useEffect } from "react"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + + export function TabBar() { const { activeTab, setActiveTab } = useMediaPanelStore(); @@ -14,15 +20,20 @@ export function TabBar() { return (
setActiveTab(tabKey)} key={tabKey} > - + + + + + +
{tab.label}
+
+
); })} diff --git a/apps/web/src/components/ui/tooltip.tsx b/apps/web/src/components/ui/tooltip.tsx index 4971e262..bfb75e2e 100644 --- a/apps/web/src/components/ui/tooltip.tsx +++ b/apps/web/src/components/ui/tooltip.tsx @@ -1,9 +1,8 @@ -"use client"; +import { cva, type VariantProps } from 'class-variance-authority'; +import { Tooltip as TooltipPrimitive } from 'radix-ui'; +import * as React from 'react'; -import * as React from "react"; -import { Tooltip as TooltipPrimitive } from "radix-ui"; - -import { cn } from "../../lib/utils"; +import { cn } from '@/lib/utils'; const TooltipProvider = TooltipPrimitive.Provider; @@ -11,22 +10,63 @@ const Tooltip = TooltipPrimitive.Root; const TooltipTrigger = TooltipPrimitive.Trigger; +const tooltipVariants = cva( + 'z-50 overflow-visible rounded-sm text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + { + variants: { + variant: { + default: 'bg-popover text-popover-foreground border px-3 py-1.5', + destructive: + 'bg-destructive/10 text-destructive dark:bg-destructive/20 border-destructive [border-width:0.5px]', + outline: 'border-border', + important: + 'bg-amber-100/90 text-amber-900 dark:bg-amber-900/20 dark:text-amber-300 border-amber-900 [border-width:0.5px]', + promotions: + 'bg-red-100/90 text-red-900 dark:bg-red-900/20 dark:text-red-300 border-red-900 [border-width:0.5px]', + personal: + 'bg-green-100/90 text-green-900 dark:bg-green-900/20 dark:text-green-300 border-green-900 [border-width:0.5px]', + updates: + 'bg-purple-100/90 text-purple-900 dark:bg-purple-900/20 dark:text-purple-300 border-purple-900 [border-width:0.5px]', + forums: + 'bg-blue-100/90 text-blue-900 dark:bg-blue-900/20 dark:text-blue-300 border-blue-900 [border-width:0.5px]', + sidebar: 'bg-white dark:bg-[#413F3E] p-2.5 flex flex-col gap-2', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +); + +interface TooltipContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + const TooltipContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - + TooltipContentProps +>(({ className, sideOffset = 4, variant, ...props }, ref) => ( + + {variant === 'sidebar' && ( + + + + )} + {props.children} + )); TooltipContent.displayName = TooltipPrimitive.Content.displayName; -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; \ No newline at end of file -- cgit v1.3.1 From 9ec5a022520aa3a2d1e18b4e3b4056f2ff5dcd35 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 4 Aug 2025 17:17:48 +0200 Subject: feat: layout guide for tiktok --- .../public/platform-guides/tiktok-blueprint.png | Bin 0 -> 32525 bytes .../src/components/editor/layout-guide-overlay.tsx | 27 ++++++++ apps/web/src/components/editor/preview-panel.tsx | 68 ++++++++++++++++++++- apps/web/src/components/icons.tsx | 30 +++++++++ apps/web/src/stores/editor-store.ts | 32 ++++++++++ 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 apps/web/public/platform-guides/tiktok-blueprint.png create mode 100644 apps/web/src/components/editor/layout-guide-overlay.tsx (limited to 'apps/web/src/components/editor') diff --git a/apps/web/public/platform-guides/tiktok-blueprint.png b/apps/web/public/platform-guides/tiktok-blueprint.png new file mode 100644 index 00000000..3477a188 Binary files /dev/null and b/apps/web/public/platform-guides/tiktok-blueprint.png differ diff --git a/apps/web/src/components/editor/layout-guide-overlay.tsx b/apps/web/src/components/editor/layout-guide-overlay.tsx new file mode 100644 index 00000000..ff4cde57 --- /dev/null +++ b/apps/web/src/components/editor/layout-guide-overlay.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { useEditorStore } from "@/stores/editor-store"; +import Image from "next/image"; + +function TikTokGuide() { + return ( +
+ TikTok layout guide +
+ ); +} + +export function LayoutGuideOverlay() { + const { layoutGuide } = useEditorStore(); + + if (layoutGuide.platform === null) return null; + if (layoutGuide.platform === "tiktok") return ; + + return null; +} diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index b9c03382..f4cd2016 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -8,7 +8,14 @@ import { useEditorStore } from "@/stores/editor-store"; import { VideoPlayer } from "@/components/ui/video-player"; import { AudioPlayer } from "@/components/ui/audio-player"; import { Button } from "@/components/ui/button"; -import { Play, Pause, Expand, SkipBack, SkipForward } from "lucide-react"; +import { + Play, + Pause, + Expand, + SkipBack, + SkipForward, + LayoutGrid, +} from "lucide-react"; import { useState, useRef, useEffect, useCallback } from "react"; import { cn } from "@/lib/utils"; import { formatTimeCode } from "@/lib/time"; @@ -16,6 +23,16 @@ import { EditableTimecode } from "@/components/ui/editable-timecode"; import { FONT_CLASS_MAP } from "@/lib/font-config"; import { useProjectStore } from "@/stores/project-store"; import { TextElementDragState } from "@/types/editor"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Checkbox } from "@/components/ui/checkbox"; +import { LayoutGuideOverlay } from "./layout-guide-overlay"; +import { Label } from "../ui/label"; +import { SocialsIcon } from "../icons"; +import { PLATFORM_LAYOUTS, type PlatformLayout } from "@/stores/editor-store"; interface ActiveElement { element: TimelineElement; @@ -496,6 +513,7 @@ export function PreviewPanel() { renderElement(elementData, index) ) )} + {activeProject?.backgroundType === "blur" && blurBackgroundElements.length === 0 && activeElements.length > 0 && ( @@ -758,6 +776,7 @@ function FullscreenPreview({ renderElement(elementData, index) ) )} + {activeProject?.backgroundType === "blur" && blurBackgroundElements.length === 0 && activeElements.length > 0 && ( @@ -799,6 +818,7 @@ function PreviewToolbar({ getTotalDuration: () => number; }) { const { isPlaying } = usePlaybackStore(); + const { layoutGuide, toggleLayoutGuide } = useEditorStore(); if (isExpanded) { return ( @@ -818,9 +838,53 @@ function PreviewToolbar({ return (
+ + + + + +
+
+

Layout guide

+

+ Show platform-specific layout guides to help align your + content with interface elements like profile pictures, + usernames, and interaction buttons. +

+
+
+
+ toggleLayoutGuide(layoutGuide.platform || "tiktok")} + /> + +
+ {Object.entries(PLATFORM_LAYOUTS).map(([platform, label]) => ( +
+ toggleLayoutGuide(platform as PlatformLayout)} + /> + +
+ ))} +
+
+
+
@@ -875,8 +871,10 @@ function PreviewToolbar({
toggleLayoutGuide(platform as PlatformLayout)} + checked={layoutGuide.platform === platform} + onCheckedChange={() => + toggleLayoutGuide(platform as PlatformLayout) + } />
diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts index db6d014a..259533bf 100644 --- a/apps/web/src/stores/project-store.ts +++ b/apps/web/src/stores/project-store.ts @@ -7,6 +7,8 @@ import { useTimelineStore } from "./timeline-store"; import { generateUUID } from "@/lib/utils"; import { CanvasSize, CanvasMode } from "@/types/editor"; +export const DEFAULT_CANVAS_SIZE: CanvasSize = { width: 1920, height: 1080 }; + const DEFAULT_PROJECT: TProject = { id: generateUUID(), name: "Untitled", @@ -18,7 +20,7 @@ const DEFAULT_PROJECT: TProject = { blurIntensity: 8, bookmarks: [], fps: 30, - canvasSize: { width: 1920, height: 1080 }, + canvasSize: DEFAULT_CANVAS_SIZE, canvasMode: "preset", }; -- cgit v1.3.1 From dee434cd33144950905eb349726d7bbc3c00c624 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 07:14:24 +0200 Subject: style: padding top in preview toolbar --- apps/web/src/components/editor/preview-panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index d965d25f..b92922d4 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -832,7 +832,7 @@ function PreviewToolbar({ return (
-- cgit v1.3.1 From e4546120008b9e5cd97ab1f8a0c63c55559dae1d Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 07:25:14 +0200 Subject: formatting --- apps/web/src/components/editor/audio-waveform.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/audio-waveform.tsx b/apps/web/src/components/editor/audio-waveform.tsx index 372a31c5..79bf401d 100644 --- a/apps/web/src/components/editor/audio-waveform.tsx +++ b/apps/web/src/components/editor/audio-waveform.tsx @@ -20,7 +20,7 @@ const AudioWaveform: React.FC = ({ useEffect(() => { let mounted = true; let ws = wavesurfer.current; - + const initWaveSurfer = async () => { if (!waveformRef.current || !audioUrl) return; @@ -90,7 +90,7 @@ const AudioWaveform: React.FC = ({ const wsToDestroy = ws; // Detach from ref immediately wavesurfer.current = null; - + // Wait a tick to destroy so any pending operations can complete requestAnimationFrame(() => { try { @@ -111,13 +111,13 @@ const AudioWaveform: React.FC = ({ return () => { // Mark component as unmounted mounted = false; - + // Store reference to current wavesurfer instance const wsToDestroy = wavesurfer.current; - + // Immediately clear the ref to prevent accessing it after unmount wavesurfer.current = null; - + // If we have an instance to clean up, do it safely if (wsToDestroy) { // Delay destruction to avoid race conditions -- cgit v1.3.1 From 949228f0bb0c3776b0459b9bbd93af4b18094626 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 08:26:39 +0200 Subject: refactor: remove unused imports --- .../src/components/editor/media-panel/tabbar.tsx | 26 ++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/media-panel/tabbar.tsx b/apps/web/src/components/editor/media-panel/tabbar.tsx index 4c3965c4..56437508 100644 --- a/apps/web/src/components/editor/media-panel/tabbar.tsx +++ b/apps/web/src/components/editor/media-panel/tabbar.tsx @@ -2,12 +2,11 @@ import { cn } from "@/lib/utils"; import { Tab, tabs, useMediaPanelStore } from "./store"; -import { Button } from "@/components/ui/button"; -import { ChevronRight, ChevronLeft } from "lucide-react"; -import { useRef, useState, useEffect } from "react"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; - - +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; export function TabBar() { const { activeTab, setActiveTab } = useMediaPanelStore(); @@ -21,7 +20,9 @@ export function TabBar() {
setActiveTab(tabKey)} key={tabKey} @@ -30,8 +31,15 @@ export function TabBar() { - -
{tab.label}
+ +
+ {tab.label} +
-- cgit v1.3.1 From efbbc48bc5d20b8df3c1fe5feab3f6471263da4e Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 08:46:17 +0200 Subject: refactor: create base view for media panel tabs --- .../src/components/editor/media-panel/index.tsx | 7 +-- .../editor/media-panel/views/base-view.tsx | 59 ++++++++++++++++++++++ .../editor/media-panel/views/captions.tsx | 9 ++++ .../editor/media-panel/views/settings.tsx | 38 ++++++-------- .../components/editor/media-panel/views/text.tsx | 5 +- 5 files changed, 89 insertions(+), 29 deletions(-) create mode 100644 apps/web/src/components/editor/media-panel/views/base-view.tsx create mode 100644 apps/web/src/components/editor/media-panel/views/captions.tsx (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/media-panel/index.tsx b/apps/web/src/components/editor/media-panel/index.tsx index 70eb69dd..21df9924 100644 --- a/apps/web/src/components/editor/media-panel/index.tsx +++ b/apps/web/src/components/editor/media-panel/index.tsx @@ -7,6 +7,7 @@ import { TextView } from "./views/text"; import { SoundsView } from "./views/sounds"; import { Separator } from "@/components/ui/separator"; import { SettingsView } from "./views/settings"; +import { Captions } from "./views/captions"; export function MediaPanel() { const { activeTab } = useMediaPanelStore(); @@ -30,11 +31,7 @@ export function MediaPanel() { Transitions view coming soon...
), - captions: ( -
- Captions view coming soon... -
- ), + captions: , filters: (
Filters view coming soon... diff --git a/apps/web/src/components/editor/media-panel/views/base-view.tsx b/apps/web/src/components/editor/media-panel/views/base-view.tsx new file mode 100644 index 00000000..41e9a2b6 --- /dev/null +++ b/apps/web/src/components/editor/media-panel/views/base-view.tsx @@ -0,0 +1,59 @@ +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +interface BaseViewProps { + children: React.ReactNode; + defaultTab?: string; + tabs?: { + value: string; + label: string; + content: React.ReactNode; + }[]; + className?: string; +} + +function ViewContent({ children }: { children: React.ReactNode }) { + return ( + +
{children}
+
+ ); +} + +export function BaseView({ + children, + defaultTab, + tabs, + className = "", +}: BaseViewProps) { + return ( +
+ {!tabs || tabs.length === 0 ? ( + {children} + ) : ( + +
+ + {tabs.map((tab) => ( + + {tab.label} + + ))} + +
+ + {tabs.map((tab) => ( + + {tab.content} + + ))} +
+ )} +
+ ); +} diff --git a/apps/web/src/components/editor/media-panel/views/captions.tsx b/apps/web/src/components/editor/media-panel/views/captions.tsx new file mode 100644 index 00000000..ef919fec --- /dev/null +++ b/apps/web/src/components/editor/media-panel/views/captions.tsx @@ -0,0 +1,9 @@ +import { BaseView } from "./base-view"; + +export function Captions() { + return ( + +
Captions
+
+ ); +} diff --git a/apps/web/src/components/editor/media-panel/views/settings.tsx b/apps/web/src/components/editor/media-panel/views/settings.tsx index b6b79b59..419cd1ee 100644 --- a/apps/web/src/components/editor/media-panel/views/settings.tsx +++ b/apps/web/src/components/editor/media-panel/views/settings.tsx @@ -7,9 +7,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Separator } from "@/components/ui/separator"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { BaseView } from "./base-view"; import { PropertyItem, PropertyItemLabel, @@ -34,25 +32,21 @@ export function SettingsView() { function ProjectSettingsTabs() { return ( -
- -
- - Project info - Background - -
- - - - - - - - - -
-
+ , + }, + { + value: "background", + label: "Background", + content: , + }, + ]} + /> ); } diff --git a/apps/web/src/components/editor/media-panel/views/text.tsx b/apps/web/src/components/editor/media-panel/views/text.tsx index ca8e88c9..49eacbc7 100644 --- a/apps/web/src/components/editor/media-panel/views/text.tsx +++ b/apps/web/src/components/editor/media-panel/views/text.tsx @@ -1,4 +1,5 @@ import { DraggableMediaItem } from "@/components/ui/draggable-item"; +import { BaseView } from "./base-view"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; import { useTimelineStore } from "@/stores/timeline-store"; import { type TextElement } from "@/types/timeline"; @@ -28,7 +29,7 @@ const textData: TextElement = { export function TextView() { return ( -
+ -
+
); } -- cgit v1.3.1 From eafea56e99f25a17dba9266c516b3e5bdb36a3c2 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 08:51:27 +0200 Subject: style: spacing --- apps/web/src/components/editor/media-panel/views/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/media-panel/views/settings.tsx b/apps/web/src/components/editor/media-panel/views/settings.tsx index 419cd1ee..d33b4cce 100644 --- a/apps/web/src/components/editor/media-panel/views/settings.tsx +++ b/apps/web/src/components/editor/media-panel/views/settings.tsx @@ -257,7 +257,7 @@ function BackgroundView() { ); return ( -
+
{blurPreviews}
-- cgit v1.3.1 From 2a7cc13d3eee7fdad3c1174d17eee8a189f12afc Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 09:06:13 +0200 Subject: fix: ts error --- apps/web/src/components/editor/media-panel/views/base-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/media-panel/views/base-view.tsx b/apps/web/src/components/editor/media-panel/views/base-view.tsx index 41e9a2b6..296295fc 100644 --- a/apps/web/src/components/editor/media-panel/views/base-view.tsx +++ b/apps/web/src/components/editor/media-panel/views/base-view.tsx @@ -3,7 +3,7 @@ import { Separator } from "@/components/ui/separator"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; interface BaseViewProps { - children: React.ReactNode; + children?: React.ReactNode; defaultTab?: string; tabs?: { value: string; -- cgit v1.3.1 From 9c6556ea05693701450c1977267b761e0fb3488c Mon Sep 17 00:00:00 2001 From: Mathis Boulais <77585805+MathisBls@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:57:47 +0200 Subject: fix: restore horizontal scroll sync after Radix ScrollArea removal (#518) --- apps/web/src/components/editor/timeline/index.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx index dd81b7b3..bb8dfbc0 100644 --- a/apps/web/src/components/editor/timeline/index.tsx +++ b/apps/web/src/components/editor/timeline/index.tsx @@ -513,15 +513,9 @@ export function Timeline() { // --- Scroll synchronization effect --- useEffect(() => { - const rulerViewport = rulerScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - const tracksViewport = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - const trackLabelsViewport = trackLabelsScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; + const rulerViewport = rulerScrollRef.current; + const tracksViewport = tracksScrollRef.current; + const trackLabelsViewport = trackLabelsScrollRef.current; if (!rulerViewport || !tracksViewport) return; -- cgit v1.3.1 From 4f6d0142866653507fabf34644a238be44951b30 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 14:07:05 +0200 Subject: fix: remaining timeline issues with targeting wrong radix ui scroll element --- apps/web/src/components/editor/snap-indicator.tsx | 4 +--- .../src/components/editor/timeline/timeline-playhead.tsx | 8 ++------ apps/web/src/hooks/use-timeline-playhead.ts | 16 ++++------------ 3 files changed, 7 insertions(+), 21 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/snap-indicator.tsx b/apps/web/src/components/editor/snap-indicator.tsx index 0a51e107..ac884b71 100644 --- a/apps/web/src/components/editor/snap-indicator.tsx +++ b/apps/web/src/components/editor/snap-indicator.tsx @@ -28,9 +28,7 @@ export function SnapIndicator({ // Track scroll position to lock snap indicator to frame useEffect(() => { - const tracksViewport = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; + const tracksViewport = tracksScrollRef.current; if (!tracksViewport) return; diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx index 695843a4..e84512ce 100644 --- a/apps/web/src/components/editor/timeline/timeline-playhead.tsx +++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx @@ -51,9 +51,7 @@ export function TimelinePlayhead({ // Track scroll position to lock playhead to frame useEffect(() => { - const tracksViewport = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; + const tracksViewport = tracksScrollRef.current; if (!tracksViewport) return; @@ -86,9 +84,7 @@ export function TimelinePlayhead({ // Get the timeline content width and viewport width for right boundary const timelineContentWidth = duration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; - const tracksViewport = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; + const tracksViewport = tracksScrollRef.current; const viewportWidth = tracksViewport?.clientWidth || 1000; // Constrain playhead to never appear outside the timeline area diff --git a/apps/web/src/hooks/use-timeline-playhead.ts b/apps/web/src/hooks/use-timeline-playhead.ts index ac59a8cd..08587228 100644 --- a/apps/web/src/hooks/use-timeline-playhead.ts +++ b/apps/web/src/hooks/use-timeline-playhead.ts @@ -119,12 +119,8 @@ export function useTimelinePlayhead({ // Auto-scroll function during dragging const performAutoScroll = useCallback(() => { - const rulerViewport = rulerScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - const tracksViewport = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; + const rulerViewport = rulerScrollRef.current; + const tracksViewport = tracksScrollRef.current; if (!rulerViewport || !tracksViewport || !isScrubbing) return; @@ -240,12 +236,8 @@ export function useTimelinePlayhead({ // Only auto-scroll during playback, not during manual interactions if (!isPlaying || isScrubbing) return; - const rulerViewport = rulerScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - const tracksViewport = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; + const rulerViewport = rulerScrollRef.current; + const tracksViewport = tracksScrollRef.current; if (!rulerViewport || !tracksViewport) return; const playheadPx = playheadPosition * 50 * zoomLevel; // TIMELINE_CONSTANTS.PIXELS_PER_SECOND = 50 -- cgit v1.3.1 From e5a0deb777be91eef119be45ca9f5a95aadad0c6 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 5 Aug 2025 17:18:41 +0200 Subject: style: move icons on timeline toolbar --- apps/web/src/components/editor/preview-panel.tsx | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index b92922d4..cc58a598 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -835,12 +835,25 @@ function PreviewToolbar({ className="flex justify-end gap-2 h-auto pb-5 pr-5 pt-4 w-full" >
+
-
) : null} @@ -771,13 +764,6 @@ function FullscreenPreview({ ) )} - {activeProject?.backgroundType === "blur" && - blurBackgroundElements.length === 0 && - activeElements.length > 0 && ( -
- Add a video or image to use blur background -
- )}
-- cgit v1.3.1 From 8c205273b7f812103ef328aa66812260e1df3183 Mon Sep 17 00:00:00 2001 From: Sai Teja Date: Sun, 10 Aug 2025 03:06:48 +0530 Subject: fix(timeline): keep playhead below dialogs (#533) --- apps/web/src/components/editor/timeline/timeline-playhead.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx index e84512ce..a1be5b26 100644 --- a/apps/web/src/components/editor/timeline/timeline-playhead.tsx +++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx @@ -122,7 +122,7 @@ export function TimelinePlayhead({ return (
Date: Sun, 10 Aug 2025 05:39:24 +0800 Subject: fix(preview): mute button not affecting embedded video audio, ensure topmost video track renders above lower tracksmute button not affecting embedded video audio (#529) (#532) --- apps/web/src/components/editor/preview-panel.tsx | 7 +++++-- apps/web/src/components/ui/video-player.tsx | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 8842b482..a47e446a 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -234,7 +234,8 @@ export function PreviewPanel() { const getActiveElements = (): ActiveElement[] => { const activeElements: ActiveElement[] = []; - tracks.forEach((track) => { + // Iterate tracks from bottom to top so topmost track renders last (on top) + ;[...tracks].reverse().forEach((track) => { track.elements.forEach((element) => { if (element.hidden) return; const elementStart = element.startTime; @@ -311,6 +312,7 @@ export function PreviewPanel() { trimEnd={element.trimEnd} clipDuration={element.duration} className="w-full h-full object-cover" + trackMuted={true} />
); @@ -434,6 +436,7 @@ export function PreviewPanel() { trimStart={element.trimStart} trimEnd={element.trimEnd} clipDuration={element.duration} + trackMuted={elementData.track.muted} />
); @@ -459,7 +462,7 @@ export function PreviewPanel() { // Audio elements (no visual representation) if (mediaItem.type === "audio") { return ( -
+
(null); const { isPlaying, currentTime, volume, speed, muted } = usePlaybackStore(); @@ -109,9 +111,9 @@ export function VideoPlayer({ if (!video) return; video.volume = volume; - video.muted = muted; + video.muted = muted || trackMuted; video.playbackRate = speed; - }, [volume, speed, muted]); + }, [volume, speed, muted, trackMuted]); return (