mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
add back drag & drop to upload
This commit is contained in:
104
interface/src/components/upload/DragNdrop.tsx
Normal file
104
interface/src/components/upload/DragNdrop.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { type ChangeEvent, useRef, useState } from 'react';
|
||||
import { AiOutlineCloudUpload } from 'react-icons/ai';
|
||||
import { MdClear } from 'react-icons/md';
|
||||
|
||||
import UploadIcon from '@mui/icons-material/Upload';
|
||||
import { Button } from '@mui/material';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import './drag-drop.css';
|
||||
|
||||
const DragNdrop = ({ onFileSelected, width, height }) => {
|
||||
const [file, setFile] = useState<File>();
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files) {
|
||||
return;
|
||||
}
|
||||
setFile(e.target.files[0]);
|
||||
};
|
||||
|
||||
const handleDrop = (event) => {
|
||||
event.preventDefault();
|
||||
const droppedFiles = event.dataTransfer.files;
|
||||
if (droppedFiles.length > 0) {
|
||||
setFile(droppedFiles[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (event) => {
|
||||
event.stopPropagation();
|
||||
setFile(undefined);
|
||||
};
|
||||
|
||||
const handleUploadClick = (event) => {
|
||||
event.stopPropagation();
|
||||
onFileSelected(file);
|
||||
};
|
||||
|
||||
const handleBrowseClick = () => {
|
||||
inputRef.current?.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="drag-drop" style={{ width: width, height: height }}>
|
||||
<div
|
||||
className={`document-uploader ${file ? 'upload-box active' : 'upload-box'}`}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
onClick={handleBrowseClick}
|
||||
>
|
||||
<>
|
||||
<div className="upload-info">
|
||||
<AiOutlineCloudUpload />
|
||||
<div>
|
||||
<p>Drag and drop a file here or click to select one</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
hidden
|
||||
id="browse"
|
||||
onChange={handleFileChange}
|
||||
ref={inputRef}
|
||||
accept=".json,.txt,.csv,.bin"
|
||||
multiple={false}
|
||||
/>
|
||||
</>
|
||||
|
||||
{file && (
|
||||
<>
|
||||
<div className="file-list">
|
||||
<div className="file-item">
|
||||
<div className="file-info">
|
||||
<p>{file.name}</p>
|
||||
</div>
|
||||
<div className="file-actions">
|
||||
<MdClear
|
||||
style={{ width: 18, verticalAlign: 'middle' }}
|
||||
onClick={(e) => handleRemoveFile(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
startIcon={<UploadIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={handleUploadClick}
|
||||
>
|
||||
{LL.UPLOAD()}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DragNdrop;
|
||||
@@ -1,19 +1,26 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import UploadIcon from '@mui/icons-material/Upload';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
import { Box, Button, LinearProgress, Typography } from '@mui/material';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import MessageBox from 'components/MessageBox';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import DragNdrop from './DragNdrop';
|
||||
|
||||
const SingleUpload = () => {
|
||||
const [md5, setMd5] = useState<string>();
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
||||
|
||||
const [file, setFile] = useState<File>();
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const {
|
||||
loading: isUploading,
|
||||
@@ -28,55 +35,40 @@ const SingleUpload = () => {
|
||||
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
||||
setFile(undefined);
|
||||
} else {
|
||||
setRestarting(true);
|
||||
setRestartNeeded(true);
|
||||
// setRestarting(true);
|
||||
}
|
||||
});
|
||||
|
||||
const [file, setFile] = useState<File>();
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const handleUploadClick = () => {
|
||||
inputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFile(e.target.files[0]);
|
||||
|
||||
await sendUpload(e.target.files[0]).catch((error: Error) => {
|
||||
if (error.message === 'The user aborted a request') {
|
||||
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||
} else if (error.message === 'Network Error') {
|
||||
toast.warning('Invalid file extension or incompatible bin file');
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
const restart = async () => {
|
||||
await restartCommand().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
if (file) {
|
||||
console.log('going to upload file ', file.name);
|
||||
await sendUpload(file).catch((error: Error) => {
|
||||
if (error.message === 'The user aborted a request') {
|
||||
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||
} else if (error.message === 'Network Error') {
|
||||
toast.warning('Invalid file extension or incompatible bin file');
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [file]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="file"
|
||||
ref={inputRef}
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<UploadIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={handleUploadClick}
|
||||
>
|
||||
{file ? LL.UPLOADING() + ` ${file.name}` : LL.UPLOAD()}
|
||||
</Button>
|
||||
<DragNdrop onFileSelected={setFile} width="340px" height="140px" />
|
||||
|
||||
{isUploading && (
|
||||
<>
|
||||
@@ -111,6 +103,19 @@ const SingleUpload = () => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{restartNeeded && (
|
||||
<MessageBox mt={4} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={restart}
|
||||
>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
)}
|
||||
|
||||
{restarting && <RestartMonitor />}
|
||||
</>
|
||||
);
|
||||
|
||||
83
interface/src/components/upload/drag-drop.css
Normal file
83
interface/src/components/upload/drag-drop.css
Normal file
@@ -0,0 +1,83 @@
|
||||
.drag-drop {
|
||||
/* background: #fff; */
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.document-uploader {
|
||||
border: 2px dashed #4282fe;
|
||||
/* background-color: #f4fbff; */
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
border-color: #6dc24b;
|
||||
}
|
||||
|
||||
.upload-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
svg {
|
||||
font-size: 36px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
div {
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
flex: 1;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #6dc24b;
|
||||
}
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
color: #d44;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type='file'] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user