import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; import CloseIcon from "@mui/icons-material/Close"; import { IconButton, Stack, styled, Typography } from "@mui/material"; import { t } from "i18next"; import React, { useCallback, useEffect, useState } from "react"; import { type FileWithPath, useDropzone } from "react-dropzone"; interface FullScreenDropZoneProps { /** * Optional override to the message show to the user when a drag is in * progress. * * Default: t("upload_dropzone_hint") */ message?: string; /** * If `true`, then drag and drop functionality is disabled. */ disabled?: boolean; /** * Callback invoked when the user drags and drops files. * * It will only be called if there is at least one file in {@link files}. */ onDrop: (files: FileWithPath[]) => void; } /** * A full screen container that accepts drag and drop of files, and also shows a * visual overlay to the user while a drag is in progress. * * It can serves as the root component of the gallery pages as the container * itself is a stack with flex 1, and so will fill all the height (and width) * available to it. The other contents of the screen can then be placed as its * children. * * It is meant to be used in tandem with "react-dropzone"; specifically, it * requires the `getRootProps` function returned by a call to * {@link useDropzone}. */ export const FullScreenDropZone: React.FC< React.PropsWithChildren > = ({ message, disabled, onDrop, children }) => { const { // A function to call to get the props we should apply to the container, getRootProps, // ... the props we should apply to the element, getInputProps, } = useDropzone({ noClick: true, noKeyboard: true, disabled, onDrop(acceptedFiles) { setIsDropPending(false); // Invoked the `onDrop` callback only if there is at least 1 file. if (acceptedFiles.length) { // Create a regular array from the readonly array returned by // dropzone. onDrop([...acceptedFiles]); } }, }); const [isDragActive, setIsDragActive] = useState(false); const [isDropPending, setIsDropPending] = useState(false); const handleDragEnter = useCallback(() => { setIsDragActive(true); }, []); const handleOverlayDrop = useCallback(() => { setIsDropPending(true); setIsDragActive(false); }, []); const handleDragLeave = useCallback(() => { setIsDragActive(false); }, []); useEffect(() => { const handleKeydown = (event: KeyboardEvent) => { if (event.code == "Escape" && !isDropPending) handleDragLeave(); }; window.addEventListener("keydown", handleKeydown); return () => window.removeEventListener("keydown", handleKeydown); }, [isDropPending, handleDragLeave]); return ( <> {(isDragActive || isDropPending) && ( {isDropPending ? ( ) : ( (message ?? t("upload_dropzone_hint")) )} )} {children} ); }; const DropZoneOverlay = styled(Stack)( ({ theme }) => ` position: absolute; left: 0; top: 0; height: 100%; width: 100%; outline: none; justify-content: center; align-items: center; transition: border 0.24s ease-in-out; border-width: 5px; border-style: solid; border-color: ${theme.vars.palette.accent.light}; background-color: ${theme.vars.palette.backdrop.base}; backdrop-filter: blur(10px); z-index: 2000; /* aboveFileViewerContentZ + delta */ `, ); const CloseButton = styled(IconButton)` position: absolute; top: 10px; right: 10px; `;