Skip to content

Commit

Permalink
Merge pull request #276 from flatironinstitute/load-url-box
Browse files Browse the repository at this point in the history
Add a text box to the Load Project window for URLs
  • Loading branch information
WardBrian authored Feb 13, 2025
2 parents 8bfebd4 + e4fa653 commit b261c17
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 16 deletions.
23 changes: 23 additions & 0 deletions gui/src/app/util/gists/doesGistExist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Octokit } from "@octokit/rest";

const doesGistExist = async (gistUri: string): Promise<boolean> => {
const parts = gistUri.split("/");
const gistId = parts[parts.length - 1];
if (!gistId) {
return false;
}
const octokit = new Octokit();
try {
const r = await octokit.request("HEAD /gists/{gist_id}", {
gist_id: gistId,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
return r.status === 200;
} catch (e) {
return false;
}
};

export default doesGistExist;
127 changes: 111 additions & 16 deletions gui/src/app/windows/LoadProjectWindow/LoadProjectPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FunctionComponent, useCallback, use, useState } from "react";
import { useNavigate } from "react-router-dom";

import { Delete } from "@mui/icons-material";
import Button from "@mui/material/Button";
Expand All @@ -10,6 +11,9 @@ import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import FormControl from "@mui/material/FormControl";
import TextField from "@mui/material/TextField";
import FormHelperText from "@mui/material/FormHelperText";

import { AlternatingTableRow } from "@SpComponents/StyledTables";
import {
Expand All @@ -22,7 +26,14 @@ import {
deserializeZipToFiles,
parseFile,
} from "@SpCore/Project/ProjectSerialization";
import doesGistExist from "@SpUtil/gists/doesGistExist";

import UploadFiles from "./UploadFiles";
import {
fromQueryParams,
QueryParamKeys,
queryStringHasParameters,
} from "@SpCore/Project/ProjectQueryLoading";

type File = { name: string; content: ArrayBuffer };

Expand Down Expand Up @@ -119,25 +130,53 @@ const LoadProjectPanel: FunctionComponent<LoadProjectProps> = ({ onClose }) => {
[importUploadedZip],
);

const { urlToLoad, setUrlToLoad, tryLoad } = useUrlLoader(setErrorText);

return (
<div className="dialogWrapper">
<Stack spacing={2}>
<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>
Other individual project files (data.json, meta.json, data.py,
etc.)
</li>
</ul>
</div>
<UploadFiles height={300} onUpload={onUpload} />
<FormControl margin="normal">
<TextField
variant="standard"
label="Project URL"
value={urlToLoad}
onChange={(e) => setUrlToLoad(e.target.value.trim())}
onBlur={() => {
if (tryLoad()) {
onClose();
}
}}
onKeyUp={(e) => {
if (e.key === "Enter" && tryLoad()) {
onClose();
}
}}
></TextField>
<FormHelperText component="div">
You can supply a URL to load a project from:
<ul style={{ margin: 0 }}>
<li>A Stan-Playground URL</li>
<li>A GitHub Gist URL</li>
</ul>
</FormHelperText>
<UploadFiles height={300} onUpload={onUpload} />
<FormHelperText component="div">
You can upload:
<ul style={{ margin: 0 }}>
<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 (data.json, meta.json, data.py,
etc.)
</li>
</ul>
</FormHelperText>
</FormControl>

{errorText !== "" && (
<Typography color="error.main">{errorText}</Typography>
)}
Expand Down Expand Up @@ -193,4 +232,60 @@ const LoadProjectPanel: FunctionComponent<LoadProjectProps> = ({ onClose }) => {
);
};

const useUrlLoader = (setErrorText: (text: string) => void) => {
const [urlToLoad, setURLRaw] = useState("");
const [query, setQuery] = useState("");

const setUrlToLoad = useCallback(
(url: string) => {
setURLRaw(url);
setQuery("");

if (url === "") return;
if (url.startsWith("https://stan-playground.flatironinstitute.org/")) {
const queriesOnly = new URLSearchParams(url.split("?", 2)[1]);
if (queryStringHasParameters(fromQueryParams(queriesOnly))) {
setQuery(`?${queriesOnly.toString()}`);
setErrorText("");
} else {
setErrorText(
"Stan-Playground URL does not contain any relevant data: " +
url +
" (should contain at least one of " +
Object.values(QueryParamKeys).join(", ") +
")",
);
}
} else if (url.startsWith("https://gist.github.com/")) {
doesGistExist(url).then((exists) => {
if (exists) {
setQuery(`?project=${url}`);
setErrorText("");
} else {
setErrorText("Gist not found: " + url);
}
});
} else {
setErrorText(
"Unsupported URL: " +
url +
" (must be a Stan-Playground URL or a GitHub Gist URL)",
);
}
},
[setErrorText],
);

const navigate = useNavigate();

const tryLoad = useCallback(() => {
if (query === "") return false;
navigate(query, { replace: true });
setURLRaw("");
return true;
}, [navigate, query]);

return { urlToLoad, setUrlToLoad, tryLoad };
};

export default LoadProjectPanel;

0 comments on commit b261c17

Please sign in to comment.