From a766eaf636d14636e60a8880d15ea57e94f4d36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ra=C4=8D=C3=A1k?= Date: Wed, 22 May 2024 21:15:07 +0200 Subject: [PATCH 1/3] 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 --- baker/formatWordpressPost.tsx | 9 +--- site/DataPageContent.scss | 2 +- site/DataPageV2.tsx | 5 -- site/GrapherImage.scss | 4 ++ site/GrapherImage.tsx | 30 +++++++++++ site/GrapherPage.tsx | 11 ++-- site/GrapherWithFallback.tsx | 23 +++------ site/InteractionNotice.tsx | 20 ++++++++ site/SiteConstants.ts | 2 + site/blocks/AllChartsListItem.tsx | 19 ++----- site/blocks/RelatedCharts.jsdom.test.tsx | 4 +- site/blocks/RelatedCharts.tsx | 37 ++++++++++---- site/collections/CollectionsPage.scss | 5 +- site/collections/DynamicCollection.tsx | 35 ++++++++++--- site/collections/StaticCollectionPage.tsx | 33 +++++++++--- site/css/content.scss | 61 +++++++++-------------- site/formatting.test.ts | 6 +-- site/formatting.tsx | 2 +- site/gdocs/components/AllCharts.scss | 2 +- site/gdocs/components/Chart.scss | 2 +- site/gdocs/components/Chart.tsx | 25 +++++++++- site/multiembedder/MultiEmbedder.tsx | 3 +- site/owid.scss | 1 + 23 files changed, 217 insertions(+), 124 deletions(-) create mode 100644 site/GrapherImage.scss create mode 100644 site/GrapherImage.tsx create mode 100644 site/InteractionNotice.tsx diff --git a/baker/formatWordpressPost.tsx b/baker/formatWordpressPost.tsx index 455fbbabf1a..302ff4f16aa 100644 --- a/baker/formatWordpressPost.tsx +++ b/baker/formatWordpressPost.tsx @@ -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" @@ -82,12 +83,6 @@ const initMathJax = () => { const formatMathJax = initMathJax() -// A modifed FontAwesome icon -const INTERACTIVE_ICON_SVG = `` - const extractLatex = (html: string): [string, string[]] => { const latexBlocks: string[] = [] html = html.replace(/\[latex\]([\s\S]*?)\[\/latex\]/gm, (_, latex) => { diff --git a/site/DataPageContent.scss b/site/DataPageContent.scss index 0800c740837..353895c58eb 100644 --- a/site/DataPageContent.scss +++ b/site/DataPageContent.scss @@ -393,7 +393,7 @@ } figure[data-grapher-src] { - height: 575px; + height: $grapher-height; } } diff --git a/site/DataPageV2.tsx b/site/DataPageV2.tsx index 3f555c25fd4..83877927fa5 100644 --- a/site/DataPageV2.tsx +++ b/site/DataPageV2.tsx @@ -120,11 +120,6 @@ export const DataPageV2 = (props: { - {variableIds.flatMap((variableId) => [ diff --git a/site/GrapherImage.scss b/site/GrapherImage.scss new file mode 100644 index 00000000000..d12c0b6eb3a --- /dev/null +++ b/site/GrapherImage.scss @@ -0,0 +1,4 @@ +.GrapherImage { + display: block; + border: 1px solid #f2f2f2; +} diff --git a/site/GrapherImage.tsx b/site/GrapherImage.tsx new file mode 100644 index 00000000000..a9eab6b6ae9 --- /dev/null +++ b/site/GrapherImage.tsx @@ -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 ( + {alt} + ) +} diff --git a/site/GrapherPage.tsx b/site/GrapherPage.tsx index 2aac7e0d1de..a4f682d7df3 100644 --- a/site/GrapherPage.tsx +++ b/site/GrapherPage.tsx @@ -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" @@ -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 @@ -123,9 +123,12 @@ window.Grapher.renderSingleGrapherOnGrapherPage(jsonConfig)` diff --git a/site/GrapherWithFallback.tsx b/site/GrapherWithFallback.tsx index 8bb8a963640..0ed8b5f5ab2 100644 --- a/site/GrapherWithFallback.tsx +++ b/site/GrapherWithFallback.tsx @@ -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, @@ -29,16 +26,12 @@ export const GrapherWithFallback = ({ // grapher is loading
- + {slug && }
)} diff --git a/site/InteractionNotice.tsx b/site/InteractionNotice.tsx new file mode 100644 index 00000000000..d480e2770d5 --- /dev/null +++ b/site/InteractionNotice.tsx @@ -0,0 +1,20 @@ +import React from "react" + +// A modified FontAwesome icon. This is a string to be used outside of React +// when baking WordPress content. +export const INTERACTIVE_ICON_SVG = `` + +export default function InteractionNotice() { + return ( +
+ + Click to open interactive version +
+ ) +} diff --git a/site/SiteConstants.ts b/site/SiteConstants.ts index 50c0b372ce3..df112224247 100644 --- a/site/SiteConstants.ts +++ b/site/SiteConstants.ts @@ -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" diff --git a/site/blocks/AllChartsListItem.tsx b/site/blocks/AllChartsListItem.tsx index 67056ac5c31..42d586375a1 100644 --- a/site/blocks/AllChartsListItem.tsx +++ b/site/blocks/AllChartsListItem.tsx @@ -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, @@ -24,14 +18,7 @@ export const AllChartsListItem = ({ href={`${BAKED_BASE_URL}/grapher/${chart.slug}`} onClick={onClick} > - + {chart.title} {chart.variantName ? ( diff --git a/site/blocks/RelatedCharts.jsdom.test.tsx b/site/blocks/RelatedCharts.jsdom.test.tsx index 6250327a2cc..6e49166210e 100755 --- a/site/blocks/RelatedCharts.jsdom.test.tsx +++ b/site/blocks/RelatedCharts.jsdom.test.tsx @@ -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` diff --git a/site/blocks/RelatedCharts.tsx b/site/blocks/RelatedCharts.tsx index 2c49649789a..bc0222c5982 100644 --- a/site/blocks/RelatedCharts.tsx +++ b/site/blocks/RelatedCharts.tsx @@ -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" @@ -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 @@ -42,6 +45,26 @@ export const RelatedCharts = ({ useEmbedChart(activeChartIdx, refChartContainer) + const grapherUrl = `${BAKED_BASE_URL}/grapher/${activeChartSlug}` + + const figure = ( +
+ +
+ ) + const singleChartView = (
@@ -49,11 +72,7 @@ export const RelatedCharts = ({ className="related-charts__chart span-cols-7 span-md-cols-12" ref={refChartContainer} > -
+ {figure}
@@ -78,11 +97,7 @@ export const RelatedCharts = ({ className="related-charts__chart span-cols-7 span-md-cols-12" ref={refChartContainer} > -
+ {figure}
{
{this.initialDynamicCollection .split(" ") - .map((chartSlug, index) => ( -
- ))} + .map((chartSlug, index) => { + const grapherUrl = `${this.props.baseUrl}/grapher/${chartSlug}` + return ( +
+ + + + +
+ ) + })}
) } diff --git a/site/collections/StaticCollectionPage.tsx b/site/collections/StaticCollectionPage.tsx index feefe2e11ed..111376663be 100644 --- a/site/collections/StaticCollectionPage.tsx +++ b/site/collections/StaticCollectionPage.tsx @@ -1,7 +1,11 @@ +import cx from "classnames" import React from "react" import { Head } from "../Head.js" +import { GRAPHER_PREVIEW_CLASS } from "../SiteConstants.js" import { SiteHeader } from "../SiteHeader.js" import { SiteFooter } from "../SiteFooter.js" +import InteractionNotice from "../InteractionNotice.js" +import GrapherImage from "../GrapherImage.js" export interface StaticCollectionPageProps { title: string @@ -35,13 +39,28 @@ export const StaticCollectionPage = (
- {charts.map((chartSlug) => ( -
- ))} + {charts.map((chartSlug) => { + const grapherUrl = `${baseUrl}/grapher/${chartSlug}` + return ( +
+ + + + +
+ ) + })}
diff --git a/site/css/content.scss b/site/css/content.scss index 3ea0f198a64..02367d9d037 100644 --- a/site/css/content.scss +++ b/site/css/content.scss @@ -37,11 +37,6 @@ border-bottom: none !important; } - > a > div { - display: inline-block; - position: relative; - } - > a > div:hover { -webkit-box-shadow: 0px 0px 4px #000; -moz-box-shadow: 0px 0px 4px #000; @@ -54,38 +49,6 @@ width: 100%; max-width: $content-max-width; } - - .interactionNotice { - font-size: 14px; - font-weight: 400; - line-height: 1.2; - color: rgba(black, 0.4); - display: none; - background-color: rgba(black, 0.03); - padding: 12px 30px; - position: relative; - text-decoration: none; - color: $controls-color; - - // Anything >680px will get the interactive graphics in-place. - // 680px is a breakpoint also used in the JavaScript code – keep it in sync. - @media screen and (max-device-width: 680px) { - display: block; - } - - .icon { - font-size: 21px; - line-height: 28px; - margin-top: -15px; - position: absolute; - left: 10px; - top: 50%; - } - - .label { - display: block; - } - } } .article-content figure[data-grapher-src].grapherPreview { @@ -100,6 +63,30 @@ figure[data-explorer-src] { position: relative; } +.interactionNotice { + font-size: 14px; + font-weight: 400; + line-height: 1.6; + display: none; + background-color: rgba(black, 0.03); + padding: 3px 0; + color: $controls-color; + justify-content: center; + align-items: center; + flex-wrap: wrap; + gap: 8px; + + // Anything >680px will get the interactive graphics in-place. + // 680px is a breakpoint also used in the JavaScript code – keep it in sync. + @media screen and (max-device-width: 680px) { + display: flex; + } + + .icon { + font-size: 21px; + } +} + /******************************************************************************* * Tables */ diff --git a/site/formatting.test.ts b/site/formatting.test.ts index 22c023c3321..61848ac98b9 100644 --- a/site/formatting.test.ts +++ b/site/formatting.test.ts @@ -1,10 +1,8 @@ import cheerio from "cheerio" import { WP_ColumnStyle } from "@ourworldindata/utils" -import { - GRAPHER_PREVIEW_CLASS, - splitContentIntoSectionsAndColumns, -} from "./formatting.js" +import { splitContentIntoSectionsAndColumns } from "./formatting.js" import { formatAuthors } from "./clientFormatting.js" +import { GRAPHER_PREVIEW_CLASS } from "./SiteConstants.js" const paragraph = `

Some paragraph

` const chart = `
` diff --git a/site/formatting.tsx b/site/formatting.tsx index 4ff0cc35c6f..a4d148e33c8 100644 --- a/site/formatting.tsx +++ b/site/formatting.tsx @@ -20,8 +20,8 @@ import { PROMINENT_LINK_CLASSNAME } from "./blocks/ProminentLink.js" import { Byline } from "./Byline.js" import { SectionHeading } from "./SectionHeading.js" import { FormattingOptions } from "@ourworldindata/types" +import { GRAPHER_PREVIEW_CLASS } from "./SiteConstants.js" -export const GRAPHER_PREVIEW_CLASS = "grapherPreview" export const SUMMARY_CLASSNAME = "wp-block-owid-summary" export const RESEARCH_AND_WRITING_CLASSNAME = "wp-block-research-and-writing" export const KEY_INSIGHTS_H2_CLASSNAME = "key-insights-heading" diff --git a/site/gdocs/components/AllCharts.scss b/site/gdocs/components/AllCharts.scss index 2cc769a98a5..20aa22fc908 100644 --- a/site/gdocs/components/AllCharts.scss +++ b/site/gdocs/components/AllCharts.scss @@ -3,7 +3,7 @@ margin-bottom: 40px; } - figure[data-grapher-src] { + figure[data-grapher-src]:not(.grapherPreview) { height: $grapher-height; } } diff --git a/site/gdocs/components/Chart.scss b/site/gdocs/components/Chart.scss index b9db840e33c..7225464cda4 100644 --- a/site/gdocs/components/Chart.scss +++ b/site/gdocs/components/Chart.scss @@ -1,4 +1,4 @@ -figure.chart { +figure.chart:not(.grapherPreview) { height: $grapher-height; } diff --git a/site/gdocs/components/Chart.tsx b/site/gdocs/components/Chart.tsx index bb10e3fc494..7da1b291621 100644 --- a/site/gdocs/components/Chart.tsx +++ b/site/gdocs/components/Chart.tsx @@ -15,6 +15,9 @@ import { } from "@ourworldindata/utils" import { renderSpans, useLinkedChart } from "../utils.js" import cx from "classnames" +import { GRAPHER_PREVIEW_CLASS } from "../../SiteConstants.js" +import InteractionNotice from "../../InteractionNotice.js" +import GrapherImage from "../../GrapherImage.js" export default function Chart({ d, @@ -94,7 +97,10 @@ export default function Chart({
+ > + {isExplorer ? ( + + ) : ( + url.slug && ( + + + + + ) + )} +
{d.caption ? (
{renderSpans(d.caption)}
) : null} diff --git a/site/multiembedder/MultiEmbedder.tsx b/site/multiembedder/MultiEmbedder.tsx index 04cbea8b0a2..bf6387e009c 100644 --- a/site/multiembedder/MultiEmbedder.tsx +++ b/site/multiembedder/MultiEmbedder.tsx @@ -32,6 +32,7 @@ import { EMBEDDED_EXPLORER_PARTIAL_GRAPHER_CONFIGS, EXPLORER_EMBEDDED_FIGURE_SELECTOR, } from "../../explorer/ExplorerConstants.js" +import { GRAPHER_PREVIEW_CLASS } from "../SiteConstants.js" import { ADMIN_BASE_URL, BAKED_GRAPHER_URL, @@ -201,7 +202,7 @@ class MultiEmbedder { this.graphersAndExplorersToUpdate.add(props.selection) ReactDOM.render(, figure) } else { - figure.classList.remove("grapherPreview") + figure.classList.remove(GRAPHER_PREVIEW_CLASS) const grapherPageConfig = deserializeJSONFromHTML(html) diff --git a/site/owid.scss b/site/owid.scss index 29b9b0198af..d27f17afdf0 100644 --- a/site/owid.scss +++ b/site/owid.scss @@ -102,6 +102,7 @@ @import "./gdocs/components/PillRow.scss"; @import "./DataPage.scss"; @import "./DataPageContent.scss"; +@import "./GrapherImage.scss"; @import "./detailsOnDemand.scss"; @import "./gdocs/pages/DataInsight.scss"; From a35b7298ed099e457cbd4dc49df5c544e25e1d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ra=C4=8D=C3=A1k?= Date: Tue, 28 May 2024 12:56:30 +0200 Subject: [PATCH 2/3] Use resolved slug for GrapherImage --- site/gdocs/components/Chart.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/site/gdocs/components/Chart.tsx b/site/gdocs/components/Chart.tsx index 7da1b291621..0dad75dd892 100644 --- a/site/gdocs/components/Chart.tsx +++ b/site/gdocs/components/Chart.tsx @@ -41,6 +41,7 @@ export default function Chart({ const url = Url.fromURL(d.url) const resolvedUrl = linkedChart.resolvedUrl + const resolvedSlug = Url.fromURL(resolvedUrl).slug const isExplorer = url.isExplorer const hasControls = url.queryParams.hideControls !== "true" const isExplorerWithControls = isExplorer && hasControls @@ -119,9 +120,9 @@ export default function Chart({

) : ( - url.slug && ( + resolvedSlug && ( - + ) From 559339b04419ab77251adaa12b442c31af8525c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ra=C4=8D=C3=A1k?= Date: Tue, 28 May 2024 12:59:12 +0200 Subject: [PATCH 3/3] Reword INTERACTIVE_ICON_SVG comment --- site/InteractionNotice.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/InteractionNotice.tsx b/site/InteractionNotice.tsx index d480e2770d5..00c9348ece7 100644 --- a/site/InteractionNotice.tsx +++ b/site/InteractionNotice.tsx @@ -1,7 +1,7 @@ import React from "react" -// A modified FontAwesome icon. This is a string to be used outside of React -// when baking WordPress content. +// 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 = `