File size: 4,730 Bytes
dd079af | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | import { useEffect, useState } from 'react';
import { Link, NavLink, useLocation } from 'react-router-dom';
import { Download, Menu, X } from 'lucide-react';
import { api } from '../lib/api';
import { SearchBox } from './SearchBox';
export function Logo({ size = 28 }: { size?: number }) {
// Three conformational "states" joined by transitions.
return (
<svg width={size} height={size} viewBox="0 0 32 32" aria-hidden className="logo-mark">
<rect width="32" height="32" rx="8" className="logo-mark__bg" />
<path d="M9 21.5 L16 10 L23 21.5" className="logo-mark__edge" />
<path d="M9 21.5 H23" className="logo-mark__edge logo-mark__edge--soft" />
<circle cx="16" cy="10" r="3.4" className="logo-mark__n1" />
<circle cx="9" cy="21.5" r="3.4" className="logo-mark__n2" />
<circle cx="23" cy="21.5" r="3.4" className="logo-mark__n3" />
</svg>
);
}
const NAV = [
{ to: '/browse', label: 'Browse' },
{ to: '/statistics', label: 'Statistics' },
{ to: '/docs', label: 'Docs' },
];
export function Header() {
const location = useLocation();
const [open, setOpen] = useState(false);
const isHome = location.pathname === '/';
useEffect(() => setOpen(false), [location.pathname, location.search]);
return (
<header className={`site-header ${isHome ? 'site-header--home' : ''}`}>
<div className="site-header__inner">
<Link to="/" className="brand" aria-label="MuSProt home">
<Logo />
<span className="brand__name">MuSProt</span>
</Link>
{!isHome && (
<div className="site-header__search">
<SearchBox placeholder="Search chain, entry, UniProt, function…" />
</div>
)}
<nav className="site-nav" aria-label="Main">
{NAV.map((n) => (
<NavLink key={n.to} to={n.to} className={({ isActive }) => `site-nav__link ${isActive ? 'is-active' : ''}`}>
{n.label}
</NavLink>
))}
<a href={api.downloadUrl} className="btn btn--primary btn--sm">
<Download aria-hidden /> Download
</a>
</nav>
<button
type="button"
className="menu-toggle"
aria-label="Menu"
aria-expanded={open}
onClick={() => setOpen((o) => !o)}
>
{open ? <X aria-hidden /> : <Menu aria-hidden />}
</button>
</div>
{open && (
<div className="mobile-nav">
{!isHome && <SearchBox placeholder="Search MuSProt…" />}
{NAV.map((n) => (
<NavLink key={n.to} to={n.to} className="mobile-nav__link">
{n.label}
</NavLink>
))}
<a href={api.downloadUrl} className="btn btn--primary">
<Download aria-hidden /> Download MuSProt.db
</a>
</div>
)}
</header>
);
}
export function Footer() {
return (
<footer className="site-footer">
<div className="site-footer__inner">
<div className="site-footer__brand">
<Link to="/" className="brand">
<Logo size={24} />
<span className="brand__name">MuSProt</span>
</Link>
<p>
A million-scale database of experimentally observed conformational states and state transitions of proteins,
built from the Protein Data Bank.
</p>
</div>
<div className="site-footer__cols">
<div>
<h3>Explore</h3>
<Link to="/browse">Browse sequences</Link>
<Link to="/browse?min_states=5">Highly multistate</Link>
<Link to="/chain/3vgk/A">Example chain</Link>
</div>
<div>
<h3>Data</h3>
<Link to="/docs">Documentation</Link>
<a href={api.downloadUrl}>Download SQLite</a>
<a href="/api/docs" target="_blank" rel="noopener noreferrer">
REST API
</a>
</div>
<div>
<h3>Sources</h3>
<a href="https://www.rcsb.org" target="_blank" rel="noopener noreferrer">
RCSB PDB
</a>
<a href="https://www.uniprot.org" target="_blank" rel="noopener noreferrer">
UniProt
</a>
<a href="https://www.cathdb.info" target="_blank" rel="noopener noreferrer">
CATH
</a>
</div>
</div>
</div>
<div className="site-footer__base">
<span>MuSProt · Multistate Protein Database</span>
<a href="https://huggingface.co/spaces/omaib/MuSProt" target="_blank" rel="noopener noreferrer">
Hosted on Hugging Face
</a>
</div>
</footer>
);
}
|