Skip to content

Commit

Permalink
Add grapher image fallback to GDoc pages (#3635)
Browse files Browse the repository at this point in the history
Add grapher image fallback to GDoc pages

Restore old functionality by adding image fallback for embedded graphers
which will be shown by skipping the dynamic embedding in the following
cases:

* The user has JS disabled
* The user doesn't have a powerful enough device, see
  `shouldProgressiveEmbed`, except on a grapher/data page
    - In this case we also show a note highlighting the possibility of
      opening the interactive chart on a separate page
  • Loading branch information
rakyi authored May 28, 2024
1 parent 37b0feb commit 0a3c640
Show file tree
Hide file tree
Showing 23 changed files with 218 additions and 124 deletions.
9 changes: 2 additions & 7 deletions baker/formatWordpressPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ import { renderExpandableParagraphs } from "../site/blocks/ExpandableParagraph.j
import {
formatUrls,
getBodyHtml,
GRAPHER_PREVIEW_CLASS,
SUMMARY_CLASSNAME,
} from "../site/formatting.js"
import { GRAPHER_PREVIEW_CLASS } from "../site/SiteConstants.js"
import { INTERACTIVE_ICON_SVG } from "../site/InteractionNotice.js"
import { renderKeyInsights, renderProminentLinks } from "./siteRenderers.js"
import { KEY_INSIGHTS_CLASS_NAME } from "../site/blocks/KeyInsights.js"
import { RELATED_CHARTS_CLASS_NAME } from "../site/blocks/RelatedCharts.js"
Expand Down Expand Up @@ -82,12 +83,6 @@ const initMathJax = () => {

const formatMathJax = initMathJax()

// A modifed FontAwesome icon
const INTERACTIVE_ICON_SVG = `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="hand-pointer" class="svg-inline--fa fa-hand-pointer fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 617">
<path fill="currentColor" d="M448,344.59v96a40.36,40.36,0,0,1-1.06,9.16l-32,136A40,40,0,0,1,376,616.59H168a40,40,0,0,1-32.35-16.47l-128-176a40,40,0,0,1,64.7-47.06L104,420.58v-276a40,40,0,0,1,80,0v200h8v-40a40,40,0,1,1,80,0v40h8v-24a40,40,0,1,1,80,0v24h8a40,40,0,1,1,80,0Zm-256,80h-8v96h8Zm88,0h-8v96h8Zm88,0h-8v96h8Z" transform="translate(0 -0.41)"/>
<path fill="currentColor" opacity="0.6" d="M239.76,234.78A27.5,27.5,0,0,1,217,192a87.76,87.76,0,1,0-145.9,0A27.5,27.5,0,1,1,25.37,222.6,142.17,142.17,0,0,1,1.24,143.17C1.24,64.45,65.28.41,144,.41s142.76,64,142.76,142.76a142.17,142.17,0,0,1-24.13,79.43A27.47,27.47,0,0,1,239.76,234.78Z" transform="translate(0 -0.41)"/>
</svg>`

const extractLatex = (html: string): [string, string[]] => {
const latexBlocks: string[] = []
html = html.replace(/\[latex\]([\s\S]*?)\[\/latex\]/gm, (_, latex) => {
Expand Down
2 changes: 1 addition & 1 deletion site/DataPageContent.scss
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@
}

figure[data-grapher-src] {
height: 575px;
height: $grapher-height;
}
}

Expand Down
5 changes: 0 additions & 5 deletions site/DataPageV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,6 @@ export const DataPageV2 = (props: {
<meta property="og:image:width" content={imageWidth} />
<meta property="og:image:height" content={imageHeight} />
<IFrameDetector />
<noscript>
<style>{`
figure[data-grapher-src] { display: none !important; }
`}</style>
</noscript>
<link rel="preconnect" href={dataApiOrigin} />
{variableIds.flatMap((variableId) =>
[
Expand Down
4 changes: 4 additions & 0 deletions site/GrapherImage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.GrapherImage {
display: block;
border: 1px solid #f2f2f2;
}
30 changes: 30 additions & 0 deletions site/GrapherImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react"

import {
DEFAULT_GRAPHER_HEIGHT,
DEFAULT_GRAPHER_WIDTH,
} from "@ourworldindata/grapher"
import { BAKED_GRAPHER_EXPORTS_BASE_URL } from "../settings/clientSettings.js"

export default function GrapherImage({
alt,
slug,
noFormatting,
}: {
slug: string
alt?: string
noFormatting?: boolean
}) {
return (
<img
className="GrapherImage"
src={`${BAKED_GRAPHER_EXPORTS_BASE_URL}/${slug}.svg`}
alt={alt}
width={DEFAULT_GRAPHER_WIDTH}
height={DEFAULT_GRAPHER_HEIGHT}
loading="lazy"
data-no-lightbox
data-no-img-formatting={noFormatting}
/>
)
}
11 changes: 7 additions & 4 deletions site/GrapherPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import React from "react"
import urljoin from "url-join"
import {
ADMIN_BASE_URL,
BAKED_GRAPHER_EXPORTS_BASE_URL,
BAKED_GRAPHER_URL,
DATA_API_URL,
} from "../settings/clientSettings.js"
Expand All @@ -29,6 +28,7 @@ import { IFrameDetector } from "./IframeDetector.js"
import { RelatedArticles } from "./RelatedArticles/RelatedArticles.js"
import { SiteFooter } from "./SiteFooter.js"
import { SiteHeader } from "./SiteHeader.js"
import GrapherImage from "./GrapherImage.js"

export const GrapherPage = (props: {
grapher: GrapherInterface
Expand Down Expand Up @@ -123,9 +123,12 @@ window.Grapher.renderSingleGrapherOnGrapherPage(jsonConfig)`
<LoadingIndicator />
</figure>
<noscript id="fallback">
<img
src={`${BAKED_GRAPHER_EXPORTS_BASE_URL}/${grapher.slug}.svg`}
/>
{grapher.slug && (
<GrapherImage
slug={grapher.slug}
alt={grapher.title}
/>
)}
<p>Interactive visualization requires JavaScript</p>
</noscript>

Expand Down
23 changes: 8 additions & 15 deletions site/GrapherWithFallback.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import {
DEFAULT_GRAPHER_HEIGHT,
DEFAULT_GRAPHER_WIDTH,
Grapher,
} from "@ourworldindata/grapher"
import { Grapher } from "@ourworldindata/grapher"
import React from "react"
import { GrapherFigureView } from "./GrapherFigureView.js"
import { BAKED_GRAPHER_EXPORTS_BASE_URL } from "../settings/clientSettings.js"
import cx from "classnames"
import { GRAPHER_PREVIEW_CLASS } from "./SiteConstants.js"
import GrapherImage from "./GrapherImage.js"

export const GrapherWithFallback = ({
grapher,
Expand Down Expand Up @@ -36,16 +33,12 @@ export const GrapherWithFallback = ({
// grapher is loading
<figure
data-grapher-src
className="GrapherWithFallback__fallback"
className={cx(
GRAPHER_PREVIEW_CLASS,
"GrapherWithFallback__fallback"
)}
>
<img
src={`${BAKED_GRAPHER_EXPORTS_BASE_URL}/${
slug ?? ""
}.svg`}
width={DEFAULT_GRAPHER_WIDTH}
height={DEFAULT_GRAPHER_HEIGHT}
loading="lazy"
/>
{slug && <GrapherImage slug={slug} />}
</figure>
)}
</>
Expand Down
20 changes: 20 additions & 0 deletions site/InteractionNotice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react"

// A modified FontAwesome icon. This is a string instead of JSX, so it can be
// also used outside of React when baking WordPress content.
export const INTERACTIVE_ICON_SVG = `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="hand-pointer" class="svg-inline--fa fa-hand-pointer fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 617">
<path fill="currentColor" d="M448,344.59v96a40.36,40.36,0,0,1-1.06,9.16l-32,136A40,40,0,0,1,376,616.59H168a40,40,0,0,1-32.35-16.47l-128-176a40,40,0,0,1,64.7-47.06L104,420.58v-276a40,40,0,0,1,80,0v200h8v-40a40,40,0,1,1,80,0v40h8v-24a40,40,0,1,1,80,0v24h8a40,40,0,1,1,80,0Zm-256,80h-8v96h8Zm88,0h-8v96h8Zm88,0h-8v96h8Z" transform="translate(0 -0.41)"/>
<path fill="currentColor" opacity="0.6" d="M239.76,234.78A27.5,27.5,0,0,1,217,192a87.76,87.76,0,1,0-145.9,0A27.5,27.5,0,1,1,25.37,222.6,142.17,142.17,0,0,1,1.24,143.17C1.24,64.45,65.28.41,144,.41s142.76,64,142.76,142.76a142.17,142.17,0,0,1-24.13,79.43A27.47,27.47,0,0,1,239.76,234.78Z" transform="translate(0 -0.41)"/>
</svg>`

export default function InteractionNotice() {
return (
<div className="interactionNotice">
<span
className="icon"
dangerouslySetInnerHTML={{ __html: INTERACTIVE_ICON_SVG }}
/>
<span className="label">Click to open interactive version</span>
</div>
)
}
2 changes: 2 additions & 0 deletions site/SiteConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export const POLYFILL_URL: string = `https://cdnjs.cloudflare.com/polyfill/v3/po
)}`

export const DEFAULT_LOCAL_BAKE_DIR = "localBake"

export const GRAPHER_PREVIEW_CLASS = "grapherPreview"
19 changes: 3 additions & 16 deletions site/blocks/AllChartsListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import {
DEFAULT_GRAPHER_HEIGHT,
DEFAULT_GRAPHER_WIDTH,
} from "@ourworldindata/grapher"
import { BasicChartInformation } from "@ourworldindata/utils"
import React from "react"
import {
BAKED_BASE_URL,
BAKED_GRAPHER_EXPORTS_BASE_URL,
} from "../../settings/clientSettings.js"
import { BAKED_BASE_URL } from "../../settings/clientSettings.js"
import GrapherImage from "../GrapherImage.js"

export const AllChartsListItem = ({
chart,
Expand All @@ -24,14 +18,7 @@ export const AllChartsListItem = ({
href={`${BAKED_BASE_URL}/grapher/${chart.slug}`}
onClick={onClick}
>
<img
src={`${BAKED_GRAPHER_EXPORTS_BASE_URL}/${chart.slug}.svg`}
loading="lazy"
data-no-lightbox
data-no-img-formatting
width={DEFAULT_GRAPHER_WIDTH}
height={DEFAULT_GRAPHER_HEIGHT}
></img>
<GrapherImage slug={chart.slug} noFormatting />
<span>{chart.title}</span>
</a>
{chart.variantName ? (
Expand Down
4 changes: 2 additions & 2 deletions site/blocks/RelatedCharts.jsdom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ it("renders active chart links and loads respective chart on click", () => {
)
).toHaveLength(1)

wrapper.find("a").forEach((link, idx) => {
wrapper.find("li > a").forEach((link, idx) => {
// Chart 2 has a higher priority, so the charts should be in reverse order: `Chart 2, Chart 1`
const expectedChartIdx = 1 - idx
link.simulate("click")
expect(wrapper.find("figure")).toHaveLength(1)
expect(
wrapper.find(
`figure[data-grapher-src="${BAKED_BASE_URL}/grapher/${charts[expectedChartIdx].slug}"]`
`figure[data-grapher-src="${BAKED_BASE_URL}/grapher/${charts[expectedChartIdx]?.slug}"]`
)
).toHaveLength(1)
// should have forced re-render by changing the `key`
Expand Down
37 changes: 26 additions & 11 deletions site/blocks/RelatedCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { useEmbedChart } from "../hooks.js"
import { GalleryArrow, GalleryArrowDirection } from "./GalleryArrow.js"
import { AllChartsListItem } from "./AllChartsListItem.js"
import { BAKED_BASE_URL } from "../../settings/clientSettings.js"
import { GRAPHER_PREVIEW_CLASS } from "../SiteConstants.js"
import GrapherImage from "../GrapherImage.js"

export const RELATED_CHARTS_CLASS_NAME = "related-charts"

Expand All @@ -30,7 +32,8 @@ export const RelatedCharts = ({

const isFirstSlideActive = activeChartIdx === 0
const isLastSlideActive = activeChartIdx === sortedCharts.length - 1
const activeChartSlug = sortedCharts[activeChartIdx]?.slug
const activeChart = sortedCharts[activeChartIdx]
const activeChartSlug = activeChart?.slug

const onClickItem = (event: React.MouseEvent, idx: number) => {
// Allow opening charts in new tab/window with ⌘+CLICK
Expand All @@ -42,18 +45,34 @@ export const RelatedCharts = ({

useEmbedChart(activeChartIdx, refChartContainer)

const grapherUrl = `${BAKED_BASE_URL}/grapher/${activeChartSlug}`

const figure = (
<figure
className={GRAPHER_PREVIEW_CLASS}
// Use unique `key` to force React to re-render tree
key={activeChartSlug}
data-grapher-src={grapherUrl}
>
<noscript>
<a href={grapherUrl}>
<GrapherImage
slug={activeChartSlug}
alt={activeChart?.title}
/>
</a>
</noscript>
</figure>
)

const singleChartView = (
<div className={RELATED_CHARTS_CLASS_NAME}>
<div className="grid grid-cols-12">
<div
className="related-charts__chart span-cols-7 span-md-cols-12"
ref={refChartContainer}
>
<figure
// Use unique `key` to force React to re-render tree
key={activeChartSlug}
data-grapher-src={`${BAKED_BASE_URL}/grapher/${activeChartSlug}`}
/>
{figure}
</div>
</div>
</div>
Expand All @@ -78,11 +97,7 @@ export const RelatedCharts = ({
className="related-charts__chart span-cols-7 span-md-cols-12"
ref={refChartContainer}
>
<figure
// Use unique `key` to force React to re-render tree
key={activeChartSlug}
data-grapher-src={`${BAKED_BASE_URL}/grapher/${activeChartSlug}`}
/>
{figure}
<div className="gallery-navigation">
<GalleryArrow
disabled={isFirstSlideActive}
Expand Down
5 changes: 4 additions & 1 deletion site/collections/CollectionsPage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@

figure {
width: 100%;
height: $grapher-height;
margin: 0;
margin-bottom: 48px;
}

figure:not(.grapherPreview) {
height: $grapher-height;
}
}
35 changes: 27 additions & 8 deletions site/collections/DynamicCollection.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cx from "classnames"
import React from "react"
import ReactDOM from "react-dom"
import { BAKED_BASE_URL } from "../../settings/clientSettings.js"
Expand All @@ -12,6 +13,9 @@ import {
import { observer } from "mobx-react"
import { WindowGraphers } from "./DynamicCollectionPage.js"
import { Grapher } from "@ourworldindata/grapher"
import { GRAPHER_PREVIEW_CLASS } from "../SiteConstants.js"
import InteractionNotice from "../InteractionNotice.js"
import GrapherImage from "../GrapherImage.js"

interface DynamicCollectionProps {
baseUrl: string
Expand Down Expand Up @@ -117,14 +121,29 @@ export class DynamicCollection extends React.Component<DynamicCollectionProps> {
<div className="grid span-cols-12">
{this.initialDynamicCollection
.split(" ")
.map((chartSlug, index) => (
<figure
key={index}
data-grapher-src={`${this.props.baseUrl}/grapher/${chartSlug}`}
data-grapher-index={index}
className="span-cols-6 span-md-cols-12"
/>
))}
.map((chartSlug, index) => {
const grapherUrl = `${this.props.baseUrl}/grapher/${chartSlug}`
return (
<figure
key={index}
className={cx(
GRAPHER_PREVIEW_CLASS,
"span-cols-6 span-md-cols-12"
)}
data-grapher-src={grapherUrl}
data-grapher-index={index}
>
<a
href={grapherUrl}
target="_blank"
rel="noopener"
>
<GrapherImage slug={chartSlug} />
<InteractionNotice />
</a>
</figure>
)
})}
</div>
)
}
Expand Down
Loading

0 comments on commit 0a3c640

Please sign in to comment.