Skip to content

Commit ce55fa8

Browse files
committed
feat: add generate BIDSPlan.yaml functionality
1 parent c98e073 commit ce55fa8

10 files changed

Lines changed: 724 additions & 73 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"bjd": "^0.3.2",
3232
"buffer": "6.0.3",
3333
"dayjs": "^1.11.10",
34+
"dicom-parser": "^1.8.21",
3435
"jquery": "^3.7.1",
3536
"jsfive": "^0.4.0",
3637
"json-stringify-safe": "^5.0.1",

src/components/User/Dashboard/DatasetOrganizer/DropZone.tsx

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
// src/components/DatasetOrganizer/DropZone.tsx
22
import { processFile, processFolder, processZip } from "./utils/fileProcessors";
3-
import { CloudUpload, Add } from "@mui/icons-material";
4-
import { Box, Typography, Paper, Button, TextField } from "@mui/material";
3+
import { CloudUpload, Add, CheckCircle } from "@mui/icons-material";
4+
import {
5+
Box,
6+
Typography,
7+
Paper,
8+
Button,
9+
TextField,
10+
CircularProgress,
11+
} from "@mui/material";
512
import { Colors } from "design/theme";
613
import React, { useState, useRef } from "react";
714
import { FileItem } from "redux/projects/types/projects.interface";
@@ -28,6 +35,7 @@ const DropZone: React.FC<DropZoneProps> = ({
2835
setExpandedIds,
2936
}) => {
3037
const [isDragging, setIsDragging] = useState(false);
38+
const [isProcessing, setIsProcessing] = useState(false); // ← add
3139
const fileInputRef = useRef<HTMLInputElement>(null);
3240
// const [basePath, setBasePath] = useState<string>(""); // change
3341

@@ -44,6 +52,7 @@ const DropZone: React.FC<DropZoneProps> = ({
4452
const handleDrop = async (e: React.DragEvent) => {
4553
e.preventDefault();
4654
setIsDragging(false);
55+
setIsProcessing(true); // ← add
4756

4857
const items = Array.from(e.dataTransfer.items); // detect if it is a folder
4958
const droppedFiles = Array.from(e.dataTransfer.files); // only gives file objects, can't detect folders
@@ -60,49 +69,51 @@ const DropZone: React.FC<DropZoneProps> = ({
6069
fileItems.push(droppedFiles[i]);
6170
}
6271
}
72+
try {
73+
// Process folders
74+
for (const folderEntry of folderEntries) {
75+
const folderFiles = await processFolder(
76+
folderEntry,
77+
null,
78+
baseDirectoryPath
79+
);
80+
setFiles((prev) => [...prev, ...folderFiles]);
81+
}
6382

64-
// Process folders
65-
for (const folderEntry of folderEntries) {
66-
// const folderFiles = await processFolder(folderEntry, null, basePath);// change
67-
const folderFiles = await processFolder(
68-
folderEntry,
69-
null,
70-
baseDirectoryPath
71-
); // add
72-
setFiles((prev) => [...prev, ...folderFiles]);
73-
}
74-
75-
// Process files
76-
for (const file of fileItems) {
77-
if (file.name.toLowerCase().endsWith(".zip")) {
78-
// const zipFiles = await processZip(file, basePath);//change
79-
const zipFiles = await processZip(file, baseDirectoryPath); //add
80-
setFiles((prev) => [...prev, ...zipFiles]);
81-
} else {
82-
// const fileItem = await processFile(file, basePath);//change
83-
const fileItem = await processFile(file, baseDirectoryPath); //add
84-
setFiles((prev) => [...prev, fileItem]);
83+
// Process files
84+
for (const file of fileItems) {
85+
if (file.name.toLowerCase().endsWith(".zip")) {
86+
const zipFiles = await processZip(file, baseDirectoryPath);
87+
setFiles((prev) => [...prev, ...zipFiles]);
88+
} else {
89+
const fileItem = await processFile(file, baseDirectoryPath);
90+
setFiles((prev) => [...prev, fileItem]);
91+
}
8592
}
93+
} finally {
94+
setIsProcessing(false);
8695
}
8796
};
8897

8998
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
9099
const selectedFiles = Array.from(e.target.files || []);
91-
92-
for (const file of selectedFiles) {
93-
if (file.name.toLowerCase().endsWith(".zip")) {
94-
// const zipFiles = await processZip(file, basePath);//change
95-
const zipFiles = await processZip(file, baseDirectoryPath); //add
96-
setFiles((prev) => [...prev, ...zipFiles]);
97-
} else {
98-
// const fileItem = await processFile(file, basePath); //change
99-
const fileItem = await processFile(file, baseDirectoryPath); //add
100-
setFiles((prev) => [...prev, fileItem]);
100+
setIsProcessing(true);
101+
102+
try {
103+
for (const file of selectedFiles) {
104+
if (file.name.toLowerCase().endsWith(".zip")) {
105+
const zipFiles = await processZip(file, baseDirectoryPath);
106+
setFiles((prev) => [...prev, ...zipFiles]);
107+
} else {
108+
const fileItem = await processFile(file, baseDirectoryPath);
109+
setFiles((prev) => [...prev, fileItem]);
110+
}
101111
}
112+
} finally {
113+
setIsProcessing(false);
114+
// Reset input
115+
e.target.value = "";
102116
}
103-
104-
// Reset input
105-
e.target.value = "";
106117
};
107118

108119
return (
@@ -141,20 +152,38 @@ const DropZone: React.FC<DropZoneProps> = ({
141152
},
142153
}}
143154
>
144-
<CloudUpload
155+
{/* <CloudUpload
145156
sx={{
146157
fontSize: files.length > 0 ? 40 : 64, // ← Smaller icon when files exist
147158
color: Colors.purple,
148159
mb: 1,
149160
}}
150-
/>
161+
/> */}
162+
{isProcessing ? (
163+
<CircularProgress size={48} sx={{ color: Colors.purple, mb: 1 }} />
164+
) : (
165+
<CloudUpload
166+
sx={{
167+
fontSize: files.length > 0 ? 40 : 64,
168+
color: Colors.purple,
169+
mb: 1,
170+
}}
171+
/>
172+
)}
173+
151174
<Typography variant={files.length > 0 ? "body1" : "h6"} gutterBottom>
152-
{files.length > 0
175+
{/* {files.length > 0
176+
? "Drop more files here"
177+
: "Drop your neuroimaging files here"} */}
178+
{isProcessing
179+
? "Processing files..."
180+
: files.length > 0
153181
? "Drop more files here"
154182
: "Drop your neuroimaging files here"}
155183
</Typography>
156184
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
157-
Supports NIfTI, SNIRF, HDF5, NeuroJSON, folders, and ZIP archives
185+
Supports NIfTI, DICOM, SNIRF, MATLAB, Homer3, HDF5, NeuroJSON,
186+
folders, and ZIP archives
158187
</Typography>
159188

160189
{files.length === 0 && (
@@ -183,7 +212,7 @@ const DropZone: React.FC<DropZoneProps> = ({
183212
multiple
184213
hidden
185214
onChange={handleFileSelect}
186-
accept=".nii,.nii.gz,.snirf,.h5,.hdf5,.jnii,.jmsh,.json,.txt,.md,.zip,.docx,.pdf,.xlsx,.xls"
215+
accept=".nii,.nii.gz,.snirf,.h5,.hdf5,.jnii,.jmsh,.json,.txt,.md,.zip,.docx,.pdf,.xlsx,.xls,.mat,.dcm,.nirs"
187216
/>
188217
</Paper>
189218
<TextField

src/components/User/Dashboard/DatasetOrganizer/FileTree.tsx

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ const FileTree: React.FC<FileTreeProps> = ({
136136
});
137137
};
138138

139+
const handleSelectAll = () => {
140+
const allIds = files.map((f) => f.id);
141+
setSelectedIds(new Set(allIds));
142+
};
143+
144+
const handleDeselectAll = () => {
145+
setSelectedIds(new Set());
146+
};
147+
139148
const handleDeleteSelected = () => {
140149
if (selectedIds.size === 0) return;
141150
if (!window.confirm(`Delete ${selectedIds.size} selected item(s)?`)) return;
@@ -225,6 +234,10 @@ const FileTree: React.FC<FileTreeProps> = ({
225234
neurojsonBinary: Colors.secondaryPurple,
226235
office: "#38bdf8",
227236
meta: Colors.yellow,
237+
matlab: Colors.black,
238+
dicom: "#34d399",
239+
nirs: Colors.darkOrange, // show homer3 in footer legend
240+
array: "#9ca3af",
228241
};
229242

230243
const color = colorMap[file.fileType || "other"] || "#9ca3af";
@@ -429,8 +442,9 @@ const FileTree: React.FC<FileTreeProps> = ({
429442
borderBottom: 1,
430443
borderColor: "divider",
431444
display: "flex",
445+
flexDirection: "column",
432446
justifyContent: "space-between",
433-
alignItems: "center",
447+
// alignItems: "center",
434448
}}
435449
>
436450
<Box>
@@ -484,21 +498,39 @@ const FileTree: React.FC<FileTreeProps> = ({
484498
</Button>
485499
</Box>
486500

487-
{selectedIds.size > 0 && (
501+
{/* Select All / Deselect All */}
502+
<Box>
488503
<Button
489504
size="small"
490-
startIcon={<Delete />}
491-
onClick={handleDeleteSelected}
505+
onClick={
506+
selectedIds.size > 0 ? handleDeselectAll : handleSelectAll
507+
}
492508
sx={{
493-
color: Colors.rose,
494-
"&:hover": {
495-
backgroundColor: "rgba(211, 47, 47, 0.1)",
496-
},
509+
fontSize: "0.75rem",
510+
textTransform: "none",
511+
color: "text.secondary",
512+
"&:hover": { backgroundColor: "rgba(128, 90, 213, 0.1)" },
497513
}}
498514
>
499-
Delete ({selectedIds.size})
515+
{selectedIds.size > 0 ? "Deselect All" : "Select All"}
500516
</Button>
501-
)}
517+
518+
{selectedIds.size > 0 && (
519+
<Button
520+
size="small"
521+
startIcon={<Delete />}
522+
onClick={handleDeleteSelected}
523+
sx={{
524+
color: Colors.rose,
525+
"&:hover": {
526+
backgroundColor: "rgba(211, 47, 47, 0.1)",
527+
},
528+
}}
529+
>
530+
Delete ({selectedIds.size})
531+
</Button>
532+
)}
533+
</Box>
502534
</Box>
503535

504536
{/* File Tree */}
@@ -582,6 +614,50 @@ const FileTree: React.FC<FileTreeProps> = ({
582614
/>
583615
<Typography variant="caption">User Meta</Typography>
584616
</Box>
617+
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
618+
<Box
619+
sx={{
620+
width: 8,
621+
height: 8,
622+
borderRadius: "50%",
623+
backgroundColor: "#34d399",
624+
}}
625+
/>
626+
<Typography variant="caption">DICOM</Typography>
627+
</Box>
628+
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
629+
<Box
630+
sx={{
631+
width: 8,
632+
height: 8,
633+
borderRadius: "50%",
634+
backgroundColor: Colors.black,
635+
}}
636+
/>
637+
<Typography variant="caption">MATLAB</Typography>
638+
</Box>
639+
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
640+
<Box
641+
sx={{
642+
width: 8,
643+
height: 8,
644+
borderRadius: "50%",
645+
backgroundColor: Colors.darkOrange,
646+
}}
647+
/>
648+
<Typography variant="caption">Homer3</Typography>
649+
</Box>
650+
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
651+
<Box
652+
sx={{
653+
width: 8,
654+
height: 8,
655+
borderRadius: "50%",
656+
backgroundColor: "#9ca3af",
657+
}}
658+
/>
659+
<Typography variant="caption">Array</Typography>
660+
</Box>
585661
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
586662
<Box
587663
sx={{

0 commit comments

Comments
 (0)