diff --git a/front-end/src/components/composite/presetFilesModal/PresetFilesModal.tsx b/front-end/src/components/composite/presetFilesModal/PresetFilesModal.tsx index 76dfdf79..9a980880 100644 --- a/front-end/src/components/composite/presetFilesModal/PresetFilesModal.tsx +++ b/front-end/src/components/composite/presetFilesModal/PresetFilesModal.tsx @@ -61,7 +61,7 @@ export const PresetFilesModal = ({ onClose, isOpen, onSubmit, currentSources }:

Upload your files

- +
diff --git a/front-end/src/components/composite/uploadModal/uploadModal.module.scss b/front-end/src/components/composite/uploadModal/uploadModal.module.scss index fdf3856d..ec71bd8e 100644 --- a/front-end/src/components/composite/uploadModal/uploadModal.module.scss +++ b/front-end/src/components/composite/uploadModal/uploadModal.module.scss @@ -16,4 +16,11 @@ .submitButton { margin-top: 1.25rem; } +.buttonRow { + display: flex; + gap: 1rem; /* space between buttons */ + justify-content: flex-start; /* or center, or flex-end */ + margin-top: 1rem; /* optional spacing from form */ +} + } \ No newline at end of file diff --git a/front-end/src/components/composite/uploadModal/uploadModal.tsx b/front-end/src/components/composite/uploadModal/uploadModal.tsx index cb03b63a..960612e5 100644 --- a/front-end/src/components/composite/uploadModal/uploadModal.tsx +++ b/front-end/src/components/composite/uploadModal/uploadModal.tsx @@ -3,7 +3,7 @@ import { BaseModal } from '@components/ui/baseModal/BaseModal'; import { UploadForm } from '@components/ui/uploadForm/UploadForm'; import { Button } from '@components/ui/button/Button'; import { useState } from 'react'; -import {rightArrowIcon} from '@assets/icons'; +import {rightArrowIcon, folderIcon, cubeIcon} from '@assets/icons'; import { ApiUtil } from '@lib/apiUtils'; import { showSuccessToast } from '@components/ui/toastNotification/ToastNotification'; import { useFiles } from '@lib/files/useFiles'; @@ -16,6 +16,7 @@ interface UploadModalProps { export const UploadModal = ({ isOpen, onClose }: UploadModalProps) => { const [files, setFiles] = useState([]); const { refetch } = useFiles(); + const [allowFolder, setAllowFolder] = useState(false); const submitFiles = async () => { const uploadPromises = files.map((file) => ApiUtil.uploadFile(file)); @@ -28,16 +29,32 @@ export const UploadModal = ({ isOpen, onClose }: UploadModalProps) => { return (
-

Upload your file

- - +

Upload your files

+ + + +
+ + + + + +
+
); diff --git a/front-end/src/components/ui/uploadForm/UploadForm.module.scss b/front-end/src/components/ui/uploadForm/UploadForm.module.scss index b6421d58..d6f64a99 100644 --- a/front-end/src/components/ui/uploadForm/UploadForm.module.scss +++ b/front-end/src/components/ui/uploadForm/UploadForm.module.scss @@ -1,4 +1,5 @@ @use '@styles/_fonts'; +@use '@styles/_variables'; .uploadForm { @include fonts.text-medium; @@ -15,14 +16,6 @@ border-radius: 10px; overflow: hidden; - &:hover { - cursor: pointer; - .uploadFormContent { - transform: scale(1.05); - filter: brightness(0.9); - } - } - &.dragover { .daytimeBg, .daytimeImage { opacity: 0; @@ -85,6 +78,12 @@ padding: 1.75rem 4rem; width: fit-content; transition: transform 0.3s ease, filter 0.3s ease, border-color 0.3s ease; + cursor: pointer; + + &:hover { + transform: scale(1.05); + filter: brightness(0.9); + } &.disabled { display: none; @@ -101,6 +100,14 @@ opacity: 0.8; } + .clickableArea { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.25rem; + cursor: pointer; + } + .text, .textHover { margin: 0; transition: opacity 0.3s ease; @@ -111,6 +118,24 @@ margin-top: -2.75rem; } + .folderLink { + all: unset; + color: var(--text-colour); + opacity: 0.7; + font-size: 0.875rem; + text-decoration: underline; + text-underline-offset: 0.2rem; + margin-top: -0.75rem; + transition: opacity 0.3s ease; + cursor: pointer; + position: relative; + z-index: map-get(variables.$z-index, 'tooltip'); + + &:hover { + opacity: 1; + } + } + .input { display: none; } diff --git a/front-end/src/components/ui/uploadForm/UploadForm.tsx b/front-end/src/components/ui/uploadForm/UploadForm.tsx index 7bb979cf..1fd7b0fd 100644 --- a/front-end/src/components/ui/uploadForm/UploadForm.tsx +++ b/front-end/src/components/ui/uploadForm/UploadForm.tsx @@ -9,19 +9,57 @@ interface UploadFormProps { files: File[]; setFiles: (files: File[]) => void; accept?: string; + allowFolder: boolean; } -export const UploadForm = ({ files, setFiles, accept = '.csv, .bin, .mp4, .mov, .fit' }: UploadFormProps) => { + +export const UploadForm = ({ files, setFiles, allowFolder = false, + accept = '.csv, .bin, .mp4, .mov, .fit' }:UploadFormProps) => { + const [isDragging, setIsDragging] = React.useState(false); + const fileInputRef = React.useRef(null); + const folderInputRef = React.useRef(null); - const handleDragOver = (e: React.DragEvent) => { + const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; - const handleDrop = (e: React.DragEvent) => { + const handleDrop = (e: React.DragEvent) => { e.preventDefault(); - if (e.dataTransfer.files) { + if(e.dataTransfer.items) { + + const newFiles: File[] = []; + + const traverseFileTree = (item: FileSystemEntry, path = ''): Promise => { + return new Promise((resolve) => { + if (item.isFile) { + (item as FileSystemFileEntry).file((file: File) => { + const f = new File([file], file.name, { type: file.type }); + newFiles.push(f); + resolve(); + }); + } else if (item.isDirectory) { + const dirReader = (item as FileSystemDirectoryEntry).createReader(); + dirReader.readEntries((entries: FileSystemEntry[]) => { + Promise.all(entries.map((entry) => traverseFileTree(entry, path+item.name + '/'))).then(() => resolve()); + }); + } else { + resolve(); + } + }); + }; + + const promises: Promise[] = []; + for (const item of Array.from(e.dataTransfer.items)) { + const entry = item.webkitGetAsEntry(); + if (entry) promises.push(traverseFileTree(entry)); + } + + Promise.all(promises).then(() => { + setFiles([...files, ...newFiles]); + }); + } else if (e.dataTransfer.files) { setFiles([...files, ...Array.from(e.dataTransfer.files)]); } setIsDragging(false); @@ -38,8 +76,18 @@ export const UploadForm = ({ files, setFiles, accept = '.csv, .bin, .mp4, .mov, setFiles(files.filter((_, i) => i !== index)); }; + const handleFolderClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + folderInputRef.current?.click(); + }; + + const handleContentClick = () => { + fileInputRef.current?.click(); + }; + return ( -