app/index.tsx (272 lines of code) (raw):

import React, { useEffect, useState } from "react"; import { render } from "react-dom"; import { BrowserRouter, Link, Route, Switch, Redirect, withRouter, BrowserRouterProps, RouteComponentProps, } from "react-router-dom"; import axios from "axios"; import FieldGroupCache from "./vidispine/FieldGroupCache"; import { LoadGroupFromServer, VidispineFieldGroup, } from "./vidispine/field-group/VidispineFieldGroup"; import ItemViewComponent from "./ItemViewComponent"; import FrontpageComponent from "./Frontpage"; import { AppSwitcher, Header, JwtDataShape, OAuthContextData, OAuthContextProvider, SystemNotification, UserContextProvider, verifyExistingLogin, PlutoThemeProvider, } from "@guardian/pluto-headers"; import { CircularProgress, CssBaseline, Typography } from "@material-ui/core"; import { Helmet } from "react-helmet"; import { setupInterceptors } from "./interceptors"; import VidispineContext, { VidispineContextType, } from "./Context/VidispineContext"; import EmbeddablePlayer from "./Embeddable/EmbeddablePlayer"; import MediaAtomToolContext, { MediaAtomToolContextType, } from "./pluto-deliverables/MediaAtomToolContext"; import NearlineComponent from "./Nearline"; interface AppState { vidispineBaseUrl?: string; fields?: FieldGroupCache; loading?: boolean; loadingStage?: number; lastError?: string | null; isLoggedIn?: boolean; } interface ConfigFileData { vidispineBaseUrl: string; mediaAtomBaseUrl: string; } declare var deploymentRootPath: string | undefined; //this will be set in the index.html template file and gives us the value of deployment-root from the server config if (deploymentRootPath == undefined) { deploymentRootPath = "/"; } //set up request and response interceptors setupInterceptors(); //think of a way to improve this later! const groupsToCache = ["Deliverable", "Newswire", "Rushes", "Asset"]; const App: React.FC<{}> = () => { const [vidispineDetails, setVidispineDetails] = useState< VidispineContextType | undefined >(undefined); const [mediaAtomDetails, setMediaAtomDetails] = useState< MediaAtomToolContextType | undefined >(undefined); const [loading, setLoading] = useState(true); const [lastError, setLastError] = useState<string | undefined>(undefined); const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false); const [userProfile, setUserProfile] = useState<JwtDataShape | undefined>( undefined ); const haveToken = () => { return window.localStorage.getItem("pluto:access-token"); }; const oAuthConfigLoaded = (oAuthConfig: OAuthContextData) => { if (haveToken()) { verifyExistingLogin(oAuthConfig) .then((profile) => { setUserProfile(profile); setIsLoggedIn(true); }) .catch((err) => console.error("Could not verify existing user profile: ", err) ); } }; /** * load in field/group definitions from VS. Updates the state.fields parameter */ const buildCache = async (vidispineBaseUrl: string) => { console.log(groupsToCache); const groupsData = await Promise.all( groupsToCache.map((groupName) => LoadGroupFromServer(vidispineBaseUrl, groupName) ) ); const maybeExistingData = vidispineDetails?.fieldCache; const newCache = new FieldGroupCache(maybeExistingData, ...groupsData); console.log( `Successfully loaded ${newCache.size()} metadata groups from Vidispine` ); return newCache; }; const initialiseComponent = async () => { try { const configResponse = await axios.get<ConfigFileData>( "/config/config.json" ); const fieldGroupCache = await buildCache( configResponse.data.vidispineBaseUrl ); setVidispineDetails({ baseUrl: configResponse.data.vidispineBaseUrl, fieldCache: fieldGroupCache, }); if (configResponse.data.mediaAtomBaseUrl) setMediaAtomDetails({ baseUrl: configResponse.data.mediaAtomBaseUrl, }); setLoading(false); setLastError(undefined); } catch (err) { console.error(err); setLoading(false); setLastError( "Could not load configuration, please try refreshing the page in a minute. More details in the console log." ); } }; useEffect(() => { initialiseComponent(); }, []); useEffect(() => { if (!window.location.href.includes("embed")) { const timeout = setTimeout(() => { if (!isLoggedIn) { console.log("Not logged in, redirecting to pluto-start."); window.location.assign( "/refreshLogin?returnTo=" + window.location.pathname ); } }, 3000); return () => { clearTimeout(timeout); }; } }, [isLoggedIn]); if (window.location.href.includes("embed")) { //if we are embedding, we just need a minimum of decorations so don't load the full UI return ( <PlutoThemeProvider> <CssBaseline /> <OAuthContextProvider onLoaded={oAuthConfigLoaded}> <UserContextProvider value={{ profile: userProfile, updateProfile: (newValue) => setUserProfile(newValue), }} > <VidispineContext.Provider value={vidispineDetails}> <Switch> <Route path="/embed/player" component={EmbeddablePlayer} /> </Switch> </VidispineContext.Provider> </UserContextProvider> </OAuthContextProvider> </PlutoThemeProvider> ); } else { return ( <PlutoThemeProvider> <CssBaseline /> <Helmet> <title>PLUTO Media Browser</title> </Helmet> <SystemNotification /> <OAuthContextProvider onLoaded={oAuthConfigLoaded}> <UserContextProvider value={{ profile: userProfile, updateProfile: (newValue) => setUserProfile(newValue), }} > <Header /> {userProfile ? <AppSwitcher /> : undefined} {lastError ? ( <div className="error-dialog"> <Typography>{lastError}</Typography> </div> ) : undefined} {loading ? <CircularProgress /> : undefined} {!lastError && !loading ? ( <VidispineContext.Provider value={vidispineDetails}> <MediaAtomToolContext.Provider value={mediaAtomDetails}> <Switch> <Route path="/item/:itemId" component={ItemViewComponent} /> <Route path="/last/:pageSize" render={( props: RouteComponentProps<LastNComponentMatches> ) => { let itemLimit: number = 15; try { itemLimit = parseInt(props.match.params.pageSize); } catch (err) { console.error( `${props.match.params.pageSize} is not a number` ); } return ( <FrontpageComponent {...props} itemLimit={itemLimit} projectIdToLoad={0} /> ); }} /> <Route path="/search" render={(props: RouteComponentProps) => ( <FrontpageComponent {...props} projectIdToLoad={0} /> )} /> <Route path="/project/:projectId" render={( props: RouteComponentProps<ProjectComponentMatches> ) => { let projectIdToLoad: number = 0; try { projectIdToLoad = parseInt( props.match.params.projectId ); } catch (err) { console.error( `${props.match.params.projectId} is not a number` ); } return ( <FrontpageComponent {...props} projectIdToLoad={projectIdToLoad} /> ); }} /> <Route path="/nearline" render={(props: RouteComponentProps) => ( <NearlineComponent {...props} projectIdToLoad={0} /> )} /> <Route path="/" exact={true} component={() => <Redirect to="/last/15" />} /> </Switch> </MediaAtomToolContext.Provider> </VidispineContext.Provider> ) : undefined} </UserContextProvider> </OAuthContextProvider> </PlutoThemeProvider> ); } }; const AppWithRouter = withRouter(App); render( <BrowserRouter basename={deploymentRootPath}> <AppWithRouter /> </BrowserRouter>, document.getElementById("app") );