diff --git a/static-site/gatsby-config.js b/static-site/gatsby-config.js index 4a6a16b60..2890acf02 100644 --- a/static-site/gatsby-config.js +++ b/static-site/gatsby-config.js @@ -73,7 +73,7 @@ module.exports = { // "gatsby-plugin-catch-links" // See https://github.com/nextstrain/nextstrain.org/issues/34 ], developMiddleware: app => { - ['/charon', '/dist', ...potentialAuspiceRoutes].forEach(path => { + ['/charon', '/whoami', '/dist', ...potentialAuspiceRoutes].forEach(path => { app.use( path, createProxyMiddleware({ diff --git a/static-site/src/components/Blurb/Blurb.jsx b/static-site/src/components/Blurb/Blurb.jsx new file mode 100644 index 000000000..d05e71569 --- /dev/null +++ b/static-site/src/components/Blurb/Blurb.jsx @@ -0,0 +1,61 @@ +import styled from "styled-components"; +import React from "react"; +import {PanelButtons} from "../panels/panelStyles"; + +const Blurb = ({title, buttons, children}) => ( + + + {title && ({title})} + {children} + {buttons && ( +
+ +
+ )} +
+
+); + +export default Blurb; + +const OuterContainer = styled.div` + min-width: 100%; + display: flex; + flex-direction: row; + justify-content: space-around; + margin-bottom: 50px; + @media (max-width: ${(props) => props.theme.mobileThreshold}) { + margin-bottom: 30px; + } + border: 1px solid #c994c7; +`; + +const InnerContainer = styled.div` + max-width: 80%; + margin: 0px auto; + text-align: center; + font-size: ${(props) => props.theme.niceFontSize}; + font-weight: 300; + line-height: ${(props) => props.theme.niceLineHeight}; + border: 1px solid #8856a7; + @media (max-width: ${(props) => props.theme.mobileThreshold}) { + max-width: 100%; + } + li { + text-align: left; + } +`; + +const Title = styled.div` + text-align: center; + font-size: 38px; + line-height: 50px; + font-weight: 300; + font-family: ${(props) => props.theme.generalFont}; + color: ${(props) => props.theme.darkGrey}; + margin: 5px 0px; + @media (max-width: ${(props) => props.theme.mobileThreshold}) { + font-size: 32px; + line-height: 42px; + } +`; diff --git a/static-site/src/components/panels/card.jsx b/static-site/src/components/panels/card.jsx new file mode 100644 index 000000000..56c5a789d --- /dev/null +++ b/static-site/src/components/panels/card.jsx @@ -0,0 +1,101 @@ +import React from "react"; +import PropTypes from "prop-types"; +import styled, {css} from "styled-components"; +import {theme} from "../../layouts/theme"; + +// eslint-disable-next-line react/prefer-stateless-function +class Card extends React.Component { + calcDim() { + /* more experimentation needed with different screen sizes here */ + if (this.props.parentWidth > 500) { + return 200; + } + return 140; // minimum card size + } + render() { + if (!this.props.parentWidth) return null; + const color = theme.rainbow10[this.props.cardIdx%theme.rainbow10.length]; + const dim = this.calcDim(); + return ( + + + {this.props.img ? + : // eslint-disable-line + + } + {this.props.title} + {this.props.private && } + + + ); + } +} + +Card.propTypes = { + title: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + img: PropTypes.string, + private: PropTypes.bool, + cardIdx: PropTypes.number.isRequired, + parentWidth: PropTypes.number.isRequired, +}; + +export default Card; + +const Container = styled.div` + position: relative; // needed for children using absolute + overflow: hidden; + min-width: ${(props) => props.dim}px; + max-width: ${(props) => props.dim}px; + min-height: ${(props) => props.dim}px; + max-height: ${(props) => props.dim}px; + padding: 6px; +`; + +const commonCardCss = css` + border-radius: 6px; + box-shadow: 3px 3px 3px 1px rgba(0, 0, 0, 0.15); + min-height: 100%; + max-height: 100%; + min-width: 100%; + max-width: 100%; +`; +const CardSwatch = styled.div` + ${commonCardCss} + background-color: ${(props) => props.color}; +`; +const CardImg = styled.img` + ${commonCardCss} +`; + +const overlayStyles = css` + position: absolute; + border-radius: ${(props) => props.availWidth<200 ? "3px" : "6px"}; + padding: ${(props) => props.availWidth<200 ? "3px 5px" : "6px 12px"}; + right: ${(props) => props.availWidth<200 ? "5px" : "12px"}; + background: rgba(0, 0, 0, 0.7); +`; +const CardTitle = styled.div` + ${overlayStyles} + top: ${(props) => props.availWidth<200 ? "20px" : "40px"}; + max-width: ${(props) => props.availWidth - (props.availWidth<200 ? 20 : 40)}px; + font-family: ${(props) => props.theme.generalFont}; + font-weight: ${(props) => props.availWidth<200 ? 300 : 500}; + font-size: ${(props) => props.availWidth<200 ? '14px' : '24px'}; + color: white; +`; +const PadlockContainer = styled.div` + ${overlayStyles} + bottom: ${(props) => props.availWidth<200 ? "10px" : "30px"}; +`; +const Padlock = ({availWidth}) => ( + + + + + +); + diff --git a/static-site/src/components/panels/panelStyles.jsx b/static-site/src/components/panels/panelStyles.jsx new file mode 100644 index 000000000..6f9ececaf --- /dev/null +++ b/static-site/src/components/panels/panelStyles.jsx @@ -0,0 +1,101 @@ +/** Common styles imported by different panels */ +import styled from "styled-components"; +import React from "react"; + +export const PanelContainer = styled.div` + display: flex; + /* background-color: #80c7cd; */ + border: 1px solid #80c7cd; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-around; + align-items: stretch; // each box in a row will be same height (cross-axis) + @media (max-width: ${(props) => props.theme.mobileThreshold}) { + flex-direction: column; + align-content: center; + } +`; + + +export const Panel = styled.div` + flex-grow: 1; // all same size + flex-shrink: 1; + min-width: ${(props) => props.fullwidth ? '100%' : '300px'}; + max-width: ${(props) => props.fullwidth ? '100%' : '33%'}; // default for _huge_ screens is 3 columns + @media (max-width: ${(props) => props.theme.bigScreenThreshold}) { + max-width: ${(props) => props.fullwidth ? '100%' : '48%'}; // essentially two columns maximum. Not 50% to allow some margin. + } + @media (max-width: ${(props) => props.theme.mobileThreshold}) { + max-width: 100%; // for mobile take up all available width + } + min-height: 100%; + max-height: 100%; + border: 1px solid #b2efd8; // TODO - dev only + margin: 0px 0px 10px 0px; +`; + + +export const PanelTitle = styled.div` + text-align: center; + font-size: 2rem; + font-weight: 400; + font-family: ${(props) => props.theme.generalFont}; + color: ${(props) => props.theme.darkGrey}; + margin: 5px 0px; +`; + +export const PanelSubtitle = styled.div` + text-align: center; + font-family: ${(props) => props.theme.generalFont}; + font-size: ${(props) => props.theme.niceFontSize}; + color: ${(props) => props.theme.medGrey}; + margin: 5px 0px; +`; + + +export const PanelButtons = ({buttons}) => ( + /** + * Render a row of buttons (often just one). + * Prop `buttons` is an array of objects, each with properties + * `to` (href), `label` (rendered text) `external` (optional boolean) + */ + + {buttons.map(({to, label, external}) => ( + + + {label} + + + ))} + +); + +const ButtonContainer = styled.div` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-evenly; + @media (max-width: ${(props) => props.theme.mobileThreshold}) { + flex-direction: column; + align-items: center; + } +`; + +const StyledButton = styled.button` + border: 1px solid #CCC; + background-color: inherit; + border-radius: 3px; + cursor: pointer; + padding: 3px 10px 3px 10px; // between border & text + margin: 3px; // outside border + font-size: 1.8rem; + font-family: ${(props) => props.theme.generalFont}; + color: ${(props) => props.theme.medGrey}; + font-weight: 400; + text-transform: uppercase; + vertical-align: middle; + &:hover { + color: black; + border: 1px solid black; + } +`; \ No newline at end of file diff --git a/static-site/src/components/panels/panelWithCards.jsx b/static-site/src/components/panels/panelWithCards.jsx new file mode 100644 index 000000000..69c875522 --- /dev/null +++ b/static-site/src/components/panels/panelWithCards.jsx @@ -0,0 +1,60 @@ +import React from "react"; +import styled from "styled-components"; +import { debounce } from 'lodash'; +import Card from "./card"; +import { Panel, PanelTitle, PanelSubtitle, PanelButtons } from "./panelStyles"; + + +class PanelWithCards extends React.Component { + constructor() { + super(); + this.state={cardsContainerWidth: 0}; + this.updateDimensions = debounce(() => { + if (!this.cardsContainerRef) return; + this.setState({cardsContainerWidth: this.cardsContainerRef.getBoundingClientRect().width}); + }, 100); + } + componentDidMount() { + this.updateDimensions(); + window.addEventListener('resize', this.updateDimensions); + } + componentWillUnmount() { + window.removeEventListener('resize', this.updateDimensions); + } + render() { + return ( + + {this.props.title && ( + {this.props.title} + )} + {this.props.subtitle && ( + {this.props.subtitle} + )} + {this.props.cards && ( + {this.cardsContainerRef = input;}}> + {/* we use `innerRef` to access the actual DOM node, as `ref` returns the styled component wrapper */} + {this.props.cards.map((props, cardIdx) => + + )} + + )} + {this.props.buttons && ()} + + ); + } +} + +export default PanelWithCards; + + +const CardsContainer = styled.div` + display: flex; + /* background-color: ; */ + border: 1px solid #ffeda0; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-evenly; + align-items: stretch; // each box in a row will be same height (cross-axis) + margin: 20px 10px +`; + diff --git a/static-site/src/layouts/theme.js b/static-site/src/layouts/theme.js index de473723d..e1d4ab819 100644 --- a/static-site/src/layouts/theme.js +++ b/static-site/src/layouts/theme.js @@ -41,8 +41,11 @@ export const theme = { md: 64, // em lg: 76 // em } - } + }, + // cross reference following with the thresholds we use in + mobileThreshold: "775px", // todo - cross reference with auspice + bigScreenThreshold: '1500px' // todo - more testing needed }; diff --git a/static-site/src/pages/testing.jsx b/static-site/src/pages/testing.jsx new file mode 100644 index 000000000..21b767605 --- /dev/null +++ b/static-site/src/pages/testing.jsx @@ -0,0 +1,143 @@ +import React from "react"; +import GenericPage from "../layouts/generic-page"; +import {version} from "styled-components"; + +import PanelWithCards from "../components/panels/panelWithCards"; +import { Panel, PanelContainer} from "../components/panels/panelStyles"; +import { UserContext } from "../layouts/userDataWrapper"; +import nCoVCards from "../components/Cards/nCoVCards"; +import coreCards from "../components/Cards/coreCards"; +import communityCards from "../components/Cards/communityCards"; +import narrativeCards from "../components/Cards/narrativeCards"; +import Blurb from "../components/Blurb/Blurb"; + +const genericTitle = "Generic Title"; +const genericSubtitle = "A showcase of datasets created by independent researchers and shared through GitHub"; + +class Index extends React.Component { //eslint-disable-line + render() { + console.log(version) + return ( + + + + As we use media queries, to test mobile you must resize the viewport (we can't just create a small div here) + P.S. This is rendered in a blurb component + + + +
+ This time with the children as a div, buttons (prop) and a list, which we render as left-aligned +
    +
  1. one
  2. +
  3. two
  4. +
  5. three
  6. +
