enzostvs's picture
enzostvs HF Staff
[BIG UPDATE 🚀] New generation files
35ef307
raw
history blame
9.39 kB
import { useRef, useState } from "react";
import {
CheckCircle,
ImageIcon,
Paperclip,
Upload,
Video,
Music,
FileVideo,
Lock,
} from "lucide-react";
import Image from "next/image";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Project } from "@/types";
import Loading from "@/components/loading";
import { useUser } from "@/hooks/useUser";
import { useEditor } from "@/hooks/useEditor";
import { useAi } from "@/hooks/useAi";
import { useLoginModal } from "@/components/contexts/login-context";
import Link from "next/link";
export const getFileType = (url: string) => {
if (typeof url !== "string") {
return "unknown";
}
const extension = url.split(".").pop()?.toLowerCase();
if (["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(extension || "")) {
return "image";
} else if (["mp4", "webm", "ogg", "avi", "mov"].includes(extension || "")) {
return "video";
} else if (["mp3", "wav", "ogg", "aac", "m4a"].includes(extension || "")) {
return "audio";
}
return "unknown";
};
export const Uploader = ({ project }: { project: Project | undefined }) => {
const { user } = useUser();
const { openLoginModal } = useLoginModal();
const {
uploadFiles,
isUploading,
files,
globalEditorLoading,
project: editorProject,
} = useEditor();
const { selectedFiles, setSelectedFiles, globalAiLoading } = useAi();
const [open, setOpen] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const getFileIcon = (url: string) => {
const fileType = getFileType(url);
switch (fileType) {
case "image":
return <ImageIcon className="size-4" />;
case "video":
return <Video className="size-4" />;
case "audio":
return <Music className="size-4" />;
default:
return <FileVideo className="size-4" />;
}
};
if (!user)
return (
<Button
size="xs"
variant="outline"
className="!rounded-md"
disabled={globalAiLoading || globalEditorLoading}
onClick={() => openLoginModal()}
>
<Paperclip className="size-3.5" />
Attach
</Button>
);
return (
<Popover open={open} onOpenChange={setOpen}>
<form className="h-[24px]">
<PopoverTrigger asChild>
<Button
size="xs"
variant={open ? "default" : "outline"}
className="!rounded-md"
disabled={globalAiLoading || globalEditorLoading}
>
<Paperclip className="size-3.5" />
Attach
</Button>
</PopoverTrigger>
<PopoverContent
align="start"
className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden"
>
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
<div className="flex items-center justify-center -space-x-4 mb-3">
<div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
🎨
</div>
<div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
📁
</div>
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
💻
</div>
</div>
<p className="text-xl font-semibold text-neutral-950">
Add Media Files
</p>
<p className="text-sm text-neutral-500 mt-1.5">
Upload images, videos, and audio files to your project!
</p>
</header>
<main className="space-y-4 p-5">
{editorProject?.private && (
<div className="flex items-center justify-center flex-col gap-2 bg-amber-500/10 rounded-md p-3 border border-amber-500/10">
<Lock className="size-4 text-lg text-amber-700" />
<p className="text-xs text-amber-700">
You can upload media files to your private project, but
probably won't be able to see them in the preview.
</p>
<Link
href={`https://huggingface.co/spaces/${editorProject.space_id}/settings`}
target="_blank"
>
<Button
variant="black"
size="xs"
className="!bg-amber-600 !text-white"
>
Make it public
</Button>
</Link>
</div>
)}
<div>
<p className="text-xs text-left text-neutral-700 mb-2">
Uploaded Media Files
</p>
{files?.length > 0 ? (
<div className="grid grid-cols-4 gap-1 flex-wrap max-h-40 overflow-y-auto">
{files.map((file: string) => {
const fileType = getFileType(file);
return (
<div
key={file}
className="select-none relative cursor-pointer bg-white rounded-md border-[2px] border-white hover:shadow-2xl transition-all duration-300"
onClick={() =>
setSelectedFiles(
selectedFiles.includes(file)
? selectedFiles.filter((f) => f !== file)
: [...selectedFiles, file]
)
}
>
{fileType === "image" ? (
<Image
src={file}
alt="uploaded image"
width={56}
height={56}
className="object-cover w-full rounded-sm aspect-square"
/>
) : fileType === "video" ? (
<div className="w-full h-14 rounded-sm bg-gray-100 flex items-center justify-center relative">
<video
src={file}
className="w-full h-full object-cover rounded-sm"
muted
preload="metadata"
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/20 rounded-sm">
<Video className="size-4 text-white" />
</div>
</div>
) : fileType === "audio" ? (
<div className="w-full h-14 rounded-sm bg-gradient-to-br from-purple-100 to-pink-100 flex items-center justify-center">
<Music className="size-6 text-purple-600" />
</div>
) : (
<div className="w-full h-14 rounded-sm bg-gray-100 flex items-center justify-center">
{getFileIcon(file)}
</div>
)}
{selectedFiles.includes(file) && (
<div className="absolute top-0 right-0 h-full w-full flex items-center justify-center bg-black/50 rounded-md">
<CheckCircle className="size-6 text-neutral-100" />
</div>
)}
</div>
);
})}
</div>
) : (
<p className="text-sm text-muted-foreground font-mono flex flex-col items-center gap-1 pt-2">
<ImageIcon className="size-4" />
No media files uploaded yet
</p>
)}
</div>
<div>
<p className="text-xs text-left text-neutral-700 mb-2">
Or import media files from your computer
</p>
<Button
variant="black"
onClick={() => fileInputRef.current?.click()}
className="relative w-full"
disabled={isUploading}
>
{isUploading ? (
<>
<Loading
overlay={false}
className="ml-2 size-4 animate-spin"
/>
Uploading media file(s)...
</>
) : (
<>
<Upload className="size-4" />
Upload Media Files
</>
)}
</Button>
<input
ref={fileInputRef}
type="file"
className="hidden"
multiple
accept="image/*,video/*,audio/*,.mp3,.mp4,.wav,.aac,.m4a,.ogg,.webm,.avi,.mov"
onChange={(e) => uploadFiles(e.target.files, project!)}
/>
</div>
</main>
</PopoverContent>
</form>
</Popover>
);
};