diff --git a/static-site/src/components/Datasets/dataset-select.jsx b/static-site/src/components/Datasets/dataset-select.jsx index cea01903a..4c48301fa 100644 --- a/static-site/src/components/Datasets/dataset-select.jsx +++ b/static-site/src/components/Datasets/dataset-select.jsx @@ -16,7 +16,7 @@ import { collectAvailableFilteringOptions, computeFilterValues } from "./filter- * @prop {string | undefined} urlDefinedFilterPath slash-separated keywords which will be applied as filters * @prop {string | undefined} intendedUri Intended URI. Browser address will be replaced with this. * @prop {Array} datasets Available datasets. Array of Objects. - * @prop {boolean} noDates Note: will be replaced in a subsequent commit + * @prop {Array} columns Columns to be rendered by the table. See for signature. * @prop {Array | undefined} interface What elements to render? Elements may be strings or functinos. Order is respected. * Available strings: "FilterSelect" "FilterDisplay", "ListDatasets" * Functions will be handed an object with key(s): `datasets` (which may be filtered), and should return a react component for rendering. @@ -100,8 +100,8 @@ class DatasetSelect extends React.Component { return ( ); default: @@ -126,7 +126,7 @@ DatasetSelect.propTypes = { urlDefinedFilterPath: PropTypes.string, intendedUri: PropTypes.string, interface: PropTypes.array, - noDates: PropTypes.bool, + columns: PropTypes.array.isRequired, datasets: PropTypes.array.isRequired }; diff --git a/static-site/src/components/Datasets/list-datasets.jsx b/static-site/src/components/Datasets/list-datasets.jsx index f3cd98d6a..273869105 100644 --- a/static-site/src/components/Datasets/list-datasets.jsx +++ b/static-site/src/components/Datasets/list-datasets.jsx @@ -1,113 +1,157 @@ import React from "react"; import "react-select/dist/react-select.css"; import "react-virtualized-select/styles.css"; -import { get } from 'lodash'; import styled from 'styled-components'; -import { MdPerson } from "react-icons/md"; import {Grid, Col, Row} from 'react-styled-flexboxgrid'; import { CenteredContainer } from "./styles"; -const logoPNG = require("../../../static/logos/favicon.png"); - -const StyledLinkContainer = styled.div` - a { - color: #444; - font-weight: ${(props) => props.bold ? 700 : "normal"}; - } - a:hover, - a:focus { - color: #5097BA; +const StyledLink = styled.a` + color: #444 !important; + font-weight: ${(props) => props.bold ? 500 : 300} !important; + &:hover, + &:focus { + color: #5097BA !important; text-decoration: underline; } `; -const StyledIconLinkContainer = styled.div` - svg { - color: #444; - } - svg:hover, - svg:focus { - color: #5097BA; - } -`; - const DatasetSelectionResultsContainer = styled.div` height: 600px; overflow-x: hidden; overflow-y: visible; `; -const DatasetContainer = styled.div` +const RowContainer = styled.div` font-family: ${(props) => props.theme.generalFont}; font-weight: 900; font-size: 18px; padding: 10px 1px 10px 1px; line-height: 24px; + ${(props) => props.border && `border-bottom: 1px solid #CCC;`} `; -const LogoContainer = styled.a` +const LogoContainerLink = styled.a` padding: 1px 1px; margin-right: 5px; width: 24px; cursor: pointer; `; -export const ListDatasets = ({datasets, showDates}) => { +const LogoContainer = styled.span` + padding: 1px 1px; + margin-right: 5px; + width: 24px; +`; + +const columnStyles = [ + [{xs: 8, sm: 6, md: 7}], // column 1 rendered as a single + [{xs: false, sm: 3, md: 3}, {xs: 4, sm: false, style: {textAlign: 'right'}}], // column 2 + [{xs: false, sm: 3, md: 2}] +]; + +const HeaderRow = ({columns}) => { + const names = columns.map((c) => c.name); + return ( + + + {/* column 1 (typically the dataset) - same rendering on main & mobile views */} + {names[0]} + + {/* column 2: (typically the contributor) - both main & mobile views */} + {names[1]} + {names[1]} + + {/* column 3: optional. Not rendered on small screens. */} + {names.length===3 && ( + {names[2]} + )} + + + ); +}; + +const NormalRow = ({columns, dataset}) => { + const names = columns.map((c) => c.name); + return ( + + + {/* column 1 (typically the dataset) - same rendering on main & mobile views */} + + + + + {/* column 2: (typically the contributor) - both main & mobile views */} + + + + + + + + {/* column 3: optional. Not rendered on small screens. */} + {names.length===3 && ( + + + + )} + + + ); +}; + +/** + * Render the value for a particular cell in the table. + * May be a link and/or have a logo, depending on the data in `columnInfo` + */ +const Value = ({dataset, columnInfo, mobileView, firstColumn}) => { + const url = typeof columnInfo.url === "function" && columnInfo.url(dataset); + const value = mobileView && typeof columnInfo.valueMobile === "function" ? + columnInfo.valueMobile(dataset) : + columnInfo.value(dataset); + const logo = mobileView && typeof columnInfo.logoMobile === "function" ? + columnInfo.logoMobile(dataset) : + typeof columnInfo.logo === "function" ? + columnInfo.logo(dataset) : + undefined; + return ( + <> + {(logo && url) ? + ({logo}) : + logo ? + ({logo}) : + null + } + {url ? + {value} : + value + } + + ); +}; + + +/** + * React component to render a table showing the `datasets` as rows with + * the specified `columns`. Open to future expansion. + * Currently only 2 or 3 columns are supported. + * If 3 columns are supplied, the 3rd will not be shown on small screens. + * @prop {Array} columns Array of columns. Each entry is an object with following properties: + * `name` {string} To be displayed in the header + * `value` {function} return the value, given an individual entry from `datasets` + * `valueMobile` {function | undefined} value to be used on small screens + * `url` {function | undefined} render the value as a link to this URL + * `logo` {function | undefined} if the function returns "nextstrain" then we render the Nextstrain logo. + * @returns React Component + */ +export const ListDatasets = ({datasets, columns}) => { return ( - - - - Dataset - - - Contributor - - {showDates && - Uploaded date - } - - Contributor - - - + - { datasets.map((dataset) => ( - - - - - {dataset.filename.replace(/_/g, ' / ').replace('.json', '')} - - - - - {dataset.contributor.includes("Nextstrain") && - nextstrain.org - } - {dataset.contributorUrl === undefined ? - dataset.contributor : - - {dataset.contributor} - } - - - {showDates && - {dataset.date_uploaded} - } - - - {dataset.contributor.includes("Nextstrain") ? - nextstrain.org : - - } - - - - - )) - } + {datasets.map((dataset) => ( + + ))} diff --git a/static-site/src/pages/influenza-page.jsx b/static-site/src/pages/influenza-page.jsx index 541531478..d9fc0ba5b 100644 --- a/static-site/src/pages/influenza-page.jsx +++ b/static-site/src/pages/influenza-page.jsx @@ -16,6 +16,8 @@ import Footer from "../components/Footer"; import { PathogenPageIntroduction } from "../components/Datasets/pathogen-page-introduction"; import DatasetSelect from "../components/Datasets/dataset-select"; +const nextstrainLogoPNG = require("../../static/logos/favicon.png"); + const title = "Influenza resources"; const abstract = `The Nextstrain team maintains datasets and other tools for analyzing a variety of influenza viruses. We track the evolution of seasonal influenza viruses (A/H3N2, A/H1N1pdm, B/Victoria, and B/Yamagata) @@ -54,6 +56,25 @@ const contents = [ } ]; +const tableColumns = [ + { + name: "Dataset", + value: (dataset) => dataset.filename.replace(/_/g, ' / ').replace('.json', ''), + url: (dataset) => dataset.url + }, + { + name: "Contributor", + value: () => "Nextstrain", + valueMobile: () => "", + url: () => "https://nextstrain.org", + logo: () => (nextstrain.org) + }, + { + name: "Uploaded Date", + value: (dataset) => dataset.date_uploaded + } +]; + class Index extends React.Component { constructor(props) { @@ -109,6 +130,7 @@ class Index extends React.Component { {this.state.dataLoaded && ( diff --git a/static-site/src/pages/sars-cov-2-page.jsx b/static-site/src/pages/sars-cov-2-page.jsx index c03267e1d..fd6bcb43c 100644 --- a/static-site/src/pages/sars-cov-2-page.jsx +++ b/static-site/src/pages/sars-cov-2-page.jsx @@ -1,6 +1,7 @@ import React from "react"; import Helmet from "react-helmet"; import ScrollableAnchor, { configureAnchors } from "react-scrollable-anchor"; +import { MdPerson } from "react-icons/md"; import { get } from 'lodash'; import config from "../../data/SiteConfig"; import NavBar from "../components/nav-bar"; @@ -21,6 +22,9 @@ import { PathogenPageIntroduction } from "../components/Datasets/pathogen-page-i import {parseNcovSitRepInfo} from "../../../auspice-client/customisations/languageSelector"; import sarscov2Catalogue from "../../content/SARS-CoV-2-Datasets.yaml"; +const nextstrainLogoPNG = require("../../static/logos/favicon.png"); + + const title = "Nextstrain SARS-CoV-2 resources"; const abstract = `Around the world, people are sequencing and sharing SARS-CoV-2 genomic data. The Nextstrain team analyzes these data on a global and continental @@ -104,6 +108,27 @@ const contents = [ } ]; +const tableColumns = [ + { + name: "Dataset", + value: (dataset) => dataset.filename.replace(/_/g, ' / ').replace('.json', ''), + url: (dataset) => dataset.url + }, + { + name: "Contributor", + value: (dataset) => dataset.contributor, + valueMobile: () => "", + url: (dataset) => dataset.contributorUrl, + logo: (dataset) => dataset.contributor==="Nextstrain Team" ? + nextstrain.org : + undefined, + logoMobile: (dataset) => dataset.contributor==="Nextstrain Team" ? + nextstrain.org : + + } +]; + + class Index extends React.Component { constructor(props) { super(props); @@ -165,7 +190,7 @@ class Index extends React.Component { {this.state.filterParsed && (