diff --git a/static-site/README.md b/static-site/README.md index 94222865f..0f2326ae5 100644 --- a/static-site/README.md +++ b/static-site/README.md @@ -81,7 +81,7 @@ We may wish to rename this directory to avoid any doubt. * "Edit" -> "Scale Image" to 250 x 250px * "File" -> "Save Image" & use PNG 3. Save the file in `./static/splash_images` -4. Edit `./src/components/Cards/coreCards.js`, `./content/community-datasets.yaml` or `./src/components/Cards/narrativeCards.js` to include the card & title. +4. Edit `./src/components/splash/showcase.yaml` to include the card & title. 5. Edit `./src/components/Footer/index.jsx` to provide credit for the photo. ### Adding new team members diff --git a/static-site/content/community-datasets.yaml b/static-site/content/community-datasets.yaml index a60f6278c..9bc4f08ba 100644 --- a/static-site/content/community-datasets.yaml +++ b/static-site/content/community-datasets.yaml @@ -23,11 +23,6 @@ data: maintainers: E. Kinganda Lusamaki, INRB & C. Pratt date: 13 Oct 2021 title: Genomic epidemiology of the 2018-21 Ebola epidemic - card: - img: "ebola2.png" - url: "/community/inrb-drc/ebola-nord-kivu" - title: "DRC Ebola (2018-19)" - frontpage: true - url: https://nextstrain.org/community/narratives/ESR-NZ/GenomicsNarrativeSARSCoV2/aotearoa-border-incursions title: Real-time genomics to track COVID-19 post-elimination border incursions in Aotearoa New Zealand maintainers: Jordan Douglas, PhD, Jemma L. Geoghegan, PhD, James Hadfield, PhD, Remco Bouckaert, PhD, Matthew Storey, MSc, Xiaoyun Ren, PhD, Joep de Ligt, PhD, Nigel French, PhD, David Welch, PhD @@ -43,11 +38,6 @@ data: maintainers: Katherine Eaton et al date: '2022-06-13' title: Yersinia Pestis (multiple datasets) - card: - img: "yersinia.png" - url: "/community/ktmeaton/yersinia-pestis/maximum-likelihood/all?m=div" - title: "Yersinia pestis" - frontpage: true - url: https://nextstrain.org/community/pestdisplace/CMDAFRICA maintainers: PestDisPlace date: 07 Sep 2019 diff --git a/static-site/src/components/Cards/coreCards.js b/static-site/src/components/Cards/coreCards.js deleted file mode 100644 index 364335cab..000000000 --- a/static-site/src/components/Cards/coreCards.js +++ /dev/null @@ -1,14 +0,0 @@ -const coreCards = [ - { - img: "mpox.png", - url: "/mpox/clade-IIb", - title: "Mpox" - }, - { - img: "avianinfluenza.png", - url: "/avian-flu", - title: "Avian influenza" - } -]; - -export default coreCards; diff --git a/static-site/src/components/Cards/nCoVCards.js b/static-site/src/components/Cards/nCoVCards.js deleted file mode 100644 index 2465bdd74..000000000 --- a/static-site/src/components/Cards/nCoVCards.js +++ /dev/null @@ -1,14 +0,0 @@ -const nCoVCards = [ - { - img: "ncov.png", - url: "/ncov/gisaid/global", - title: "Latest global analysis - GISAID data" - }, - { - img: "ncov.png", - url: "/ncov/open/global", - title: "Latest global analysis - open data" - } -]; - -export default nCoVCards; diff --git a/static-site/src/components/Cards/narrativeCards.js b/static-site/src/components/Cards/narrativeCards.js deleted file mode 100644 index 9fe46de8c..000000000 --- a/static-site/src/components/Cards/narrativeCards.js +++ /dev/null @@ -1,14 +0,0 @@ -const narrativeCards = [ - { - img: "wnv2.png", - url: "/narratives/twenty-years-of-WNV", - title: "WNV in the Americas" - }, - { - img: "ebola3.png", - url: "/narratives/inrb-ebola-example-sit-rep", - title: "DRC Ebola Sit Rep" - } -]; - -export default narrativeCards; diff --git a/static-site/src/components/ListResources/index.tsx b/static-site/src/components/ListResources/index.tsx index 5170ebe85..2db23f7da 100644 --- a/static-site/src/components/ListResources/index.tsx +++ b/static-site/src/components/ListResources/index.tsx @@ -1,9 +1,9 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useCallback, createContext, useContext } from 'react'; import styled from 'styled-components'; import Select, { MultiValue } from 'react-select'; -import ScrollableAnchor from '../../../vendored/react-scrollable-anchor/index'; +import ScrollableAnchor, { goToAnchor } from '../../../vendored/react-scrollable-anchor/index'; import {Tooltip} from 'react-tooltip-v5'; -import { useFilterOptions } from './useFilterOptions'; +import { createFilterOption, useFilterOptions } from './useFilterOptions'; import { useSortAndFilter } from "./useSortAndFilter"; import { useDataFetch } from "./useDataFetch"; import {Spinner} from '../Spinner/Spinner'; @@ -11,14 +11,16 @@ import { ResourceGroup } from './ResourceGroup'; import { ErrorContainer } from "../../pages/404"; import { TooltipWrapper } from "./IndividualResource"; import {ResourceModal, SetModalResourceContext} from "./Modal"; -import { Showcase, useShowcaseCards} from "./Showcase"; -import { Card, FilterOption, Group, GroupDisplayNames, QuickLink, Resource } from './types'; +import { CardImgWrapper, CardInner, CardOuter, CardTitle, Showcase } from "../Showcase"; +import { FilterCard, FilterOption, Group, GroupDisplayNames, QuickLink, Resource } from './types'; interface ListResourcesProps extends ListResourcesResponsiveProps { elWidth: number } -export const LIST_ANCHOR = "list"; +const LIST_ANCHOR = "list"; + +const SetSelectedFilterOptions = createContext> | null>(null); /** * A React component to fetch data and display the available resources, @@ -67,7 +69,13 @@ function ListResources({ return ( - + + Showcase resources: click to filter the resources to a pathogen + + + + + @@ -107,7 +115,7 @@ interface ListResourcesResponsiveProps { /** Should the group name itself be a url? (which we let the server redirect) */ defaultGroupLinks: boolean groupDisplayNames: GroupDisplayNames - showcase: Card[] + showcase: FilterCard[] } /** @@ -222,4 +230,72 @@ const SortContainer = styled.div` margin-left: 20px; margin-right: 5px; } -` \ No newline at end of file +` + +const Byline = styled.div` + font-size: 1.6rem; + border-top: 1px rgb(230, 230, 230) solid; +` + + +/*** SHOWCASE ***/ + + +interface FilterShowcaseTileProps { + card: FilterCard +} + + +const FilterShowcaseTile = ({ card }: FilterShowcaseTileProps) => { + const setSelectedFilterOptions = useContext(SetSelectedFilterOptions); + + if (!setSelectedFilterOptions) { + throw new Error("Usage of this component requires the SetSelectedFilterOptions context to be set.") + } + + const filter = useCallback( + () => { + setSelectedFilterOptions(card.filters.map(createFilterOption)); + goToAnchor(LIST_ANCHOR); + }, + [setSelectedFilterOptions, card] + ) + + return ( + + +
+ + {card.name} + + +
+
+
+ ) +} + + +/** + * Given a set of user-defined cards, restrict them to the set of cards for + * which the filters are valid given the resources known to the resource listing + * UI + */ +const useShowcaseCards = (cards?: FilterCard[], groups?: Group[]) => { + const [restrictedCards, setRestrictedCards] = useState([]); + useEffect(() => { + if (!cards || !groups) return; + const words = groups.reduce((words, group) => { + for (const resource of group.resources) { + for (const word of resource.nameParts) { + words.add(word); + } + } + return words; + }, new Set()); + setRestrictedCards(cards.filter((card) => { + return card.filters.every((word) => words.has(word)) + })); + }, [cards, groups]); + return restrictedCards; +} diff --git a/static-site/src/components/ListResources/types.ts b/static-site/src/components/ListResources/types.ts index 8ca659eac..7bbed24a1 100644 --- a/static-site/src/components/ListResources/types.ts +++ b/static-site/src/components/ListResources/types.ts @@ -1,3 +1,5 @@ +import { Card } from "../Showcase/types" + export interface FilterOption { value: string label: string @@ -45,9 +47,7 @@ export interface UpdateCadence { } // See coreShowcase in static-site/content/resource-listing.yaml -export interface Card { - name: string - img: string +export interface FilterCard extends Card { filters: string[] } diff --git a/static-site/src/components/ListResources/Showcase.tsx b/static-site/src/components/Showcase/index.tsx similarity index 60% rename from static-site/src/components/ListResources/Showcase.tsx rename to static-site/src/components/Showcase/index.tsx index cb989e62a..11b5ade16 100644 --- a/static-site/src/components/ListResources/Showcase.tsx +++ b/static-site/src/components/Showcase/index.tsx @@ -1,25 +1,22 @@ /* eslint-disable react/prop-types */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; import styled from 'styled-components'; -import {CardInner, CardImg, CardTitle} from "../Cards/styles"; +import { CardImg } from "../Cards/styles"; import { theme } from "../../layouts/theme"; -import { goToAnchor } from '../../../vendored/react-scrollable-anchor/index'; -import { createFilterOption } from "./useFilterOptions"; -import { LIST_ANCHOR } from "./index"; -import { Card, FilterOption, Group } from './types'; +import { Card } from './types'; const cardWidthHeight = 160; // pixels const expandPreviewHeight = 50 //pixels const transitionDuration = "0.3s" const transitionTimingFunction = "ease" -interface ShowcaseProps { - cards: Card[] - setSelectedFilterOptions: React.Dispatch> +interface ShowcaseProps { + cards: AnyCard[] + CardComponent: React.FunctionComponent<{ card: AnyCard }> } -export const Showcase = ({cards, setSelectedFilterOptions}: ShowcaseProps) => { +export const Showcase = ({cards, CardComponent}: ShowcaseProps) => { const [cardsContainerHeight, setCardsContainerHeight] = useState(0); const [isExpanded, setIsExpanded] = useState(false); @@ -44,14 +41,11 @@ export const Showcase = ({cards, setSelectedFilterOptions}: ShowcaseProps) => { return (
- - Showcase resources: click to filter the resources to a pathogen - - {cards.map((el) => ( - - ))} + {cards.map((el) => { + return + })} @@ -65,37 +59,9 @@ export const Showcase = ({cards, setSelectedFilterOptions}: ShowcaseProps) => { ) } -interface ShowcaseTileProps { - card: Card - setSelectedFilterOptions: React.Dispatch> -} - /** * NOTE: Many of the React components here are taken from the existing Cards UI */ -const ShowcaseTile = ({card, setSelectedFilterOptions}: ShowcaseTileProps) => { - const filter = useCallback( - () => { - setSelectedFilterOptions(card.filters.map(createFilterOption)); - goToAnchor(LIST_ANCHOR); - }, - [setSelectedFilterOptions, card] - ) - - return ( - - -
- - {card.name} - - -
-
-
- ) -} - const ShowcaseContainer = styled.div<{$expandedHeight: number}>` position: relative; @@ -114,6 +80,7 @@ const ShowcaseContainer = styled.div<{$expandedHeight: number}>` const ArrowButton = styled.div` text-align: center; + font-size: 1.8rem; width: 100%; height: 1em; cursor: pointer; @@ -156,7 +123,7 @@ const Spacer = styled.div` min-height: 25px; ` -const CardOuter = styled.div` +export const CardOuter = styled.div` background-color: #FFFFFF; padding: 0; overflow: hidden; @@ -167,6 +134,27 @@ const CardOuter = styled.div` max-height: ${cardWidthHeight}px; ` +export const CardInner = styled.div` + margin: 5px 10px 5px 10px; + cursor: pointer; +`; + +export const CardTitle = styled.div<{$squashed: boolean}>` + font-family: ${(props) => props.theme.generalFont}; + font-weight: 500; + font-size: ${(props) => props.$squashed ? "21px" : "25px"}; + @media (max-width: 768px) { + font-size: 22px; + } + position: absolute; + border-radius: 3px; + padding: 10px 20px 10px 10px; + top: 15px; + left: 20px; + color: white; + background: rgba(0, 0, 0, 0.7); +`; + const themeColors = [...theme.titleColors]; const getColor = () => { // rotate colors by moving the first color (which is always defined) to the end @@ -175,7 +163,7 @@ const getColor = () => { return themeColors.at(-1); } -const CardImgWrapper = ({filename}) => { +export const CardImgWrapper = ({filename}) => { let src; try { // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -186,30 +174,3 @@ const CardImgWrapper = ({filename}) => { } return } - -const Byline = styled.div` - font-size: 1.6rem; - border-top: 1px rgb(230, 230, 230) solid; -` - -/** - * Given a set of user-defined cards, restrict them to the set of cards for - * which the filters are valid given the resources known to the resource listing - * UI - */ -export const useShowcaseCards = (cards?: Card[], groups?: Group[]) => { - const [restrictedCards, setRestrictedCards] = useState([]); - useEffect(() => { - if (!cards || !groups) return; - const words = groups.reduce((words, group) => { - for (const resource of group.resources) { - for (const word of resource.nameParts) { - words.add(word); - } - } - return words; - }, new Set()); - setRestrictedCards(cards.filter((card) => card.filters.every((word) => words.has(word)))); - }, [cards, groups]); - return restrictedCards; -} \ No newline at end of file diff --git a/static-site/src/components/Showcase/types.ts b/static-site/src/components/Showcase/types.ts new file mode 100644 index 000000000..a47fb7a48 --- /dev/null +++ b/static-site/src/components/Showcase/types.ts @@ -0,0 +1,4 @@ +export interface Card { + name: string + img: string +} diff --git a/static-site/src/components/splash/groupCards.jsx b/static-site/src/components/splash/groupCards.jsx index b7563d2b2..999c87650 100644 --- a/static-site/src/components/splash/groupCards.jsx +++ b/static-site/src/components/splash/groupCards.jsx @@ -3,7 +3,7 @@ import Cards from "../Cards"; import { theme } from "../../layouts/theme"; import { UserContext } from "../../layouts/userDataWrapper"; -export const createGroupCards = (groups, colors = [...theme.titleColors]) => groups.map((group) => { +const createGroupCards = (groups, colors = [...theme.titleColors]) => groups.map((group) => { const groupColor = colors[0]; colors.push(colors.shift()); diff --git a/static-site/src/components/splash/index.jsx b/static-site/src/components/splash/index.jsx index 127dc3d86..edec09b7d 100644 --- a/static-site/src/components/splash/index.jsx +++ b/static-site/src/components/splash/index.jsx @@ -1,30 +1,19 @@ import React, { useEffect } from "react"; import ScrollableAnchor, { configureAnchors } from '../../../vendored/react-scrollable-anchor/index'; -import Cards from "../Cards"; -import nCoVCards from "../Cards/nCoVCards"; -import coreCards from "../Cards/coreCards"; -import communityDatasets from "../../../content/community-datasets.yaml"; -import narrativeCards from "../Cards/narrativeCards"; import Title from "./title"; import * as Styles from "./styles"; import { SmallSpacer, BigSpacer, HugeSpacer, FlexCenter, Line } from "../../layouts/generalComponents"; import Footer from "../Footer"; -import { createGroupCards } from "./groupCards"; +import { CardImgWrapper, CardInner, CardOuter, CardTitle, Showcase } from "../Showcase"; +import { cards } from "./showcase.yaml"; -const Section = ({id, title, abstract, cards, buttonText, buttonLink}) => ( +const Section = ({id, title, abstract, buttonText, buttonLink}) => (
{title} {abstract} -
- -
{buttonText} @@ -70,6 +59,14 @@ const Splash = () => { + + + Featured analyses + + + + +
@@ -78,7 +75,6 @@ const Splash = () => { title="SARS-CoV-2 (COVID-19)" abstract="We are incorporating SARS-CoV-2 genomes as soon as they are shared and providing analyses and situation reports. In addition we have developed a number of resources and tools, and are facilitating independent groups to run their own analyses." - cards={nCoVCards} buttonText="See all resources" buttonLink="/sars-cov-2" /> @@ -86,7 +82,6 @@ const Splash = () => { id="groups" title="Nextstrain Groups" abstract="We want to enable research labs, public health entities and others to share their datasets and narratives through Nextstrain with complete control of their data and audience." - cards={createGroupCards([{name: "neherlab"}, {name: "spheres"}])} buttonText="See all groups" buttonLink="/groups" /> @@ -94,7 +89,6 @@ const Splash = () => { id="pathogens" title="Explore pathogens" abstract="Genomic analyses of specific pathogens kept up-to-date by the Nextstrain team." - cards={coreCards} buttonText="See all pathogens" buttonLink="/pathogens" /> @@ -105,7 +99,6 @@ const Splash = () => { Analyses by independent groups stored and accessed via public GitHub repos )} - cards={communityDatasets.data.filter((c) => c?.card?.frontpage).map((e) => e.card).slice(0, 2)} buttonText="Learn more" buttonLink="/community" /> @@ -113,7 +106,6 @@ const Splash = () => { id="narratives" title="Narratives" abstract="Narratives are a method of data-driven storytelling. They allow authoring of content which is displayed alongside a view into the data." - cards={narrativeCards} buttonText="Find out more" buttonLink="https://docs.nextstrain.org/en/latest/guides/communicate/narratives-intro.html" /> @@ -206,3 +198,19 @@ const Splash = () => { } export default Splash; + + +const UrlShowcaseTile = ({ card }) => { + return ( + + + + + {card.name} + + + + + + ) +} diff --git a/static-site/src/components/splash/showcase.yaml b/static-site/src/components/splash/showcase.yaml new file mode 100644 index 000000000..f08765f1e --- /dev/null +++ b/static-site/src/components/splash/showcase.yaml @@ -0,0 +1,37 @@ +cards: +- name: SARS-CoV-2 + img: ncov.png + url: /ncov/ +- name: H5N1 cattle outbreak + img: avianinfluenza.png + url: /avian-flu/h5n1-cattle-outbreak/genome +- name: avian influenza + img: avianinfluenza.png + url: /avian-flu/h5n1/ha/2y +- name: mpox + img: mpox.png + url: /mpox/clade-IIb +- name: Ebola in the DRC + img: ebola2.png + url: /community/inrb-drc/ebola-nord-kivu +- name: measles + img: measles.jpg + url: /measles/N450 +- name: seasonal influenza + img: seasonalinfluenza.png + url: /seasonal-flu +- name: RSV + img: rsv1.png + url: /rsv +- name: Yersinia pestis + img: yersinia.png + url: /community/ktmeaton/yersinia-pestis/maximum-likelihood/all?m=div +- name: WNV in the Americas (narrative) + img: wnv2.png + url: /narratives/twenty-years-of-WNV +- name: Chikungunya + img: chikv.png + url: /groups/ViennaRNA/CHIKVnext +- name: HIV + img: hiv_community.png + url: /groups/LANL-HIV-DB/HIV/env diff --git a/static-site/static/splash_images/hiv_community.png b/static-site/static/splash_images/hiv_community.png new file mode 100644 index 000000000..3b7beff12 Binary files /dev/null and b/static-site/static/splash_images/hiv_community.png differ