mirror of
https://github.com/ente-io/ente.git
synced 2025-07-04 06:26:53 +00:00
147 lines
4.7 KiB
TypeScript
147 lines
4.7 KiB
TypeScript
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<FullScreenDropZoneProps>
|
|
> = ({ 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 <input> 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 (
|
|
<>
|
|
<input {...getInputProps()} />
|
|
<Stack
|
|
sx={{ flex: 1 }}
|
|
{...getRootProps({ onDragEnter: handleDragEnter })}
|
|
>
|
|
{(isDragActive || isDropPending) && (
|
|
<DropZoneOverlay
|
|
onDrop={handleOverlayDrop}
|
|
onDragLeave={handleDragLeave}
|
|
>
|
|
<CloseButton
|
|
disabled={isDropPending}
|
|
onClick={handleDragLeave}
|
|
>
|
|
<CloseIcon />
|
|
</CloseButton>
|
|
<Typography variant="h3">
|
|
{isDropPending ? (
|
|
<ActivityIndicator />
|
|
) : (
|
|
(message ?? t("upload_dropzone_hint"))
|
|
)}
|
|
</Typography>
|
|
</DropZoneOverlay>
|
|
)}
|
|
{children}
|
|
</Stack>
|
|
</>
|
|
);
|
|
};
|
|
|
|
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;
|
|
`;
|