From b1638da4ad17cd96ebfb2db636bc9ba44a700037 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Wed, 31 Jul 2024 15:19:30 +0000 Subject: [PATCH] Allow multiple files in upload window, give more control --- .../app/pages/HomePage/LoadProjectWindow.tsx | 173 ++++++++++-------- 1 file changed, 96 insertions(+), 77 deletions(-) diff --git a/gui/src/app/pages/HomePage/LoadProjectWindow.tsx b/gui/src/app/pages/HomePage/LoadProjectWindow.tsx index a971836..e54aed7 100644 --- a/gui/src/app/pages/HomePage/LoadProjectWindow.tsx +++ b/gui/src/app/pages/HomePage/LoadProjectWindow.tsx @@ -1,6 +1,8 @@ +import { Delete } from "@mui/icons-material"; import Button from "@mui/material/Button"; +import IconButton from "@mui/material/IconButton"; +import Stack from "@mui/material/Stack"; import { - FieldsContentsMap, FileNames, FileRegistry, mapFileContentsToModel, @@ -8,13 +10,9 @@ import { import { ProjectContext } from "@SpCore/ProjectContextProvider"; import { deserializeZipToFiles, parseFile } from "@SpCore/ProjectSerialization"; import UploadFilesArea from "@SpPages/UploadFilesArea"; -import { - FunctionComponent, - useCallback, - useContext, - useEffect, - useState, -} from "react"; +import { FunctionComponent, useCallback, useContext, useState } from "react"; + +type File = { name: string; content: ArrayBuffer }; type LoadProjectWindowProps = { onClose: () => void; @@ -24,12 +22,8 @@ const LoadProjectWindow: FunctionComponent = ({ onClose, }) => { const { update } = useContext(ProjectContext); - const [errorText, setErrorText] = useState(null); - const [filesUploaded, setFilesUploaded] = useState< - { name: string; content: ArrayBuffer }[] | null - >(null); - const [showReplaceProjectOptions, setShowReplaceProjectOptions] = - useState(false); + const [errorText, setErrorText] = useState(""); + const [filesUploaded, setFilesUploaded] = useState([]); const importUploadedFiles = useCallback( async (o: { replaceProject: boolean }) => { @@ -49,25 +43,19 @@ const LoadProjectWindow: FunctionComponent = ({ files: fileManifest, clearExisting: replaceProject, }); - } else if ( - filesUploaded.length === 1 && - filesUploaded[0].name.endsWith(".stan") - ) { - // a single .stan file - if (replaceProject) { - update({ type: "retitle", title: filesUploaded[0].name }); - } - const fileManifest: Partial = { - stanFileContent: parseFile(filesUploaded[0].content), - }; - update({ - type: "loadFiles", - files: fileManifest, - clearExisting: replaceProject, - }); } else { + let stanFileName = ""; const files: Partial = {}; + for (const file of filesUploaded) { + if (file.name.endsWith(".stan")) { + if (stanFileName !== "") { + throw Error("Only one .stan file can be uploaded at a time"); + } + files["main.stan"] = parseFile(file.content); + stanFileName = file.name; + continue; + } if (!Object.values(FileNames).includes(file.name as any)) { throw Error(`Unrecognized file: ${file.name}`); } @@ -80,6 +68,10 @@ const LoadProjectWindow: FunctionComponent = ({ files: fileManifest, clearExisting: replaceProject, }); + + if (stanFileName !== "" && fileManifest.meta === undefined) { + update({ type: "retitle", title: stanFileName }); + } } onClose(); } catch (e: any) { @@ -89,58 +81,85 @@ const LoadProjectWindow: FunctionComponent = ({ [filesUploaded, onClose, update], ); - useEffect(() => { - if (!filesUploaded) return; - if (filesUploaded.length === 1 && !filesUploaded[0].name.endsWith(".zip")) { - // The user has uploaded a single file and it is not a zip file. In - // this case we want to give the user the option whether or not to - // replace the current project. - setShowReplaceProjectOptions(true); - } else { - // Otherwise, we just go ahead and import the files, replacing the - // entire project - importUploadedFiles({ replaceProject: true }); - } - }, [filesUploaded, importUploadedFiles]); + const onUpload = useCallback( + (fs: { name: string; content: ArrayBuffer }[]) => { + if (fs.length === 1 && fs[0].name.endsWith(".zip")) { + setFilesUploaded(fs); + importUploadedFiles({ replaceProject: true }); + } else { + setFilesUploaded((prev) => { + const newNames = fs.map((f) => f.name); + const oldToKeep = prev.filter((f) => !newNames.includes(f.name)); + return [...oldToKeep, ...fs]; + }); + } + }, + [importUploadedFiles], + ); return (
-
- You can upload: -
    -
  • A .zip file that was previously exported
  • -
  • - A directory of files that were extracted from an exported .zip file -
  • -
  • An individual *.stan file
  • -
  • An individual data.json file
  • -
-
-
{errorText}
- {!filesUploaded ? ( +
- + You can upload: +
    +
  • A .zip file that was previously exported
  • +
  • + A directory of files that were extracted from an exported .zip + file +
  • +
  • An individual *.stan file
  • +
  • + Other individual project files (meta.json, data.json, init.json, + etc.) +
  • +
- ) : ( -
- {filesUploaded.map((file) => ( -
{file.name}
- ))} -
- )} - {showReplaceProjectOptions && ( -
- -   - -
- )} + + {errorText !== "" &&
{errorText}
} + + {filesUploaded.length > 0 && ( + <> +
+ + + {filesUploaded.map(({ name, content }) => ( + + + + + + ))} + +
{name}{content.byteLength} bytes + { + setFilesUploaded((prev) => + prev.filter((f) => f.name !== name), + ); + }} + size="small" + > + + +
+
+
+ + or + +
+ + )} +
); };