so much stuff???
This commit is contained in:
173
apps/web/src/components/editor/timeline.tsx
Normal file
173
apps/web/src/components/editor/timeline.tsx
Normal file
@ -0,0 +1,173 @@
|
||||
"use client";
|
||||
|
||||
import { ScrollArea } from "../ui/scroll-area";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Scissors,
|
||||
ArrowLeftToLine,
|
||||
ArrowRightToLine,
|
||||
Trash2,
|
||||
Snowflake,
|
||||
Copy,
|
||||
SplitSquareHorizontal,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
} from "../ui/tooltip";
|
||||
import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store";
|
||||
|
||||
export function Timeline() {
|
||||
const { tracks, addTrack } = useTimelineStore();
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
{/* Toolbar */}
|
||||
<div className="border-b flex items-center px-2 py-1 gap-1">
|
||||
<TooltipProvider delayDuration={500}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Scissors className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split clip (S)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<ArrowLeftToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split and keep left (A)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<ArrowRightToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split and keep right (D)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<SplitSquareHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Separate audio (E)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Duplicate clip (Ctrl+D)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Snowflake className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Freeze frame (F)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete clip (Delete)</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
{/* Tracks Area */}
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="min-w-[800px]">
|
||||
{/* Time Markers */}
|
||||
<div className="py-2 pt-1 flex items-center">
|
||||
{Array.from({ length: 16 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-[50px] flex items-end justify-center text-xs text-muted-foreground"
|
||||
>
|
||||
{i}s
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Timeline Tracks */}
|
||||
{tracks.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="w-16 h-16 rounded-full bg-muted/30 flex items-center justify-center mb-4">
|
||||
<SplitSquareHorizontal className="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No tracks in timeline
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground/70 mt-1">
|
||||
Add a video or audio track to get started
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2.5">
|
||||
{tracks.map((track) => (
|
||||
<TimelineTrackComponent key={track.id} track={track} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TimelineTrackComponent({ track }: { track: TimelineTrack }) {
|
||||
const getTrackColor = (type: string) => {
|
||||
switch (type) {
|
||||
case "video":
|
||||
return "bg-blue-500/20 border-blue-500/30";
|
||||
case "audio":
|
||||
return "bg-green-500/20 border-green-500/30";
|
||||
case "effects":
|
||||
return "bg-purple-500/20 border-purple-500/30";
|
||||
default:
|
||||
return "bg-gray-500/20 border-gray-500/30";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center px-2">
|
||||
<div className="w-24 text-xs text-muted-foreground flex-shrink-0 mr-2">
|
||||
{track.name}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 h-[60px]">
|
||||
{track.clips.length === 0 ? (
|
||||
<div className="h-full rounded-sm border-2 border-dashed border-muted/30 flex items-center justify-center text-xs text-muted-foreground">
|
||||
Drop media here
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`h-full rounded-sm border cursor-pointer transition-colors ${getTrackColor(track.type)} flex items-center px-2`}
|
||||
>
|
||||
<span className="text-xs text-foreground/80">
|
||||
{track.clips.length} clip{track.clips.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user