mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: layout guide for tiktok
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { useEditorStore } from "@/stores/editor-store";
|
||||
import Image from "next/image";
|
||||
|
||||
function TikTokGuide() {
|
||||
return (
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<Image
|
||||
src="/platform-guides/tiktok-blueprint.png"
|
||||
alt="TikTok layout guide"
|
||||
className="absolute inset-0 w-full h-full object-contain"
|
||||
draggable={false}
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LayoutGuideOverlay() {
|
||||
const { layoutGuide } = useEditorStore();
|
||||
|
||||
if (layoutGuide.platform === null) return null;
|
||||
if (layoutGuide.platform === "tiktok") return <TikTokGuide />;
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
)}
|
||||
<LayoutGuideOverlay />
|
||||
{activeProject?.backgroundType === "blur" &&
|
||||
blurBackgroundElements.length === 0 &&
|
||||
activeElements.length > 0 && (
|
||||
@@ -758,6 +776,7 @@ function FullscreenPreview({
|
||||
renderElement(elementData, index)
|
||||
)
|
||||
)}
|
||||
<LayoutGuideOverlay />
|
||||
{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 (
|
||||
<div
|
||||
data-toolbar
|
||||
className="flex justify-between gap-2 px-1.5 pr-4 py-1.5 border border-border/50 w-auto absolute bottom-4 right-4 bg-black/20 rounded-full backdrop-blur-l text-white"
|
||||
className="flex justify-end gap-2 h-auto pb-5 pr-5 w-full"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="icon"
|
||||
className="h-auto p-0"
|
||||
title="Toggle layout guide"
|
||||
>
|
||||
<SocialsIcon className="!size-6" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Layout guide</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Show platform-specific layout guides to help align your
|
||||
content with interface elements like profile pictures,
|
||||
usernames, and interaction buttons.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="none"
|
||||
checked={layoutGuide.platform === null}
|
||||
onCheckedChange={() => toggleLayoutGuide(layoutGuide.platform || "tiktok")}
|
||||
/>
|
||||
<Label htmlFor="none">None</Label>
|
||||
</div>
|
||||
{Object.entries(PLATFORM_LAYOUTS).map(([platform, label]) => (
|
||||
<div key={platform} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={platform}
|
||||
checked={layoutGuide.platform === platform}
|
||||
onCheckedChange={() => toggleLayoutGuide(platform as PlatformLayout)}
|
||||
/>
|
||||
<Label htmlFor={platform}>{label}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button
|
||||
variant="text"
|
||||
size="icon"
|
||||
|
||||
Reference in New Issue
Block a user