+
    +
  • a point
  • +
  • another point
  • +
+
+
+ +

Following card selection component isn't constrained (note that we don't intent to use this):

+ + +

Here's a very simple PanelContainer with one Panel

+ + + + +

Here's an example of a PanelContainer with two children

+ + + + + +

Here's an example of a PanelContainer with four panels as children, the first of which is "fullwidth"

+ + "} subtitle={"This sets the 'fullwidth' prop"}/> + + Here's normal (bespoke) content rendered within a Panel component which sorts out the dimensions (but nothing else) + + + + + +

Here's an example of what the main splash page might look like

+ + + + + + + + +
+ ); + } +} + +export default Index; + + +/** + * This has to be a component itself, as it needs to access + * UserContext, which means it has to be a _child_ of `` + */ +class GroupsSection extends React.Component { //eslint-disable-line + static contextType = UserContext; + render() { + /** + * This is essentially a copy-and-paste of the function in `userGroups.jsx` + * but as this work is a proof-of-principle, I didn't want to modify + * production code. + */ + const loggedIn = !!this.context.user; + const cards = this.context.visibleGroups ? + this.context.visibleGroups + .map((g) => ({ + title: g.name, + url: `/groups/${g.name}`, + private: g.private + })) + .filter((c) => loggedIn ? c.private : true) : + []; + return ( + + ); + } +}