From 05449eb101b43cb6b13e5ed49628d7b8e19e77cb Mon Sep 17 00:00:00 2001 From: CowMan999 Date: Sat, 29 Nov 2025 16:40:36 -0500 Subject: [PATCH 1/6] updated handleDrop to include files from dropped folders in UploadForm --- .../components/ui/uploadForm/UploadForm.tsx | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/front-end/src/components/ui/uploadForm/UploadForm.tsx b/front-end/src/components/ui/uploadForm/UploadForm.tsx index 7bb979cf..bb06e27d 100644 --- a/front-end/src/components/ui/uploadForm/UploadForm.tsx +++ b/front-end/src/components/ui/uploadForm/UploadForm.tsx @@ -4,16 +4,35 @@ import {uploadIcon, closeIcon} from '@assets/icons'; import cx from 'classnames'; import daytime from '@assets/upload_form_daytime.png'; import nighttime from '@assets/upload_form_nighttime.png'; +// +//import { useRef, useEffect } from "react"; 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 inputRef = useRef(null); +// +// useEffect(() => { +// const input = inputRef.current; +// if (!input) return; +// +// if (allowFolder) { +// input.setAttribute("webkitdirectory", ""); +// input.setAttribute("directory", ""); +// } else { +// input.removeAttribute("webkitdirectory"); +// input.removeAttribute("directory"); +// } +// }, [allowFolder]); + const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); @@ -21,7 +40,39 @@ export const UploadForm = ({ files, setFiles, accept = '.csv, .bin, .mp4, .mov, const handleDrop = (e: React.DragEvent) => { e.preventDefault(); - if (e.dataTransfer.files) { + if(e.dataTransfer.items) { + + const newFiles: File[] = []; + + const traverseFileTree = (item: any, path = ""): Promise => { + return new Promise((resolve) => { + if (item.isFile) { + item.file((file: File) => { + const f = new File([file], file.name, { type: file.type }); + newFiles.push(f); + resolve(); + }); + } else if (item.isDirectory) { + const dirReader = item.createReader(); + dirReader.readEntries((entries: any[]) => { + Promise.all(entries.map((entry) => traverseFileTree(entry, path + item.name + "/"))).then(() => resolve()); + }); + } else { + resolve(); + } + }); + }; + + const promises: Promise[] = []; + for (let i = 0; i < e.dataTransfer.items.length; i++) { + const entry = e.dataTransfer.items[i].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); @@ -63,6 +114,8 @@ export const UploadForm = ({ files, setFiles, accept = '.csv, .bin, .mp4, .mov, accept={accept} multiple={true} onChange={(e) => {handleFileChange(e);}} + ///* @ts-expect-error */ + // webkitdirectory="" /> {files.length > 0 && ( From 1cae25682d67c19e61254dcf60b70110235bdea8 Mon Sep 17 00:00:00 2001 From: CowMan999 Date: Sat, 29 Nov 2025 17:36:37 -0500 Subject: [PATCH 2/6] added support for folder upload via popup menu, button to toggle file/folder upload present on modal --- .../uploadModal/uploadModal.module.scss | 7 ++++ .../composite/uploadModal/uploadModal.tsx | 37 ++++++++++++++----- .../components/ui/uploadForm/UploadForm.tsx | 25 ++----------- 3 files changed, 38 insertions(+), 31 deletions(-) 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..7370e01c 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)); @@ -29,15 +30,31 @@ export const UploadModal = ({ isOpen, onClose }: UploadModalProps) => {

Upload your file

