add drag and drop to the timeline

This commit is contained in:
Maze Winther
2025-06-22 14:50:22 +02:00
parent 6c8d6d99a9
commit cb2a7484ed
4 changed files with 110 additions and 48 deletions

View File

@ -2,14 +2,20 @@
import { Button } from "../ui/button";
import { AspectRatio } from "../ui/aspect-ratio";
import { DragOverlay } from "../ui/drag-overlay";
import { useMediaStore } from "@/stores/media-store";
import { Plus, Image, Video, Music, Upload } from "lucide-react";
import { useState, useRef } from "react";
import { Plus, Image, Video, Music } from "lucide-react";
import { useDragDrop } from "@/hooks/use-drag-drop";
export function MediaPanel() {
const { mediaItems, addMediaItem } = useMediaStore();
const [isDragOver, setIsDragOver] = useState(false);
const dragCounterRef = useRef(0);
const { isDragOver, dragProps } = useDragDrop({
onDrop: (files) => {
// TODO: Handle file drop functionality
console.log("Files dropped on media panel:", files);
},
});
const handleAddSampleMedia = () => {
// Just for testing - add a sample media item
@ -30,55 +36,14 @@ export function MediaPanel() {
}
};
const handleDragEnter = (e: React.DragEvent) => {
e.preventDefault();
dragCounterRef.current += 1;
if (!isDragOver) {
setIsDragOver(true);
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
dragCounterRef.current -= 1;
if (dragCounterRef.current === 0) {
setIsDragOver(false);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setIsDragOver(false);
dragCounterRef.current = 0;
// TODO: Handle file drop functionality
};
return (
<div
className={`h-full overflow-y-auto transition-colors duration-200 relative ${
isDragOver ? "bg-accent/30 border-accent" : ""
}`}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
{...dragProps}
>
{/* Drag Overlay */}
{isDragOver && (
<div className="absolute inset-0 bg-accent/20 backdrop-blur-lg border-2 border-dashed border-accent rounded-lg flex items-center justify-center z-10 pointer-events-none">
<div className="text-center">
<Upload className="h-8 w-8 text-accent mx-auto mb-2" />
<p className="text-sm font-medium text-accent">Drop files here</p>
<p className="text-xs text-muted-foreground">
Images, videos, and audio files
</p>
</div>
</div>
)}
<DragOverlay isVisible={isDragOver} />
<div className="space-y-4 p-2 h-full">
{/* Media Grid */}

View File

@ -17,13 +17,33 @@ import {
TooltipTrigger,
TooltipProvider,
} from "../ui/tooltip";
import { DragOverlay } from "../ui/drag-overlay";
import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store";
import { useDragDrop } from "@/hooks/use-drag-drop";
export function Timeline() {
const { tracks, addTrack } = useTimelineStore();
const { isDragOver, dragProps } = useDragDrop({
onDrop: (files) => {
// TODO: Handle file drop functionality for timeline
console.log("Files dropped on timeline:", files);
},
});
return (
<div className="h-full flex flex-col">
<div
className={`h-full flex flex-col transition-colors duration-200 relative ${
isDragOver ? "bg-accent/30 border-accent" : ""
}`}
{...dragProps}
>
<DragOverlay
isVisible={isDragOver}
title="Drop files here"
description="Add media to timeline tracks"
/>
{/* Toolbar */}
<div className="border-b flex items-center px-2 py-1 gap-1">
<TooltipProvider delayDuration={500}>

View File

@ -0,0 +1,25 @@
import { Upload } from "lucide-react";
interface DragOverlayProps {
isVisible: boolean;
title?: string;
description?: string;
}
export function DragOverlay({
isVisible,
title = "Drop files here",
description = "Images, videos, and audio files",
}: DragOverlayProps) {
if (!isVisible) return null;
return (
<div className="absolute inset-0 bg-accent/20 backdrop-blur-lg border-2 border-dashed border-accent flex items-center justify-center z-10 pointer-events-none">
<div className="text-center">
<Upload className="h-8 w-8 text-accent mx-auto mb-2" />
<p className="text-sm font-medium text-accent">{title}</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
</div>
);
}

View File

@ -0,0 +1,52 @@
import { useState, useRef } from "react";
interface UseDragDropOptions {
onDrop?: (files: FileList) => void;
}
export function useDragDrop(options: UseDragDropOptions = {}) {
const [isDragOver, setIsDragOver] = useState(false);
const dragCounterRef = useRef(0);
const handleDragEnter = (e: React.DragEvent) => {
e.preventDefault();
dragCounterRef.current += 1;
if (!isDragOver) {
setIsDragOver(true);
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
dragCounterRef.current -= 1;
if (dragCounterRef.current === 0) {
setIsDragOver(false);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setIsDragOver(false);
dragCounterRef.current = 0;
if (options.onDrop && e.dataTransfer.files) {
options.onDrop(e.dataTransfer.files);
}
};
const dragProps = {
onDragEnter: handleDragEnter,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
};
return {
isDragOver,
dragProps,
};
}