diff --git a/web/src/App.tsx b/web/src/App.tsx index 2f422a7..cd10d4a 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -5,40 +5,46 @@ import { Glossary } from "./components/Glossary"; import { Samples } from "./components/Samples"; import { Metrics } from "./components/Metrics"; import { BASE } from "./site"; +import { MetricsProvider } from "./context/Metrics"; +import { SampleManifestProvider } from "./context/SampleManifest"; function App() { return ( - -
- - } /> - } /> - } /> - } /> - -
+ + + +
+ + } /> + } /> + } /> + } /> + +
+
+
); } diff --git a/web/src/components/Metrics.tsx b/web/src/components/Metrics.tsx index 1ccacfc..322f51d 100644 --- a/web/src/components/Metrics.tsx +++ b/web/src/components/Metrics.tsx @@ -1,11 +1,11 @@ -import { useEffect, useState } from "react"; -import { MetricsStore, fetchMetrics } from "../utils/fetchMetrics"; -import { MetricsTable } from "./MetricsTable"; +import { useEffect } from "react"; import { useSearchParams } from "react-router"; +import { MetricsTable } from "./MetricsTable"; +import { useMetrics } from "../context/Metrics"; function validatedPostProcessor( searchParams: URLSearchParams, - store: MetricsStore, + store: ReturnType, ) { if (store.postprocessors.length == 0) { return ""; @@ -24,20 +24,7 @@ function validatedPostProcessor( export function Metrics() { const [searchParams, setSearchParams] = useSearchParams(); - const [store, setStore] = useState(new MetricsStore()); - - useEffect(() => { - async function loadMetrics() { - try { - const newStore = await fetchMetrics(); - setStore(newStore); - } catch (error) { - console.log("Error loading metrics", error); - } - } - - loadMetrics(); - }, []); + const store = useMetrics(); useEffect(() => { if (store.postprocessors.length == 0) { diff --git a/web/src/components/Samples.tsx b/web/src/components/Samples.tsx index 31193a7..712022c 100644 --- a/web/src/components/Samples.tsx +++ b/web/src/components/Samples.tsx @@ -1,25 +1,15 @@ -import { useEffect, useState } from "react"; +import { useState, useEffect } from "react"; import { useSearchParams } from "react-router"; import SampleDisplay from "./SampleDisplay"; import { Dropdown } from "./Dropdown"; import { fetchSamples } from "../utils/fetchSamples"; -import { BASE } from "../site"; import type { Sample } from "../types"; import SampleSelector from "./SampleSelector"; - -interface Manifest { - models: string[]; - languages: string[]; - templates: string[]; - postprocessors: string[]; -} - -// Default empty options to use before loading -const emptyOptions: string[] = []; +import { useSampleManifest } from "../context/SampleManifest"; function validateSearchParams( searchParams: URLSearchParams, - manifest: Manifest, + manifest: ReturnType, ) { let newSearchParams: URLSearchParams | undefined; @@ -39,49 +29,19 @@ function validateSearchParams( export function Samples() { const [searchParams, setSearchParams] = useSearchParams(); - const [manifest, setManifest] = useState({ - models: emptyOptions, - languages: emptyOptions, - templates: emptyOptions, - postprocessors: emptyOptions, - }); - const [isLoading, setIsLoading] = useState(true); const [isInitialized, setIsInitialized] = useState(false); - const [error, setError] = useState(null); const [samples, setSamples] = useState([]); const [current, setCurrent] = useState(); - - // Load manifest.json - useEffect(() => { - async function loadManifest() { - try { - const response = await fetch(BASE + "/samples/manifest.json"); - - if (!response.ok) { - throw new Error(`Failed to load manifest: ${response.statusText}`); - } - - const data = await response.json(); - setManifest(data); - setIsLoading(false); - } catch (err) { - console.error("Error loading manifest:", err); - setError(err instanceof Error ? err.message : "Failed to load options"); - setIsLoading(false); - } - } - - loadManifest(); - }, []); + const manifest = useSampleManifest(); useEffect(() => { - if (isLoading || isInitialized) return; + if (!manifest || isInitialized) return; setSearchParams(validateSearchParams(searchParams, manifest), { replace: true, }); setIsInitialized(true); - }, [isLoading, isInitialized, manifest, searchParams, setSearchParams]); + }, [manifest, isInitialized, searchParams, setSearchParams]); useEffect(() => { if (!isInitialized) return; @@ -115,11 +75,7 @@ export function Samples() { }, [isInitialized, searchParams, current, setCurrent, samples, manifest]); if (!isInitialized || samples.length == 0 || current === undefined) { - return
Loading options...
; - } - - if (error) { - return
Error: {error}
; + return
Loading samples...
; } return ( diff --git a/web/src/utils/fetchMetrics.ts b/web/src/context/Metrics.tsx similarity index 77% rename from web/src/utils/fetchMetrics.ts rename to web/src/context/Metrics.tsx index 59fbe02..299ef66 100644 --- a/web/src/utils/fetchMetrics.ts +++ b/web/src/context/Metrics.tsx @@ -1,3 +1,4 @@ +import { createContext, useContext, useEffect, useState } from "react"; import { BASE } from "../site"; export const METRIC_DESCRIPTIONS: { [K in MetricName]: string } = { @@ -105,8 +106,32 @@ export class MetricsStore { } } -export async function fetchMetrics() { - const store = new MetricsStore(); - await store.load(); - return store; +const MetricsContext = createContext(undefined); + +export function MetricsProvider({ children }: { children: React.ReactNode }) { + const [metrics, setMetrics] = useState(); + + useEffect(() => { + const loadMetrics = async () => { + const store = new MetricsStore(); + await store.load(); + setMetrics(store); + }; + + loadMetrics(); + }, []); + + return ( + + {children} + + ); +} + +export function useMetrics() { + const context = useContext(MetricsContext); + if (context === undefined) { + throw new Error("useMetrics must be used within a MetricsProvider"); + } + return context; } diff --git a/web/src/context/SampleManifest.tsx b/web/src/context/SampleManifest.tsx new file mode 100644 index 0000000..dfeb115 --- /dev/null +++ b/web/src/context/SampleManifest.tsx @@ -0,0 +1,68 @@ +import { createContext, useContext, useEffect, useState } from "react"; +import { BASE } from "../site"; + +interface Manifest { + models: string[]; + languages: string[]; + templates: string[]; + postprocessors: string[]; +} + +const emptyManifest: Manifest = { + models: [], + languages: [], + templates: [], + postprocessors: [], +}; + +const SampleManifestContext = createContext(undefined); + +export function SampleManifestProvider({ + children, +}: { + children: React.ReactNode; +}) { + const [manifest, setManifest] = useState(emptyManifest); + const [error, setError] = useState(null); + + useEffect(() => { + async function loadManifest() { + try { + const response = await fetch(BASE + "/samples/manifest.json"); + + if (!response.ok) { + throw new Error(`Failed to load manifest: ${response.statusText}`); + } + + const data = await response.json(); + setManifest(data); + } catch (err) { + console.error("Error loading manifest:", err); + setError(err instanceof Error ? err.message : "Failed to load options"); + } + } + + loadManifest(); + }, []); + + if (error) { + // You might want to handle this differently depending on your needs + console.error(error); + } + + return ( + + {children} + + ); +} + +export function useSampleManifest() { + const context = useContext(SampleManifestContext); + if (context === undefined) { + throw new Error( + "useSampleManifest must be used within a SampleManifestProvider", + ); + } + return context; +}