Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 60 additions & 14 deletions app/components/timeline/MediaBin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,29 @@ const AudioPreview = ({ src }: { src: string }) => {
);
};

// Empty state component for when no media files are present
const EmptyState = memo(({ onUploadClick }: { onUploadClick: () => void }) => {
return (
<div className="flex flex-col items-center justify-center py-8 text-center">
<button
onClick={onUploadClick}
className="flex flex-col items-center gap-3 p-6 rounded-lg transition-colors hover:bg-accent/30 cursor-pointer border-2 border-dashed border-border/30 hover:border-primary/50"
title="Click to import media"
>
<Upload className="h-10 w-10 text-muted-foreground/50" />
<div>
<p className="text-xs text-muted-foreground font-medium">
No media files
</p>
<p className="text-xs text-muted-foreground/70 mt-0.5">
Import videos, images, or audio to get started
</p>
</div>
</button>
</div>
);
});

// This is required for the data router
export function loader() {
return null;
Expand All @@ -251,6 +274,9 @@ export default function MediaBin() {
// Drag & Drop state for external file imports
const [isDragOver, setIsDragOver] = useState(false);

// File input ref for click-to-upload
const fileInputRef = useRef<HTMLInputElement>(null);

// Arrange & sorting state
const [arrangeMode, setArrangeMode] = useState<"default" | "group">(
"default"
Expand Down Expand Up @@ -360,6 +386,28 @@ export default function MediaBin() {
[onAddMedia]
);

const handleUploadClick = useCallback(() => {
fileInputRef.current?.click();
}, []);

const handleFileInputChange = useCallback(
async (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
for (const file of files) {
try {
await onAddMedia(file);
} catch (err) {
console.error("Failed to import file:", file.name, err);
}
}
// Reset input so same file can be selected again
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
},
[onAddMedia]
);

const getMediaIcon = (mediaType: string) => {
switch (mediaType) {
case "video":
Expand Down Expand Up @@ -711,13 +759,7 @@ export default function MediaBin() {
))}

{defaultArrangedItems.length === 0 && (
<div className="flex flex-col items-center justify-center py-8 text-center">
<FileImage className="h-8 w-8 text-muted-foreground/50 mb-3" />
<p className="text-xs text-muted-foreground">No media files</p>
<p className="text-xs text-muted-foreground/70 mt-0.5">
Import videos, images, or audio to get started
</p>
</div>
<EmptyState onUploadClick={handleUploadClick} />
)}
</>
)}
Expand Down Expand Up @@ -884,13 +926,7 @@ export default function MediaBin() {
))}

{counts.all === 0 && (
<div className="flex flex-col items-center justify-center py-8 text-center">
<FileImage className="h-8 w-8 text-muted-foreground/50 mb-3" />
<p className="text-xs text-muted-foreground">No media files</p>
<p className="text-xs text-muted-foreground/70 mt-0.5">
Import videos, images, or audio to get started
</p>
</div>
<EmptyState onUploadClick={handleUploadClick} />
)}
</div>
)}
Expand Down Expand Up @@ -1019,6 +1055,16 @@ export default function MediaBin() {
</div>
</div>
)}

{/* Hidden file input for click-to-upload */}
<input
ref={fileInputRef}
type="file"
accept="video/*,image/*,audio/*"
multiple
className="hidden"
onChange={handleFileInputChange}
/>
</div>
);
}