From a88fd38fc4e83d1bf770eeb69af11eda367dc0b3 Mon Sep 17 00:00:00 2001 From: caseymm Date: Wed, 30 Mar 2022 15:20:15 -0700 Subject: [PATCH 01/15] init setup --- src/routes.json | 8 + .../annual-report/2021/annual-report.jsx | 2557 +++++++++++++++++ .../annual-report/2021/annual-report.scss | 2452 ++++++++++++++++ .../2021/country-blurb/country-blurb.jsx | 59 + .../2021/country-blurb/country-blurb.scss | 65 + .../annual-report/2021/country-usage.json | 1 + .../annual-report/2021/india-projects.json | 42 + src/views/annual-report/2021/l10n.json | 367 +++ src/views/annual-report/2021/people.json | 232 ++ src/views/annual-report/2021/supporters.json | 90 + 10 files changed, 5873 insertions(+) create mode 100644 src/views/annual-report/2021/annual-report.jsx create mode 100644 src/views/annual-report/2021/annual-report.scss create mode 100644 src/views/annual-report/2021/country-blurb/country-blurb.jsx create mode 100644 src/views/annual-report/2021/country-blurb/country-blurb.scss create mode 100644 src/views/annual-report/2021/country-usage.json create mode 100644 src/views/annual-report/2021/india-projects.json create mode 100644 src/views/annual-report/2021/l10n.json create mode 100644 src/views/annual-report/2021/people.json create mode 100644 src/views/annual-report/2021/supporters.json diff --git a/src/routes.json b/src/routes.json index a29daadfe..85733c247 100644 --- a/src/routes.json +++ b/src/routes.json @@ -30,6 +30,14 @@ "title": "Annual Report 2020", "viewportWidth": "device-width" }, + { + "name": "annual-report-2021", + "pattern": "^/annual-report/2021/?(\\?.*)?$", + "routeAlias": "/annual-report/2021/?$", + "view": "annual-report/2021/annual-report", + "title": "Annual Report 2021", + "viewportWidth": "device-width" + }, { "name": "camp", "pattern": "^/camp/?$", diff --git a/src/views/annual-report/2021/annual-report.jsx b/src/views/annual-report/2021/annual-report.jsx new file mode 100644 index 000000000..d8e2d12f3 --- /dev/null +++ b/src/views/annual-report/2021/annual-report.jsx @@ -0,0 +1,2557 @@ +const bindAll = require('lodash.bindall'); +const classNames = require('classnames'); +const React = require('react'); +const MediaQuery = require('react-responsive').default; +const FormattedMessage = require('react-intl').FormattedMessage; +const injectIntl = require('react-intl').injectIntl; +const intlShape = require('react-intl').intlShape; + +const render = require('../../../lib/render.jsx'); +const frameless = require('../../../lib/frameless'); + +const Avatar = require('../../../components/avatar/avatar.jsx'); +const Page = require('../../../components/page/www/page.jsx'); +const Grid = require('../../../components/grid/grid.jsx'); +const Button = require('../../../components/forms/button.jsx'); +const FlexRow = require('../../../components/flex-row/flex-row.jsx'); +const Comment = require('../../../components/comment/comment.jsx'); +const CountryBlurb = require('./country-blurb/country-blurb.jsx'); +const TextAndMediaSnippet = require('../../../components/text-and-media-snippet/text-and-media-snippet.jsx'); +const TimelineCard = require('../../../components/timeline-card/timeline-card.jsx'); +const WorldMap = require('../../../components/world-map/world-map.jsx'); +const CountryUsage = require('./country-usage.json'); +const IndiaProjects = require('./india-projects.json'); +const PeopleGrid = require('../../../components/people-grid/people-grid.jsx'); +const People = require('./people.json'); +const VideoPreview = require('../../../components/video-preview/video-preview.jsx'); +const Supporters = require('./supporters.json'); +import {TwitterTweetEmbed} from 'react-twitter-embed'; + + +require('./annual-report.scss'); + +// Director’s Message / Mission / Reach / Themes / Founder's Message / Supporters / Team / Donate + +// Some constants used for the page subnav and section refs +const SECTIONS = { + founders_message: 'founders_message', + mission: 'mission', + reach: 'reach', + themes: 'themes', + directors_message: 'directors_message', + supporters: 'supporters', + team: 'team', + donate: 'donate' +}; + +const SECTION_NAMES = { + directors_message: , + mission: , + reach: , + themes: , + founders_message: , + supporters: , + team: , + donate: +}; + +// Constants used for world map data processing/formatting for use with Plotly +const countryNames = Object.keys(CountryUsage); +const countryData = countryNames.map(key => + `${CountryUsage[key].display}
${CountryUsage[key].count.toLocaleString('en')}` +); +const colorIndex = countryNames.map(key => CountryUsage[key]['log count']); +const minColor = 'rgba(46, 142, 184, .05)'; +const maxColor = 'rgba(46, 142, 184, 1)'; + +// Create the div given a list of supporter names, +// this will contain two columns of names either of equal size +// or with the left column containing 1 more item than the right +const createSupportersLists = (inKind, supportersList) => { + supportersList.sort(); + const splitIndex = Math.ceil(supportersList.length / 2); + const firstHalf = supportersList.slice(0, splitIndex); + const secondHalf = supportersList.slice(splitIndex); + + return ( +
+
    + { + firstHalf.map((supporter, index) => ( +
  • + {supporter} + {inKind.includes(supporter) && + + } +
  • + )) + } +
+
    + { + secondHalf.map((supporter, index) => ( +
  • + {supporter} + {inKind.includes(supporter) && + + } +
  • + )) + } +
+
+ ); +}; + +class AnnualReport extends React.Component { + constructor (props) { + super(props); + + // Storage for each of the section refs when we need to refer + // to them in the scroll handling code + // These will be stored with a short lowercase key representing + // the specific section (e.g. 'mission') + this.sectionRefs = {}; + + this.subnavRef = null; + + this.state = { + currentlyVisible: SECTIONS.directors_message, // The currently visible section + dropdownVisible: false + }; + + bindAll(this, [ + 'scrollTo', + 'setRef', + 'setSubnavRef', + 'handleSubnavItemClick', + 'getDimensionsOfSection', + 'handleScroll', + 'handleDropDownClick' + ]); + } + + componentDidMount () { + window.addEventListener('scroll', this.handleScroll); + } + + componentWillUnMount () { + window.removeEventListener('scroll', this.handleScroll); + } + + // A generic handler for a subnav item that takes the name of the + // section to scroll to (all lowercase) + handleSubnavItemClick (sectionName) { + // Return a button click handler that will close the dropdown if open + // and scrolls to the correct section + return () => { + this.setState({dropdownVisible: false}); + this.scrollTo(this.sectionRefs[sectionName]); + }; + } + + scrollTo (element) { + if (element) { + const sectionTop = this.getDimensionsOfSection(element).offsetTop; + window.scrollTo({top: sectionTop, behavior: 'smooth'}); + // The smooth scrolling doesn't work on Safari + // but this code allows scrolling to the correct part of the section + // in Safari since the css property 'scrollMarginTop' is also not supported there + } + } + + // Generically create a ref for the given section, stored in + // this.sectionRefs + setRef (sectionName) { + return ref => (this.sectionRefs[sectionName] = ref); + } + + setSubnavRef (ref) { + this.subnavRef = ref; + } + + // Calculate the dimensions of a given section for use in figuring out + // which section is currently visible + getDimensionsOfSection (sectionRef) { + const {height} = sectionRef.getBoundingClientRect(); + const offsetTop = sectionRef.offsetTop; + const offsetBottom = offsetTop + height; + + return { + height, + offsetTop, + offsetBottom + }; + } + + // While scrolling, update the subnav to reflect the currently visible section + handleScroll () { + const subnavHeight = this.getDimensionsOfSection(this.subnavRef).height; + // The additional 50 is to account for the main site nav height + const currentScrollPosition = window.scrollY + subnavHeight + 50; + + // Find which section is currently visible based on our scroll position + for (const key in this.sectionRefs) { + if (!this.sectionRefs.hasOwnProperty(key)) continue; + const currentRef = this.sectionRefs[key]; + const {offsetBottom, offsetTop} = this.getDimensionsOfSection(currentRef); + if (currentScrollPosition > offsetTop && currentScrollPosition < offsetBottom) { + if (this.state.currentlyVisible !== key) { + this.setState({currentlyVisible: key}); + return; + } + } + } + } + + // Click handler for responsive subnav dropdown + handleDropDownClick () { + this.setState({dropdownVisible: !this.state.dropdownVisible}); + } + + render () { + IndiaProjects[0].alt = this.props.intl.formatMessage({id: 'annualReport.2021.altIndia1'}); + IndiaProjects[1].alt = this.props.intl.formatMessage({id: 'annualReport.2021.altIndia2'}); + IndiaProjects[2].alt = this.props.intl.formatMessage({id: 'annualReport.2021.altIndia3'}); + IndiaProjects[3].alt = this.props.intl.formatMessage({id: 'annualReport.2021.altIndia4'}); + // Element containing buttons to scroll to each of the sections in the + // annual report. The layout of this component will be different on + // different screen sizes (see below) + const subnav = + ( + + + + + + + + + + + + + + {SECTION_NAMES.director_message} + + + + + + + + + + + ); + + return ( +
+
+ {/* Top Bar */} + +
+ {SECTION_NAMES[this.state.currentlyVisible]} + +
+ {this.state.dropdownVisible ? + /* Bottom Bar */ +
+
+ {subnav} +
: + null + } +
+ {/* For large screens, show whole subnav, with no dropdown */} + + {subnav} + +
+
+
+
+ +
+

+ +

+

+ +

+
+ {this.props.intl.formatMessage( +
+ + {this.props.intl.formatMessage( + + + + {/* Show the wave icon inside this div in smaller screens */} +
+ {this.props.intl.formatMessage( +

+ +

+
+
+ +

+ +

+
+
+

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+
+ {this.props.intl.formatMessage( +
+

Mitch Resnick

+

+ +

+

+ +

+
+
+
+
+
+
+ {this.props.intl.formatMessage( + {this.props.intl.formatMessage( +
+
+
+
+

+
+

+ +

+
+

+ +

+

+

+

+ + + + ) + }} + /> +

+
+
+ +
+
+

+ +

+

+ +

+
+ {this.props.intl.formatMessage( +
+
+ +
+

+

+
+
+ {this.props.intl.formatMessage( +
+
+ +
+
+

+

+
+ {this.props.intl.formatMessage( +
+
+ +
+

+

+ +

+
+
+ {this.props.intl.formatMessage( +
+
+ +
+
+

+

+
+ {this.props.intl.formatMessage( +
+
+ +
+

+

+
+
+ {this.props.intl.formatMessage( +
+
+ +
+
+

+

+
+ {this.props.intl.formatMessage( +
+
+ +
+

+

+
+
+ {this.props.intl.formatMessage( +
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+ {this.props.intl.formatMessage( +
+
+ + +
+ ) + }} + /> +

+ +

+
+ +
+
+
+ + +
+ ) + }} + /> +

+ +

+
+ +
+
+
+ + +
+ ) + }} + /> +

+ +

+
+ +
+
+ {this.props.intl.formatMessage( +
+ +

+ +

+
+ + ) + }} + /> + {this.props.intl.formatMessage( + + ) + }} + /> +
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
0
+
+
+ +
+
+
+ +
+
+
+ + + + + + + + + + + + +
+
+
+
+
+
+

+ +

+
+ {this.props.intl.formatMessage( + + + +
+

+ +

+
+ {this.props.intl.formatMessage( +
+
+ +
+ {this.props.intl.formatMessage( +
+
+
+
+
+
+ {this.props.intl.formatMessage( +

+ +

+
+
+ + +
+ ) + }} + /> +

+ +

+
+ + +
+ ) + }} + /> +
+
+ + +
+ {this.props.intl.formatMessage( +
+
+ +
+
+
+

+ +

+

+ +

+
+
+
+

+ +

+
+
+

+ +

+
+
+

+ +

+
+
+
+
+

+ +

+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+ {/* eslint-disable max-len */} +
+
+
+
+ +
+

+ +

+

+ +

+
+
+ +
+
+

+ +

+
+ {this.props.intl.formatMessage( +
+

+ + + + ) + }} + /> +

+

+ +

+
+ +
+
+
+
+
+

+ + + + +

+
+ {this.props.intl.formatMessage( +
+
+
+

+ +

+

+ +

+

+ +

+
+ + ) + }} + /> + {this.props.intl.formatMessage( + + ) + }} + /> +
+
+
+

+ +

+

+ +

+

+ +

+
+ + {this.props.intl.formatMessage( + + ) + }} + /> +
+
+
+
+
+ +
+

+ +

+ + + +
+
+
+
+ + + + + + + + + +
+
+
+
+ + + + + + + + + Quest Alliance + + ) + }} + /> + + + + Scratch Team members + + ) + }} + /> + +
+
+

+ +

+

+ +

+

+ +

+
+
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+ +
+
+ +
+

+ +

+
+
+ +
+
+
+ {/* eslint-disable max-len */} +
+
+ +
+
+
+

+ +

+

+ +

+

+ +

+
+
+ {this.props.intl.formatMessage( +
+
+
+
+
+ +