add home page
10 changed files+469−251
web-ui/package-lock.jsonmodified+13−1View file
@@ -19,7 +19,8 @@
1919 "react-plotly.js": "^2.6.0",
2020 "react-router-dom": "^7.1.3",
2121 "rehype-katex": "^7.0.1",
22- "remark-math": "^6.0.0"
22+ "remark-math": "^6.0.0",
23+ "yaml": "^2.7.0"
2324 },
2425 "devDependencies": {
2526 "@eslint/js": "^9.17.0",
@@ -7931,6 +7932,17 @@
79317932 "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
79327933 "dev": true
79337934 },
7935+ "node_modules/yaml": {
7936+ "version": "2.7.0",
7937+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
7938+ "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
7939+ "bin": {
7940+ "yaml": "bin.mjs"
7941+ },
7942+ "engines": {
7943+ "node": ">= 14"
7944+ }
7945+ },
79347946 "node_modules/yocto-queue": {
79357947 "version": "0.1.0",
79367948 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
web-ui/package.jsonmodified+2−1View file
@@ -23,7 +23,8 @@
2323 "react-plotly.js": "^2.6.0",
2424 "react-router-dom": "^7.1.3",
2525 "rehype-katex": "^7.0.1",
26- "remark-math": "^6.0.0"
26+ "remark-math": "^6.0.0",
27+ "yaml": "^2.7.0"
2728 },
2829 "devDependencies": {
2930 "@eslint/js": "^9.17.0",
web-ui/src/App.tsxmodified+28−9View file
@@ -1,9 +1,10 @@
1-import { BrowserRouter, Routes, Route, Link, Navigate } from "react-router-dom";
1+import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
22 import { useEffect, useState } from "react";
33 import axios from "axios";
44 import { ScrollToTop } from "./components/ScrollToTop";
55 import "./components/AppHeader.css";
66 import Home from "./pages/Home";
7+import BenchmarkView from "./pages/BenchmarkView";
78 import About from "./pages/About";
89 import Paper from "./pages/Paper";
910 import Monitor from "./pages/Monitor";
@@ -55,9 +56,7 @@ function App() {
5556 right: 0,
5657 padding: "0.35rem min(2rem, 4%)",
5758 backgroundColor: "white",
58- // borderBottom: "1px solid #eaeaea",
5959 zIndex: 1000,
60- // boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
6160 }}
6261 >
6362 <div
@@ -69,7 +68,7 @@ function App() {
6968 }}
7069 >
7170 <Link
72- to="/datasets"
71+ to="/"
7372 style={{
7473 display: "flex",
7574 alignItems: "center",
@@ -114,6 +113,26 @@ function App() {
114113 </span>
115114 </Link>
116115 <div style={{ display: "flex", gap: "1.5rem" }}>
116+ <Link
117+ to="/datasets"
118+ style={{
119+ color: "#0066cc",
120+ textDecoration: "none",
121+ fontWeight: "500",
122+ }}
123+ >
124+ Datasets
125+ </Link>
126+ <Link
127+ to="/algorithms"
128+ style={{
129+ color: "#0066cc",
130+ textDecoration: "none",
131+ fontWeight: "500",
132+ }}
133+ >
134+ Algorithms
135+ </Link>
117136 <Link
118137 to="/paper"
119138 style={{
@@ -154,22 +173,22 @@ function App() {
154173 <div>Error: {error}</div>
155174 ) : (
156175 <Routes>
157- <Route path="/" element={<Navigate to="/datasets" replace />} />
176+ <Route path="/" element={<Home />} />
158177 <Route
159178 path="/datasets"
160- element={<Home benchmarkData={benchmarkData} />}
179+ element={<BenchmarkView benchmarkData={benchmarkData} />}
161180 />
162181 <Route
163182 path="/algorithms"
164- element={<Home benchmarkData={benchmarkData} />}
183+ element={<BenchmarkView benchmarkData={benchmarkData} />}
165184 />
166185 <Route
167186 path="/dataset/:datasetName"
168- element={<Home benchmarkData={benchmarkData} />}
187+ element={<BenchmarkView benchmarkData={benchmarkData} />}
169188 />
170189 <Route
171190 path="/algorithm/:algorithmName"
172- element={<Home benchmarkData={benchmarkData} />}
191+ element={<BenchmarkView benchmarkData={benchmarkData} />}
173192 />
174193 <Route path="/about" element={<About />} />
175194 <Route path="/paper" element={<Paper />} />
web-ui/src/components/Button.cssadded+18−0View file
@@ -0,0 +1,18 @@
1+.soft-button {
2+ display: inline-block;
3+ padding: 0.6rem 1.2rem;
4+ background-color: #2b7de9;
5+ color: white;
6+ text-decoration: none;
7+ border-radius: 20px;
8+ font-weight: 500;
9+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
10+ transition: all 0.2s ease;
11+ cursor: pointer;
12+}
13+
14+.soft-button:hover {
15+ background-color: #1a68d4;
16+ transform: translateY(-1px);
17+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
18+}
web-ui/src/content/home-content.ymladded+48−0View file
@@ -0,0 +1,48 @@
1+title: Welcome to Benchcompress
2+description:
3+ Benchcompress is an open-source project for comparing compression algorithms
4+ on scientific data arrays.
5+
6+sections:
7+ algorithms:
8+ title: Algorithms
9+ description:
10+ Explore the supported compression algorithms, including traditional methods
11+ like zlib and modern approaches like ANS.
12+ link: /algorithms
13+ linkText: View Algorithms
14+
15+ datasets:
16+ title: Datasets
17+ description:
18+ Explore the curated collection of scientific datasets, including
19+ electrophysiology recordings, seismic data, and more. See the performance
20+ of the various compression algorithms on each dataset.
21+ link: /datasets
22+ linkText: View Datasets
23+
24+ paper:
25+ title: Research Paper
26+ description:
27+ Read the work-in-progress paper describing the methodology,
28+ implementation, and findings of this compression benchmarking research.
29+ link: /paper
30+ linkText: Read Paper
31+
32+ monitor:
33+ title: Live Monitor
34+ description:
35+ The benchmarking runs automatically in a GitHub Actions workflow. Track
36+ current progress, view completed benchmarks, and monitor performance
37+ metrics as they are generated.
38+ link: /monitor
39+ linkText: View Monitor
40+
41+ source:
42+ title: Source Code
43+ description:
44+ Explore the GitHub repository to view the source code, contribute to the
45+ project, or run your own benchmarks locally.
46+ link: https://github.com/magland/benchcompress
47+ linkText: View on GitHub
48+ external: true
web-ui/src/env.d.tsadded+1−0View file
@@ -0,0 +1 @@
1+declare const __BUILD_DATE__: string;
web-ui/src/pages/BenchmarkView.tsxadded+251−0View file
@@ -0,0 +1,251 @@
1+import { useLocation, useNavigate, useParams } from "react-router-dom";
2+import { useReducer, useEffect } from "react";
3+import { tabsReducer } from "../reducers/tabsReducer";
4+import { AlgorithmContent } from "../components/algorithm/AlgorithmContent";
5+import { DatasetContent } from "../components/dataset/DatasetContent";
6+import {
7+ AlgorithmTable,
8+ DatasetTable,
9+} from "../components/tables/DatasetAlgorithmTables";
10+import { useBenchmarkChartData } from "../hooks/useBenchmarkChartData";
11+import { useTagFilter } from "../hooks/useTagFilter";
12+import { BenchmarkData } from "../types";
13+
14+interface BenchmarkViewProps {
15+ benchmarkData: BenchmarkData | null;
16+}
17+
18+export default function BenchmarkView({ benchmarkData }: BenchmarkViewProps) {
19+ const location = useLocation();
20+ const navigate = useNavigate();
21+ const { datasetName, algorithmName } = useParams<{
22+ datasetName?: string;
23+ algorithmName?: string;
24+ }>();
25+
26+ const [tabsState, dispatch] = useReducer(tabsReducer, {
27+ tabs: [
28+ { id: "datasets", label: "Datasets", route: "/datasets" },
29+ { id: "algorithms", label: "Algorithms", route: "/algorithms" },
30+ ],
31+ activeTabId: "datasets",
32+ });
33+
34+ // Effect to handle URL changes and update tabs
35+ useEffect(() => {
36+ if (datasetName) {
37+ dispatch({
38+ type: "ADD_TAB",
39+ payload: {
40+ id: `dataset-${datasetName}`,
41+ label: datasetName,
42+ route: `/dataset/${datasetName}`,
43+ },
44+ });
45+ } else if (algorithmName) {
46+ dispatch({
47+ type: "ADD_TAB",
48+ payload: {
49+ id: `algorithm-${algorithmName}`,
50+ label: algorithmName,
51+ route: `/algorithm/${algorithmName}`,
52+ },
53+ });
54+ } else if (location.pathname.includes("/algorithms")) {
55+ dispatch({ type: "SET_ACTIVE_TAB", payload: "algorithms" });
56+ } else if (location.pathname.includes("/datasets")) {
57+ dispatch({ type: "SET_ACTIVE_TAB", payload: "datasets" });
58+ }
59+ }, [datasetName, algorithmName, location.pathname]);
60+
61+ // Handle tab click
62+ const handleTabClick = (tabId: string, route: string) => {
63+ dispatch({ type: "SET_ACTIVE_TAB", payload: tabId });
64+ navigate(route);
65+ };
66+
67+ // Get specific dataset or algorithm if viewing one
68+ const dataset = datasetName
69+ ? benchmarkData?.datasets.find((d) => d.name === datasetName)
70+ : undefined;
71+ const algorithm = algorithmName
72+ ? benchmarkData?.algorithms.find((a) => a.name === algorithmName)
73+ : undefined;
74+
75+ // Get chart data for specific dataset or algorithm view
76+ const chartData = useBenchmarkChartData(
77+ benchmarkData?.results || [],
78+ dataset?.name || null,
79+ algorithm?.name || null,
80+ );
81+
82+ // Get selected tags from URL
83+ const searchParams = new URLSearchParams(location.search);
84+ const selectedTags = searchParams.get("tag")?.split(",") || [];
85+
86+ // Set up tag filtering for datasets and algorithms
87+ const {
88+ availableTags: availableDatasetTags,
89+ filteredItems: filteredDatasets,
90+ } = useTagFilter(
91+ benchmarkData?.datasets || [],
92+ location.pathname.includes("/datasets") ? selectedTags : [],
93+ );
94+
95+ const {
96+ availableTags: availableAlgorithmTags,
97+ filteredItems: filteredAlgorithms,
98+ } = useTagFilter(
99+ benchmarkData?.algorithms || [],
100+ location.pathname.includes("/algorithms") ? selectedTags : [],
101+ );
102+
103+ // Handle tag toggling by updating URL
104+ const handleTagToggle = (tag: string) => {
105+ const newTags = selectedTags.includes(tag)
106+ ? selectedTags.filter((t) => t !== tag)
107+ : [...selectedTags, tag];
108+
109+ const params = new URLSearchParams();
110+ if (newTags.length > 0) {
111+ params.set("tag", newTags.join(","));
112+ }
113+ navigate({ search: params.toString() });
114+ };
115+
116+ return (
117+ <div>
118+ <main>
119+ <div
120+ style={{
121+ position: "fixed",
122+ top: "3rem",
123+ left: 0,
124+ right: 0,
125+ backgroundColor: "white",
126+ zIndex: 999,
127+ padding: "0 2rem 0 2rem",
128+ marginTop: "-4px",
129+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
130+ borderBottom: "1px solid #eaeaea",
131+ }}
132+ >
133+ <div
134+ style={{
135+ paddingBottom: "2px",
136+ display: "flex",
137+ gap: "4px",
138+ overflowX: "auto",
139+ width: "100%",
140+ backgroundColor: "white",
141+ }}
142+ >
143+ {tabsState.tabs.map((tab) => (
144+ <div
145+ key={tab.id}
146+ style={{
147+ display: "flex",
148+ alignItems: "center",
149+ gap: "4px",
150+ }}
151+ >
152+ <button
153+ onClick={() => handleTabClick(tab.id, tab.route)}
154+ style={{
155+ padding: "8px 16px",
156+ border: "none",
157+ background: "none",
158+ borderBottom:
159+ tabsState.activeTabId === tab.id
160+ ? "2px solid #0066cc"
161+ : "none",
162+ color:
163+ tabsState.activeTabId === tab.id ? "#0066cc" : "#666",
164+ fontWeight:
165+ tabsState.activeTabId === tab.id ? "600" : "normal",
166+ cursor: "pointer",
167+ textDecoration: "none",
168+ whiteSpace: "nowrap",
169+ }}
170+ >
171+ {tab.label}
172+ </button>
173+ {tab.id !== "datasets" && tab.id !== "algorithms" && (
174+ <button
175+ onClick={(e) => {
176+ e.stopPropagation();
177+ const newActiveTab =
178+ tab.id === tabsState.activeTabId
179+ ? tabsState.tabs[0].id // Default to first tab if closing active
180+ : tabsState.activeTabId;
181+ dispatch({ type: "CLOSE_TAB", payload: tab.id });
182+ // Navigate if closing active tab
183+ if (tab.id === tabsState.activeTabId) {
184+ const defaultTab = tabsState.tabs.find(
185+ (t) => t.id === newActiveTab,
186+ );
187+ if (defaultTab) {
188+ navigate(defaultTab.route);
189+ }
190+ }
191+ }}
192+ style={{
193+ padding: "4px",
194+ border: "none",
195+ background: "none",
196+ color: "#666",
197+ cursor: "pointer",
198+ fontSize: "12px",
199+ display: "flex",
200+ alignItems: "center",
201+ justifyContent: "center",
202+ width: "20px",
203+ height: "20px",
204+ borderRadius: "50%",
205+ marginRight: "4px",
206+ marginLeft: "-4px",
207+ }}
208+ aria-label="Close tab"
209+ >
210+ ×
211+ </button>
212+ )}
213+ </div>
214+ ))}
215+ </div>
216+ </div>
217+
218+ <div style={{ padding: "3rem 0 1rem 0" }}>
219+ {dataset ? (
220+ <DatasetContent
221+ dataset={dataset}
222+ benchmarkData={benchmarkData}
223+ chartData={chartData}
224+ />
225+ ) : algorithm ? (
226+ <AlgorithmContent
227+ algorithm={algorithm}
228+ benchmarkData={benchmarkData}
229+ chartData={chartData}
230+ />
231+ ) : tabsState.activeTabId === "datasets" ? (
232+ <DatasetTable
233+ filteredDatasets={filteredDatasets}
234+ availableDatasetTags={availableDatasetTags}
235+ selectedTags={selectedTags}
236+ toggleTag={handleTagToggle}
237+ benchmarkResults={benchmarkData?.results || []}
238+ />
239+ ) : (
240+ <AlgorithmTable
241+ filteredAlgorithms={filteredAlgorithms}
242+ availableAlgorithmTags={availableAlgorithmTags}
243+ selectedTags={selectedTags}
244+ toggleTag={handleTagToggle}
245+ />
246+ )}
247+ </div>
248+ </main>
249+ </div>
250+ );
251+}
web-ui/src/pages/Home.tsxmodified+90−240View file
@@ -1,253 +1,103 @@
1-import { useLocation, useNavigate, useParams } from "react-router-dom";
2-import { useReducer, useEffect } from "react";
3-import { tabsReducer } from "../reducers/tabsReducer";
4-import { AlgorithmContent } from "../components/algorithm/AlgorithmContent";
5-import { DatasetContent } from "../components/dataset/DatasetContent";
6-import {
7- AlgorithmTable,
8- DatasetTable,
9-} from "../components/tables/DatasetAlgorithmTables";
10-import { useBenchmarkChartData } from "../hooks/useBenchmarkChartData";
11-import { useTagFilter } from "../hooks/useTagFilter";
12-import { BenchmarkData } from "../types";
1+import React from "react";
2+import { Link } from "react-router-dom";
3+import yaml from "yaml";
4+import contentYaml from "../content/home-content.yml?raw";
5+import { HomeContent, HomeSection } from "../types/home-content";
6+import "../components/Button.css";
137
14-interface HomeProps {
15- benchmarkData: BenchmarkData | null;
16-}
17-
18-export default function Home({ benchmarkData }: HomeProps) {
19- const location = useLocation();
20- const navigate = useNavigate();
21- const { datasetName, algorithmName } = useParams<{
22- datasetName?: string;
23- algorithmName?: string;
24- }>();
25-
26- const [tabsState, dispatch] = useReducer(tabsReducer, {
27- tabs: [
28- { id: "datasets", label: "Datasets", route: "/datasets" },
29- { id: "algorithms", label: "Algorithms", route: "/algorithms" },
30- ],
31- activeTabId: "datasets",
32- });
33-
34- // Effect to handle URL changes and update tabs
35- useEffect(() => {
36- if (datasetName) {
37- dispatch({
38- type: "ADD_TAB",
39- payload: {
40- id: `dataset-${datasetName}`,
41- label: datasetName,
42- route: `/dataset/${datasetName}`,
43- },
44- });
45- } else if (algorithmName) {
46- dispatch({
47- type: "ADD_TAB",
48- payload: {
49- id: `algorithm-${algorithmName}`,
50- label: algorithmName,
51- route: `/algorithm/${algorithmName}`,
52- },
53- });
54- } else if (location.pathname.includes("/algorithms")) {
55- dispatch({ type: "SET_ACTIVE_TAB", payload: "algorithms" });
56- } else if (location.pathname.includes("/datasets")) {
57- dispatch({ type: "SET_ACTIVE_TAB", payload: "datasets" });
58- }
59- }, [datasetName, algorithmName, location.pathname]);
8+const content = yaml.parse(contentYaml) as HomeContent;
609
61- // Handle tab click
62- const handleTabClick = (tabId: string, route: string) => {
63- dispatch({ type: "SET_ACTIVE_TAB", payload: tabId });
64- navigate(route);
65- };
66-
67- // Get specific dataset or algorithm if viewing one
68- const dataset = datasetName
69- ? benchmarkData?.datasets.find((d) => d.name === datasetName)
70- : undefined;
71- const algorithm = algorithmName
72- ? benchmarkData?.algorithms.find((a) => a.name === algorithmName)
73- : undefined;
10+const SectionCard: React.FC<{ section: HomeSection }> = ({ section }) => {
11+ if (section.external) {
12+ return (
13+ <div
14+ style={{
15+ padding: "1.5rem",
16+ border: "1px solid #eaeaea",
17+ borderRadius: "8px",
18+ backgroundColor: "#f9f9f9",
19+ }}
20+ >
21+ <h2 style={{ marginBottom: "1rem" }}>{section.title}</h2>
22+ <p style={{ marginBottom: "1rem" }}>{section.description}</p>
23+ <a
24+ href={section.link}
25+ target="_blank"
26+ rel="noopener noreferrer"
27+ className="soft-button"
28+ >
29+ {section.linkText}
30+ </a>
31+ </div>
32+ );
33+ }
7434
75- // Get chart data for specific dataset or algorithm view
76- const chartData = useBenchmarkChartData(
77- benchmarkData?.results || [],
78- dataset?.name || null,
79- algorithm?.name || null,
35+ return (
36+ <div
37+ style={{
38+ padding: "1.5rem",
39+ border: "1px solid #eaeaea",
40+ borderRadius: "8px",
41+ backgroundColor: "#f9f9f9",
42+ }}
43+ >
44+ <h2 style={{ marginBottom: "1rem" }}>{section.title}</h2>
45+ <p style={{ marginBottom: "1rem" }}>{section.description}</p>
46+ <Link to={section.link} className="soft-button">
47+ {section.linkText}
48+ </Link>
49+ </div>
8050 );
51+};
8152
82- console.log("chartData", chartData);
83-
84- // Get selected tags from URL
85- const searchParams = new URLSearchParams(location.search);
86- const selectedTags = searchParams.get("tag")?.split(",") || [];
87-
88- // Set up tag filtering for datasets and algorithms
89- const {
90- availableTags: availableDatasetTags,
91- filteredItems: filteredDatasets,
92- } = useTagFilter(
93- benchmarkData?.datasets || [],
94- location.pathname.includes("/datasets") ? selectedTags : [],
95- );
53+export default function Home() {
54+ return (
55+ <div style={{ maxWidth: "1200px", margin: "0 auto", padding: "2rem" }}>
56+ <h1 style={{ marginBottom: "2rem" }}>{content.title}</h1>
9657
97- const {
98- availableTags: availableAlgorithmTags,
99- filteredItems: filteredAlgorithms,
100- } = useTagFilter(
101- benchmarkData?.algorithms || [],
102- location.pathname.includes("/algorithms") ? selectedTags : [],
103- );
58+ <p
59+ style={{ fontSize: "1.1rem", lineHeight: "1.6", marginBottom: "2rem" }}
60+ >
61+ {content.description}
62+ </p>
10463
105- // Handle tag toggling by updating URL
106- const handleTagToggle = (tag: string) => {
107- const newTags = selectedTags.includes(tag)
108- ? selectedTags.filter((t) => t !== tag)
109- : [...selectedTags, tag];
64+ <div
65+ style={{
66+ display: "grid",
67+ gap: "2rem",
68+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
69+ }}
70+ >
71+ {Object.entries(content.sections).map(([key, section]) => (
72+ <SectionCard key={key} section={section} />
73+ ))}
74+ </div>
11075
111- const params = new URLSearchParams();
112- if (newTags.length > 0) {
113- params.set("tag", newTags.join(","));
114- }
115- navigate({ search: params.toString() });
116- };
76+ <hr
77+ style={{
78+ margin: "3rem 0",
79+ border: "none",
80+ borderTop: "1px solid #eaeaea",
81+ }}
82+ />
11783
118- return (
119- <div>
120- <main>
121- <div
122- style={{
123- position: "fixed",
124- top: "3rem",
125- left: 0,
126- right: 0,
127- backgroundColor: "white",
128- zIndex: 999,
129- padding: "0 2rem 0 2rem",
130- marginTop: "-4px",
131- boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
132- borderBottom: "1px solid #eaeaea",
133- }}
134- >
135- <div
136- style={{
137- paddingBottom: "2px",
138- display: "flex",
139- gap: "4px",
140- overflowX: "auto",
141- width: "100%",
142- backgroundColor: "white",
143- }}
84+ <footer
85+ style={{ textAlign: "center", color: "#666", fontSize: "0.9rem" }}
86+ >
87+ <p>
88+ Last updated: {__BUILD_DATE__}
89+ <br />
90+ Released under{" "}
91+ <a
92+ href="https://github.com/magland/benchcompress/blob/main/LICENSE"
93+ target="_blank"
94+ rel="noopener noreferrer"
95+ style={{ color: "#666", textDecoration: "underline" }}
14496 >
145- {tabsState.tabs.map((tab) => (
146- <div
147- key={tab.id}
148- style={{
149- display: "flex",
150- alignItems: "center",
151- gap: "4px",
152- }}
153- >
154- <button
155- onClick={() => handleTabClick(tab.id, tab.route)}
156- style={{
157- padding: "8px 16px",
158- border: "none",
159- background: "none",
160- borderBottom:
161- tabsState.activeTabId === tab.id
162- ? "2px solid #0066cc"
163- : "none",
164- color:
165- tabsState.activeTabId === tab.id ? "#0066cc" : "#666",
166- fontWeight:
167- tabsState.activeTabId === tab.id ? "600" : "normal",
168- cursor: "pointer",
169- textDecoration: "none",
170- whiteSpace: "nowrap",
171- }}
172- >
173- {tab.label}
174- </button>
175- {tab.id !== "datasets" && tab.id !== "algorithms" && (
176- <button
177- onClick={(e) => {
178- e.stopPropagation();
179- const newActiveTab =
180- tab.id === tabsState.activeTabId
181- ? tabsState.tabs[0].id // Default to first tab if closing active
182- : tabsState.activeTabId;
183- dispatch({ type: "CLOSE_TAB", payload: tab.id });
184- // Navigate if closing active tab
185- if (tab.id === tabsState.activeTabId) {
186- const defaultTab = tabsState.tabs.find(
187- (t) => t.id === newActiveTab,
188- );
189- if (defaultTab) {
190- navigate(defaultTab.route);
191- }
192- }
193- }}
194- style={{
195- padding: "4px",
196- border: "none",
197- background: "none",
198- color: "#666",
199- cursor: "pointer",
200- fontSize: "12px",
201- display: "flex",
202- alignItems: "center",
203- justifyContent: "center",
204- width: "20px",
205- height: "20px",
206- borderRadius: "50%",
207- marginRight: "4px",
208- marginLeft: "-4px",
209- }}
210- aria-label="Close tab"
211- >
212- ×
213- </button>
214- )}
215- </div>
216- ))}
217- </div>
218- </div>
219-
220- <div style={{ padding: "3rem 0 1rem 0" }}>
221- {dataset ? (
222- <DatasetContent
223- dataset={dataset}
224- benchmarkData={benchmarkData}
225- chartData={chartData}
226- />
227- ) : algorithm ? (
228- <AlgorithmContent
229- algorithm={algorithm}
230- benchmarkData={benchmarkData}
231- chartData={chartData}
232- />
233- ) : tabsState.activeTabId === "datasets" ? (
234- <DatasetTable
235- filteredDatasets={filteredDatasets}
236- availableDatasetTags={availableDatasetTags}
237- selectedTags={selectedTags}
238- toggleTag={handleTagToggle}
239- benchmarkResults={benchmarkData?.results || []}
240- />
241- ) : (
242- <AlgorithmTable
243- filteredAlgorithms={filteredAlgorithms}
244- availableAlgorithmTags={availableAlgorithmTags}
245- selectedTags={selectedTags}
246- toggleTag={handleTagToggle}
247- />
248- )}
249- </div>
250- </main>
97+ Apache License 2.0
98+ </a>
99+ </p>
100+ </footer>
251101 </div>
252102 );
253103 }
web-ui/src/types/home-content.tsadded+15−0View file
@@ -0,0 +1,15 @@
1+export interface HomeSection {
2+ title: string;
3+ description: string;
4+ link: string;
5+ linkText: string;
6+ external?: boolean;
7+}
8+
9+export interface HomeContent {
10+ title: string;
11+ description: string;
12+ sections: {
13+ [key: string]: HomeSection;
14+ };
15+}
web-ui/vite.config.tsmodified+3−0View file
@@ -5,4 +5,7 @@ import react from '@vitejs/plugin-react'
55 export default defineConfig({
66 plugins: [react()],
77 base: '/benchcompress/', // Base URL for GitHub Pages deployment
8+ define: {
9+ __BUILD_DATE__: JSON.stringify(new Date().toLocaleDateString())
10+ }
811 })