summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/preview-panel.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components/editor/preview-panel.tsx')
-rw-r--r--apps/web/src/components/editor/preview-panel.tsx129
1 files changed, 85 insertions, 44 deletions
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx
index 6cb06061..c6f4e159 100644
--- a/apps/web/src/components/editor/preview-panel.tsx
+++ b/apps/web/src/components/editor/preview-panel.tsx
@@ -20,6 +20,7 @@ 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";
+import { EditableTimecode } from "@/components/ui/editable-timecode";
import { FONT_CLASS_MAP } from "@/lib/font-config";
import { BackgroundSettings } from "../background-settings";
import { useProjectStore } from "@/stores/project-store";
@@ -233,6 +234,7 @@ export function PreviewPanel() {
tracks.forEach((track) => {
track.elements.forEach((element) => {
+ if (element.hidden) return;
const elementStart = element.startTime;
const elementEnd =
element.startTime +
@@ -357,20 +359,18 @@ export function PreviewPanel() {
style={{
left: `${
50 +
- (
- (dragState.isDragging && dragState.elementId === element.id
- ? dragState.currentX
- : element.x) / canvasSize.width
- ) *
+ ((dragState.isDragging && dragState.elementId === element.id
+ ? dragState.currentX
+ : element.x) /
+ canvasSize.width) *
100
}%`,
top: `${
50 +
- (
- (dragState.isDragging && dragState.elementId === element.id
- ? dragState.currentY
- : element.y) / canvasSize.height
- ) *
+ ((dragState.isDragging && dragState.elementId === element.id
+ ? dragState.currentY
+ : element.y) /
+ canvasSize.height) *
100
}%`,
transform: `translate(-50%, -50%) rotate(${element.rotation}deg) scale(${scaleRatio})`,
@@ -408,7 +408,7 @@ export function PreviewPanel() {
return (
<div
key={element.id}
- className="absolute inset-0 bg-gradient-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center"
+ className="absolute inset-0 bg-linear-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center"
>
<div className="text-center">
<div className="text-2xl mb-2">🎬</div>
@@ -564,7 +564,7 @@ function FullscreenToolbar({
toggle: () => void;
getTotalDuration: () => number;
}) {
- const { isPlaying } = usePlaybackStore();
+ const { isPlaying, seek } = usePlaybackStore();
const { activeProject } = useProjectStore();
const [isDragging, setIsDragging] = useState(false);
@@ -624,9 +624,15 @@ function FullscreenToolbar({
className="flex items-center gap-2 p-1 pt-2 w-full text-white"
>
<div className="flex items-center gap-1 text-[0.70rem] tabular-nums text-white/90">
- <span className="text-primary">
- {formatTimeCode(currentTime, "HH:MM:SS:FF", activeProject?.fps || 30)}
- </span>
+ <EditableTimecode
+ time={currentTime}
+ duration={totalDuration}
+ format="HH:MM:SS:FF"
+ fps={activeProject?.fps || 30}
+ onTimeChange={seek}
+ disabled={!hasAnyElements}
+ className="text-white/90 hover:bg-white/10"
+ />
<span className="opacity-50">/</span>
<span>
{formatTimeCode(
@@ -691,7 +697,7 @@ function FullscreenToolbar({
style={{ width: `${progress}%` }}
/>
<div
- className="absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-sm bg-white border border-black/20"
+ className="absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-xs bg-white border border-black/20"
style={{ left: `${progress}%` }}
/>
</div>
@@ -700,11 +706,11 @@ function FullscreenToolbar({
<Button
variant="text"
size="icon"
- className="!size-4 text-white/80 hover:text-white"
+ className="size-4! text-white/80 hover:text-white"
onClick={onToggleExpanded}
title="Exit fullscreen (Esc)"
>
- <Expand className="!size-4" />
+ <Expand className="size-4!" />
</Button>
</div>
);
@@ -738,7 +744,7 @@ function FullscreenPreview({
getTotalDuration: () => number;
}) {
return (
- <div className="fixed inset-0 z-[9999] flex flex-col">
+ <div className="fixed inset-0 z-9999 flex flex-col">
<div className="flex-1 flex items-center justify-center bg-background">
<div
className="relative overflow-hidden border border-border m-3"
@@ -801,7 +807,7 @@ function PreviewToolbar({
toggle: () => void;
getTotalDuration: () => number;
}) {
- const { isPlaying } = usePlaybackStore();
+ const { isPlaying, seek } = usePlaybackStore();
const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore();
const { activeProject } = useProjectStore();
const {
@@ -821,6 +827,18 @@ function PreviewToolbar({
setCanvasSizeToOriginal(aspectRatio);
};
+ const totalDuration = getTotalDuration();
+
+ const skipBackward = () => {
+ const newTime = Math.max(0, currentTime - 1);
+ setCurrentTime(newTime);
+ };
+
+ const skipForward = () => {
+ const newTime = Math.min(totalDuration, currentTime + 1);
+ setCurrentTime(newTime);
+ };
+
if (isExpanded) {
return (
<FullscreenToolbar
@@ -844,17 +862,18 @@ function PreviewToolbar({
<div>
<p
className={cn(
- "text-[0.75rem] text-muted-foreground flex items-center gap-1",
+ "text-[0.75rem] text-muted-foreground flex items-center gap-1 w-[10rem]",
!hasAnyElements && "opacity-50"
)}
>
- <span className="text-primary tabular-nums">
- {formatTimeCode(
- currentTime,
- "HH:MM:SS:FF",
- activeProject?.fps || 30
- )}
- </span>
+ <EditableTimecode
+ time={currentTime}
+ duration={getTotalDuration()}
+ format="HH:MM:SS:FF"
+ fps={activeProject?.fps || 30}
+ onTimeChange={seek}
+ disabled={!hasAnyElements}
+ />
<span className="opacity-50">/</span>
<span className="tabular-nums">
{formatTimeCode(
@@ -865,26 +884,48 @@ function PreviewToolbar({
</span>
</p>
</div>
- <Button
- variant="text"
- size="icon"
- onClick={toggle}
- disabled={!hasAnyElements}
- className="h-auto p-0"
- >
- {isPlaying ? (
- <Pause className="h-3 w-3" />
- ) : (
- <Play className="h-3 w-3" />
- )}
- </Button>
+ <div className="flex items-center gap-1">
+ <Button
+ variant="text"
+ size="icon"
+ onClick={skipBackward}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ title="Skip backward 1s"
+ >
+ <SkipBack className="h-3 w-3" />
+ </Button>
+ <Button
+ variant="text"
+ size="icon"
+ onClick={toggle}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ >
+ {isPlaying ? (
+ <Pause className="h-3 w-3" />
+ ) : (
+ <Play className="h-3 w-3" />
+ )}
+ </Button>
+ <Button
+ variant="text"
+ size="icon"
+ onClick={skipForward}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ title="Skip forward 1s"
+ >
+ <SkipForward className="h-3 w-3" />
+ </Button>
+ </div>
<div className="flex items-center gap-3">
<BackgroundSettings />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
- className="!bg-panel-accent text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light"
+ className="bg-panel-accent! text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light"
disabled={!hasAnyElements}
>
{getDisplayName()}
@@ -915,11 +956,11 @@ function PreviewToolbar({
<Button
variant="text"
size="icon"
- className="!size-4 text-muted-foreground"
+ className="size-4! text-muted-foreground"
onClick={onToggleExpanded}
title="Enter fullscreen"
>
- <Expand className="!size-4" />
+ <Expand className="size-4!" />
</Button>
</div>
</div>