import React, { useEffect, useState, useRef, useCallback } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; import { useDropzone } from "react-dropzone"; import { Tab, Transition, Popover } from "@headlessui/react"; import { Control, Controller } from "react-hook-form"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // services import { FileService } from "services/file.service"; // hooks import useOutsideClickDetector from "hooks/use-outside-click-detector"; // components import { Button, Input, Loader } from "@plane/ui"; // constants import { MAX_FILE_SIZE } from "constants/common"; const tabOptions = [ { key: "unsplash", title: "Unsplash", }, { key: "images", title: "Images", }, { key: "upload", title: "Upload", }, ]; type Props = { label: string | React.ReactNode; value: string | null; control: Control; onChange: (data: string) => void; disabled?: boolean; }; // services const fileService = new FileService(); export const ImagePickerPopover: React.FC = observer((props) => { const { label, value, control, onChange, disabled = false } = props; const [image, setImage] = useState(null); const [isImageUploading, setIsImageUploading] = useState(false); const [isOpen, setIsOpen] = useState(false); const [searchParams, setSearchParams] = useState(""); const [formData, setFormData] = useState({ search: "", }); const ref = useRef(null); const router = useRouter(); const { workspaceSlug } = router.query; const { workspace: { currentWorkspace }, appConfig: { envConfig }, } = useMobxStore(); const { data: unsplashImages, error: unsplashError } = useSWR( `UNSPLASH_IMAGES_${searchParams}`, () => fileService.getUnsplashImages(searchParams), { revalidateOnFocus: false, revalidateOnReconnect: false, } ); const { data: projectCoverImages } = useSWR(`PROJECT_COVER_IMAGES`, () => fileService.getProjectCoverImages(), { revalidateOnFocus: false, revalidateOnReconnect: false, }); const imagePickerRef = useRef(null); const onDrop = useCallback((acceptedFiles: File[]) => { setImage(acceptedFiles[0]); }, []); const { getRootProps, getInputProps, isDragActive, fileRejections } = useDropzone({ onDrop, accept: { "image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"], }, maxSize: envConfig?.file_size_limit ?? MAX_FILE_SIZE, }); const handleSubmit = async () => { setIsImageUploading(true); if (!image || !workspaceSlug) return; const formData = new FormData(); formData.append("asset", image); formData.append("attributes", JSON.stringify({})); fileService .uploadFile(workspaceSlug.toString(), formData) .then((res) => { const oldValue = value; const isUnsplashImage = oldValue?.split("/")[2] === "images.unsplash.com"; const imageUrl = res.asset; onChange(imageUrl); setIsImageUploading(false); setImage(null); setIsOpen(false); if (isUnsplashImage) return; if (oldValue && currentWorkspace) fileService.deleteFile(currentWorkspace.id, oldValue); }) .catch((err) => { console.log(err); }); }; useEffect(() => { if (!unsplashImages || value !== null) return; onChange(unsplashImages[0].urls.regular); }, [value, onChange, unsplashImages]); useOutsideClickDetector(imagePickerRef, () => setIsOpen(false)); return ( setIsOpen((prev) => !prev)} disabled={disabled} > {label}
{tabOptions.map((tab) => { if (!unsplashImages && unsplashError && tab.key === "unsplash") return null; if (projectCoverImages && projectCoverImages.length === 0 && tab.key === "images") return null; return ( `rounded px-4 py-1 text-center text-sm outline-none transition-colors ${ selected ? "bg-custom-primary text-white" : "text-custom-text-100" }` } > {tab.title} ); })} {(unsplashImages || !unsplashError) && (
( setFormData({ ...formData, search: e.target.value })} ref={ref} placeholder="Search for images" className="w-full text-sm" /> )} />
{unsplashImages ? ( unsplashImages.length > 0 ? (
{unsplashImages.map((image) => (
{ setIsOpen(false); onChange(image.urls.regular); }} > {image.alt_description}
))}
) : (

No images found.

) ) : ( )}
)} {(!projectCoverImages || projectCoverImages.length !== 0) && ( {projectCoverImages ? ( projectCoverImages.length > 0 ? (
{projectCoverImages.map((image, index) => (
{ setIsOpen(false); onChange(image); }} > {`Default
))}
) : (

No images found.

) ) : ( )}
)}
{image !== null || (value && value !== "") ? ( <> image ) : (
{isDragActive ? "Drop image here to upload" : "Drag & drop image here"}
)}
{fileRejections.length > 0 && (

{fileRejections[0].errors[0].code === "file-too-large" ? "The image size cannot exceed 5 MB." : "Please upload a file in a valid format."}

)}

File formats supported- .jpeg, .jpg, .png, .webp, .svg

); });