- - + + + +
+ + + + + +
+
); diff --git a/front-end/src/components/ui/uploadForm/UploadForm.tsx b/front-end/src/components/ui/uploadForm/UploadForm.tsx index bb06e27d..49f85693 100644 --- a/front-end/src/components/ui/uploadForm/UploadForm.tsx +++ b/front-end/src/components/ui/uploadForm/UploadForm.tsx @@ -4,35 +4,18 @@ import {uploadIcon, closeIcon} from '@assets/icons'; import cx from 'classnames'; import daytime from '@assets/upload_form_daytime.png'; import nighttime from '@assets/upload_form_nighttime.png'; -// -//import { useRef, useEffect } from "react"; interface UploadFormProps { files: File[]; setFiles: (files: File[]) => void; accept?: string; - // allowFolder?: boolean; + allowFolder: boolean; } -export const UploadForm = ({ files, setFiles,/*allowFolder = false,*/ 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 inputRef = useRef(null); -// -// useEffect(() => { -// const input = inputRef.current; -// if (!input) return; -// -// if (allowFolder) { -// input.setAttribute("webkitdirectory", ""); -// input.setAttribute("directory", ""); -// } else { -// input.removeAttribute("webkitdirectory"); -// input.removeAttribute("directory"); -// } -// }, [allowFolder]); - const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); @@ -114,8 +97,8 @@ export const UploadForm = ({ files, setFiles,/*allowFolder = false,*/ accept = ' accept={accept} multiple={true} onChange={(e) => {handleFileChange(e);}} - ///* @ts-expect-error */ - // webkitdirectory="" + /* @ts-expect-error */ + webkitdirectory={allowFolder ? "" : undefined} /> {files.length > 0 && ( From 108102182e6812ac61b6987ad76f97dd71f9cdfc Mon Sep 17 00:00:00 2001 From: CowMan999 Date: Sat, 29 Nov 2025 17:39:34 -0500 Subject: [PATCH 3/6] informs user if they are uploading file/folder via forum --- front-end/src/components/composite/uploadModal/uploadModal.tsx | 2 +- front-end/src/components/ui/uploadForm/UploadForm.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/front-end/src/components/composite/uploadModal/uploadModal.tsx b/front-end/src/components/composite/uploadModal/uploadModal.tsx index 7370e01c..91c6b125 100644 --- a/front-end/src/components/composite/uploadModal/uploadModal.tsx +++ b/front-end/src/components/composite/uploadModal/uploadModal.tsx @@ -29,7 +29,7 @@ export const UploadModal = ({ isOpen, onClose }: UploadModalProps) => { return (
-

Upload your file

+

Upload your files

