Skip to content

Commit def8302

Browse files
committed
feat: add base path to zip and folder processors
1 parent d90edfa commit def8302

3 files changed

Lines changed: 224 additions & 13 deletions

File tree

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ const DropZone: React.FC<DropZoneProps> = ({
5959

6060
// Process folders
6161
for (const folderEntry of folderEntries) {
62-
const folderFiles = await processFolder(folderEntry, null);
62+
const folderFiles = await processFolder(folderEntry, null, basePath);
6363
setFiles((prev) => [...prev, ...folderFiles]);
6464
}
6565

6666
// Process files
6767
for (const file of fileItems) {
6868
if (file.name.toLowerCase().endsWith(".zip")) {
69-
const zipFiles = await processZip(file);
69+
const zipFiles = await processZip(file, basePath);
7070
setFiles((prev) => [...prev, ...zipFiles]);
7171
} else {
7272
const fileItem = await processFile(file, basePath);
@@ -80,7 +80,7 @@ const DropZone: React.FC<DropZoneProps> = ({
8080

8181
for (const file of selectedFiles) {
8282
if (file.name.toLowerCase().endsWith(".zip")) {
83-
const zipFiles = await processZip(file);
83+
const zipFiles = await processZip(file, basePath);
8484
setFiles((prev) => [...prev, ...zipFiles]);
8585
} else {
8686
const fileItem = await processFile(file, basePath);

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

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { generateId } from "./utils/fileProcessors";
12
import {
23
Folder,
34
InsertDriveFile,
@@ -7,6 +8,7 @@ import {
78
NoteAdd,
89
Edit,
910
Description,
11+
Add,
1012
} from "@mui/icons-material";
1113
import {
1214
Box,
@@ -44,6 +46,69 @@ const FileTree: React.FC<FileTreeProps> = ({
4446
const [noteDialogOpen, setNoteDialogOpen] = useState(false);
4547
const [editingNoteId, setEditingNoteId] = useState<string | null>(null);
4648
const [noteText, setNoteText] = useState("");
49+
const [metaEditorOpen, setMetaEditorOpen] = useState(false);
50+
const [metaType, setMetaType] = useState<
51+
"readme" | "subject" | "instructions" | null
52+
>(null);
53+
const [metaFileName, setMetaFileName] = useState("");
54+
const [metaContent, setMetaContent] = useState("");
55+
56+
// In FileTree.tsx
57+
const metaConfigs = {
58+
readme: {
59+
label: "Add README File",
60+
defaultFilename: "README.md",
61+
placeholder:
62+
"Enter dataset description, authors, license, and other important information...",
63+
},
64+
subject: {
65+
label: "Add Subject/Session Info",
66+
defaultFilename: "participants.txt",
67+
placeholder:
68+
"Enter subject IDs, session info, and participant metadata...\n\nExample:\nSubject ID: sub-01\nSession: ses-01\nAge: 25\nSex: M",
69+
},
70+
instructions: {
71+
label: "Add Conversion Instructions",
72+
defaultFilename: "CONVERSION_NOTES.md",
73+
placeholder:
74+
"Enter instructions for converting this dataset to BIDS format...\n\nExample:\n- Rename T1w files to sub-XX_T1w.nii.gz\n- Create JSON sidecars for each scan\n- Map task names to BIDS task labels",
75+
},
76+
};
77+
78+
const handleOpenMetaEditor = (
79+
type: "readme" | "subject" | "instructions"
80+
) => {
81+
const config = metaConfigs[type];
82+
setMetaType(type);
83+
setMetaFileName(config.defaultFilename);
84+
setMetaContent("");
85+
setMetaEditorOpen(true);
86+
};
87+
88+
const handleSaveMetaFile = () => {
89+
if (!metaFileName.trim()) {
90+
alert("Please enter a filename");
91+
return;
92+
}
93+
94+
const newFile: FileItem = {
95+
id: generateId(),
96+
name: metaFileName.trim(),
97+
type: "file",
98+
parentId: null,
99+
fileType: "meta",
100+
content: metaContent,
101+
contentType: "text",
102+
sourcePath: undefined,
103+
isUserMeta: true,
104+
};
105+
106+
setFiles((prev) => [...prev, newFile]);
107+
setMetaEditorOpen(false);
108+
setMetaType(null);
109+
setMetaFileName("");
110+
setMetaContent("");
111+
};
47112

48113
const handleToggleExpand = (id: string) => {
49114
setExpandedIds((prev) => {
@@ -325,6 +390,48 @@ const FileTree: React.FC<FileTreeProps> = ({
325390
</Typography>
326391
</Box>
327392

393+
<Box sx={{ display: "flex", gap: 0.5, flexWrap: "wrap", mb: 1 }}>
394+
<Button
395+
size="small"
396+
startIcon={<Add />}
397+
onClick={() => handleOpenMetaEditor("readme")}
398+
sx={{
399+
fontSize: "0.75rem",
400+
textTransform: "none",
401+
color: "text.secondary",
402+
"&:hover": { backgroundColor: "rgba(128, 90, 213, 0.1)" },
403+
}}
404+
>
405+
README
406+
</Button>
407+
<Button
408+
size="small"
409+
startIcon={<Add />}
410+
onClick={() => handleOpenMetaEditor("subject")}
411+
sx={{
412+
fontSize: "0.75rem",
413+
textTransform: "none",
414+
color: "text.secondary",
415+
"&:hover": { backgroundColor: "rgba(128, 90, 213, 0.1)" },
416+
}}
417+
>
418+
Subject ID
419+
</Button>
420+
<Button
421+
size="small"
422+
startIcon={<Add />}
423+
onClick={() => handleOpenMetaEditor("instructions")}
424+
sx={{
425+
fontSize: "0.75rem",
426+
textTransform: "none",
427+
color: "text.secondary",
428+
"&:hover": { backgroundColor: "rgba(128, 90, 213, 0.1)" },
429+
}}
430+
>
431+
Instructions
432+
</Button>
433+
</Box>
434+
328435
{selectedIds.size > 0 && (
329436
<Button
330437
size="small"
@@ -482,6 +589,73 @@ const FileTree: React.FC<FileTreeProps> = ({
482589
</Button>
483590
</DialogActions>
484591
</Dialog>
592+
593+
{/* Meta File Editor Dialog */}
594+
<Dialog
595+
open={metaEditorOpen}
596+
onClose={() => setMetaEditorOpen(false)}
597+
maxWidth="sm"
598+
fullWidth
599+
>
600+
<DialogTitle sx={{ color: Colors.darkPurple }}>
601+
{metaType && metaConfigs[metaType].label}
602+
</DialogTitle>
603+
<DialogContent>
604+
<TextField
605+
margin="dense"
606+
label="Filename"
607+
fullWidth
608+
variant="outlined"
609+
value={metaFileName}
610+
onChange={(e) => setMetaFileName(e.target.value)}
611+
sx={{
612+
mb: 2,
613+
mt: 1,
614+
"& .MuiInputLabel-root.Mui-focused": { color: Colors.purple },
615+
"& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline":
616+
{
617+
borderColor: Colors.purple,
618+
},
619+
}}
620+
/>
621+
<TextField
622+
multiline
623+
rows={6}
624+
fullWidth
625+
variant="outlined"
626+
placeholder={metaType ? metaConfigs[metaType].placeholder : ""}
627+
value={metaContent}
628+
onChange={(e) => setMetaContent(e.target.value)}
629+
sx={{
630+
"& .MuiInputLabel-root.Mui-focused": { color: Colors.purple },
631+
"& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline":
632+
{
633+
borderColor: Colors.purple,
634+
},
635+
}}
636+
/>
637+
</DialogContent>
638+
<DialogActions>
639+
<Button
640+
onClick={() => setMetaEditorOpen(false)}
641+
sx={{ color: Colors.purple }}
642+
>
643+
Cancel
644+
</Button>
645+
<Button
646+
onClick={handleSaveMetaFile}
647+
variant="contained"
648+
sx={{
649+
background: `linear-gradient(135deg, ${Colors.rose} 0%, ${Colors.purple} 100%)`,
650+
"&:hover": {
651+
background: `linear-gradient(135deg, ${Colors.purple} 0%, ${Colors.rose} 100%)`,
652+
},
653+
}}
654+
>
655+
Save
656+
</Button>
657+
</DialogActions>
658+
</Dialog>
485659
</>
486660
);
487661
};

src/components/User/Dashboard/DatasetOrganizer/utils/fileProcessors.ts

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,27 @@ export const processFile = async (
158158
};
159159

160160
// Process ZIP files
161-
export const processZip = async (file: File): Promise<FileItem[]> => {
161+
export const processZip = async (
162+
file: File,
163+
basePath?: string
164+
): Promise<FileItem[]> => {
162165
const zip = new JSZip();
163166
const zipName = file.name;
164167

165168
try {
166169
const contents = await zip.loadAsync(file);
167170
const entries: FileItem[] = [];
168171
const pathMap: Record<string, string> = {};
172+
// ✅ ADD: Create root ZIP container
173+
const zipRootId = generateId();
174+
entries.push({
175+
id: zipRootId,
176+
name: zipName,
177+
type: "zip",
178+
parentId: null,
179+
sourcePath: zipName,
180+
});
181+
169182
const paths = Object.keys(contents.files).sort();
170183

171184
for (const path of paths) {
@@ -177,20 +190,25 @@ export const processZip = async (file: File): Promise<FileItem[]> => {
177190
const parts = path.split("/");
178191
const fileName = parts.pop()!;
179192
let currentPath = "";
180-
let parentId: string | null = null;
193+
// let parentId: string | null = null;
194+
let parentId: string | null = zipRootId;
181195

182196
// Create folder hierarchy
183197
parts.forEach((part) => {
184198
const folderPath = currentPath ? `${currentPath}/${part}` : part;
185199
if (!pathMap[folderPath]) {
186200
const folderId = generateId();
187201
pathMap[folderPath] = folderId;
202+
const folderSourcePath = basePath
203+
? `${basePath}/${zipName}/${folderPath}`.replace(/\/+/g, "/")
204+
: `${zipName}/${folderPath}`;
188205
entries.push({
189206
id: folderId,
190207
name: part,
191208
type: "folder",
192209
parentId: parentId,
193-
sourcePath: `${zipName}/${folderPath}`,
210+
// sourcePath: `${zipName}/${folderPath}`,
211+
sourcePath: folderSourcePath,
194212
});
195213
}
196214
parentId = pathMap[folderPath];
@@ -202,13 +220,19 @@ export const processZip = async (file: File): Promise<FileItem[]> => {
202220
const fileType = getFileType(fileName);
203221
const ext = fileName.toLowerCase().split(".").pop();
204222

223+
// Add basePath to file sourcePath
224+
const fileSourcePath = basePath
225+
? `${basePath}/${zipName}/${path}`.replace(/\/+/g, "/")
226+
: `${zipName}/${path}`;
227+
205228
const entry: FileItem = {
206229
id: fileId,
207230
name: fileName,
208231
type: "file",
209232
parentId: parentId,
210233
fileType: fileType as any,
211-
sourcePath: `${zipName}/${path}`,
234+
// sourcePath: `${zipName}/${path}`,
235+
sourcePath: fileSourcePath,
212236
};
213237

214238
// Extract content based on file type
@@ -323,19 +347,27 @@ export const processZip = async (file: File): Promise<FileItem[]> => {
323347

324348
export const processFolder = async (
325349
folderEntry: FileSystemDirectoryEntry,
326-
parentId: string | null
350+
parentId: string | null,
351+
basePath?: string
327352
): Promise<FileItem[]> => {
328353
const entries: FileItem[] = [];
329354
const folderId = generateId();
330-
const basePath = folderEntry.name;
355+
// const basePath = folderEntry.name;
356+
const folderName = folderEntry.name;
357+
358+
// Add basePath to root folder sourcePath
359+
const rootSourcePath = basePath
360+
? `${basePath}/${folderName}`.replace(/\/+/g, "/")
361+
: folderName;
331362

332363
// Add the folder itself
333364
entries.push({
334365
id: folderId,
335366
name: folderEntry.name,
336367
type: "folder",
337368
parentId: parentId,
338-
sourcePath: basePath,
369+
// sourcePath: basePath,
370+
sourcePath: rootSourcePath,
339371
});
340372

341373
// Helper: Promisify readEntries
@@ -376,7 +408,11 @@ export const processFolder = async (
376408

377409
// Process each entry
378410
for (const entry of allEntries) {
379-
const entryPath = `${currentPath}/${entry.name}`;
411+
// const entryPath = `${currentPath}/${entry.name}`;
412+
// Construct full path with basePath
413+
const entryPath = basePath
414+
? `${basePath}/${currentPath}/${entry.name}`.replace(/\/+/g, "/")
415+
: `${currentPath}/${entry.name}`;
380416

381417
if (entry.isFile) {
382418
// Process file
@@ -399,14 +435,15 @@ export const processFolder = async (
399435
await traverseDirectory(
400436
entry as FileSystemDirectoryEntry,
401437
subFolderId,
402-
entryPath
438+
// entryPath
439+
`${currentPath}/${entry.name}`
403440
);
404441
}
405442
}
406443
}
407444

408445
// Start traversal
409-
await traverseDirectory(folderEntry, folderId, basePath);
446+
await traverseDirectory(folderEntry, folderId, folderName);
410447

411448
return entries;
412449
};

0 commit comments

Comments
 (0)