Skip to content

Commit

Permalink
Allow multiple files in upload window, give more control
Browse files Browse the repository at this point in the history
  • Loading branch information
WardBrian committed Jul 31, 2024
1 parent 13a32a4 commit b1638da
Showing 1 changed file with 96 additions and 77 deletions.
173 changes: 96 additions & 77 deletions gui/src/app/pages/HomePage/LoadProjectWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
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,
} from "@SpCore/FileMapping";
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;
Expand All @@ -24,12 +22,8 @@ const LoadProjectWindow: FunctionComponent<LoadProjectWindowProps> = ({
onClose,
}) => {
const { update } = useContext(ProjectContext);
const [errorText, setErrorText] = useState<string | null>(null);
const [filesUploaded, setFilesUploaded] = useState<
{ name: string; content: ArrayBuffer }[] | null
>(null);
const [showReplaceProjectOptions, setShowReplaceProjectOptions] =
useState<boolean>(false);
const [errorText, setErrorText] = useState<string>("");
const [filesUploaded, setFilesUploaded] = useState<File[]>([]);

const importUploadedFiles = useCallback(
async (o: { replaceProject: boolean }) => {
Expand All @@ -49,25 +43,19 @@ const LoadProjectWindow: FunctionComponent<LoadProjectWindowProps> = ({
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<FieldsContentsMap> = {
stanFileContent: parseFile(filesUploaded[0].content),
};
update({
type: "loadFiles",
files: fileManifest,
clearExisting: replaceProject,
});
} else {
let stanFileName = "";
const files: Partial<FileRegistry> = {};

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}`);
}
Expand All @@ -80,6 +68,10 @@ const LoadProjectWindow: FunctionComponent<LoadProjectWindowProps> = ({
files: fileManifest,
clearExisting: replaceProject,
});

if (stanFileName !== "" && fileManifest.meta === undefined) {
update({ type: "retitle", title: stanFileName });
}
}
onClose();
} catch (e: any) {
Expand All @@ -89,58 +81,85 @@ const LoadProjectWindow: FunctionComponent<LoadProjectWindowProps> = ({
[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 (
<div className="dialogWrapper">
<div>
You can upload:
<ul>
<li>A .zip file that was previously exported</li>
<li>
A directory of files that were extracted from an exported .zip file
</li>
<li>An individual *.stan file</li>
<li>An individual data.json file</li>
</ul>
</div>
<div className="ErrorText">{errorText}</div>
{!filesUploaded ? (
<Stack spacing={2}>
<div>
<UploadFilesArea height={300} onUpload={setFilesUploaded} />
You can upload:
<ul>
<li>A .zip file that was previously exported</li>
<li>
A directory of files that were extracted from an exported .zip
file
</li>
<li>An individual *.stan file</li>
<li>
Other individual project files (meta.json, data.json, init.json,
etc.)
</li>
</ul>
</div>
) : (
<div>
{filesUploaded.map((file) => (
<div key={file.name}>{file.name}</div>
))}
</div>
)}
{showReplaceProjectOptions && (
<div>
<Button onClick={() => importUploadedFiles({ replaceProject: true })}>
Load into a NEW project
</Button>
&nbsp;
<Button
onClick={() => importUploadedFiles({ replaceProject: false })}
>
Load into EXISTING project
</Button>
</div>
)}
<UploadFilesArea height={300} onUpload={onUpload} />
{errorText !== "" && <div className="ErrorText">{errorText}</div>}

{filesUploaded.length > 0 && (
<>
<div>
<table className="project-summary-table">
<tbody>
{filesUploaded.map(({ name, content }) => (
<tr key={name}>
<td>{name}</td>
<td>{content.byteLength} bytes</td>
<td>
<IconButton
onClick={() => {
setFilesUploaded((prev) =>
prev.filter((f) => f.name !== name),
);
}}
size="small"
>
<Delete fontSize="inherit" />
</IconButton>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div>
<Button
onClick={() => importUploadedFiles({ replaceProject: true })}
>
Load into a NEW project
</Button>
<span style={{ margin: "0 10px" }}>or</span>
<Button
onClick={() => importUploadedFiles({ replaceProject: false })}
>
Load into EXISTING project
</Button>
</div>
</>
)}
</Stack>
</div>
);
};
Expand Down

0 comments on commit b1638da

Please sign in to comment.