frontend/app/ByProjectComponent.tsx (110 lines of code) (raw):

import React, { useState, useEffect } from "react"; import { RouteChildrenProps } from "react-router"; import { authenticatedFetch } from "./auth"; import NewVaultSelector from "./searchnbrowse/NewVaultSelector"; import ProjectLockerSearchBar from "./projectsearch/ProjectLockerSearchBar"; import ProjectContentSummary from "./projectsearch/ProjectContentSummary"; import { Grid, makeStyles } from "@material-ui/core"; interface ByProjectComponentWrapperProps {} interface ByProjectComponentWrapperState { lastError?: string; } /** * this class is no more than a wrapper to implement an error boundary and prevent the component from breaking the entire page */ class ByProjectComponent extends React.Component< RouteChildrenProps<ByProjectComponentWrapperProps>, ByProjectComponentWrapperState > { constructor(props: RouteChildrenProps<ByProjectComponentWrapperProps>) { super(props); this.state = { lastError: undefined, }; } static getDerivedStateFromError(error: Error) { return { lastError: error.toString(), }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error("ByProjectComponent failed: ", error); console.log(errorInfo); } render() { return <ProjectSearchBar {...this.props} />; } } interface ProjectSearchBarState {} const useStyles = makeStyles({ parentBox: { marginLeft: "3%", marginRight: "3%", width: "94%", overflow: "hidden", }, }); /** * the actual search bar is implemented here * @param props * @constructor */ const ProjectSearchBar: React.FC<RouteChildrenProps> = (props) => { const [loading, setLoading] = useState(false); const [currentProjectSearch, setCurrentProjectSearch] = useState< string | undefined >(undefined); const [projectLockerBaseUrl, setProjectLockerBaseUrl] = useState(""); const [lastError, setLastError] = useState<string | undefined>(undefined); const [vaultId, setVaultId] = useState(""); const classes = useStyles(); function breakdownSearchParams() { const fullstring = props.location.search.slice(1); const parts = fullstring.split("&"); const elems = parts.map((entry) => entry.split("=")); return elems.reduce<Map<string, string>>((acc, elem) => { acc.set(elem[0], elem[1]); return acc; }, new Map<string, string>()); } function setupCurrentSearch() { const searchParams = breakdownSearchParams(); console.log(searchParams); const projectValue = searchParams.get("project"); projectValue ? setCurrentProjectSearch(projectValue) : null; } async function loadFrontendConfig() { const response = await authenticatedFetch("/api/config", {}); if (response.ok) { const content = await response.json(); setProjectLockerBaseUrl(content.projectLockerBaseUrl); } else { const content = await response.text(); setLastError(content); } } //set up the frontend config on load useEffect(() => { loadFrontendConfig().then(() => { setupCurrentSearch(); }); }, []); //if the current project search is updated, reflect it in the url useEffect(() => { if (currentProjectSearch) { props.history.push(`?project=${currentProjectSearch}`); } else { props.history.push("/byproject"); } }, [currentProjectSearch]); return ( <div className="windowpanel"> <ProjectLockerSearchBar className={classes.parentBox} projectLockerBaseUrl={projectLockerBaseUrl} projectSelectionChanged={(newProject) => setCurrentProjectSearch(newProject) } size={8} /> <Grid container direction="row" justify="center" className={classes.parentBox} > <Grid item xs={9}> <NewVaultSelector currentvault={vaultId} vaultWasChanged={(newVaultId) => setVaultId(newVaultId)} /> </Grid> </Grid> <ProjectContentSummary vaultId={vaultId} projectId={currentProjectSearch} plutoBaseUrl={projectLockerBaseUrl} /> </div> ); }; export default ByProjectComponent;