drop
This commit is contained in:
108
src/components/FileDropzone.jsx
Normal file
108
src/components/FileDropzone.jsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
/**
|
||||
* An otherwise invisible dropzone wrapper.
|
||||
* Renders seamlessly around children without altering layout or style,
|
||||
* and highlights with an outline only while a file is dragged over it.
|
||||
*/
|
||||
export function FileDropzone({
|
||||
accept = 'application/pdf,.pdf',
|
||||
disabled = false,
|
||||
onFileDrop,
|
||||
children,
|
||||
sx,
|
||||
...props
|
||||
}) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const dragCounter = useRef(0);
|
||||
|
||||
const handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (disabled) return;
|
||||
dragCounter.current += 1;
|
||||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragCounter.current -= 1;
|
||||
if (dragCounter.current <= 0) {
|
||||
dragCounter.current = 0;
|
||||
setIsDragging(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (disabled) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
return;
|
||||
}
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
};
|
||||
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragCounter.current = 0;
|
||||
setIsDragging(false);
|
||||
if (disabled) return;
|
||||
|
||||
const file = e.dataTransfer.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (accept) {
|
||||
const allowed = accept
|
||||
.split(',')
|
||||
.map((s) => s.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
const name = (file.name || '').toLowerCase();
|
||||
const dotIndex = name.lastIndexOf('.');
|
||||
const ext = dotIndex !== -1 ? name.slice(dotIndex) : '';
|
||||
const mimeType = (file.type || '').toLowerCase();
|
||||
|
||||
const matches = allowed.some((item) => {
|
||||
if (item.startsWith('.')) {
|
||||
return ext === item;
|
||||
}
|
||||
return mimeType === item;
|
||||
});
|
||||
|
||||
if (!matches) return;
|
||||
}
|
||||
|
||||
onFileDrop?.(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
position: 'relative',
|
||||
borderRadius: 1,
|
||||
flexShrink: 0,
|
||||
// Keep outline inset so it never affects external geometry or causes scrollbars:
|
||||
outline: isDragging ? '2px dashed' : 'none',
|
||||
outlineColor: 'primary.main',
|
||||
outlineOffset: -2,
|
||||
transition: 'outline 0.15s ease-in-out',
|
||||
...sx,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{typeof children === 'function' ? children({ isDragging }) : children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default FileDropzone;
|
||||
@@ -7,6 +7,7 @@ import TextField from '@mui/material/TextField';
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { PanelTile } from 'dashgui/panels';
|
||||
import FileDropzone from '../FileDropzone';
|
||||
import { manifestConfig, partyDisplayName } from './manifestConfig';
|
||||
|
||||
class ManifestHeader extends PureComponent {
|
||||
@@ -122,16 +123,42 @@ class ManifestHeader extends PureComponent {
|
||||
/>
|
||||
) : null}
|
||||
{onPdfUpload ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={pdfUploading ? <CircularProgress size={16} color="inherit" /> : <UploadFileIcon />}
|
||||
<FileDropzone
|
||||
accept="application/pdf,.pdf"
|
||||
disabled={saving || pdfUploading}
|
||||
onClick={this.handleUploadClick}
|
||||
sx={{ whiteSpace: 'nowrap', height: 40, alignSelf: { xs: 'stretch', sm: 'center' }, px: 2 }}
|
||||
onFileDrop={onPdfUpload}
|
||||
sx={{
|
||||
alignSelf: { xs: 'stretch', sm: 'center' },
|
||||
flexShrink: 0,
|
||||
width: { xs: '100%', sm: 175 },
|
||||
}}
|
||||
>
|
||||
{pdfUploading ? 'Wird analysiert…' : 'PDF hochladen'}
|
||||
</Button>
|
||||
{({ isDragging }) => (
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={pdfUploading ? <CircularProgress size={16} color="inherit" /> : <UploadFileIcon />}
|
||||
disabled={saving || pdfUploading}
|
||||
onClick={this.handleUploadClick}
|
||||
sx={{
|
||||
whiteSpace: 'nowrap',
|
||||
height: 40,
|
||||
width: '100%',
|
||||
px: 2,
|
||||
boxSizing: 'border-box',
|
||||
bgcolor: isDragging ? 'action.hover' : 'transparent',
|
||||
borderColor: isDragging ? 'primary.main' : undefined,
|
||||
transition: 'background-color 0.15s, border-color 0.15s',
|
||||
}}
|
||||
>
|
||||
{pdfUploading
|
||||
? 'Wird analysiert…'
|
||||
: isDragging
|
||||
? 'PDF hier ablegen'
|
||||
: 'PDF hochladen'}
|
||||
</Button>
|
||||
)}
|
||||
</FileDropzone>
|
||||
) : null}
|
||||
</Stack>
|
||||
{onPdfUpload ? (
|
||||
|
||||
Reference in New Issue
Block a user