enabled-clip-selection

This commit is contained in:
aashishparuvada
2025-06-23 20:05:49 +05:30
parent 294ba01abe
commit 12d26c574b
3 changed files with 669 additions and 278 deletions

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,7 @@ export function Timeline() {
// Timeline shows all tracks (video, audio, effects) and their clips. // Timeline shows all tracks (video, audio, effects) and their clips.
// You can drag media here to add it to your project. // You can drag media here to add it to your project.
// Clips can be trimmed, deleted, and moved. // Clips can be trimmed, deleted, and moved.
const { tracks, addTrack, addClipToTrack, removeTrack, toggleTrackMute, removeClipFromTrack, moveClipToTrack, getTotalDuration } = const { tracks, addTrack, addClipToTrack, removeTrack, toggleTrackMute, removeClipFromTrack, moveClipToTrack, getTotalDuration, selectedClip, clearSelectedClip } =
useTimelineStore(); useTimelineStore();
const { mediaItems, addMediaItem } = useMediaStore(); const { mediaItems, addMediaItem } = useMediaStore();
const { currentTime, duration, seek, setDuration, isPlaying, play, pause, toggle } = usePlaybackStore(); const { currentTime, duration, seek, setDuration, isPlaying, play, pause, toggle } = usePlaybackStore();
@ -67,6 +67,18 @@ export function Timeline() {
} }
}, [contextMenu]); }, [contextMenu]);
// Keyboard event for deleting selected clip
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.key === "Delete" || e.key === "Backspace") && selectedClip) {
removeClipFromTrack(selectedClip.trackId, selectedClip.clipId);
clearSelectedClip();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedClip, removeClipFromTrack, clearSelectedClip]);
const handleDragEnter = (e: React.DragEvent) => { const handleDragEnter = (e: React.DragEvent) => {
// When something is dragged over the timeline, show overlay // When something is dragged over the timeline, show overlay
e.preventDefault(); e.preventDefault();
@ -699,6 +711,8 @@ function TimelineTrackContent({
addClipToTrack, addClipToTrack,
removeClipFromTrack, removeClipFromTrack,
toggleTrackMute, toggleTrackMute,
selectedClip,
selectClip,
} = useTimelineStore(); } = useTimelineStore();
const { currentTime } = usePlaybackStore(); const { currentTime } = usePlaybackStore();
const [isDropping, setIsDropping] = useState(false); const [isDropping, setIsDropping] = useState(false);
@ -1261,11 +1275,19 @@ function TimelineTrackContent({
); );
const clipLeft = clip.startTime * 50 * zoomLevel; const clipLeft = clip.startTime * 50 * zoomLevel;
// Correctly declare isSelected inside the map
const isSelected = selectedClip && selectedClip.trackId === track.id && selectedClip.clipId === clip.id;
return ( return (
<div <div
key={clip.id} key={clip.id}
className={`timeline-clip absolute h-full rounded-sm border transition-all duration-200 ${getTrackColor(track.type)} flex items-center py-3 min-w-[80px] overflow-hidden group hover:shadow-lg`} className={`timeline-clip absolute h-full rounded-sm border transition-all duration-200 ${getTrackColor(track.type)} flex items-center py-3 min-w-[80px] overflow-hidden group hover:shadow-lg ${isSelected ? "ring-2 ring-blue-500 z-10" : ""}`}
style={{ width: `${clipWidth}px`, left: `${clipLeft}px` }} style={{ width: `${clipWidth}px`, left: `${clipLeft}px` }}
onClick={(e) => {
e.stopPropagation();
selectClip(track.id, clip.id);
}}
tabIndex={0}
onContextMenu={(e) => { onContextMenu={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -1342,16 +1364,13 @@ function TimelineTrackContent({
}} }}
> >
<div <div
className={`absolute -top-2 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full border-2 border-white shadow-md ${wouldOverlap ? "bg-red-500" : "bg-blue-500" className={`absolute -top-2 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full border-2 border-white shadow-md ${wouldOverlap ? "bg-red-500" : "bg-blue-500"}`}
}`}
/> />
<div <div
className={`absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full border-2 border-white shadow-md ${wouldOverlap ? "bg-red-500" : "bg-blue-500" className={`absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full border-2 border-white shadow-md ${wouldOverlap ? "bg-red-500" : "bg-blue-500"}`}
}`}
/> />
<div <div
className={`absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs text-white px-1 py-0.5 rounded whitespace-nowrap ${wouldOverlap ? "bg-red-500" : "bg-blue-500" className={`absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs text-white px-1 py-0.5 rounded whitespace-nowrap ${wouldOverlap ? "bg-red-500" : "bg-blue-500"}`}
}`}
> >
{wouldOverlap ? "⚠️" : ""} {wouldOverlap ? "⚠️" : ""}
{dropPosition.toFixed(1)}s {dropPosition.toFixed(1)}s

View File

@ -21,6 +21,11 @@ export interface TimelineTrack {
interface TimelineStore { interface TimelineStore {
tracks: TimelineTrack[]; tracks: TimelineTrack[];
// Selection
selectedClip: { trackId: string; clipId: string } | null;
selectClip: (trackId: string, clipId: string) => void;
clearSelectedClip: () => void;
// Actions // Actions
addTrack: (type: "video" | "audio" | "effects") => string; addTrack: (type: "video" | "audio" | "effects") => string;
removeTrack: (trackId: string) => void; removeTrack: (trackId: string) => void;
@ -50,6 +55,14 @@ interface TimelineStore {
export const useTimelineStore = create<TimelineStore>((set, get) => ({ export const useTimelineStore = create<TimelineStore>((set, get) => ({
tracks: [], tracks: [],
selectedClip: null,
selectClip: (trackId, clipId) => {
set({ selectedClip: { trackId, clipId } });
},
clearSelectedClip: () => {
set({ selectedClip: null });
},
addTrack: (type) => { addTrack: (type) => {
const newTrack: TimelineTrack = { const newTrack: TimelineTrack = {