import { useFieldValue, useTranslate } from "ra-core"; import { createRef, useCallback, useState } from "react"; import type { ReactCropperElement } from "react-cropper"; import { Cropper } from "react-cropper"; import { useDropzone } from "react-dropzone"; import { useFormContext } from "react-hook-form"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ds/ui/avatar"; import { Button } from "@/components/ds/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ds/ui/dialog"; import "cropperjs/dist/cropper.css"; const ImageEditorField = (props: ImageEditorFieldProps) => { const { getValues } = useFormContext(); const source = getValues(props.source); const imageUrl = source?.src; const [isDialogOpen, setIsDialogOpen] = useState(false); const translate = useTranslate(); const { type = "image", emptyText, linkPosition = "none" } = props; const commonProps = { src: imageUrl, onClick: () => setIsDialogOpen(true), style: { cursor: "pointer" }, className: `${props.className || ""}`, }; const width = props.width || (type === "avatar" ? 50 : 200); const height = props.height || (type === "avatar" ? 50 : 200); return ( <>
{props.type === "avatar" ? ( {emptyText} ) : ( {translate("crm.image_editor.alt")} )} {linkPosition !== "none" && ( )}
setIsDialogOpen(false)} {...props} /> ); }; const ImageEditorDialog = (props: ImageEditorDialogProps) => { const { setValue, handleSubmit } = useFormContext(); const cropperRef = createRef(); const initialValue = useFieldValue({ source: props.source }); const [file, setFile] = useState(); const [imageSrc, setImageSrc] = useState( initialValue?.src, ); const translate = useTranslate(); const onDrop = useCallback((files: File[]) => { const preview = URL.createObjectURL(files[0]); setFile(files[0]); setImageSrc(preview); }, []); const updateImage = () => { const cropper = cropperRef.current?.cropper; const croppedImage = cropper?.getCroppedCanvas().toDataURL(); if (croppedImage) { setImageSrc(croppedImage); const newFile = file ?? new File([], initialValue?.src); setValue( props.source, { src: croppedImage, title: newFile.name, rawFile: newFile, }, { shouldDirty: true }, ); props.onClose(); if (props.onSave) { handleSubmit(props.onSave)(); } } }; const deleteImage = () => { setValue(props.source, null, { shouldDirty: true }); if (props.onSave) { handleSubmit(props.onSave)(); } setImageSrc(undefined); props.onClose(); }; const { getRootProps, getInputProps } = useDropzone({ accept: { "image/jpeg": [".jpeg", ".png"] }, onDrop, maxFiles: 1, }); return ( {props.type === "avatar" && ( )} {translate("crm.image_editor.dialog.title")}

{translate("crm.image_editor.dialog.dropzone")}

{imageSrc && ( )}
); }; export default ImageEditorField; export interface ImageEditorFieldProps { source: string; width?: number; height?: number; type?: "avatar" | "image"; onSave?: any; linkPosition?: "right" | "bottom" | "none"; backgroundImageColor?: string; className?: string; emptyText?: string; } export interface ImageEditorDialogProps extends ImageEditorFieldProps { open: boolean; onClose: () => void; }