diff options
Diffstat (limited to 'apps/web/src/components/editor/timeline.tsx')
| -rw-r--r-- | apps/web/src/components/editor/timeline.tsx | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx new file mode 100644 index 00000000..da44022e --- /dev/null +++ b/apps/web/src/components/editor/timeline.tsx @@ -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>
+ );
+}
|
