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 --- apps/web/src/components/editor/preview-panel.tsx | 68 +++++++++++++++++++++++- 1 file changed, 66 insertions(+), 2 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 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/preview-panel.tsx') 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 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/preview-panel.tsx') 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 e908bad1679b8182ffbe6006e71bf38c7ada1bc0 Mon Sep 17 00:00:00 2001 From: Justin322322 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/preview-panel.tsx') 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 (