refactor: separate timeline toolbar into new component and add new TrackType type
This commit is contained in:
219
apps/web/src/components/editor/timeline-toolbar.tsx
Normal file
219
apps/web/src/components/editor/timeline-toolbar.tsx
Normal file
@ -0,0 +1,219 @@
|
||||
"use client";
|
||||
|
||||
import type { TrackType } from "@/types/timeline";
|
||||
import {
|
||||
ArrowLeftToLine,
|
||||
ArrowRightToLine,
|
||||
Copy,
|
||||
Pause,
|
||||
Play,
|
||||
Scissors,
|
||||
Snowflake,
|
||||
SplitSquareHorizontal,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "../ui/tooltip";
|
||||
|
||||
interface TimelineToolbarProps {
|
||||
isPlaying: boolean;
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
speed: number;
|
||||
tracks: any[];
|
||||
toggle: () => void;
|
||||
setSpeed: (speed: number) => void;
|
||||
addTrack: (type: TrackType) => string;
|
||||
addClipToTrack: (trackId: string, clip: any) => void;
|
||||
handleSplitSelected: () => void;
|
||||
handleDuplicateSelected: () => void;
|
||||
handleFreezeSelected: () => void;
|
||||
handleDeleteSelected: () => void;
|
||||
}
|
||||
|
||||
export function TimelineToolbar({
|
||||
isPlaying,
|
||||
currentTime,
|
||||
duration,
|
||||
speed,
|
||||
tracks,
|
||||
toggle,
|
||||
setSpeed,
|
||||
addTrack,
|
||||
addClipToTrack,
|
||||
handleSplitSelected,
|
||||
handleDuplicateSelected,
|
||||
handleFreezeSelected,
|
||||
handleDeleteSelected,
|
||||
}: TimelineToolbarProps) {
|
||||
return (
|
||||
<div className="border-b flex items-center px-2 py-1 gap-1">
|
||||
<TooltipProvider delayDuration={500}>
|
||||
{/* Play/Pause Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="icon"
|
||||
onClick={toggle}
|
||||
className="mr-2"
|
||||
>
|
||||
{isPlaying ? (
|
||||
<Pause className="h-4 w-4" />
|
||||
) : (
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isPlaying ? "Pause (Space)" : "Play (Space)"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
|
||||
{/* Time Display */}
|
||||
<div
|
||||
className="text-xs text-muted-foreground font-mono px-2"
|
||||
style={{ minWidth: "18ch", textAlign: "center" }}
|
||||
>
|
||||
{currentTime.toFixed(1)}s / {duration.toFixed(1)}s
|
||||
</div>
|
||||
|
||||
{/* Test Clip Button - for debugging */}
|
||||
{tracks.length === 0 && (
|
||||
<>
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const trackId = addTrack("video");
|
||||
addClipToTrack(trackId, {
|
||||
mediaId: "test",
|
||||
name: "Test Clip",
|
||||
duration: 5,
|
||||
startTime: 0,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
});
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
Add Test Clip
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add a test clip to try playback</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon" onClick={handleSplitSelected}>
|
||||
<Scissors className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split clip (S)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon">
|
||||
<ArrowLeftToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split and keep left (A)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon">
|
||||
<ArrowRightToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split and keep right (D)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon">
|
||||
<SplitSquareHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Separate audio (E)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="icon"
|
||||
onClick={handleDuplicateSelected}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Duplicate clip (Ctrl+D)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon" onClick={handleFreezeSelected}>
|
||||
<Snowflake className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Freeze frame (F)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon" onClick={handleDeleteSelected}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete clip (Delete)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
|
||||
{/* Speed Control */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Select
|
||||
value={speed.toFixed(1)}
|
||||
onValueChange={(value) => setSpeed(parseFloat(value))}
|
||||
>
|
||||
<SelectTrigger className="w-[90px] h-8">
|
||||
<SelectValue placeholder="1.0x" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0.5">0.5x</SelectItem>
|
||||
<SelectItem value="1.0">1.0x</SelectItem>
|
||||
<SelectItem value="1.5">1.5x</SelectItem>
|
||||
<SelectItem value="2.0">2.0x</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Playback Speed</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -5,14 +5,9 @@ import { useMediaStore } from "@/stores/media-store";
|
||||
import { usePlaybackStore } from "@/stores/playback-store";
|
||||
import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store";
|
||||
import {
|
||||
ArrowLeftToLine,
|
||||
ArrowRightToLine,
|
||||
Copy,
|
||||
MoreVertical,
|
||||
Pause,
|
||||
Play,
|
||||
Scissors,
|
||||
Snowflake,
|
||||
SplitSquareHorizontal,
|
||||
Trash2,
|
||||
Volume2,
|
||||
@ -22,23 +17,9 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../ui/button";
|
||||
import { ScrollArea } from "../ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "../ui/tooltip";
|
||||
|
||||
import AudioWaveform from "./audio-waveform";
|
||||
|
||||
import { TimelineToolbar } from "./timeline-toolbar";
|
||||
|
||||
export function Timeline() {
|
||||
// Timeline shows all tracks (video, audio, effects) and their clips.
|
||||
@ -71,7 +52,6 @@ export function Timeline() {
|
||||
speed,
|
||||
} = usePlaybackStore();
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [zoomLevel, setZoomLevel] = useState(1);
|
||||
const dragCounterRef = useRef(0);
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
@ -217,7 +197,7 @@ export function Timeline() {
|
||||
const bx2 = clamp(x2, 0, rect.width);
|
||||
const by1 = clamp(y1, 0, rect.height);
|
||||
const by2 = clamp(y2, 0, rect.height);
|
||||
let newSelection: { trackId: string; clipId: string; }[] = [];
|
||||
let newSelection: { trackId: string; clipId: string }[] = [];
|
||||
tracks.forEach((track, trackIdx) => {
|
||||
track.clips.forEach((clip) => {
|
||||
const effectiveDuration = clip.duration - clip.trimStart - clip.trimEnd;
|
||||
@ -335,8 +315,6 @@ export function Timeline() {
|
||||
toast.error("Failed to add media to timeline");
|
||||
}
|
||||
} else if (e.dataTransfer.files?.length > 0) {
|
||||
// Handle file drops by creating new tracks
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const processedItems = await processMediaFiles(e.dataTransfer.files);
|
||||
for (const processedItem of processedItems) {
|
||||
@ -364,8 +342,6 @@ export function Timeline() {
|
||||
// Show error if file processing fails
|
||||
console.error("Error processing external files:", error);
|
||||
toast.error("Failed to process dropped files");
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -570,162 +546,21 @@ export function Timeline() {
|
||||
onMouseLeave={() => setIsInTimeline(false)}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
{/* Toolbar */}
|
||||
<div className="border-b flex items-center px-2 py-1 gap-1">
|
||||
<TooltipProvider delayDuration={500}>
|
||||
{/* Play/Pause Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="icon"
|
||||
onClick={toggle}
|
||||
className="mr-2"
|
||||
>
|
||||
{isPlaying ? (
|
||||
<Pause className="h-4 w-4" />
|
||||
) : (
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isPlaying ? "Pause (Space)" : "Play (Space)"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
|
||||
{/* Time Display */}
|
||||
<div className="text-xs text-muted-foreground font-mono px-2"
|
||||
style={{ minWidth: '18ch', textAlign: 'center' }}
|
||||
>
|
||||
{currentTime.toFixed(1)}s / {duration.toFixed(1)}s
|
||||
</div>
|
||||
|
||||
{/* Test Clip Button - for debugging */}
|
||||
{tracks.length === 0 && (
|
||||
<>
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const trackId = addTrack("video");
|
||||
addClipToTrack(trackId, {
|
||||
mediaId: "test",
|
||||
name: "Test Clip",
|
||||
duration: 5,
|
||||
startTime: 0,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
});
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
Add Test Clip
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add a test clip to try playback</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon" onClick={handleSplitSelected}>
|
||||
<Scissors className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split clip (S)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon">
|
||||
<ArrowLeftToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split and keep left (A)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon">
|
||||
<ArrowRightToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Split and keep right (D)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon">
|
||||
<SplitSquareHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Separate audio (E)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="icon"
|
||||
onClick={handleDuplicateSelected}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Duplicate clip (Ctrl+D)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon" onClick={handleFreezeSelected}>
|
||||
<Snowflake className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Freeze frame (F)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="text" size="icon" onClick={handleDeleteSelected}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete clip (Delete)</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="w-px h-6 bg-border mx-1" />
|
||||
|
||||
{/* Speed Control */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Select
|
||||
value={speed.toFixed(1)}
|
||||
onValueChange={(value) => setSpeed(parseFloat(value))}
|
||||
>
|
||||
<SelectTrigger className="w-[90px] h-8">
|
||||
<SelectValue placeholder="1.0x" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0.5">0.5x</SelectItem>
|
||||
<SelectItem value="1.0">1.0x</SelectItem>
|
||||
<SelectItem value="1.5">1.5x</SelectItem>
|
||||
<SelectItem value="2.0">2.0x</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Playback Speed</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<TimelineToolbar
|
||||
isPlaying={isPlaying}
|
||||
currentTime={currentTime}
|
||||
duration={duration}
|
||||
speed={speed}
|
||||
tracks={tracks}
|
||||
toggle={toggle}
|
||||
setSpeed={setSpeed}
|
||||
addTrack={addTrack}
|
||||
addClipToTrack={addClipToTrack}
|
||||
handleSplitSelected={handleSplitSelected}
|
||||
handleDuplicateSelected={handleDuplicateSelected}
|
||||
handleFreezeSelected={handleFreezeSelected}
|
||||
handleDeleteSelected={handleDeleteSelected}
|
||||
/>
|
||||
|
||||
{/* Timeline Container */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
@ -781,17 +616,19 @@ export function Timeline() {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`absolute top-0 bottom-0 ${isMainMarker
|
||||
? "border-l border-muted-foreground/40"
|
||||
: "border-l border-muted-foreground/20"
|
||||
}`}
|
||||
className={`absolute top-0 bottom-0 ${
|
||||
isMainMarker
|
||||
? "border-l border-muted-foreground/40"
|
||||
: "border-l border-muted-foreground/20"
|
||||
}`}
|
||||
style={{ left: `${time * 50 * zoomLevel}px` }}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-1 left-1 text-xs ${isMainMarker
|
||||
? "text-muted-foreground font-medium"
|
||||
: "text-muted-foreground/70"
|
||||
}`}
|
||||
className={`absolute top-1 left-1 text-xs ${
|
||||
isMainMarker
|
||||
? "text-muted-foreground font-medium"
|
||||
: "text-muted-foreground/70"
|
||||
}`}
|
||||
>
|
||||
{(() => {
|
||||
const formatTime = (seconds: number) => {
|
||||
@ -852,12 +689,13 @@ export function Timeline() {
|
||||
>
|
||||
<div className="flex items-center flex-1 min-w-0">
|
||||
<div
|
||||
className={`w-3 h-3 rounded-full flex-shrink-0 ${track.type === "video"
|
||||
? "bg-blue-500"
|
||||
: track.type === "audio"
|
||||
? "bg-green-500"
|
||||
: "bg-purple-500"
|
||||
}`}
|
||||
className={`w-3 h-3 rounded-full flex-shrink-0 ${
|
||||
track.type === "video"
|
||||
? "bg-blue-500"
|
||||
: track.type === "audio"
|
||||
? "bg-green-500"
|
||||
: "bg-purple-500"
|
||||
}`}
|
||||
/>
|
||||
<span className="ml-2 text-sm font-medium truncate">
|
||||
{track.name}
|
||||
@ -1197,7 +1035,7 @@ function TimelineTrackContent({
|
||||
});
|
||||
};
|
||||
|
||||
const updateTrimFromMouseMove = (e: { clientX: number; }) => {
|
||||
const updateTrimFromMouseMove = (e: { clientX: number }) => {
|
||||
if (!resizing) return;
|
||||
|
||||
const clip = track.clips.find((c) => c.id === resizing.clipId);
|
||||
@ -1632,18 +1470,18 @@ function TimelineTrackContent({
|
||||
}
|
||||
|
||||
if (mediaItem.type === "audio") {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<AudioWaveform
|
||||
audioUrl={mediaItem.url}
|
||||
height={24}
|
||||
className="w-full"
|
||||
/>
|
||||
return (
|
||||
<div className="w-full h-full flex items-center gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<AudioWaveform
|
||||
audioUrl={mediaItem.url}
|
||||
height={24}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback for videos without thumbnails
|
||||
return (
|
||||
@ -1681,12 +1519,13 @@ function TimelineTrackContent({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full h-full transition-all duration-150 ease-out ${isDraggedOver
|
||||
? wouldOverlap
|
||||
? "bg-red-500/15 border-2 border-dashed border-red-400 shadow-lg"
|
||||
: "bg-blue-500/15 border-2 border-dashed border-blue-400 shadow-lg"
|
||||
: "hover:bg-muted/20"
|
||||
}`}
|
||||
className={`w-full h-full transition-all duration-150 ease-out ${
|
||||
isDraggedOver
|
||||
? wouldOverlap
|
||||
? "bg-red-500/15 border-2 border-dashed border-red-400 shadow-lg"
|
||||
: "bg-blue-500/15 border-2 border-dashed border-blue-400 shadow-lg"
|
||||
: "hover:bg-muted/20"
|
||||
}`}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
// Only show track menu if we didn't click on a clip
|
||||
@ -1710,12 +1549,13 @@ function TimelineTrackContent({
|
||||
<div className="h-full relative track-clips-container min-w-full">
|
||||
{track.clips.length === 0 ? (
|
||||
<div
|
||||
className={`h-full w-full rounded-sm border-2 border-dashed flex items-center justify-center text-xs text-muted-foreground transition-colors ${isDropping
|
||||
? wouldOverlap
|
||||
? "border-red-500 bg-red-500/10 text-red-600"
|
||||
: "border-blue-500 bg-blue-500/10 text-blue-600"
|
||||
: "border-muted/30"
|
||||
}`}
|
||||
className={`h-full w-full rounded-sm border-2 border-dashed flex items-center justify-center text-xs text-muted-foreground transition-colors ${
|
||||
isDropping
|
||||
? wouldOverlap
|
||||
? "border-red-500 bg-red-500/10 text-red-600"
|
||||
: "border-blue-500 bg-blue-500/10 text-blue-600"
|
||||
: "border-muted/30"
|
||||
}`}
|
||||
>
|
||||
{isDropping
|
||||
? wouldOverlap
|
||||
@ -1860,4 +1700,3 @@ function TimelineTrackContent({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { create } from "zustand";
|
||||
import type { TrackType } from "@/types/timeline";
|
||||
|
||||
export interface TimelineClip {
|
||||
id: string;
|
||||
@ -13,7 +14,7 @@ export interface TimelineClip {
|
||||
export interface TimelineTrack {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "video" | "audio" | "effects";
|
||||
type: TrackType;
|
||||
clips: TimelineClip[];
|
||||
muted?: boolean;
|
||||
}
|
||||
@ -52,7 +53,7 @@ interface TimelineStore {
|
||||
endDrag: () => void;
|
||||
|
||||
// Actions
|
||||
addTrack: (type: "video" | "audio" | "effects") => string;
|
||||
addTrack: (type: TrackType) => string;
|
||||
removeTrack: (trackId: string) => void;
|
||||
addClipToTrack: (trackId: string, clip: Omit<TimelineClip, "id">) => void;
|
||||
removeClipFromTrack: (trackId: string, clipId: string) => void;
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { TimelineTrack, TimelineClip } from "@/stores/timeline-store";
|
||||
|
||||
export type TrackType = "video" | "audio" | "effects";
|
||||
|
||||
export interface TimelineClipProps {
|
||||
clip: TimelineClip;
|
||||
track: TimelineTrack;
|
||||
|
Reference in New Issue
Block a user