add drag and drop to the timeline
This commit is contained in:
@ -2,14 +2,20 @@
|
|||||||
|
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { AspectRatio } from "../ui/aspect-ratio";
|
import { AspectRatio } from "../ui/aspect-ratio";
|
||||||
|
import { DragOverlay } from "../ui/drag-overlay";
|
||||||
import { useMediaStore } from "@/stores/media-store";
|
import { useMediaStore } from "@/stores/media-store";
|
||||||
import { Plus, Image, Video, Music, Upload } from "lucide-react";
|
import { Plus, Image, Video, Music } from "lucide-react";
|
||||||
import { useState, useRef } from "react";
|
import { useDragDrop } from "@/hooks/use-drag-drop";
|
||||||
|
|
||||||
export function MediaPanel() {
|
export function MediaPanel() {
|
||||||
const { mediaItems, addMediaItem } = useMediaStore();
|
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 = () => {
|
const handleAddSampleMedia = () => {
|
||||||
// Just for testing - add a sample media item
|
// 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`h-full overflow-y-auto transition-colors duration-200 relative ${
|
className={`h-full overflow-y-auto transition-colors duration-200 relative ${
|
||||||
isDragOver ? "bg-accent/30 border-accent" : ""
|
isDragOver ? "bg-accent/30 border-accent" : ""
|
||||||
}`}
|
}`}
|
||||||
onDragEnter={handleDragEnter}
|
{...dragProps}
|
||||||
onDragOver={handleDragOver}
|
|
||||||
onDragLeave={handleDragLeave}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
>
|
>
|
||||||
{/* Drag Overlay */}
|
<DragOverlay isVisible={isDragOver} />
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-4 p-2 h-full">
|
<div className="space-y-4 p-2 h-full">
|
||||||
{/* Media Grid */}
|
{/* Media Grid */}
|
||||||
|
@ -17,13 +17,33 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
} from "../ui/tooltip";
|
} from "../ui/tooltip";
|
||||||
|
import { DragOverlay } from "../ui/drag-overlay";
|
||||||
import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store";
|
import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store";
|
||||||
|
import { useDragDrop } from "@/hooks/use-drag-drop";
|
||||||
|
|
||||||
export function Timeline() {
|
export function Timeline() {
|
||||||
const { tracks, addTrack } = useTimelineStore();
|
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 (
|
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 */}
|
{/* Toolbar */}
|
||||||
<div className="border-b flex items-center px-2 py-1 gap-1">
|
<div className="border-b flex items-center px-2 py-1 gap-1">
|
||||||
<TooltipProvider delayDuration={500}>
|
<TooltipProvider delayDuration={500}>
|
||||||
|
25
apps/web/src/components/ui/drag-overlay.tsx
Normal file
25
apps/web/src/components/ui/drag-overlay.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
52
apps/web/src/hooks/use-drag-drop.ts
Normal file
52
apps/web/src/hooks/use-drag-drop.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user