diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx
index 756a403a4..83440aecc 100644
--- a/resources/scripts/components/server/files/FileManagerContainer.tsx
+++ b/resources/scripts/components/server/files/FileManagerContainer.tsx
@@ -13,6 +13,7 @@ import tw from 'twin.macro';
import { Button } from '@/components/elements/button/index';
import { ServerContext } from '@/state/server';
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
+import FileManagerStatus from '@/components/server/files/FileManagerStatus';
import MassActionsBar from '@/components/server/files/MassActionsBar';
import UploadButton from '@/components/server/files/UploadButton';
import ServerContentBlock from '@/components/elements/ServerContentBlock';
@@ -104,6 +105,7 @@ export default () => {
))}
+
)}
diff --git a/resources/scripts/components/server/files/FileManagerStatus.tsx b/resources/scripts/components/server/files/FileManagerStatus.tsx
new file mode 100644
index 000000000..a286c4155
--- /dev/null
+++ b/resources/scripts/components/server/files/FileManagerStatus.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import tw, { styled } from 'twin.macro';
+import { ServerContext } from '@/state/server';
+import { bytesToString } from '@/lib/formatters';
+
+const SpinnerCircle = styled.circle`
+ transition: stroke-dashoffset 0.35s;
+ transform: rotate(-90deg);
+ transform-origin: 50% 50%;
+`;
+
+function Spinner({ progress }: { progress: number }) {
+ const stroke = 3;
+ const radius = 20;
+ const normalizedRadius = radius - stroke * 2;
+ const circumference = normalizedRadius * 2 * Math.PI;
+
+ return (
+
+ );
+}
+
+function FileManagerStatus() {
+ const uploads = ServerContext.useStoreState((state) => state.files.uploads);
+
+ return (
+
+ {uploads.length > 0 && (
+
+ {uploads
+ .sort((a, b) => a.total - b.total)
+ .map((f) => (
+
+
+
+
+
+
+
+ {f.name} ({bytesToString(f.loaded)}/{bytesToString(f.total)})
+
+
+
+ ))}
+
+ )}
+
+ );
+}
+
+export default FileManagerStatus;
diff --git a/resources/scripts/components/server/files/UploadButton.tsx b/resources/scripts/components/server/files/UploadButton.tsx
index db566f4ba..7b1d7c8a1 100644
--- a/resources/scripts/components/server/files/UploadButton.tsx
+++ b/resources/scripts/components/server/files/UploadButton.tsx
@@ -7,7 +7,6 @@ import styled from 'styled-components/macro';
import { ModalMask } from '@/components/elements/Modal';
import Fade from '@/components/elements/Fade';
import useEventListener from '@/plugins/useEventListener';
-import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import useFlash from '@/plugins/useFlash';
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
import { ServerContext } from '@/state/server';
@@ -19,18 +18,40 @@ const InnerContainer = styled.div`
${tw`bg-black w-full border-4 border-primary-500 border-dashed rounded p-10 mx-10`}
`;
+function isFileOrDirectory(event: DragEvent): boolean {
+ if (!event.dataTransfer?.types) {
+ return false;
+ }
+
+ for (let i = 0; i < event.dataTransfer.types.length; i++) {
+ // Check if the item being dragged is not a file.
+ // On Firefox a file of type "application/x-moz-file" is also in the array.
+ if (event.dataTransfer.types[i] !== 'Files' && event.dataTransfer.types[i] !== 'application/x-moz-file') {
+ return false;
+ }
+ }
+
+ return true;
+}
+
export default ({ className }: WithClassname) => {
const fileUploadInput = useRef(null);
- const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
+ const [timeouts, setTimeouts] = useState([]);
const [visible, setVisible] = useState(false);
- const [loading, setLoading] = useState(false);
const { mutate } = useFileManagerSwr();
const { clearFlashes, clearAndAddHttpError } = useFlash();
+
+ const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const directory = ServerContext.useStoreState((state) => state.files.directory);
+ const appendFileUpload = ServerContext.useStoreActions((actions) => actions.files.appendFileUpload);
+ const removeFileUpload = ServerContext.useStoreActions((actions) => actions.files.removeFileUpload);
useEventListener(
'dragenter',
(e) => {
+ if (!isFileOrDirectory(e)) {
+ return;
+ }
e.stopPropagation();
setVisible(true);
},
@@ -40,6 +61,9 @@ export default ({ className }: WithClassname) => {
useEventListener(
'dragexit',
(e) => {
+ if (!isFileOrDirectory(e)) {
+ return;
+ }
e.stopPropagation();
setVisible(false);
},
@@ -57,27 +81,47 @@ export default ({ className }: WithClassname) => {
};
}, [visible]);
- const onFileSubmission = (files: FileList) => {
- const form = new FormData();
- Array.from(files).forEach((file) => form.append('files', file));
+ useEffect(() => {
+ return () => timeouts.forEach(clearTimeout);
+ }, []);
- setLoading(true);
+ const onFileSubmission = (files: FileList) => {
+ const formData: FormData[] = [];
+ Array.from(files).forEach((file) => {
+ const form = new FormData();
+ form.append('files', file);
+ formData.push(form);
+ });
clearFlashes('files');
- getFileUploadUrl(uuid)
- .then((url) =>
- axios.post(`${url}&directory=${directory}`, form, {
- headers: {
- 'Content-Type': 'multipart/form-data',
- },
- })
+ Promise.all(
+ Array.from(formData).map((f) =>
+ getFileUploadUrl(uuid).then((url) =>
+ axios.post(`${url}&directory=${directory}`, f, {
+ headers: { 'Content-Type': 'multipart/form-data' },
+ onUploadProgress: (data: ProgressEvent) => {
+ // @ts-expect-error this is valid
+ const name = f.getAll('files')[0].name;
+
+ appendFileUpload({
+ name: name,
+ loaded: data.loaded,
+ total: data.total,
+ });
+
+ if (data.loaded === data.total) {
+ const timeout = setTimeout(() => removeFileUpload(name), 2000);
+ setTimeouts((t) => [...t, timeout]);
+ }
+ },
+ })
+ )
)
+ )
.then(() => mutate())
.catch((error) => {
console.error(error);
clearAndAddHttpError({ error, key: 'files' });
- })
- .then(() => setVisible(false))
- .then(() => setLoading(false));
+ });
};
return (
@@ -97,14 +141,13 @@ export default ({ className }: WithClassname) => {
onFileSubmission(e.dataTransfer.files);
}}
>
-
+
Drag and drop files to upload.
-
{
}
}}
/>
-