diff --git a/front-end/src/components/ui/uploadForm/UploadForm.tsx b/front-end/src/components/ui/uploadForm/UploadForm.tsx index 49f85693..da30bd2a 100644 --- a/front-end/src/components/ui/uploadForm/UploadForm.tsx +++ b/front-end/src/components/ui/uploadForm/UploadForm.tsx @@ -89,7 +89,7 @@ export const UploadForm = ({ files, setFiles, allowFolder = false, accept = '.cs [styles.dragover]: isDragging })}> upload icon -

Choose a file or drag it here

+

Choose a {allowFolder ? "folder" : "file"} or drag it here

Drop the file

Date: Sun, 30 Nov 2025 13:18:53 -0500 Subject: [PATCH 4/6] fixed code to follow linting standards --- .../composite/uploadModal/uploadModal.tsx | 8 ++--- .../components/ui/uploadForm/UploadForm.tsx | 36 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/front-end/src/components/composite/uploadModal/uploadModal.tsx b/front-end/src/components/composite/uploadModal/uploadModal.tsx index 91c6b125..6ad83868 100644 --- a/front-end/src/components/composite/uploadModal/uploadModal.tsx +++ b/front-end/src/components/composite/uploadModal/uploadModal.tsx @@ -33,15 +33,15 @@ export const UploadModal = ({ isOpen, onClose }: UploadModalProps) => { -
+
-
+
diff --git a/front-end/src/components/ui/uploadForm/UploadForm.tsx b/front-end/src/components/ui/uploadForm/UploadForm.tsx index da30bd2a..f67e7f71 100644 --- a/front-end/src/components/ui/uploadForm/UploadForm.tsx +++ b/front-end/src/components/ui/uploadForm/UploadForm.tsx @@ -13,7 +13,9 @@ interface UploadFormProps { } -export const UploadForm = ({ files, setFiles, allowFolder = false, 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 handleDragOver = (e: React.DragEvent) => { @@ -23,22 +25,22 @@ export const UploadForm = ({ files, setFiles, allowFolder = false, accept = '.cs const handleDrop = (e: React.DragEvent) => { e.preventDefault(); - if(e.dataTransfer.items) { + if(e.dataTransfer.items) { const newFiles: File[] = []; - const traverseFileTree = (item: any, path = ""): Promise => { + const traverseFileTree = (item: FileSystemEntry, path = ''): Promise => { return new Promise((resolve) => { if (item.isFile) { - item.file((file: File) => { + (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.createReader(); - dirReader.readEntries((entries: any[]) => { - Promise.all(entries.map((entry) => traverseFileTree(entry, path + item.name + "/"))).then(() => resolve()); + const dirReader = (item as FileSystemDirectoryEntry).createReader(); + dirReader.readEntries((entries: FileSystemEntry[]) => { + Promise.all(entries.map((entry) => traverseFileTree(entry, path+item.name + '/'))).then(() => resolve()); }); } else { resolve(); @@ -47,15 +49,15 @@ export const UploadForm = ({ files, setFiles, allowFolder = false, accept = '.cs }; const promises: Promise[] = []; - for (let i = 0; i < e.dataTransfer.items.length; i++) { - const entry = e.dataTransfer.items[i].webkitGetAsEntry(); + 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) { + } else if (e.dataTransfer.files) { setFiles([...files, ...Array.from(e.dataTransfer.files)]); } setIsDragging(false); @@ -81,24 +83,24 @@ export const UploadForm = ({ files, setFiles, allowFolder = false, accept = '.cs >
- nighttime - daytime + nighttime + daytime
0, [styles.dragover]: isDragging })}> - upload icon -

Choose a {allowFolder ? "folder" : "file"} or drag it here

+ upload icon +

Choose a {allowFolder ? 'folder' : 'file'} or drag it here

Drop the file

{handleFileChange(e);}} - /* @ts-expect-error */ - webkitdirectory={allowFolder ? "" : undefined} + /* @ts-expect-error – webkitdirectory is a non-standard attribute but required for folder upload */ + webkitdirectory={allowFolder ? '' : undefined} />
{files.length > 0 && ( From 5f0c3621c410ba2a176dfb5aa573479f092a8e7a Mon Sep 17 00:00:00 2001 From: CowMan999 Date: Sun, 30 Nov 2025 13:22:15 -0500 Subject: [PATCH 5/6] fixed issue with presetfilesmodal --- .../components/composite/presetFilesModal/PresetFilesModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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

- +
From 6964fef6599e7dcd6db505dfbaf283e3a5424abf Mon Sep 17 00:00:00 2001 From: Kai Arseneau Date: Fri, 16 Jan 2026 12:07:05 -0500 Subject: [PATCH 6/6] Adding folder as separate button Seems you cannot have folder and file enabled as the same selection mechanism, choice between two buttons or a toggle --- .../composite/uploadModal/uploadModal.tsx | 2 +- .../ui/uploadForm/UploadForm.module.scss | 41 +++++++++++--- .../components/ui/uploadForm/UploadForm.tsx | 55 +++++++++++++++---- 3 files changed, 77 insertions(+), 21 deletions(-) diff --git a/front-end/src/components/composite/uploadModal/uploadModal.tsx b/front-end/src/components/composite/uploadModal/uploadModal.tsx index 6ad83868..960612e5 100644 --- a/front-end/src/components/composite/uploadModal/uploadModal.tsx +++ b/front-end/src/components/composite/uploadModal/uploadModal.tsx @@ -31,7 +31,7 @@ export const UploadModal = ({ isOpen, onClose }: UploadModalProps) => {

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 f67e7f71..1fd7b0fd 100644 --- a/front-end/src/components/ui/uploadForm/UploadForm.tsx +++ b/front-end/src/components/ui/uploadForm/UploadForm.tsx @@ -17,13 +17,15 @@ 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.items) { @@ -74,8 +76,18 @@ export const UploadForm = ({ files, setFiles, allowFolder = false, setFiles(files.filter((_, i) => i !== index)); }; + const handleFolderClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + folderInputRef.current?.click(); + }; + + const handleContentClick = () => { + fileInputRef.current?.click(); + }; + return ( -