From 95627d8c5fcad3007ac5907cb905e8e57e41bc51 Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Wed, 31 Jan 2024 21:42:39 +0000 Subject: [PATCH 01/14] =?UTF-8?q?=F0=9F=8E=89=20update=20explorer=20title?= =?UTF-8?q?=20and=20subtitle=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- baker/algolia/indexExplorersToAlgolia.ts | 6 +++++- baker/siteRenderers.tsx | 4 +++- explorer/Explorer.sample.tsx | 2 +- explorer/Explorer.scss | 8 ++++---- explorer/Explorer.tsx | 13 +++++++++++-- explorer/ExplorerGrammar.ts | 2 +- site/ChartsIndexPage.tsx | 4 ++++ site/ExplorerPage.tsx | 4 +++- site/gdocs/components/ProminentLink.tsx | 13 ++++++------- site/owid.scss | 5 +++++ 10 files changed, 43 insertions(+), 18 deletions(-) diff --git a/baker/algolia/indexExplorersToAlgolia.ts b/baker/algolia/indexExplorersToAlgolia.ts index 812d9486086..cab27ac9de9 100644 --- a/baker/algolia/indexExplorersToAlgolia.ts +++ b/baker/algolia/indexExplorersToAlgolia.ts @@ -158,9 +158,13 @@ const getExplorerRecords = async (): Promise => { ? textChunks : [""] + const formattedTitle = `${getNullishJSONValueAsPlaintext( + title + )} Data Explorer` + return textChunksForIteration.map((chunk, i) => ({ slug, - title: getNullishJSONValueAsPlaintext(title), + title: formattedTitle, subtitle: getNullishJSONValueAsPlaintext(subtitle), views_7d: pageviews[`/explorers/${slug}`]?.views_7d ?? 0, text: chunk, diff --git a/baker/siteRenderers.tsx b/baker/siteRenderers.tsx index e805c32d833..126d0cff3c8 100644 --- a/baker/siteRenderers.tsx +++ b/baker/siteRenderers.tsx @@ -751,7 +751,9 @@ const getExplorerTitleByUrl = async (url: Url): Promise => { : undefined) ) } - return explorer.explorerTitle + // Maintaining old behaviour so that we don't have to redesign WP prominent links + // since we're removing WP soon + return `${explorer.explorerTitle} Data Explorer` } /** diff --git a/explorer/Explorer.sample.tsx b/explorer/Explorer.sample.tsx index 05cd40c7164..1cbb00d7f06 100644 --- a/explorer/Explorer.sample.tsx +++ b/explorer/Explorer.sample.tsx @@ -4,7 +4,7 @@ import { GrapherTabOption } from "@ourworldindata/types" import { GrapherProgrammaticInterface } from "@ourworldindata/grapher" import { Explorer, ExplorerProps } from "./Explorer.js" -const SampleExplorerOfGraphersProgram = `explorerTitle CO₂ Data Explorer +const SampleExplorerOfGraphersProgram = `explorerTitle CO₂ isPublished false explorerSubtitle Download the complete Our World in Data CO₂ and GHG Emissions Dataset. subNavId co2 diff --git a/explorer/Explorer.scss b/explorer/Explorer.scss index dfa1486a656..731ab139297 100644 --- a/explorer/Explorer.scss +++ b/explorer/Explorer.scss @@ -39,10 +39,10 @@ html.IsInIframe #ExplorerContainer { .ExplorerSubtitle { color: #7a899e; font-size: 13px; - - a { - @include owid-link-60; - } + } + .ExplorerDownloadLink { + font-size: 13px; + @include owid-link-60; } } diff --git a/explorer/Explorer.tsx b/explorer/Explorer.tsx index 007e2816cdf..7a973de0524 100644 --- a/explorer/Explorer.tsx +++ b/explorer/Explorer.tsx @@ -786,9 +786,8 @@ export class Explorer private renderHeaderElement() { return (
-
- {this.explorerProgram.explorerTitle} + {this.explorerProgram.explorerTitle} Data Explorer
+ {this.explorerProgram.downloadDataLink && ( + + Download this dataset + + )}
) } diff --git a/explorer/ExplorerGrammar.ts b/explorer/ExplorerGrammar.ts index 30339ef40a0..ddbb3934d06 100644 --- a/explorer/ExplorerGrammar.ts +++ b/explorer/ExplorerGrammar.ts @@ -49,7 +49,7 @@ export const ExplorerGrammar: Grammar = { explorerTitle: { ...StringCellDef, keyword: "explorerTitle", - valuePlaceholder: "Life Expectancy Data Explorer", + valuePlaceholder: "Life Expectancy", description: "The title will appear in the top left corner of the Explorer.", }, diff --git a/site/ChartsIndexPage.tsx b/site/ChartsIndexPage.tsx index 3d90929f227..b0499e58625 100644 --- a/site/ChartsIndexPage.tsx +++ b/site/ChartsIndexPage.tsx @@ -115,6 +115,7 @@ export const ChartsIndexPage = (props: { ({ title, explorerTitle, + explorerSubtitle, slug, }) => (
  • @@ -124,6 +125,9 @@ export const ChartsIndexPage = (props: { {explorerTitle ?? title} +

    + {explorerSubtitle} +

  • ) )} diff --git a/site/ExplorerPage.tsx b/site/ExplorerPage.tsx index aba5a1d4761..44a26838aa9 100644 --- a/site/ExplorerPage.tsx +++ b/site/ExplorerPage.tsx @@ -67,6 +67,7 @@ export const ExplorerPage = (props: ExplorerPageSettings) => { subNavId, subNavCurrentId, explorerTitle, + explorerSubtitle, slug, thumbnail, hideAlertBanner, @@ -100,7 +101,8 @@ window.Explorer.renderSingleExplorerOnExplorerPage(explorerProgram, grapherConfi diff --git a/site/gdocs/components/ProminentLink.tsx b/site/gdocs/components/ProminentLink.tsx index dd088085f15..0ce78cba5c4 100644 --- a/site/gdocs/components/ProminentLink.tsx +++ b/site/gdocs/components/ProminentLink.tsx @@ -50,17 +50,16 @@ export const ProminentLink = (props: { title = title ?? linkedDocument?.title description = description ?? linkedDocument?.excerpt thumbnail = thumbnail ?? linkedDocument?.["featured-image"] - } else if (linkType === "grapher" || linkType === "explorer") { + } else if (linkType === "grapher") { href = `${linkedChart?.resolvedUrl}` title = title ?? linkedChart?.title thumbnail = thumbnail ?? linkedChart?.thumbnail description = - // Adding extra context for graphers by default - // Not needed for Explorers as their titles are self-explanatory - description ?? - (linkType === "grapher" - ? "See the data in our interactive visualization" - : "") + description ?? "See the data in our interactive visualization" + } else if (linkType === "explorer") { + href = `${linkedChart?.resolvedUrl}` + title = title ?? `${linkedChart?.title} Data Explorer` + thumbnail = thumbnail ?? linkedChart?.thumbnail } const Thumbnail = ({ thumbnail }: { thumbnail: string }) => { diff --git a/site/owid.scss b/site/owid.scss index 1838caee6a9..dd90848b397 100644 --- a/site/owid.scss +++ b/site/owid.scss @@ -1005,6 +1005,11 @@ html:not(.js) { .ChartsIndexPage #explorers-section { padding-bottom: $vertical-spacing; border-bottom: 1px solid $blue-20; + .charts-index-page__explorer-subtitle { + @include body-3-medium-italic; + margin-top: 0; + margin-bottom: 4px; + } } .NotFoundPage > main { From adba983fa0fb2a76c7046a8ec4e68fbfd11fe4e1 Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Fri, 9 Feb 2024 21:52:43 +0000 Subject: [PATCH 02/14] =?UTF-8?q?=F0=9F=8E=89=20tags=20for=20explorers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adminSiteClient/AdminApp.tsx | 6 ++ adminSiteClient/AdminSidebar.tsx | 7 ++ adminSiteClient/ExplorerTagsPage.tsx | 94 ++++++++++++++++++++ adminSiteServer/adminRouter.tsx | 36 ++++++++ adminSiteServer/apiRouter.ts | 26 ++++++ db/migration/1707502831161-ExplorersXTags.ts | 23 +++++ explorer/ExplorerConstants.ts | 2 + 7 files changed, 194 insertions(+) create mode 100644 adminSiteClient/ExplorerTagsPage.tsx create mode 100644 db/migration/1707502831161-ExplorersXTags.ts diff --git a/adminSiteClient/AdminApp.tsx b/adminSiteClient/AdminApp.tsx index e7a579b9bf0..0b3ca18a193 100644 --- a/adminSiteClient/AdminApp.tsx +++ b/adminSiteClient/AdminApp.tsx @@ -20,6 +20,7 @@ import { TestIndexPage } from "./TestIndexPage.js" import { NotFoundPage } from "./NotFoundPage.js" import { PostEditorPage } from "./PostEditorPage.js" import { DeployStatusPage } from "./DeployStatusPage.js" +import { ExplorerTagsPage } from "./ExplorerTagsPage.js" import { SuggestedChartRevisionApproverPage } from "./SuggestedChartRevisionApproverPage.js" import { SuggestedChartRevisionListPage } from "./SuggestedChartRevisionListPage.js" import { SuggestedChartRevisionImportPage } from "./SuggestedChartRevisionImportPage.js" @@ -303,6 +304,11 @@ export class AdminApp extends React.Component<{ path="/deploys" component={DeployStatusPage} /> + ( Explorers +
      +
    • + + Explorer Tags + +
    • +
  • DATA
  • diff --git a/adminSiteClient/ExplorerTagsPage.tsx b/adminSiteClient/ExplorerTagsPage.tsx new file mode 100644 index 00000000000..a1603cb220a --- /dev/null +++ b/adminSiteClient/ExplorerTagsPage.tsx @@ -0,0 +1,94 @@ +import React from "react" +import { observer } from "mobx-react" +import { observable, runInAction } from "mobx" + +import { AdminLayout } from "./AdminLayout.js" +import { AdminAppContext, AdminAppContextType } from "./AdminAppContext.js" +import { DbChartTagJoin } from "@ourworldindata/utils" +import { GetAllExplorersTagsRoute } from "../explorer/ExplorerConstants.js" +import { EditableTags } from "./EditableTags.js" + +type ExplorerWithTags = { + slug: string + tags: DbChartTagJoin[] +} + +@observer +export class ExplorerTagsPage extends React.Component { + static contextType = AdminAppContext + context!: AdminAppContextType + @observable explorers: ExplorerWithTags[] = [] + @observable tags: DbChartTagJoin[] = [] + + componentDidMount() { + this.getData() + } + + render() { + return ( + +
    +

    Explorer tags

    +

    + Explorer data is currently stored in Git. This is why + we're managing these tags (which are stored in the + database) on a separate page. +

    + + + + + + + + + {this.explorers.map((explorer) => ( + + + + + ))} + +
    ExplorerTags
    {explorer.slug} + { + this.saveTags( + explorer.slug, + tags + ) + }} + /> +
    +
    +
    + ) + } + + async getData() { + const { tags } = await this.context.admin.getJSON("/api/tags.json") + runInAction(() => { + this.tags = tags + }) + const json = (await this.context.admin.getJSON( + GetAllExplorersTagsRoute + )) as { + explorers: ExplorerWithTags[] + } + runInAction(() => { + this.explorers = json.explorers + }) + } + + async saveTags(slug: string, tags: DbChartTagJoin[]) { + const tagIds = tags.map((tag) => tag.id) + await this.context.admin.requestJSON( + `/api/explorer/${slug}/tags`, + { + tagIds, + }, + "POST" + ) + } +} diff --git a/adminSiteServer/adminRouter.tsx b/adminSiteServer/adminRouter.tsx index b144559abd8..0286513a83a 100644 --- a/adminSiteServer/adminRouter.tsx +++ b/adminSiteServer/adminRouter.tsx @@ -30,6 +30,7 @@ import { DefaultNewExplorerSlug, EXPLORERS_PREVIEW_ROUTE, GetAllExplorersRoute, + GetAllExplorersTagsRoute, } from "../explorer/ExplorerConstants.js" import { ExplorerProgram, @@ -253,6 +254,41 @@ adminRouter.get(`/${GetAllExplorersRoute}`, async (req, res) => { res.send(await explorerAdminServer.getAllExplorersCommand()) }) +adminRouter.get(`/${GetAllExplorersTagsRoute}`, async (req, res) => { + const explorers = await db + .knexRaw( + `SELECT + e.slug, + CASE + WHEN COUNT(t.id) = 0 THEN JSON_ARRAY() + ELSE JSON_ARRAYAGG(JSON_OBJECT('name', t.name, 'id', t.id)) + END AS tags + FROM + explorers e + LEFT JOIN explorers_x_tags ext ON + e.slug = ext.explorerSlug + LEFT JOIN tags t ON + ext.tagId = t.id + WHERE + e.isPublished = 1 + GROUP BY + e.slug`, + db.knexInstance() + ) + .then((result) => { + return result.map((row: any) => { + return { + slug: row.slug, + tags: JSON.parse(row.tags), + } + }) + }) + + res.send({ + explorers, + }) +}) + adminRouter.get(`/${EXPLORERS_PREVIEW_ROUTE}/:slug`, async (req, res) => { const slug = slugify(req.params.slug) const filename = slug + EXPLORER_FILE_SUFFIX diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index 9184c5688ba..e3b7a303436 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -2609,4 +2609,30 @@ apiRouter.get( } ) +apiRouter.post("/explorer/:slug/tags", async (req: Request, res: Response) => { + const { slug } = req.params + const { tagIds } = req.body + const explorer = await db.knexRaw( + `SELECT * FROM explorers WHERE slug = ?`, + db.knexInstance(), + [slug] + ) + if (!explorer) + throw new JsonError(`No explorer found for slug ${slug}`, 404) + + db.knexInstance().transaction(async (t) => { + await t.raw(`DELETE FROM explorers_x_tags WHERE explorerSlug = ?`, [ + slug, + ]) + for (const tagId of tagIds) { + await t.raw( + `INSERT INTO explorers_x_tags (explorerSlug, tagId) VALUES (?, ?)`, + [slug, tagId] + ) + } + }) + + return { success: true } +}) + export { apiRouter } diff --git a/db/migration/1707502831161-ExplorersXTags.ts b/db/migration/1707502831161-ExplorersXTags.ts new file mode 100644 index 00000000000..d0a19b60a1d --- /dev/null +++ b/db/migration/1707502831161-ExplorersXTags.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class ExplorersXTags1707502831161 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + queryRunner.query(` + CREATE TABLE explorers_x_tags ( + id INT AUTO_INCREMENT PRIMARY KEY, + explorerSlug VARCHAR(150) NOT NULL, + tagId INT NOT NULL, + UNIQUE KEY (explorerSlug, tagId), + FOREIGN KEY (explorerSlug) REFERENCES explorers(slug), + FOREIGN KEY (tagId) REFERENCES tags(id) + ); + + `) + } + + public async down(queryRunner: QueryRunner): Promise { + queryRunner.query(` + DROP TABLE IF EXISTS explorers_x_tags; + `) + } +} diff --git a/explorer/ExplorerConstants.ts b/explorer/ExplorerConstants.ts index dfb6510c465..ec54c6e51d9 100644 --- a/explorer/ExplorerConstants.ts +++ b/explorer/ExplorerConstants.ts @@ -72,6 +72,8 @@ export const ExplorerContainerId = "ExplorerContainer" export const GetAllExplorersRoute = "allExplorers.json" +export const GetAllExplorersTagsRoute = "allExplorersTags.json" + export const EXPLORERS_ROUTE_FOLDER = "explorers" // Url path: http://owid.org/{explorers} export const EXPLORERS_GIT_CMS_FOLDER = "explorers" // Disk path: /home/owid/git-content/{explorers} export const EXPLORERS_PREVIEW_ROUTE = `${EXPLORERS_ROUTE_FOLDER}/preview` From 727653b4794af4defa18408a47e1e5bf2ba3681e Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Sat, 10 Feb 2024 00:05:33 +0000 Subject: [PATCH 03/14] =?UTF-8?q?=F0=9F=8E=89=20explorer=20tiles=20compone?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/GdocBase.ts | 13 ++++ db/model/Gdoc/enrichedToMarkdown.ts | 1 + db/model/Gdoc/enrichedToRaw.ts | 7 ++ db/model/Gdoc/exampleEnrichedBlocks.ts | 11 ++++ db/model/Gdoc/rawToArchie.ts | 18 +++++ db/model/Gdoc/rawToEnriched.ts | 47 ++++++++++++- .../types/src/gdocTypes/ArchieMlComponents.ts | 18 +++++ packages/@ourworldindata/types/src/index.ts | 2 + packages/@ourworldindata/utils/src/Util.ts | 3 +- public/explorer-thumbnail.webp | Bin 0 -> 40772 bytes site/gdocs/components/ArticleBlock.tsx | 10 ++- site/gdocs/components/ExplorerTiles.scss | 59 +++++++++++++++++ site/gdocs/components/ExplorerTiles.tsx | 62 ++++++++++++++++++ site/owid.scss | 1 + 14 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 public/explorer-thumbnail.webp create mode 100644 site/gdocs/components/ExplorerTiles.scss create mode 100644 site/gdocs/components/ExplorerTiles.tsx diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 8b7d9708cb8..98a6f23b59b 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -446,6 +446,19 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { return links }) + .with( + { + type: "explorer-tiles", + }, + (explorerTiles) => + explorerTiles.explorers.map(({ url }) => + Link.createFromUrl({ + url, + source: this, + componentType: "explorer-tiles", + }) + ) + ) .with( { type: "research-and-writing", diff --git a/db/model/Gdoc/enrichedToMarkdown.ts b/db/model/Gdoc/enrichedToMarkdown.ts index 1106b9d75af..86cc8aa2319 100644 --- a/db/model/Gdoc/enrichedToMarkdown.ts +++ b/db/model/Gdoc/enrichedToMarkdown.ts @@ -263,6 +263,7 @@ ${links}` }) return "\n" + rows.join("\n") // markdown tables need a leading empty line }) + .with({ type: "explorer-tiles" }, () => undefined) // Note: dropped .with({ type: "blockquote" }, (b): string | undefined => { const text = excludeNullish( b.text.map((text) => diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 5eef3e6bb02..9716631f298 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -36,6 +36,7 @@ import { RawBlockVideo, RawBlockTable, RawBlockBlockquote, + RawBlockExplorerTiles, } from "@ourworldindata/utils" import { spanToHtmlString } from "./gdocUtils.js" import { match, P } from "ts-pattern" @@ -421,6 +422,12 @@ export function enrichedBlockToRawBlock( }, } }) + .with({ type: "explorer-tiles" }, (b): RawBlockExplorerTiles => { + return { + type: "explorer-tiles", + value: b, + } + }) .with({ type: "blockquote" }, (b): RawBlockBlockquote => { return { type: "blockquote", diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index a164992f511..0a8ed27f731 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -529,6 +529,17 @@ export const enrichedBlockExamples: Record< ], parseErrors: [], }, + ["explorer-tiles"]: { + type: "explorer-tiles", + title: "Explore the data", + subtitle: + "Our explorers show even more data than our normal visualizations.", + explorers: [ + { url: "https://ourworldindata.org/explorers/energy" }, + { url: "https://ourworldindata.org/explorers/poverty-explorer" }, + ], + parseErrors: [], + }, blockquote: { type: "blockquote", text: [enrichedBlockText], diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index 28210b86be4..c35fefe9e8d 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -36,6 +36,7 @@ import { RawBlockTable, RawBlockTableRow, RawBlockBlockquote, + RawBlockExplorerTiles, } from "@ourworldindata/utils" import { match } from "ts-pattern" @@ -588,6 +589,22 @@ function* rawBlockRowToArchieMLString( yield "{}" } +function* rawBlockExplorerTilesToArchieMLString( + block: RawBlockExplorerTiles +): Generator { + yield "{.explorer-tiles}" + yield* propertyToArchieMLString("title", block.value) + yield* propertyToArchieMLString("subtitle", block.value) + if (block.value.explorers) { + yield "[.explorers]" + for (const explorer of block.value.explorers) { + yield* propertyToArchieMLString("url", explorer) + } + yield "[]" + } + yield "{}" +} + function* rawBlockBlockquoteToArchieMLString( blockquote: RawBlockBlockquote ): Generator { @@ -683,6 +700,7 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator( .with({ type: "entry-summary" }, rawBlockEntrySummaryToArchieMLString) .with({ type: "table" }, rawBlockTableToArchieMLString) .with({ type: "table-row" }, rawBlockRowToArchieMLString) + .with({ type: "explorer-tiles" }, rawBlockExplorerTilesToArchieMLString) .with({ type: "blockquote" }, rawBlockBlockquoteToArchieMLString) .exhaustive() yield* content diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index bd497e098bd..ec3d1f8d785 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -13,6 +13,7 @@ import { EnrichedBlockHorizontalRule, EnrichedBlockHtml, EnrichedBlockImage, + EnrichedBlockExplorerTiles, EnrichedBlockVideo, EnrichedBlockKeyInsights, EnrichedBlockList, @@ -110,8 +111,9 @@ import { RawBlockBlockquote, EnrichedBlockBlockquote, traverseEnrichedSpan, + RawBlockExplorerTiles, } from "@ourworldindata/utils" -import { checkIsInternalLink } from "@ourworldindata/components" +import { checkIsInternalLink, getLinkType } from "@ourworldindata/components" import { extractUrl, getTitleSupertitleFromHeadingText, @@ -193,6 +195,7 @@ export function parseRawBlocksToEnrichedBlocks( .with({ type: "expandable-paragraph" }, parseExpandableParagraph) .with({ type: "align" }, parseAlign) .with({ type: "entry-summary" }, parseEntrySummary) + .with({ type: "explorer-tiles" }, parseExplorerTiles) .with({ type: "table" }, parseTable) .exhaustive() } @@ -1795,6 +1798,48 @@ function parseEntrySummary( } } +function parseExplorerTiles( + raw: RawBlockExplorerTiles +): EnrichedBlockExplorerTiles { + function createError(error: ParseError): EnrichedBlockExplorerTiles { + return { + type: "explorer-tiles", + title: "", + subtitle: "", + explorers: [], + parseErrors: [error], + } + } + + if (!raw.value.title) + return createError({ message: "Explorer tiles missing title" }) + + if (!raw.value.subtitle) + return createError({ message: "Explorer tiles missing subtitle" }) + + if (!raw.value.explorers || !raw.value.explorers.length) + return createError({ message: "Explorer tiles missing explorers" }) + + const parsedExplorerUrls: { url: string }[] = [] + for (const explorer of raw.value.explorers) { + const url = extractUrl(explorer.url) + if (getLinkType(url) !== "explorer") { + return createError({ + message: `Explorer tiles contains a non-explorer URL: ${url}`, + }) + } + parsedExplorerUrls.push({ url }) + } + + return { + type: "explorer-tiles", + title: raw.value.title, + subtitle: raw.value.subtitle, + explorers: parsedExplorerUrls, + parseErrors: [], + } +} + export function parseRefs({ refs, refsByFirstAppearance, diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index ef32a56a413..ea703e0d58f 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -675,6 +675,22 @@ export type EnrichedBlockBlockquote = { citation?: string } & EnrichedBlockWithParseErrors +export type RawBlockExplorerTiles = { + type: "explorer-tiles" + value: { + title?: string + subtitle?: string + explorers?: { url: string }[] + } +} + +export type EnrichedBlockExplorerTiles = { + type: "explorer-tiles" + title: string + subtitle: string + explorers: { url: string }[] +} & EnrichedBlockWithParseErrors + export type Ref = { id: string // Can be -1 @@ -694,6 +710,7 @@ export type OwidRawGdocBlock = | RawBlockChart | RawBlockScroller | RawBlockChartStory + | RawBlockExplorerTiles | RawBlockImage | RawBlockVideo | RawBlockList @@ -732,6 +749,7 @@ export type OwidEnrichedGdocBlock = | EnrichedBlockChart | EnrichedBlockScroller | EnrichedBlockChartStory + | EnrichedBlockExplorerTiles | EnrichedBlockImage | EnrichedBlockVideo | EnrichedBlockList diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index bc64d838285..047aead918a 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -166,6 +166,7 @@ export { type RawBlockChartStory, type RawBlockChartValue, type RawBlockExpandableParagraph, + type RawBlockExplorerTiles, type RawBlockGraySection, type RawBlockHeading, type RawBlockHorizontalRule, @@ -211,6 +212,7 @@ export { type EnrichedBlockChart, type EnrichedBlockChartStory, type EnrichedBlockExpandableParagraph, + type EnrichedBlockExplorerTiles, type EnrichedBlockGraySection, type EnrichedBlockHeading, type EnrichedBlockHorizontalRule, diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index e06849c8a84..c86fd33ae19 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1643,7 +1643,8 @@ export function traverseEnrichedBlocks( "sdg-toc", "topic-page-intro", "all-charts", - "entry-summary" + "entry-summary", + "explorer-tiles" ), }, callback diff --git a/public/explorer-thumbnail.webp b/public/explorer-thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..f416d77d3587f9dd8b049749f59341a143ee891a GIT binary patch literal 40772 zcmV(>K-j-hNk&FIp8xrsXJ+pjh=>W` zWAi?N)C)Mv3rVQ%)T7EQNuUadUP2YnC8H*^Wj!!!TycLPvHeJZ+SR9OPvOUl^hbV_ znLEHe{`A7mYTImpLT}rf%Y_Y?rF|-#b1ojA*0wqIiI2}yTv?w>v3C!)%bAHKL_~?1 z8un@FBO<8rkQkFTMrvjPCLy&?Alf$a73G+m97k!dL%gLAKqt%>^CF zs+5TG@ERUK=LgTVZL1|)`72a=ce%S;ce%T@TXjimNn6!FXU=cUHP>3TX6LwXNl)q; z;S#yKMY;ut9qsgJhPy+LflKOAo6#Ze67@nOcXuaj9Gc$%Td*w)?=dsnGk55d?r}0C z+L|4Y_Naq9TSDC3-O?^jgu5R1l_9lphl#s;rMMo&-L=Q)iMz+XaknEl^kBO<^JRrw?Akp>g}4)*2X~@s+#SY{aBF)ZNo(7-ZJXns zwf=5**_C2a%9o^4GqbiK2mn|%>Z~d$k{any3^(8Z-=b=^+J9J(ZMALAO^*xlNPr&j z9xC~i+{cA<7*CdDY3d8;sUq8Qw+S{3FFGe;(tR4OVHJtN7c zasY8-Gxe!3bE2Z?2pBXoCvD?6Rn!FlSd&`lD7%M*-AS7u5(a6}-5eV04bQCyxIf!{ z+vsi(I<{@Awv{iqR)CDN_g>qU&vomcPzhohkvl~FhXhG-+h$kJ3FRUQ;4(nH?Ck%4 z?vnFV2FK^jnPXc!M{{g*X3rTPbMVmr_{}}{!OXVNV%pfYZQEY&-mzP&wQaRFO51Di zwz1jTwmn=a=b5+Opwr*oqJ5{c2w~hU6VzhRqjh$(O zY2#$t*j8)X&a|=3*3R0yPhi_=SHIX!<|)K>%I#vUQrnvAqI27}vaP`>p>x;Hp>=mB z@{irRqzZTIiMuC5a%|hGtrWfQg1g@1XX}x@FPeWa1VGX0zbweM+qOB!y#-PPf_Qj< z`VXGz|5wYF@>X28boXhOy?b}>-QHWe_wK#>zW;Z!*4k_R*MF_GYr-j?2xsI(c<4@a zBlJeN)7YsR?Ff&?edYz+2zR#=^~D`dgryVVaB#XN!l5Iy>yW#QzXEjO&aS#qzXjYP zizs)uXrdcor}2@9h-eh;!W~Y8OX}XZJJF5CBRq9?hg)@`t~$|;zBSQFPt}P|RafDu zySqblGu&Y`LK}x@;gGt+NQ~3ixTd(n#@%j&E^ORqqKPKDAr9kFW4EfSZZx`TT%wJG zECB$NR1&2_rR;7I?5@4S-v9S8jDTH$fHDjKmaU>^I;oX~khK4@VB2lm=pt=h;U<%4 zguH)vq-|R!OU_o5T#3a=MVW<(g~^pwS$Ouph4cS^e)%WcBW9M#lE*1#mc`60kC`o5 z%w#dskeFr3lw#;aWDHs&k6{N?B8%lQb4JKz=@)hM62#0tnJkZ)@n``vUM)b(6*H?4 zGc&Wiv8Y0E)G5w5euH^>5AxdT5;G5pnVA{w4((tbQ_N&BGk7cwvoB_@kgp~54%!qm zv-BBaUO#(%AoKI#KF@^q z@PIZS?whpycrMlS@vKS(=1Pzyo8G4pn3U6*(1z=EcetVDY)9zhl@__qz%UM#) zEYgQNlD3Fx$<>KAXm3t3w$@Z4s7jMla7kySrKD|tlO)<#&hZ^>W@theq~vI{(I?uh zc~y|qu;f%$QbA~mH*<2m7~zpIlH$NpZ)Um_VDWLE<7MExD43ZAVoW-I;iEtw)!1V%D2X;gPGh>q$~t z`QLEjfmVVnIep)lq$OOzfz3jFPD${ve!x54LjyO7fE0Km$k9l#l`|(KaaKz6T+W)6 z15+MlOJcACMuqpa$@N+;6o>9iQZR`(0!y+ctY+ow|QgS8CP^n5Jq1AV; zQKi)~N4uGl$}VSpY9=!JRlOYf(OSt}a6Qom-RVpUEEAQDUbSf7<-ygfsgx9gy!=`S zTD!oiIfwq0HxK%6PEv$JC9S>i_;pV2Te;SD?m7>9Ys__zhts=OQ`z2^oNr5G-Dz62 z+beNBJxalg3Lo6B46af6;SMPzWvUA{iLTQ|8LBMWQ*3L_V)o!hQm)BJHutslbzyC( z>jqj(zjW_mpygV?Dw$?TVv;LXap~8=IHSQc0~}0;v}sFD?<{SgWxL4|t@`Wj?M-M) z19oxLy{L&JeW;YFi|dp=g-q+Ax663X)}5A#|4uSB=R~8xhoI9pks;{I0x4Z*SINh7 z=>YAK&T`iGo6}oZ-vhVN%7Qduwg#N5n+!}&BPrG_s9qVuo~M~^V9R#W1EV(LxZdw(njQ zCc3jGJaDxnpSsExv8MS>rhl`QfH`fs_WK-tqUB3aFTQgaR94cE1`jHA_qDYti33`7 zZyNI+SbtoSpv|PEFcDZe<0in8QnI3D=FQkgP$9qjXqug5cphUnD72YV5;u zx^?5YHVLdd+S3UEt=Vna5J94;6XnBwJ8ccQXf5^CfI!ObmW?JFBo9tnF z?q#)y;A{GJkV%OLQV3f*>CxOuUVj;Tfi@bSXfWx~k+}||&9*ddVF<|3V8WA5ir#5y z&|HTm*@^>61rd13F{L*peyt!W+bx?^_8y+IXn_a3xeHRjHrSHo>!-Hlbomk5Ll@|d zS1qHHudPW!Mu829Vi>6Kz7Ac&&>d2ATJ&?qO94a{*K6%SMok2N+g@RH*fl{4+5Fl? z8E8Ys8d%Smx%}u%alKN0bIu4Cte!cgDig&n*|4_ryxQ%XE?WB6x3nr^|L`IuSAk6e zt)|{&Fm043Ne)3Y;%`&Y!J8!)-hZK`7dM3zS^cm|WdkZBGR#{`o)W@33GB}w8w1U+ z!qn*X+(5=BJf4CCb7=+Dzj}_Cyo~0iuIho>lQtH}U^*CIecJh%SR&IEQq0lbE2v&H zI1G4mTftj46~tBDgDZQwFMYp%|CSUFCDB=uBbbe^Qx zIX-{lNK)vUhbb{9CcVCFJizN)9WF(xtWpbtFgG5!Hbw1<=yncx@Izp&9oEQvwixO> zYPOuU<+Oav3}}hRz{==BE1=Tx1#jk%iRD4%KvU^$2OBS@qrnt5KzDFM)9z*uVTn(E2^|O(O&ON-9&IwY}{B1)mn%~ zt!QsZ5^g`Yq`nQ3q((r}82U=S zZDBnF^Oj0-xeaelkXr@y-RHUi?+t?sRuTx(NGHr<^o8ztD0nTWW-qLAtB8edaBi@gSliuL5mX)?VZWxtgQ-QidGkd9jm6Wo<)KXxWAfj+U%6x zExhScfQ-Eh?8$TB+PrmZskw1c5k}*!o1I)B#N4jhX2BP;%MB>PXyU;*HV0QH(@z*v zJz>{8l!tXA-o$y3@#SAu>yp~$ZEKAK=*7b zQ<~GdB`XuIndEB8jR~{U94%Q%%ki{&CPbf8_Iw83-f-L?(p7?64Q``K zCAoT-+-KG49Nsk(9xiUyI&<70kAA@QI2}n)&}*GmdVP3EiZ_6c1($5kK`=%Obcd8Y zNlf?yF!bfU0Fd+rp>m1#@P;XkN_n$^BU(D7GfVcoMgHm0#J^BQthM7}QakF$1?Oq7 zYA#Fi$sj^(=q5P!_c8TyCE6!1Ig#X>?3!BteRu>a;03_LR&Am^|HHYQH5;los{qL< zRO2Q+bE{PP-CR4{JD{yG8O<@RFj-Zg<*1EgMWJ;6GjF~K^Ah7zq~c(?%1 z0DjB2e@j|OoVH4nR)Lz!Lu+FFJeJ-rPO|{Pp@+gyy5!7~rFDC2ut}Jx`2UdoT_QN& zn6|0s`99iQ{!I|tcyMjOjR&{hAKLm#+qNEU+y8Uze}->=`rn^mbdO6Qj_e13r=r8+ zfUa7^FgCY%=gk6@Ms>U~9?1$2+R+4FP6y!CY%BKzK6RTwmiEr)Y+(|u!zLm4L~EH_ z%WV6ni^Y1#bf%7YQ-E!l7m5qu*yxrLtgcKwbygsgn?sl-fglqkxO9R^JS1CT<3XnS zKpt>#Ndo}#9!>JXNE52^6j&Ho^6`{P}ZtV!148*IR*ZAx0pxBysA zUn9H~)pm?yfuGy=)S8>%UWzQRnx&ztO%2Vv*N9{EiVs16ZAU#w!c-w_TTX$ck}k2c zzb{n~l_bn7$yrQA#PsSD*8(SaLUpuMa?N+wqw zI$8>B_RtQr3Aara~muAQ48$>K}vGF52< zgjrAZ#mSQY21zM&T4PQ75nQ^1;cTn$IO>*?bhuKDI~_#MBbs+_ntx&zBh%VIqRm^r zuOW#h8YLszvm7W=1gh7wN>Z=fDZ0*a-#zry!BW=RV`X)aXNdl0KIl zppdZ&>MYi&R@ZnVFjHu+LK@Q9DaMeUoI&OaLtK2|j-*^Ot>5_c?S^rEX}$|2T`S{G%F`&1sm7fFLZ%-7 zGaUq>V6X~`gDa2L7lssA4#h^YS6K$jokf~rKX?=U*-X=t6lgOA{cvBrZOQ5~7OtLP z_!HvaW9JC8 zcfXUuBWHTsF40LDtdeE_g%nK6cq|Wi$HZu8`xkql zGU9G8)Mkei)T9OgtfvgYcELn6-sh=P>=d7ASAMWJH&0!+Ul@}f3309}sa}vJVcK3A zc`5M$taNyl?c{q|3=7`PY894omr@eJ{AUw|1!R++RCT5eqrC%xnSDjxwWw;2UDnw$ zGB_BCG{xe$PD1q31V(#IZpCannrLqbT0yMf(zoBf0130vxprO|7nS9${9_{A3q-|`P>>v~SH}>Jqz{6b5dbOc=5ad!RAb)jwAAH*ObXW6W zS5V9h)e7IOI^DO`6Iwes(towjNlJBUaMWsDSZ#jh9*fE&Wl@R^onIutQazzN$*F(H z3rp=a`E>SXY?bmu-2BQ~{ZaA2k1^$l-OFa~=sgl^y^l;a`XGxw+z6 zzI;7^P@NvwE3X#Lq@a0V2O(qat$Dy~YAX|#%XMvZnJH>02%b|)`C{-qo6JuG5}9^2j(6%0F2h8NB+GH7J3M{gx+}!MK082 zW)uLlD8pWbwQp=J%!(vs6(vbgQSvK@>6Lz{UvtWs27}Tc8eQ3D;pHIirsCp=Af?yg zsV{2`eaj}c3=4+}*B1xuI7h4tb&Z;JHb>&fQ1@DLa4eSID1z%h`&bCH$vKrOP6}U@ zdBwhW@du^>04El%gJVE$8E!jHR=bXM))y5ck1UME$f1?PL2;zu!<|GujBp~%R^(uE z#DVVQ{=h{#@ZJK7yj7o3K@bT{b3L02#ZDUq%6`aeIu-JG3mXxx7Na7`_?~XIQYu-S z=F@$os%1b3C=_|{hL|PJG;ap!3kzPgTJHVaLEd4n_E*NjPE0gV*cy!7Q;iHM@T9|a zvSTW13PRy|BK3+IZ9tBp8J0fSa1vA{?^dDZ-zr&sVHTpxkM&}^k6(xug14V>>qm)y_rE#5rM$*cS`oQ92&R_{;PZp$u_G)-%cF-kt^sJU_bl%ilsLv0< zceC}Sz-JNlMA=V(K%1y?d%Ls)0_hSv^32carV<-usp)?UDUAOhj7ZEvYMQ93_ z++=Hn2SB9<*A8HDVx>09MCV1xW0ijOYkz$Eha(Xx{_#f155t>JQfZUaOfnH=r zrqEvIpz=~#uDN2$UbkiGAFa5Da*TY+GB`6qRd!N_p!WDLz@NWd4JwuUdxEeMh`bfDGjhL5%3XGG&;C-1vlG zxHfXFGyt`kK}vxO-~Q_#i)E76(RC^8@~587tjcO7Hgn@%JeW+orGziWNN2d3|E$$w z2XKlN6u;z1%g73xrpixTQpc?F7_qnJ`_}xh4w@UQ`i2OT(B^NdtDWM)4?Hq^(&L?X zqS{;X#V-c>n_61r0gtZZF104zH>3f5E@?1Z);o)EJgcE*Drl~o)=>!=k$UWW80L`E z!SGOobRPWMpa1Mzi|%wDez&N@Sso(Dd$`#j;S+kIQIaL zzi|G&shd#&Sj*TRWK3{d zQ56?oe{;fRLMxASzb=!l|0$ErGmy%GHvYz@r3j)cFtg1agq+68G=Q4yY$v0N@A$QE z{@LQC5tuH4>3s-q@Nf^aG$3UOHn#gnRW#cK`lbOLk5C#xRnD?3Zheil_3W490zTtw zbO~~cs;XMeYKx+~t!o%Dz>(4AlUiH+Qv-mB?U9Ycbf0*s>^Wz)78NSRn~QXih1hkd zq9pfJN_?9L0rETLKKNjyeq#2JROK@ex1QJ=gzny^UXs-mAM8%97<3s~r@SrMh z@w69!kzyZ7Z}C>7QP>Y0Qc%>V0lF9(Mq`q0P*nvw21WH~s z*)rXs-5*?gK9g>*7Y)Ss_b-1FGy%t!AF{mtvkD%Xaa-Gz76vp1}%Qn3&{G zi)7`pH{iabWqF&a#&r4qyh+!{gVQhs-vMR`)3$Rgn;k52ZG?aUsZ`2oCv>A=XsS(> zk^$Wb88LJ)o%#c7Y8}AtQcjmN^}B!N>$J5MyWoWpAgqGSTY_n*Xe7 z1e7V=KvQ#(WD=EqarvJd5m2dN)I+OW5v20@gD6Ldj?J{Gy#({%58~4{K3SbjE4=w| z2M{&^$2UC7t#L^?^5d=$(wT987Zby0(zUQ^t5_fkGPtLttqg0Q_@n z><(KBO`n)xoqCdelnc#SBx@KQyG9D8eQDL1E~#?{b9RdJ9=J2GxB zcV!w&@Zb@Gg)r-B`S|5;d`Eky*Z9yDI`N+HHJb#ViH?$ZEsfVkMV$pszu(En)8dC> zuGH3haBToumv%4BqXk5N%ELN{;qEo0YG)aO?v`qtLp{oFm({FTl2FXKbnwp3)FZR){4JLKl_Kn(?X|e>Nm@dJM&-L%`c9l5odJ!pAkwy)t{u@5|#&V4KaFr?FiAY=ATH&Ph2#ETQXtg3y)HlC zpV*+{;mRKO3));ifnup$g z(*xY?s9Db+{%)mF=45hoG&Sa-(&*x&JJ#7@7@c651ny^uGx{Y9Gu~2n7y4ISPRaVTrO+`Y zExSXT$!@<>jb|yk63FsiFnBb*Y0=tQ{ODF^H_$v@Imp-89tbSV?7WlHAGalV?IQWq ztszY6ODf`>h*fY?A<>GW>0mmPa|G$}^Avrw0ts)J?B zqw?`)Xloe^!3!v_kU|elS9ieeEV8Z`85d0q!Ij+-B^4)l28+eGZbu4pA2rNY*eNP> z6pwSRa|}^AAlQaRWTobggjTC6V92G-QTp(UxaUhk1G{HiRl}P^h=0#HpRMI2oxGq) z#Pqgp+!%LjsswcQpi3J$X90&2$F{;Y&(&NnIrOBiF( zl=C?`(K@IpS(#@B-)If!I+c9IL+g_;rC1nr2R&ho&(*m(LL0fsDz5$d(yh&eF3M0w z^hcD;!`)ARH*ZW{b)38)#EZ>p85wG`Q4gK})T_K+3JZKnj)Oz2pr}2)U9dFft}#deG8&o;Y}8 zA^B+~2rXT;z>*y*^$UvK!U5`L<82cG9&HXQf#l=aGFh09vZCDkLflMtNNVrATM=bm zB$d|7+2***6?B!i$mU5BAGp^PpClf&>Wr`M=>O_4AeJ-N*iXGdQ1mz z3cMLZ*7`)(gWZDsw?uhkacB^1dK2y$gcP11ux1o!<}u8J+T2iX;S_&C$dVg)$nn+3 zrZ#H}05qvwY*YQi$hvjhiSzWHv)6rha_R0zXu-do$%JBY1SafA4i7Z zR_7MP|CItSJ_p{UEd9&Wzf3)5fS3+Ks_~?QDD)yp2Z2kjv{5YQV=WKDPb0YH@CCk*=jR=6mU279uQkhm4SSY zqqbG)GjJz^YM?wUX)UuoS?&}N87llb}J$-vc zyUJ=yCdwf`!-7}qj~4l;FjSUlJ&c7QC!>-k{lKS>=x ztKDBUSKgDz43w?7%7W?&%N&azqARt2qa@)!B6~Y}aLuYIh=nqkM*oBqDhZOgKoR2p zA!*Z6Ch5mURqO*spSC1|gG&0Ya|j|SH)tg zWf1YodpsTwJJ~`Dbl)Il_vxxa3>qoBkia%2&}rOTHs_y&rK4Cim4^!Z-K@1sw%tI;`h&z_KdXYO?n1^k+sQg&AMcG zTRs{snWn`=dx5h{V6`M+sBaC1xO{mHc+{y=VSS=WHE!Wt&^|S92DZ-1f%Za4M6Rv8 z(^GLeLjm1+gj1K2SSgVw=`3f{#^zZZv?v9Yh6QzhXmw!E=q(L1c-lMpc(7-`egtu& z@0-=@l1mz6Oo4R#zfyS4fW*|8Oy2W~-`;vCqlg*7g>6*1NjD%toYXga9RAssE2j5r z#8PiJt>q`g{gn(aqH(ijU(jGvax_Sj5MoAPt_B`7DWhZDhGCgpxL;z%^5P@;Iq{5@ zqxAR~cW3J@qh9cChNlGzV`!mD)$?xc^-BZ8C4~b=_SNqavxpS=^YHm z)acvfqitVkwOy$`(Qitkd+~a%QI9y=Z7@ryIwKD5eBznDy0?9alTk z2LGzzb2?x{sa>%V3$(n$DM>#mLOV}7U-Bc_KE3_fzdPh(S*Hb8z=5J^{e1gur0sJk z5!epgk)+OZZOH`DWRKBUu+yy1NAlB|H0h}iYn7kh5$#nk*W$0B3|Tdu>Yg&zr}AMZ z87M_au~|33Z1a2~;|%48O-2Cm0cyMhOH@_X%?G2E&OBJnHu1*B(&u{Z6A7!S6~B%p zdtFCNE|Y!;vlt6>>4tw|CXnR@9=yJ=V6Pvv(&aKKzY59s9 zq3?A&_*k)y_FS#rtyf;f%lp~2f*ip{p+I*LZyo+Hqk!hIk`M23Rn*ciEVc zUR;ldahNRGP|>C(j=KxKs~eFnxY2pjj|ShY%5rmx5onGxFjvD*ApBP@Dg=`ee*c!o3p6=@PrP9&}rbq(1U?YDaG&YQr1 z+?%oFtmFzQBsIkOf(WXWRj%Ux11T%i$Q+}bl0$@C$ zVlN@46W_GU744raHvXk$x-oY#+jgc~{rI*T;2UVx>m4R6P#Rhr`jbzmPLVLqui}8T zk5txI5%$fjobjf|X9-_a+OCY2L-KSMPLiyuRHsD47+p;M5`-zdv68?+8y;w3VPguC z@IMPMltxIgi>$ zk`!VQ#;>^uG@-H?6_U=9^R&#=yuR8h?9Z`}rj4bY18xwHLc%7xI_#84v{502Pyell z9ND_{b`)nS7blzS6rpuyy=IV=?2n9zfxyzVqY{Pn6JU(z$+t>>Jx!_tyVC&Qc=O@z zO#oU+yh*evLwD}yhdUNyPrqI%-lOH)=#!}>d#wL1XD@AP@Z5Yc_gW@4vt364I;YK` zQPJ342s{|OpGV$V7D_@C<^f}XQSw9Cl=oF>P#z`aJJW}`Tgf8x`2bUtz3*31Qz2E* z&>3RbYIobe|F!w`NP9e|*9`!><489nV5n<$oml9N(N^d8co<_?$qVwP!(618A2O7j^k)(kDK5M z%Xvv&Y(}3ila)j4M@T`@<&`N~I@(5k(P`cc8+MH_tY@JH5&dGL*hhk1L;!Tum!{=V zB*Q7i9X@TDpASA-q>10Ctuh+QQ_*$uC5dfPV+{gM{cMRj6TCTruiv9hpfpi+9+?}s z?#WVz1i9!lJaQ9i>l?mRk3BJ1NDo@jk`%RMbKH4YoT30f(mXH(X5Kop;QFuPyMJN` zt^I?fayDF$aG-?$1s;Ib(?;r{(c5N#sHzeBAG%&uWH{1DO3p80e~XgXnN42UszCMY zBTu>9l9$u4H@o38C%hoUGNglt9#jbkGiNUp%ztT?@qN5}b!g>itSP%_dv9ezM$kI9 zvKYzZxrj%V=3SGbqzH6O|SXHuzx$>vf z6(+Fv?~{WApH?=>mSx~&F9Lcgay*e|xZ3uAIbZcnEBR0|j2S%@a}`bMG`TO)iPnZ= zSJrN&FFBaWng}}~F4nn;w1ex@!})=kT~%7Di@h6f5{A{xb;XHMUKjU?eKGLOQ=|y& zBt1ft(RqITift+I%>Y4`yGP(G5b9b(v4iP!0xH|W#Q`nTH6V;l1T3z+y-J6G#8A8r zFZR!PwHFx;8|*@*H^C5h@_b^xzTx0GRu<}D`Uimz`kTG~;dUsJW;~lF4R?^lvi5EU zyL?Htim}UN>r}4ZIOKRtKAv%}occ|f`Zr0LpXyN}-{Z{yKxLK$F@FMrE6aG^N??FX z+-B57I+hB%R2MBnL*Bh{Ow1!jQ(Xy^I^j_OgnfOhDzyMQfv@MVy@@0S+a7Q9nvt6a z9!5>muJ=BsMZ?;&NP*WRCH;|_7Q%E+a|tgj5Swx-r>^DWNml?&gEP=m(eW-;dP0nJ zs-0CyLB~|$-Lk?A%FoKBh>n^N^ps>n|>6G8ouC z!va8sj49VE!8&*%fj#QPxhSI7UU=gHJd6||Nx=fCX`LC~FWbt$i>^Ya5%;tJ%tQNg zkq{S$rC3^x3y=Elu{!Bl*?a{WyOURokymVwY54=Hu5|BkS^W5nKM&xyr}JcNZC?T3 zq>7?YrP4HPOv{yS5CHm!6JIa-v;oj=4?ER(0xWiPy}54aO48ECg??^FmhCdii##;P zT30qfo;f@a{M9svVk;m@KaAThO*EGD%N4_}We?iKGEDC=QNA?-GK0kS3knf)|IXDG zH~{U7=#}#l@X>p$q@yuop=}_|jg?rZfy~#lQZQ9%WyFNrM)vPh=!f2rY*ZS9W-lxb zbl25oTfwbs$U#2&A9}>2XH}LfGCe#yKqp6%atpyH5x8LJ1j|9h<))$EF-kLi8r)zH*Fm#43=2~F2^1^#o%Q8^P%mvH3Z$gPV-;qM-v zlN?8bO7*%JA||kcr80yljND42g#POwoH9AM?p61hb%wR{R`6(L5p?nb0EJ!?S172z zG&ajRu8|8nW(n>5p8rKAYnR1=#_mY64@HuEP#TjPJCo&DWD7iR7j8`p@pyo(cx2gD zvf%RvXEyfY6SI z1`6A$%ZbDRx2A`okVPnuiRMw7s7*bsZS;)$tyFg;w3O1?LY;2E*8W5Iy|?0T z4V4M6vW36KrgGYXEJvWogI5hRD!l5dF1OHyRYlPuEumahCvUfgiy_un*PLT%C+i}m;0;Vb z<*fTog%9xncYW&Om3c3x!uu`d-yhu5n=N6kz9OkD@Xe(h=mVE#&6054V zD^;a?x(t`c@F4iZaPxr)o6PjpB5)IK@cLGe5amg``qm?WD$Z2!-xz23lYQ7ujnIih`Q;U8r4q&G%8b2(q?j}}h2?Ciy z>9_khaC*{k(}9KIhD2EP&omfl!a9^?SVhX6!Ide1C6ah3n+Bzt|8538c}9_oKuowC z33BD39zzCGZ?Mv{$lLwd99jWpCS2sZbICLuH<7YMoPsrMsy&m%N3l9PMtU z6)6z@#sr<1=IMqd2vU&!i<*CKNvDfEVxYCAa%70{%aSwsf0b5S5f#KkWk+%OtwB!G zv$o6?FO`Ki0As3Pu#?2%5<4{|Gq0~T2uJda`tYZH^x>a601ZH_gsUbeTuQim@!s0NV}ys;4G{a;CzCn@z+?OiN%jDw92xs31I-8e zv3`~d^Rg7XAVCuRgxf9Vs&A;^%1Wp0a?2OFczvsX1b~IaHL`czv7JfLR8>tybP=_ zwYz)Mzcd435?Gop3Rgu?$Q1gQ7{Av=#rmX|T!*dl1}z{xC@g2o|FF!}+F3nX1K>bA zI+!KnSI>N8RU-78KaTZ=Wui|$lIhuz??u>#JjJmyt$Dnu;~OsIV@Y&ggCwxBg$wP0 z@|vE7zhzyx_}u5iwPuD(CQzLuV~84+v_d5rCBUaWoC(r^2 zZoyZ+_t%L>`J>RctptNs>=N6syXb{ia#qZIUiMW!4)=-mGEJ9gtUs)g~;p-HD}! zD$7NOV%iA$UmdPTzOH0-ao1MvN&>gE(p?fUrvpN~Ov`v_03|2?s-cQEP>fWj;9y8{ z@LsVEd5h8y=-L-Cpd@;dI5PIJbvyA;(8naSd?r68`aIv*xz1I^`jOkiJNMpgmh8g^JA# zaMINzFm!V9wA(|#$q91lD`hyhTcYDav;|3516lm#s#l(1cv{wX0KhND zy)0T{iHRYK=z{YiS+>bLRQ_enmbDfHx4v*)y;o-Oxpg}KP{}rd;Zq+BCTwGBFHwtQ z07yz$?5 zoV`Q2R*hbtUB?M_^`MnXkJfFiUeML@Fx66;)NOT{VF{xEDaUgf`0GfVUTF8@&MD{r z4x7MK#zAph?j&ecuElg+a?!$=qU9rGq#>0p%PY);VD#IPh@lC`82@*;dMB+k4I~LOX3_x@4>Ax}2sNB)Fsn(T7Yc<>47;#oMJGb~m0U zl%~eEnoQ(t;LQ?W;^;ype!aVpwnxD!XJ~`3ql{+$%W; z(VvEn-w5YO5X-#Z+^b2yKe>@K;Uf^w4uV|0axW-h%SLJo!<9-*RaSI|R;2|Q3#CkJ zY%#UbE~bK(c@=HqK7i}k=4)eY+EOXj-_OOf(%;KmcX+Wn8{Yun+y-U4%Ekpm6&X&r zzHiM`c(24xgo-7Q7%j1@?$$7pQ83e_h<&K=CRN??-@}`S;VKCh*STm6*Zmu^Xx_rN zhN(bf%QA`f3~xi0)m&V%H{5O3$}K)v4RSXr;)qIO_Y_r@qgiK~*g|;mxOiUU(g)X{XFf2$xR*(hS9Jx%7iEb-a!&c7R1mw4_(mqy>^*RT| zsEqjKER&>EY07qcKabLQV|gqswDbGW!(iHSi(j)m0Of3)!glRw4sV)TYX~l`dofYE zB|XNQRJl_1&0zezKsk98?Zu*nnR=kT2OhZE=F~A0&p}&=PrD^uf0?&MF;nz#@KZ%} zzFl3;46km&k_|AZae67Z3JFq{&LE>+=?Pw}6G=Mo2y=A0uLoKW6~+*SpBkt768f$PfhU;4rGX+ z3FKNtRy<^3(fVRWU8C#s!81_a*WxS5J?`o7VQ|=3E&(PfRM3iPlQcn_8^B^Q0lpU= zBDwDZWX-^hUE<7vU8V4m;z-(Zm|cDA(%?vAwEGft8OjrIMV;}+a@*MjK1KC!2*D*7|j{{haE=NLq-MS$Oj7;VnOtv=2sA;*- zdNthS<|XOH6&`OIrQv{HXSA!&gxDg+YP)&Wt)ICHd$CLf4B5W~h7ztQ%r{3$=Vb(q zXtZI{rtf`g30@l4wl({MM`~bWgFA# zRYH#DTe`(9p#oRez1N&UA`i4AbCML!_qs2oF7SDa*7j}mcu@*E^ThBhqQ)= zNVF#&5_72Z#dD|O)S4?GxP6a#`XiM7pAD>1uTu$x2k`EcpzRHJ?gD>2m+51Q9>@}t zuC_I?RlJ8XZ5gqvw4=kxzcgk0(v23&ba?d$|LBT`(R;sNX8yY0OfTf>DnjeGJO>~^ z97|n2I?qvY71X-nn8izvE6v~zha@+cW~5ELWlqu_Mv8)t3W)ZYQn_%`nz=7{wZbKHF{DS(Tj`bzc&N$gEpP=< znoI+}n0LCA6H1v?AE6@I`@R786>`+Gv&mG_u>P4!X;~x6q(g24>oh&@!4ar%4Ddd8 zQj`^Q4$FBAza%}pO&T8OK7h$mo_s-Cdh>rh>_j8ar;^%42f?GvUJ#Q9ebsRDAAO49 zqo~nce15g|rf7@pp;0FcnJ%t3b+qSm2 zK)}D~89x6+$(hk^ayXyH!ya*8nJ#;vCs#2Cm&F@z8{9875m}oDx)VvP0uR7vmw3&? zv@UoK4%nD1{4b-Jn}r5VZ4s6HM-Dh+WX$*{Kn`z?wQ=K0(sEzv0`HU%pX;Iq55siO zpUFaUcau(vJxF20b!g3Rm>aaG(pu;n@jxb(cm~>g8(*}sF8rB8s9w_&L?aS7EV+5k$9l@{B%P&C!y$Gl{>>##edTW*DovD6c~3(TU`e$dMwaQ)eSS+6inh;2o&JV*5JJqn4wkc23vZQF=RP)7|kE8wsZMk z{Og0%;0R2E0RneM$T74dFd)G&dt{-g{+{|;&Q;pHsx1d z?UU2wf~N$O8*e5+gc!NbNg$JEzqoc0@YmGgcB%PcOd4_!6(gEg3fUAg&1@t&xKE|U zWnmJ)>slIvpXo6k(`wbk{ggS>J9vDUMqjxtnne=Eti%e5%`mSWWf|Z(o=OOh9Gj$j zDF+;%df}`!dccBdA|Te$lkd|^AmJwzdNi%jJPW4{pz&r&iC&V35Ve;Q$SSqibZP}l z$siYtSWi@GfW5-OW$5cNWvLxbv?1eG+bAw`45=jm&fF|fh-cxEelAzG#*Ta(7V&p70LlLG)`n(2D`<5@i-)U~o9W!7EgI-9}ED_|IEmo$5bKZ*^icj`!}wjNu0 z4q(AFp*pyESlQzY2NffRXZz=+Vnj}G6Dsg!1;^nICd>tPZ@PaWyvbRe@G+W#w_r;e8)d*%Zn#sj(F>l~ z={ZnRZoO4EYjgPGWF|gA4a?P69vfsPCrNNLK1vU%m7Zc>@6b*0YKn67aBqM-h6hy& z8CrRDj1F=LagtW&`!XZWzo#>?qcpqzGGD1TR|34{ZkPWCV6>O-V<$E9ARoM?P8kuU zq62LFr{f}U)ERo_0AIPAV(FCkGHKXJIY(JrvH}|#2UMav5W6hMFjn!IRqa^krr5ZV zI6M5hC3><%PGU1e6>GFDz7ICV5-{&#$5nT`_6+$&abl9{pf)uRi;tIp@p0PMP){m0 zKWW4?AZc`%^>B(Uy|ChEhS7d@q?ZGb537~_O0PL|l`nBBTl)K}-GCr92A;$*qx98b zBYLCww480heoAl>JB=m?Bi#ba#57gXogEosdW$sq1yzU~lB8!A}#i6TP;?YzYr1|8q;b#EoPAm;O_cFdXh~r47IM&O$5?etK zbdWI~Ad6FH$4~u*NubslY)TWOG=TA8CRskjUpHajiY*eaiJg>aFPin9HFC@Cdv!epj0C0qDM`gtHE)kDpC8CbcEI8Fhp)0v+41VgJz&)?NSJ2}@zCTQ|rzfIsqAJ0(nxQ(d8(=?J#E zf1TuEAU_Fkt#cS3r4g<2XeS?RsNUkxmDpJ^t$yPbt~A8Uxk#sEI=VP4sZ(8b>Vj6T!|>YAJ57ccnr?F@@ywkYctLu-yC|x7sIgRHm*-JpgVZu z?n}Xl7zJU<>YshdU6M4FZmno$=t?fY%Fl}}_+5K)#w<_G!{lVA1r)rT8k8C+yYpkS z_hV;PK3<*yPzW|_U8zlXegu@YXOLIfe`go1#Kl{eb6{}iN0g(bdcA3hclotcgeX3j zeKBUx_9B0^jYzxnB%Bg-248`?(o4^4SAksz>P@VaDJCgo;%C?Ku`Chn`kVk;_!gt^ z`n}g4y?hG@Dukt)r`U5-+m66@w3}nFM{eE8b+93i_H&$)Og<$?dpQ8(gG`{-92CT1 zx%?g93WD-G_Z0ixsa03#W%c|t-0byhq|%`3HrTLILB20D1I7l~UV2$Q3-g-oE4E0m z*}&K^Q)f32SpSm(-nuHWg6mf-G0g$`Km(>(sV#oAr8iD?{zm{t205P2G*Y*>1Zmtv z65Wu3RxSWd6|JgN<raqw82 z2gN%3v)$>nEpoTIbfy!y8fS^EnvpK*=uYhPYaeH$X5721n9fO_`@ft zkSPA_Kw;QN&)?iZT>prn;bJG2S-M>|C;Hol5A!a*_*-Hg$r>r8Gxa^apiA4R%_pj7wP)&5G?otxU}RXcfT z3O?SY>S}F?T*^RLE(3*s(>wJNE6C3o&<%R*J;FGC znE=O8YX_*&^R0aVlt0=Nm7XNNZY)?~98cAf)VCe&}x zKusCxtYAXLpG)O-coJ-_bMD#SOUmV&Q#L{H{3wDUN;krtO8bG^)rXNmMXuPd=_ zwgEyM`qek_6oywT#II?S=X;A~47ZCeb^^GvBlQtJuy%4EXx7UB3TJYQ3}LyVIfDDM zKl`ItBlpf|^%-D(1=1s2eK*_bMZHr`SNNE%0J+Y@=6FR0NvUW?Zi94iCbKXs**H`-w}X7P3D^COCz0PSZaiu zBEx7up9gUB9sbug*Jk+7`W>nPQ6m3oI=Sn&=(}M*JAzLkRi)Hplv2$J@->v`Emi*i z1ESoOTGqT6r|331$W2fZ&x;sq@nm~@Fv_i$@=*27>=%?n2NpN@KlOHC-SJV{61kA3 zPBz|bVX?~ozClrH zx|ju5eDj&XhRWEegheejy_4L8c;z!W%OJ{Q!`w)>IHc5jGt)NryMBeRJFG5h^O+$* z)I*?pmvBqe0 zT1QPr=^lq-jp}*Com$MWtT;Z(jt%l-{ruZGiH+6qt6zJcQn2HPIZTYxHCB)h)mA81 z*inTRO;aRhur0c?Gn2a_?oaHx(qOcZC!yA2p@s_JJAmByFzd;8YfZlpTOV6yut)X+ z4{0^t>d>6at#j8yyNI+4cLbufYgwxI(U&SVNFFF%8BK=vuBX&vapfklIaWkb-PM+a zDmIqfbu?Oj$-^jis8ax|o>EtAhG~lGN>F-%C~#-%PJ-&`n9}4-d4`#d#f|}sW_{9I1chI&yG#CsH^u%<%dX=wlkIv%2J_jeLoBOBjIN8pN$gV>IS^g?H{wu7T73-H{5*+P3idX7w9{Sd7{Q(cyg> z4xTGbUD9Bvh-YE38J?|vSb#{UxH_NOhZxbx%QG%8C_CSSeQW{`Ov*IjLbE3?ua*(% z<(cjEoFaF>>XC3+vV}huO9~u^b5D9|zPKt4D!$pv?6bCNDXJu|RFKg!MbHrdwuW@3yT?>Boa4pU(Kde?+4_%_s0`Are z7bR>&4x$Txg%~kX$fnfLs{i`VwgMIxJF=(eJ^8+;u7q0EuSK+0cIC#+;d`CkxLwXO zGOImNFH zzddOI80qDFyv&%7O=n0*hT2?T1RKh(1wn`!u*B533X8Kx(OVdlk^h9t4~n(L7W@*u zQ9Xx)YK@&3MhCfV#9d-3WaGaW;4!>PWX3^M*rUHGVy40WCT=S*G zSoZM|vE-7Fy0qXo#v!XaIf?V)i@X4ROLuNuz$acl`(C9EGI@tj?bpn}(#%aW`c0{N67x+%2?`r`YD8^|z77kX-Gn zbT&n4GNS%{rUhlNYRC zBX9N{1BHRbX&{zEsR^4*9!8J0x7f3%mz5X0C3<_5g%8F3nth)nuAJUtBjih!A?a(h zOMg5WavRqtzwwK8XU8XED;Ifd-z71F{D}KRf>pM^?#`-;j1YVLp8SF^lw%n87X!dn ztdZ0DX1DT?CvG1Xz7mN=Pd89+#?CEgq9TBn+iXTiLLp*t^|at=hK?)@vD z&}7+F3OHkBfKxrknC3wV`IlF1I{L>;WmWR=>`r*8J1_wAFOu-WO6ut9bOWz&1r`^X zl4#O9wU4IWyC^|*4kLgq{Ce8877od^^1L;6j!~&jn*N9rZ)iHX$c{iMX~Z=AYRZNDvO)xiXJ=B)v-; z>6LDA8#LV|MpW`RvHdKp`Gh(AkzwmyMhezbXdHHy`9g z;LSS2w`E*ODD;*(mz3Da%F7!7xLiYL?kFUX zNreN^ZoZeE0Aqt(cY5BR8;|x1S6*WhiS{eh*@3Y^7BiX?oHcXMCpL1897K6WufITZ z_6#y7kDvABV zCG}OhbLga2OK7^YoTkxBT85r-!1G=#mKfbyZqpg%-k0dDwxC-X4Pxw(!Y6O`-9!rA zZ9%UV6P6mD(x@eTQE%ORqxdeQG4OZ{vNb?+glJRXG1|{zP^mmk7CpvjYAy|B zfRh7`zft!m|COa9zD{`0LP>${o0S_~)(97lEFSj~spNWa5=Q`YoFGPTUEP@h)4m8* z9Xtcoj$e@4;s+Aj%`>S&)=%I+%Ge6NwT(DTWzpfDNlu-1lkx0UX-+Pdc4|WtCZ=u6 zPmeaFT(Vw_Pwl*naSbkEWhyzUZzuRh%X%}jEv>HK_&&DbYVQia1`5Od=Opl68OT|p z7fbZoP8~Z_dwXW(W+y$xVSlCLtIGkg@tK^wlEdx7r-r2Jg&)}Ve|)k_hrM~BEw!&nk_$8X^tLdYHh$w0w+G! z>7R-#$0YAv<&=^)>=%Ja8Tnks)MG&wK05Sz>s6W8nVk z#8T{?D?1(Fx-~Cm%2Bra$&Al4_{YuT;**lQOo&po2v8n!2xKP8ddDun2#dkSNrDJ0YQKNjcI~n; z8=h$I#z5xwSy7otuRpBwn6dl5x86^*M6R<**tfkz^2F|3yde+*LZ9ax=srAmIkz09Uvl0UK(d=nf*DdO~DHDzR^yA|cUWL zGe54Dtiytyj}0L@xp*&&Y}S=R=S7IF(1o9n>s)s9It8((3&1_v_v_C6MNHO0lTTg0 zNE?sI#XIwZQvx_(K<)P`c(0%ZQsfpocApZ}n2mH)Dv5&_P^<;o5H@+p!F;TjIaW3= z1KjrFzw4i}h1X@t2D0DsC;s0g#x(myZN%=OfhJ8{vwzW`5YKK(IvSea#mO~?(nG!s zSe%o4&@U*;q9MD16?Sga{w$JOiqc(oyMxRO5cQ;_*OF_8oq#nK0Cd|Ui=`t6Z)J`0 zDiwN^aB8J8$Y(SQ3X{qBdB8)t;={YL^MQ|7AG*6R{6aeQ*6q~p`qn9dtK$^4LJsP2 zV!cuT2>a8m^T11n?1*>~i&Vi|0fhx074aePn3jW7wFT*qX`}{|U`l1RaO1;blw|4p zWATvDGzzj?*n&zucfTalAeFCifH}78I&5yvxv)}l(6h$ir}}Pj=Z0v~8L|N&>@CBB+1T#B zb-z91Ng#bxGy)RQSV=~vcrbXCo(E0_p{gqmK{$A!Y)VF!W(}kd^vE+B1dt4Q5&&Fk z=V3iLdIT|8bX5*ehJvyzOTLZ}ogDlrQ|zn+V_V5&Ps1w>sH9{Ipt5{1!=PMQf6UB? zT@`<)BYl8Ew|4`T73n&;0QI;qU*MAFK^O@S=ay&%b^1n}7N32VUy5 zB*nt=8q;S=8R=xi$mU@kV{X0)t3%mQ&9LN;Y19t@2%3;+ya;SYHydKa)=LSvP?Bwx zH)oaB`-x35H%f{Wa+zl*uP5xkv{Xs5W!lFW>!9~P`H6A#{S<>90K!-To=11<%pn)k zsp<|`y?T`6S2Lt1kmV(41;CCo^v}3ARHwvjvd1qspDFGhu!o|}@;vJo+HV93^Sw3w;_p9w_q)I0r|;i<^UZ($?)Sd;bHDxgZ~o0sf3yEl17Fjc0ZH3& zIM2eZ%lF1&021lSHS2hyM-eHa-%}qD$sM`e*G-E@M3|7RG6%E%C!VZMY8F)^&Ed7rl@pXWB zyd1(%_2pa078`Yk3QqPBw7PU+S;abCfE!@d$6m#Wido=Y)mz}z2Pz_drnl*Nix*Jm z0GOyS*@dgl#CAy9e~Zi3xtGj+eYI4CD5WN-o?|L4PDVQw*xDSM)IG@oq&1r?8CwKt z^}FAKdT*u~ZvMfhQw7L6WF{-|$`uV!_jUA&`Ky>)pVn?1^O+=|8#6^oGS!rwfIq|N zjwD5I{-}j)AU{!%s@^K@>NzNu~FR%94ZH)UT<17scN>A6h+j|xP8Swz-00F=k# zuu4okAbzD)mhbA+1&bM%Ke`hKJ=i^bnIP^C^nI0==lb*~r%ANN_k8Z<+xQQD%bq!s zjb)&%Fc=T($R?E>qDb=x3AlOSVh-LJ9p)0iTCzp_1yts2GTS_|F%*ZWm2D-_`nn;u z9tCK#L2YRa>Gp6~st-wZ{mL8no&~2O z?*gtax?X;x-C?fBkmcBNlnMAW&=BOwizst!ED<;5pD5CJmG+W=C}5A;?|^6+;=w4S zcTsH6T_7aa!%B5m#N0N21gy9yu~AxG!z40f#FZ%}Lrz;QL}?OC_F^?2FA~a@W)gdb zRn{n;U8S_bo5IgSn)C@0M)c(keyzDQ=6UwK(N#DEjqZ8yG=zOAom_>Fncn0Uz+`)B zr?!96Tj{@tUr<7=dRVQT=SV-ti3=O;mBjvl zxwN*L?cGlHPC9}1qMX65AjdKJ;bK%%c!D@J>(_G5UVT79D;RXA&m3-zIpjKrHV!76 zUIb|#43XAGA~k<*?qSy6fFLmhCDG*5)VmBdHq*i$@F_Nk#3CrA>qFU2&*f(5Lz--s zHowlD*!VehWkhL7eBiMSfeqsl9@a}hF;vwXWgF8Dj#?2C73H1Eh}H1oDhJ=Z*?=RU zihxM;O^Y!co|yz}Sq>D21BfR9^wb?Px73-oSaX2(8D~Onr6pftOh*UAh*9Xg46stj zROo^PGKz)2^5ksvYCe(WiW0V7D9+l_Uj!uu8csh+N67b)c~AfHPex zd%t2~pUfsv0Iwl9g@$>BN6@7_%2dExvvG}lwu;56w8W|U^`Z!CWec1~X$XPPh9f*N zOflTsl2mOi%8EZkMwQJUMTw-IiU=gBp;wx{N+hq`hJ7BtEfKwi&lGm4Xt%Bynv~Oz zTke9^plz5OK3M?G>PfmDD#~WA1zht3&>o^MY{(!_xHiSx zlR_>eh&T(?24yVhOT~y0BU)t8o7~m2mb1eU#jO73~F(mgrD}Kl5 z``kNP7=;Sp(#1}#5(9uvqv0HqdijhVTf{mrm(ohGR?buGFPJ8tsMZRQZcXTvncdmb z)*2550Fxzu@{&VC0M7KlEj*DBAR4VL)XF?fIr(-wUa9daZI!|w178+a z8q(49B$v6;*KuzZUJ;_xpt0yN%OERU;i%|`0FME8OEv}agj*S2Q+o8tZ*&xgIK&~i zTUD1a?c2VkL z76GC{xp*|Imsf1X1a7HS095FY*(4OtQ$&K8>ik@+(RjZU;O3G)ICIRz8_Rz5|LgA> zI}JLDh6>_|GRzxYma3Kk4k~DkVgSEM1957+3TW8rTHOzBzR<~&aBFPDo$Z98d*7~2 zYA&ry$5*LZyQ9$#pzNmB(VG~iCsj%B65!{)Dgj(z`n zDlPW1+@U>}18y(l4X>z1c!#cllbD8jGJ zfDvIgVv(wVB(ZWxE7a|?cz9kBW5dz)8T_r&pnb>Q#FP~EmOz6>vk$si) zokt>WrDKFIJlP>Io3LVwuzk?~QG-+4;D5D|2Nyfj&crOZZ|vjHGz zCajiy4XLL%xNnB52Y>@RvNPC|Xhzb{wWZcwe^tb3WvS!G1pDQALNiuGTdDPOXTt0w1n$K-%!g)l) z6?Vd>fq6HSZFNht;G?p`C~}lWF|wFQ1XC;Tt(@yCb?GI&oF#H}Fw>wUh2`pHjg2y8 za1@qH-GyFr;9hlis16>*3jIks`@awLcItzhFOU0;G0*v;1!lyr_trs4+XPq=z_Rq6 zkFp`U33G}v5_qBCn~jALw!?ZDT1YAXqu-t&33s79)q|e(;}8qs;Zy)KW5aA799_L% zYybey-Ms2O&7Ix%p;9CP)K1a>5cU=2_vJRRAre6|l5AwpATww+PUZZ$NUu;H>E~aB z(ifrI0_pCty#DND(RS=p`dd&laCKhd*6F z83|Nt`h|FlG|jt_AXfs|B?0(fAj@#FolvR^g&3@q4yN1b%cDymo{<_DZMK`ahd%N0 zGN0p-zC!-leJ4XDsIc<4E&%w=afPry8xEgN^=?}ACrhzQ7gJDz+@z;gA&`?iaPfvy zEmc~4*FE$Mxvt2uhaF5#lE|&)N-bZ2N2`wE`YXl0Ar|^EOZJFjZuK=5gZJ4J5Pg%9 zzW>{)-x48rP?dzScsVt$KVcq4H z2+SP;$QWCe@9+K!f)+6i7pK3{1o9!N-W0skN&fut=fCj(|I?s1^=KpTZL*X;F*9dn zTnIWMVEkR`v z;1us=raE~Lf|)``d^|G0)+~apn06iJRPcVsr~m!Q|MWY*`}b!*{h--&7)R_jIW1&G z?aT?%+#W6Jn)tHr-wT?9g29S9V<OaS zo`!f9(Vd+giQVWh3jnXN4MLpPMx~I=n1fAJlIxZW0a)KXcAoOZbQXQ#=^;6|~*L>h?-+ zx$8B?)gxg5WVd|?Iy;idsopK8Pe*^rk^<_5dTp&Sy_3MB@9Eb5NtSn5KjDyi8<9dT zi}!M)nB2l|mq#M)`T>$3_yB{Gulc-6^2iu;QKn*a)d1Si4knUW9bx(Kz0v@!5EF1fXuPW=! zD!pX}A1{gwlKo1iy{4Rb&n-f?cR%dX#sqO^;vrJZFWt^vStJPRNf`2DB|{pLSMNRC z-0qz>x^Dm|A65MqQYJg{=@oRwN-UR3Yh7z#v(FOFJ6$rycwsx$Ze8o8Y;HRbMw&d` z<^RxU4EJL^V-9^9$ek+yoGL3ds;B9GA(aNPU-pX;ur3kD$<>ED{$%oc3SHP9%WUlf z=uB=e3;#mtbjIz!?~=0mPGNiKbEWbZI;q!7XGn3nzFNzW?D1mI$ z{;yIKGUq3S1wkvy1}iE|udsWvqRhJ~u4umjCjj6{t%U-G#yyB95RD*_-k4M9Dd+xX zq5C{Q29+dZty#Pw?72pp^OQK$#%Q3{Mpt?*g)FH^Vnk(+X*ZhyuE`?UkuUy zemznD3)T$?vs9_Y_mYgn46TbMhanS>V-z#Ns0msNDSLv-SQu#l@JNF>40D$I)wJmz zH9j43Y0W@08D&eY2N7~qx+06d=eV^IkG=s_%*)9+`QYM?i~Q%KU5L(1MolowUR)~y8Ga4)So+uEiOMomdc@-u`+<-Rmi^EW zG|?%URy-PnTLBsZ{>V68NE# z*`D^RwV0dQ!dL0UtR!l&p!XLr^YKGJ`?I6QBwXk^^s_22N+wg2KVe3O^*NI~Dyq^^ z(sF7RE1Z9Q)wMr<>iRD~cID4^-1N6B7n@Heq!V}wgI;^|ZrY%cz&CQL*AEy`N(-im zHqSRk7`d6neXpHG?<+79As+NAe*F5sf8=k?zak&_&wkn){gJ=+hjeo#pD|XUnBF5| zE2k_e@=dpX{_vBZ{KWb9*Q^#f#^b+i2(2<~v_*)Nph%|bGz(#fP@;V_bt^w6pOV#P zm^33wmSoq^Q~dY3s9$1{s#~}~2s#4*Zb-4bZ7$@=L@Vyj%|94#l&ySvZ7F6+3O@qY zXi zVEH~mwHHv20cF8!n?1WlWJv7)?0`bv9Fp_i>d0pnuQo_`jK*Pq23>hUbgfGz0VQz0 zlq2J!5=)^PTQRld%^WD5pODyPBM2ERm^PwLLyQ0br$!P=x6xM`tSjhA&(vC+^MS~s z8d$98f9q_Ok!{cx{0bBSO4|ORL+OGJjh^4^vpLU%5g~BfK!)%yU7je1sX-dHPDkgA zKFa?S=V@7I`vka`z@#H3Zj4>Z_GV6t#siEtYwg{EXQ?NNW>_N&x*FT?tY!mB6Jd=c zsSvc`O1mB(WMFXs`YpOhkU~zjjdXtw7k#hn)tFDY^HslCTSC{dK_*?{j1DlQEw;~b{*Tb`RvG&q}Zd_k_*$_-jqK^9@v(+3bb(>{`Ky2{>4T-#I6T6y>FnbMI;b(aVfDsfX` zs++`5X`XrS82WA(cw-H`wf%R|`x97yClpKK1%92~O3bQu9+UTz2{@U@q4WW@etXQx z_Z}Ms8nAd@saf;__Anf?l76IA5`TALrwf{WVccSC(Bba^UQw!}ac!A~NDE&EcoiJw zP-#q3fj$ux!tzO_73k=7AlOkz zDS)b4a4RaQmYlC;+7dve#rxkmip8R9=+(|*f{<=EvgtDt&f;h+or5}6uGwS>h0Nzd zM&A*2J8h_*C3jY;)OGwk?)VgJo|g7j;`6TN#GLDQtU8(25i3$=E!&XJ(Ahrnv8#>I z+PjR$3_yf7`!n_U3(ClW%DuD3O7Pk~}TLI5Te0ipH(As5Mn= zDR!>KK-}5eR{ouM%U9D*_NjT^E&(l7ObMgX_)Wc9kL&GpJLm8D(CzsFDl%B;Am@Kk zS2|HwLEDJ1xFCgL)=|g4V{dgB*Gw2%x{KYk3hEoc3+>|(1-)kl zD4}NDXyrZ7Ta8~)U3Rtr+*uLIU2B1-HF2xBQF<{-z6aJYGXU zUigh(TBS*9Io7{*;8NwF?)qj@0Qv+(U3GBQ^ux^l&t%EeA(Z|_QU4b+HB$fETrlxkC+CJY^1uqbHON%e95Hq zrs&`QHC$Q!j;PGowBR*!TlcnlASSLfYHg~PziQu(WjL*NsfFmI`osWw=y@Wd4yu=) z;Sy~rR(B^zyuo`~{l0(FmYz$F2Rp&WwDfkdDogpsp*iOZPwHCuT_r-cfNiA)->lcWdvyjxP3L z^EcUB`3JZ3g8*PNk#W@@)5qdc2_qI@Xxv(Dy&GzQ@U+e^ORj5?w*g?Pce<^A$g&PA zf}*O@gQsgHXr*Vqm;XJFB?s*-e?+diL#BN)XF6v(=AIdY?p0M@4gkhGHj>&qK+M(I zITE&Kj90H2nX6ZeolM#AUW%-IuR+(u+nNs;y;P+cO|xE~@G8x4#I|cyvG$GC%iCwl z3djvpy>>^>YIkM3DGTY4*tiU|@*n3tbTP^y*Ba`9F80xfuv3I5^#`2#Syq4AU5)Le z;MAl;v#dP;c$PfnE5f`4K)<3F?BHB7f@fk_tn>u|;~oK{;^NYqX{6nfR*8;~EeQ0p zzq~+AaQDhe=;2=kOi`zd;XzsJs%SFlEprQeA*H1(MR5C$Fb#D$ecVJq+m9gdB>rOQ#dcfnF+ z+P5~|?Z-K9m}~5Ss$ZD+E>ptKn+zO3;YaGgVBp`=ucLDUpxfvVn{q>ONxR}_@udQ zsCEr(Q@Bb#pno38`!ZD#aiC`qv|`LO$v=N9Yg zMM0=ua%wG}CY_e!WFJAZ^J@)bTz6sO!RXVB0OhyYrLZ{*8Z^(O#IWvEx;$_z%zQON z`D0UcYn46pe5$Xa8qi`dNR)VmYBCAGPUhhfMEBBUHVI;*wmA(y^;a!vKxlp4j6I-p zuH02SR>Zk<-q^LU{V)zng{YX0L#&B_|ra=b)`FkuPOp{Pz zQ2;>huU>sjTD^Lw+wB_GIJt*i^1XGoZ-8OdN>|l1zYN1Vs;Ty92gt?vC~t9+Aw;=1 za)`QISN`hyP7kUp%RGA)x@8matd#u3pZ)9hHiQh2@y4DuOH*!AzC1AieRqAcj?~~4 zn$^7v&VkBi+@O@UcdrKIX&EhPvZrV>eFuMF6_z#ruINvH!ZXlo)gt3O~KGt`O_P~D#L%cG=fRz9Tbpl?Nz}ub!+KT@u(RcwSzP0O*H4qNe*iO*rar{@2h@gK31O}2T-6U zbsW@_y;pu^^&Jwpd@W(wD&D*Y!*+1@5M2f>i5kXbdmI>*RDQT(NdWu`n#({mxVMtz zYnd0(RpFIinXj4bWFp4|@@tFfP*j)Z7NHopQt(#US@6ppi@M`T<_rD&e7BO$BJVlw zocJDXHD#%a1zKc6p_)Q*@z#yrw{)-Mi_#O#2ucl}k+|89Ds1 z2NHk30l;GXvF1vp?zZTdq_9??W;!-?Yi;Ac8Ir>KrxGe-)XBaBqd%47B`y26OzRpQ z)1P|R9qb1HU0e6PK5k0gYM2(h%86V+x3w!e@5r=z+$;xGrAtH&HJzHTvAO0B49}uM zx`v$7^t9ybj&*?Ezo3)Rn|K8j@*b2A{qDBzb-Bxmj;k?ICnyub65aq97SEqJ)T+{L zf)D0x6nq7ZlBBEf+-3!yR3yoJr=b-?_RiQC0OD_z1B3%|KZZr5B$im}WAOzXrh5YV z4*r2Nw6D60Kd5xS)Wg_;LziXJ5v?6)GqR~L$AqV~2|?727Ps9z6Q&2C17Li<&=jot zu0_$QawB8LqcF-ut~GHvbKbS6TzkK&Eb9yyzbHAVUG6>IQwQf!X4CX9x4rZ1rDf$t z#atf%i0)Q{^Z@?Tf`%O554*%?;ajc!%u0-Vj5hA=Gb^Z;%TyR~Q7IuRnsJj-S}|l2 zGp_$n5N@^d?^WQT6u-h7FQOR$qibn#aWXG!|F0)*zt3(ndJ@)1(u-Mt_08HyG-X4J zsqj$M*gXhG5VbO`O(ReIW)BB}SaG%OehQONRP>!kAyI!Uxp#-aSUR;H|2{PH66<&s zSU{NJmkN~>Xz5W`TDBaw!Xg*uUp-`}(sHWhVdGh>K&4u(v5MBFCw>!UtvHixw59(T zN;GSJ-PC>obVIsHe8=W*l;i(+www#fO0JhIjQ640xteDmY^5!I*u-2y0MmD4x!~k=+X| zFqfO>()*Dw@beuuFG3S=dH@059wFhA5%(%Np$aCAkzk-vSNN*e`|!^vyRTab7K`>i*^iuymL15$(Pl`6R+ zsSlpC3Z1vMXUmpMYwKMfK9F>(=bY=|0c>rSb%;mQeB&^w9YvN*{e>$VU!W#XQ~;(m zVn=tZNMcwgR=iI3Eiz_3i8`WGp?1VoI9JS8#n9G$wR2v%#xY;b!6DrU9=KI-5GLFo zK?)*JCbo5Ao&dL1uJvcXB!>Lsmb*=A?g!UyT}pzc#}dPI86#JK!k`CVRI@fgB2J;z zN*PN_YWbIskLhp5oZ`D*$M7WcyB-{k0Ba^p@fEa8dGW}Bw}8`HX# z;)XieXJW}c?Zuf$qJ;RUq(=(+3>QS7zP3&84MfqB`u;gO8^DlboT4IT@5SE;Nq|%3 zilRg9#iP~^%ncmPnXM`)6T0iW&F}6juiBI7VKJZPSJvHQdf#FDC+$YFMq-HR0nK&5 zE~2b>-2h;{Qp+jZBgh;_D1+JbU{r8Lurm>VVaL#iF*7s*zwt86P|`(Ie*2 z!_tOl!Oy1ok<8nWZJx%juuO%63{}7Fa~lq3n+oL-X`ShNQ~8Dqa$v=jaag8j$qVbT zCpoEzm}8ou@KbXGt=(b=0K>ZDP|JhpA|tzBJ=Q53D-`)=!;pbI$mIWI|4$W6KR zuCgR0ykfIv-9i$`o3i+@`BTSTCaNMlWBJS)F@36~fhgdO2#a~8MnE(w-Og#8SnVO8 ziwX_cqXXx?*+lZ@H0d_dK6@5~($RhZFa)OpkF)S8SPIUY3=hAnB`0=QWA5LY3VFT~ zZ!y!CQo3Mj@ZKZf1)JyXJ7$bw?@}=gIPa~Zg2%y&%H&pB7m=stWK}N9JnxT7X^Vt{ zuOUhLTCW{e*j-7Iq^KTjaTdF+b}ioq;{FuiJm>!|V9Fe%v~WQdA|(Cd^Ky43Cj1d zP;jN8Tc|w3kG&GFZcTX!X~onfkRt(lYFX3P62{Yx!Ez4nYi!TW*8Op?{V_W^Ns^+E zaBiy{zx{fP3pjF2lsM#nSW_C~&$YC(%u7-K7kO$Pn8_A2>$I#xBWG6nB8+(Lf**#s1|e)huxE+CZVLDnp2LRgps2{_yg%AeRi2H zE8HKEq|>+PLQ%O~N+;Vq8}UW{Io@v3rs>}`n{OX>2j>Bxx00O0f8G;ghCi_Nset{~ z$+xQILsy&dTgfQ9WVM{^kFrebin9@w(?|>i5>4N*u?VQ=+`PS@$jS%)GnJn1Z82I3ed*;V6A-WvWFmq zlz21aur%C@r|7c}8GsRVSCU9Y^?=td!{)ocMKczO;`Up83=VQb zqPk?xMx9_$z}8u8#bOB~KbXYBJQEiAY}Fgq{n=XPR1sTGBQq4SZwnZiLG&n?J2-`W zNPq>9lCa%6OrY^Rz2x%)6MB{l2OOpEBz;B9=Ggw1QDhGgKe1hDH2LM!|3J26$|H9` zBm?G!Y`yMSHs>USPP5oD0;Zfic`sPa8w^vf_cpMQWjT+nyR%h3TlZz(GNHa~#qu=q zbhhp^m}k&gc^f+KNEi3f5f(&h3SfPlL&w6H9M+U0vmlh7e!`Ll7l`P%!*|$0k%Ldj zviCHXLTd;Og5;0RLH*9lu~QSfyem|juwY##*9uH zE}Vq!TtHZbDKvjQNG6>(q`621thi__PM92kmP!I;Kw}w;(xPvE^P9ShjhAu&Qo-FY zx0e_@8MY6$KP(6qMo&oxngO(gYguDxQ!XgGP#h=%FvWn>^mtU%f0P@H89Kpo8u?o~ z!!ivUI>SY9W68!(b7|Aa10>98_hI|VZfeDh$Dye#b z0B!&%;Ok62CzZ_?IegKTFLT!UX+u)#h}30!H}~hnVKvY8(2f#);r6)}6=}7$MD43SX za=+%QIOK|zw6k?n?BQa~M)5HFWLcK8U=;(ie(akU#edpaVy$JO5RsI7x@hkj5DQLp zw)kngdGJj@p_bZQUv6Vl%sCh*R1T1mZfUZOoX}g-M>~uT44D6CB9St@{Ve{m50)E~ zZP_wx5;6$5dH_IDP4zbQv{}tcJzW<%1RIjkS zH2SCvn7hEl8b)C-?Fh^M>|2%(E1Jz5>By9g#6{d;ygvv_c#P)X$aKvjCsIPYNh$$i zPjI#ZfIqixI$ixB5K1*O2+stC6niVs45j&U<#dx;g^hwgBS;os=3W1f|G072Wn}u% zb6IVM-IQ3u9zgknZ~6cHN-HJrL|b!mPAAI%QF)O%;2PJVyM&E!uHkg3LmSLS*q$I* zAU%IJQc)nADeiDQo8_}L@fy4%Q=RJp%S4!+rn>U-^3HvA8&KtiHIX-X+`-d2rP5s0 zqT4*o`RN8Q9%tMpxfIQGlc0yxhpPx+#Gd|aM70S_Ok3@hPSy{2Y0wH~V^(U-N^j=Q z;c7K75Kqwfhhw+S@V_W;lDNetoBnnf%oXb4K*C5)Yn)>I00XY8Jo*sDw>@5Xz+&wT# z{@=fdZvVvp#}|5IXY!n(Lt>32MT`Oe^Rnea%XXC!HUz^*q1#?n>t>xBR5&cfZk2Vr zu>;@M(kuRF=e`@*l}>;;4Slfs1J{8B)Eoev{6!|c5vB8X1Sk2*Ae3j;Rtq)WRH|7c zHJ2wfKn@f+TdgmdRsv5`m`(wVz|{77M7j;ZrW*3h&eN5U&4@ zX!B(s_)~q=te=`IKA&CFn=>Gn^VD3Bm`R6c=UNx*)`=AjQ5Tj?0EYI?lZye!{1jBS z2=8TX2Cx;!n!wYe)e=qmhMxt=faXO*h71<7SeR*6%Kxb>A**aZU;zZ{4hQmHo2D#%zaP1UAPL%)c&p3WYVv$)avPZQA&NR zJxX8%CS6AlJ+IY+)gBcp3B%NG6=da4=m0`r(>2|eLNuvyb2SkbufPg>zdwPQ zEQ6$}m2GN!Nj7`($-&`k1;m>Sg*0+f_?{awb%#h8qaOCw5`Zu3* zxtix+ogfO_55UoL5`-mFbB(Qn|MBknCJ7aOgt2ds1=HT8?juiK_ZM6)e;P<oJeNozAl7Y-!GnB=#Klh$?>sFw|(tB$?!Np|*q?Im^VD zXO668ni*Hzx?I$cj7l=i`;YW<5#{#3lRxlI@ZT?*7hd*}t99ws!kD}+9a9iO=Tj!` z{)O_1v+ogNOLv5!A)>BD*2=VxZM@qEE&?!Ji=+1;?9_!y4Cv2$qYF?<$Ea_(^;WUu z7{~vKL7j-0d&Iy@_9oi2x3fNvk`$MLTuc`oQ>^6&%C*|0S^yx~#5ZA_>%0ePoW^+R z9DpR80u8OEqtkv-$Z0L6f99u6|1%N$6LFvg)Rt2Pj81(0Rp;~1%FYmslZC`d0&s*@ zqmHz@`EdPseDv=6vrkCOz$n69nvohC3zTK?@F3_U3rBkM=Mow}e)`}@7be*lzY6L` zs*f?@p?t1PKL0l@ojvIp1K6Wu@hMne!u*>*rkm&Or5}G+yQ0R%zT*7(|2@W@Z&A6p+k~&SbH4t_%|4{k!Ro6P*JH00}iz+E(Kx%{UF9 zn}Takq&dAbvt*ec*;Cll^E2K%h&z~;>!Of0vu%}35Y%l~Yg1~IPbp2jDNfU~TaC?( z0OhR}b&Rg43CeA;dLU73RZYtq#SF7U5qe9+fU*k}K&oIZiV@TX(;d?rJCF`2w;DqG zLcst~u#2?{(@+y0s?o06_Qe%f)~9#RohJ0X`VCc)m4 z(@vF=C#5KNw^*748vxY7jQu2LI;B~db<_D9P%OA$uooALPnY*iC7b0^w9`#ZuG0E| zYPin&nw!w`Ze`9efhWMdw~r_y&)2a$2&W+vJaPbVCU0u6F}Yf^oV2;V+5xoe1p@=- ze4jz!(;98^DeIGJ$@)F5GyrfAZ=Y`>#cEG?gO3B$kyWJerhS%dS)Y9C;PnHQP=?A8 zM|n7$Ll6k_gZa|}=))UcF#YXs52Y6Y>2ATn@<8q!NwspiSnj50P)@CvsL3;1XV$Dk zRZ4w;c3OXo3}j8bQ{QD%geG9B=AoVR)G>;q5++lg*<3sHp&&_-lTa#le+Ek|GG*$3 zk^~HMT5a7Sm4f|cE#xx-jWsA4{u40v(z7J=>ExWBtoF`01Fvqu@4deUUtC~jAvB5D z8@CaQPF85i<|#!r1A*8MYZxt=s~C_?(G2QGOTaAwWV6{y3$4~j#+834oZ5~A(QPqw z7Rv!8fz&;{&#QrDK+FrDH@+vn+E-@LTdC7YH32|kE#XP`m|^LqU#HLVEsCwb590{{ z^8=duA`c$NLf9?-uOp_}XNA+lU;+i8PPl zr(N#eS_sURb1HwD56uh98pC*r23Q)zW_w_9z@LuT{V2cD8~`^D-mjzZgsv&ImZ|G* zE^L{T#mkeDp?idc(q&xI&b*)n85*`|Yaz&i5eiIfii;IFXde6PaMBR@@T6OCRMlkK zGo59Jj)+pteVWg)sl`02DiFNGlu7iF zst(S147;?eaT9a8FEKNTO`kR#(3rfkWz z1sicRq{$R%_SXp?6Qr8AHvQ*lB*kAxq~JbPYJ@T_NY3K=!CBV+H#tcUo&fd;KsRIK zNM#}!p<*P^ycoq(gH3_zJoJHA1D5~e9eQeTuV&sjTHE`T~de;)ItC8mL+ z_GpaXh~h%zgM64#Vogv^ah3u@`fF{`OWKo5UeeTLVr;Z_dTxR;(cgYuWC^)hiz-EC z&)$^a6{rnG4uXYago-Key;6E(8YZPTMrqSuPGk>CQq*Ix<0ZquzT}ovCC9NO17Lmr zxzUX}RhqLnazP`Ir=>QaoVicIhGzBXxSWWJH~H>dYe&v3s*n2L9Jhy%F<%@yUzX)V zc@Hke;_ao>%J`|((3pk}2s7Do9McWVohymq6q5w((gbAVqaiGQ`izPvH zdW#cAt8Xn@>3QQ<8s^khG0t5S;ORI$V;8_E+QubbZOCDvq2TJd|ba>q(+eF#O)J z%Iw?IEcM!Z@>O@f^wxTsxu_jw0C2vDq|eW$ppZK1|Af-0wPez&X{8G{W^NCQ5;N+y zM%#2F;G&b*7V5xOd$DhdqRG$x5#)MuJ7enqYayAWwsdRd3 z=m!QS*r1$RJLKt!45C&WXVSdTyP*wZMGte*gb3uV9{A8=8-}BIZ|veFMxTEttI(jQ zKFhP0l*tOKdHrQm8{`V)3hB%gG3LC9aDDoixEz$XZqd8o7PN0itt3d{r^B}x}(L&IQn~^2LS50 zx>mb!hzR~KH{usEP)d()tF7cf zI=MEr)K8}+!de79B39ny7jx68kgeE?1REo8q0e-9!WEuw2>=|8cBy7knPj=-j&}6Z zPFtWxE zthB!GTfb--{aKt>{qO{xt0DJXCSlu0~_UY5?L-W zy^iD1hr%W$>Eiv)$hK>YGclPWF5A_BOOo?;t=@Z2Tg&~q#!{1)>wsZ;3`z~&%LIBD zd1O-CLtfHK*V`>8M8MY)3xkM$^c*9w*@q{j*rmrD3IV&W>xoZ6)p zC?QkX@6MO9+=@>FM#fdz9-0>bx{qe_ zY^SYSz-2k+InNSuU$9@VH~2(^BNA9*PLuDo3g68YMzxxmF3xXm(XE6VO^Ax$A?Bu5 zJcx_ET2U+&oA*rXIFeHO@ssV=xaVyvW(r#|Z_`@L!rReqoh+s27t{{p#rPMs{>V4; zt7fosV-MxBL$h3LQhKfCbFFVUiM^5}>AFWT+^Udx`Ah2!rza*NIQ#6gxAm?aoXhqO zypHS0R}-$uQ(j`0b=cn6sGMKFohkQ`%5^e|%kvE90>qz}qbGI77tnrJ24MOVolmH4kO8^8$V8 z^9hHfkv$M}TXNW-uagRp$Q0f2Om!FgfTF?g6>gOsMJwo~XT)IJ+DhOjmlo0c3DUoF zw9foC3&yxTLXEf0MElAy_I*=CtSpzFAu60FB=itYw>j#)CH5N(_C5M3phBe>tD9*e z$+@P6Z(>SPRIIBfNyXUt6(?(_FHn>DEx1l;X*Xm0cq2xZk(l*i7#Qc6k3kvk4wet# z&m0JsKRa4GA3&~e^JiEtGG9&FsrjJ~%PHzOE zK}RsALF-f~uC^FOw}6(IJ1G4?tJjJ#u9o<{D5p$8ag+Zv`s7}BjTCrAs{fH}q1jKT z-aAidePBvVOVSC@kpY*!Zu3h>3tlWEc}80?HQt-D{!(+b)8^G`9EH6@f}0y0e;<5b z+@usXBHZeY30;#HjIW%h z2f;m#)7)SheIlvgBpk(Ea$tCp;s$>c?^F1NrI?q%Vu58ccvLx<|6nqfO#O2HI}y>O>k=C}`ySD-1on{5m?Rfuhmi+2XhuD8K YbKY*DsPh(2rz$ZD9TJ-ScIbdf6J&!(e*gdg literal 0 HcmV?d00001 diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index 7de53f5cf1a..ed715022fec 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -20,7 +20,7 @@ import { convertHeadingTextToId } from "@ourworldindata/components" import SDGGrid from "./SDGGrid.js" import { BlockErrorBoundary, BlockErrorFallback } from "./BlockErrorBoundary.js" import { match } from "ts-pattern" -import { renderSpans } from "../utils.js" +import { renderSpans, useLinkedChart } from "../utils.js" import Paragraph from "./Paragraph.js" import TableOfContents from "./TableOfContents.js" import urlSlug from "url-slug" @@ -34,6 +34,7 @@ import { ResearchAndWriting } from "./ResearchAndWriting.js" import { AllCharts } from "./AllCharts.js" import Video from "./Video.js" import { Table } from "./Table.js" +import { ExplorerTiles } from "./ExplorerTiles.js" export type Container = | "default" @@ -62,6 +63,7 @@ const layouts: { [key in Container]: Layouts} = { ["default"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["divider"]: "col-start-2 span-cols-12", ["explorer"]: "col-start-2 span-cols-12", + ["explorer-tiles"]: "grid grid-cols-12 span-cols-12 col-start-2", ["gray-section"]: "span-cols-14 grid grid-cols-12-full-width", ["heading"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["horizontal-rule"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", @@ -623,6 +625,12 @@ export default function ArticleBlock({ ) }) + .with({ type: "explorer-tiles" }, (block) => ( + + )) .exhaustive() return ( diff --git a/site/gdocs/components/ExplorerTiles.scss b/site/gdocs/components/ExplorerTiles.scss new file mode 100644 index 00000000000..e1a6d3b871a --- /dev/null +++ b/site/gdocs/components/ExplorerTiles.scss @@ -0,0 +1,59 @@ +.article-block__explorer-tiles { + margin-bottom: 40px; + .explorer-tiles__title { + margin: 0; + } + .explorer-tiles__subtitle { + color: $blue-60; + margin: 0 0 16px 0; + } + .explorer-tiles__cta { + display: block; + padding: 8.5px 16px; + color: $vermillion; + border: 1px solid $vermillion; + border-radius: 0; + width: fit-content; + justify-self: end; + grid-row: span 2; + align-self: center; + height: 40px; + &:hover { + color: $accent-vermillion; + border-color: $accent-vermillion; + } + @include md-down { + justify-self: start; + margin-top: 16px; + order: 4; + } + @include sm-only { + text-align: center; + width: 100%; + } + } + .explorer-tiles-grid { + row-gap: var(--grid-gap); + } + .explorer-tile { + height: 130px; + background-image: url("/explorer-thumbnail.webp"); + background-size: cover; + position: relative; + } + .explorer-tile__text-container { + position: absolute; + bottom: 16px; + left: 16px; + } + .explorer-tile__title, + .explorer-tile__suffix { + margin: 0; + } + .explorer-tile__title { + color: #fff; + } + .explorer-tile__suffix { + color: $blue-40; + } +} diff --git a/site/gdocs/components/ExplorerTiles.tsx b/site/gdocs/components/ExplorerTiles.tsx new file mode 100644 index 00000000000..abd1d674cc2 --- /dev/null +++ b/site/gdocs/components/ExplorerTiles.tsx @@ -0,0 +1,62 @@ +import { EnrichedBlockExplorerTiles } from "@ourworldindata/types" +import React from "react" +import { useLinkedChart } from "../utils.js" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { faArrowRight } from "@fortawesome/free-solid-svg-icons" + +function ExplorerTile({ url }: { url: string }) { + const { linkedChart, errorMessage } = useLinkedChart(url) + if (errorMessage) { + return

    {errorMessage}

    + } + if (!linkedChart) { + return null + } + return ( + +
    +

    + {linkedChart.title} +

    +

    Data Explorer

    +
    +
    + ) +} + +type ExplorerTilesProps = EnrichedBlockExplorerTiles & { + className?: string +} + +export function ExplorerTiles({ + className, + title, + subtitle, + explorers, +}: ExplorerTilesProps) { + return ( +
    +

    + {title} +

    + + See all our charts and explorers{" "} + + +

    + {subtitle} +

    +
    + {explorers.map((explorer, i) => ( + + ))} +
    +
    + ) +} diff --git a/site/owid.scss b/site/owid.scss index dd90848b397..26aa80d9d6e 100644 --- a/site/owid.scss +++ b/site/owid.scss @@ -94,6 +94,7 @@ @import "./gdocs/components/AdditionalCharts.scss"; @import "./gdocs/components/ResearchAndWriting.scss"; @import "./gdocs/components/Chart.scss"; +@import "./gdocs/components/ExplorerTiles.scss"; @import "./DataPage.scss"; @import "./DataPageContent.scss"; @import "./detailsOnDemand.scss"; From a588142b0f00003d5b9f8fcde9693a70b1f8cd1a Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Mon, 12 Feb 2024 21:23:22 +0000 Subject: [PATCH 04/14] =?UTF-8?q?=E2=9C=A8=20drop=20FK=20constraint=20for?= =?UTF-8?q?=20explorer=20tags,=20remove=20"x"=20from=20table=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/migration/1707502831161-ExplorersXTags.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/db/migration/1707502831161-ExplorersXTags.ts b/db/migration/1707502831161-ExplorersXTags.ts index d0a19b60a1d..d17e46534f4 100644 --- a/db/migration/1707502831161-ExplorersXTags.ts +++ b/db/migration/1707502831161-ExplorersXTags.ts @@ -3,12 +3,11 @@ import { MigrationInterface, QueryRunner } from "typeorm" export class ExplorersXTags1707502831161 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { queryRunner.query(` - CREATE TABLE explorers_x_tags ( + CREATE TABLE explorer_tags ( id INT AUTO_INCREMENT PRIMARY KEY, explorerSlug VARCHAR(150) NOT NULL, tagId INT NOT NULL, UNIQUE KEY (explorerSlug, tagId), - FOREIGN KEY (explorerSlug) REFERENCES explorers(slug), FOREIGN KEY (tagId) REFERENCES tags(id) ); @@ -17,7 +16,7 @@ export class ExplorersXTags1707502831161 implements MigrationInterface { public async down(queryRunner: QueryRunner): Promise { queryRunner.query(` - DROP TABLE IF EXISTS explorers_x_tags; + DROP TABLE IF EXISTS explorer_tags; `) } } From 34a8d2ee0b1ad338dd9d606652d947f5a8c108ab Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Mon, 12 Feb 2024 22:18:42 +0000 Subject: [PATCH 05/14] =?UTF-8?q?=E2=9C=A8=20improve=20ExplorerTagsPage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adminSiteClient/ExplorerTagsPage.tsx | 212 +++++++++++++++--- adminSiteServer/adminRouter.tsx | 12 +- adminSiteServer/apiRouter.ts | 21 +- ...XTags.ts => 1707502831161-ExplorerTags.ts} | 2 +- 4 files changed, 192 insertions(+), 55 deletions(-) rename db/migration/{1707502831161-ExplorersXTags.ts => 1707502831161-ExplorerTags.ts} (89%) diff --git a/adminSiteClient/ExplorerTagsPage.tsx b/adminSiteClient/ExplorerTagsPage.tsx index a1603cb220a..804f156f37a 100644 --- a/adminSiteClient/ExplorerTagsPage.tsx +++ b/adminSiteClient/ExplorerTagsPage.tsx @@ -1,12 +1,17 @@ import React from "react" import { observer } from "mobx-react" -import { observable, runInAction } from "mobx" +import { action, computed, observable, runInAction } from "mobx" import { AdminLayout } from "./AdminLayout.js" import { AdminAppContext, AdminAppContextType } from "./AdminAppContext.js" import { DbChartTagJoin } from "@ourworldindata/utils" -import { GetAllExplorersTagsRoute } from "../explorer/ExplorerConstants.js" +import { + GetAllExplorersRoute, + GetAllExplorersTagsRoute, +} from "../explorer/ExplorerConstants.js" import { EditableTags } from "./EditableTags.js" +import { ExplorerProgram } from "../explorer/ExplorerProgram.js" +import cx from "classnames" type ExplorerWithTags = { slug: string @@ -17,48 +22,161 @@ type ExplorerWithTags = { export class ExplorerTagsPage extends React.Component { static contextType = AdminAppContext context!: AdminAppContextType - @observable explorers: ExplorerWithTags[] = [] + @observable explorersWithTags: ExplorerWithTags[] = [] + @observable explorers: ExplorerProgram[] = [] @observable tags: DbChartTagJoin[] = [] + @observable newExplorerSlug = "" + @observable newExplorerTags: DbChartTagJoin[] = [] componentDidMount() { this.getData() } + // Don't show explorers that already have tags + @computed get filteredExplorers() { + return this.explorers.filter((explorer) => { + return !this.explorersWithTags.find((e) => e.slug === explorer.slug) + }) + } + render() { return (

    Explorer tags

    - Explorer data is currently stored in Git. This is why - we're managing these tags (which are stored in the - database) on a separate page. + This page is for managing the tags for each explorer. + Explorer data is currently stored in{" "} + git, + but tags are stored in the database, which means it's + hard to strictly enforce{" "} + + referential integrity. + +

    +

    + If you update an explorer's slug, you'll need to delete + the old row in this table and create a new one for the + new slug.

    + - - + + + - {this.explorers.map((explorer) => ( - - - - - ))} + {/* Existing Explorers Rows */} + {this.explorersWithTags.map((explorer) => { + const isSlugValid = this.explorers.find( + (e) => e.slug === explorer.slug + ) + + return ( + + + + + + ) + })} + {/* New Explorer Row */} + + + + +
    ExplorerTagsExplorerTagsControls
    {explorer.slug} - { - this.saveTags( - explorer.slug, - tags - ) - }} - /> -
    + {explorer.slug} + {isSlugValid ? null : ( +

    + + ❗️ this slug doesn't + match any explorer in + the database - is it for + an explorer that has + been deleted or renamed? + +

    + )} +
    + { + this.saveTags( + explorer.slug, + tags + ) + }} + /> + + +
    + + + { + this.newExplorerTags = tags + }} + /> + + +
    @@ -67,17 +185,21 @@ export class ExplorerTagsPage extends React.Component { } async getData() { - const { tags } = await this.context.admin.getJSON("/api/tags.json") + const [{ tags }, explorersWithTags, explorers] = await Promise.all([ + this.context.admin.getJSON("/api/tags.json") as Promise<{ + tags: DbChartTagJoin[] + }>, + this.context.admin.getJSON(GetAllExplorersTagsRoute) as Promise<{ + explorers: ExplorerWithTags[] + }>, + this.context.admin.getJSON(GetAllExplorersRoute) as Promise<{ + explorers: ExplorerProgram[] + }>, + ]) runInAction(() => { this.tags = tags - }) - const json = (await this.context.admin.getJSON( - GetAllExplorersTagsRoute - )) as { - explorers: ExplorerWithTags[] - } - runInAction(() => { - this.explorers = json.explorers + this.explorersWithTags = explorersWithTags.explorers + this.explorers = explorers.explorers }) } @@ -91,4 +213,26 @@ export class ExplorerTagsPage extends React.Component { "POST" ) } + + async deleteExplorerTags(slug: string) { + if ( + window.confirm( + `Are you sure you want to delete the tags for "${slug}"?` + ) + ) { + await this.context.admin.requestJSON( + `/api/explorer/${slug}/tags`, + {}, + "DELETE" + ) + await this.getData() + } + } + + @action.bound async saveNewExplorer() { + await this.saveTags(this.newExplorerSlug, this.newExplorerTags) + await this.getData() + this.newExplorerTags = [] + this.newExplorerSlug = "" + } } diff --git a/adminSiteServer/adminRouter.tsx b/adminSiteServer/adminRouter.tsx index 0286513a83a..590cbdba3bd 100644 --- a/adminSiteServer/adminRouter.tsx +++ b/adminSiteServer/adminRouter.tsx @@ -258,21 +258,17 @@ adminRouter.get(`/${GetAllExplorersTagsRoute}`, async (req, res) => { const explorers = await db .knexRaw( `SELECT - e.slug, - CASE + ext.explorerSlug as slug, + CASE WHEN COUNT(t.id) = 0 THEN JSON_ARRAY() ELSE JSON_ARRAYAGG(JSON_OBJECT('name', t.name, 'id', t.id)) END AS tags FROM - explorers e - LEFT JOIN explorers_x_tags ext ON - e.slug = ext.explorerSlug + explorer_tags ext LEFT JOIN tags t ON ext.tagId = t.id - WHERE - e.isPublished = 1 GROUP BY - e.slug`, + ext.explorerSlug`, db.knexInstance() ) .then((result) => { diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index e3b7a303436..61407a38838 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -2612,27 +2612,24 @@ apiRouter.get( apiRouter.post("/explorer/:slug/tags", async (req: Request, res: Response) => { const { slug } = req.params const { tagIds } = req.body - const explorer = await db.knexRaw( - `SELECT * FROM explorers WHERE slug = ?`, - db.knexInstance(), - [slug] - ) + const explorer = await db.knexTable("explorers").where({ slug }).first() if (!explorer) throw new JsonError(`No explorer found for slug ${slug}`, 404) db.knexInstance().transaction(async (t) => { - await t.raw(`DELETE FROM explorers_x_tags WHERE explorerSlug = ?`, [ - slug, - ]) + await t.table("explorer_tags").where({ explorerSlug: slug }).delete() for (const tagId of tagIds) { - await t.raw( - `INSERT INTO explorers_x_tags (explorerSlug, tagId) VALUES (?, ?)`, - [slug, tagId] - ) + await t.table("explorer_tags").insert({ explorerSlug: slug, tagId }) } }) return { success: true } }) +apiRouter.delete("/explorer/:slug/tags", async (req: Request) => { + const { slug } = req.params + await db.knexTable("explorer_tags").where({ explorerSlug: slug }).delete() + return { success: true } +}) + export { apiRouter } diff --git a/db/migration/1707502831161-ExplorersXTags.ts b/db/migration/1707502831161-ExplorerTags.ts similarity index 89% rename from db/migration/1707502831161-ExplorersXTags.ts rename to db/migration/1707502831161-ExplorerTags.ts index d17e46534f4..2cf8fc8bda6 100644 --- a/db/migration/1707502831161-ExplorersXTags.ts +++ b/db/migration/1707502831161-ExplorerTags.ts @@ -1,6 +1,6 @@ import { MigrationInterface, QueryRunner } from "typeorm" -export class ExplorersXTags1707502831161 implements MigrationInterface { +export class ExplorerTags1707502831161 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { queryRunner.query(` CREATE TABLE explorer_tags ( From cc618dc7b2f90629a1e3a019c77fd624b469dd8b Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Mon, 12 Feb 2024 23:41:51 +0000 Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=8E=89=20use=20explorers=20from=20t?= =?UTF-8?q?he=20DB=20to=20power=20gdocs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adminSiteServer/adminRouter.tsx | 39 +---------- adminSiteServer/apiRouter.ts | 19 ++---- adminSiteServer/app.tsx | 4 -- adminSiteServer/mockSiteRouter.tsx | 16 +---- baker/GrapherBaker.tsx | 19 ++---- baker/SiteBaker.tsx | 10 ++- baker/siteRenderers.tsx | 13 ++-- datapage/Datapage.ts | 8 +-- db/db.ts | 64 +++++++++++++++++++ db/model/Gdoc/GdocBase.ts | 29 +++++---- db/model/Gdoc/GdocFactory.ts | 21 ++---- .../types/src/gdocTypes/Gdoc.ts | 2 + 12 files changed, 111 insertions(+), 133 deletions(-) diff --git a/adminSiteServer/adminRouter.tsx b/adminSiteServer/adminRouter.tsx index 590cbdba3bd..d7850b2bef3 100644 --- a/adminSiteServer/adminRouter.tsx +++ b/adminSiteServer/adminRouter.tsx @@ -255,30 +255,7 @@ adminRouter.get(`/${GetAllExplorersRoute}`, async (req, res) => { }) adminRouter.get(`/${GetAllExplorersTagsRoute}`, async (req, res) => { - const explorers = await db - .knexRaw( - `SELECT - ext.explorerSlug as slug, - CASE - WHEN COUNT(t.id) = 0 THEN JSON_ARRAY() - ELSE JSON_ARRAYAGG(JSON_OBJECT('name', t.name, 'id', t.id)) - END AS tags - FROM - explorer_tags ext - LEFT JOIN tags t ON - ext.tagId = t.id - GROUP BY - ext.explorerSlug`, - db.knexInstance() - ) - .then((result) => { - return result.map((row: any) => { - return { - slug: row.slug, - tags: JSON.parse(row.tags), - } - }) - }) + const explorers = await db.getExplorerTags(db.knexInstance()) res.send({ explorers, @@ -307,8 +284,6 @@ adminRouter.get("/datapage-preview/:id", async (req, res) => { const variableId = expectInt(req.params.id) const variableMetadata = await getVariableMetadata(variableId) if (!variableMetadata) throw new JsonError("No such variable", 404) - const publishedExplorersBySlug = - await explorerAdminServer.getAllPublishedExplorersBySlugCached() res.send( await renderDataPageV2({ @@ -316,7 +291,6 @@ adminRouter.get("/datapage-preview/:id", async (req, res) => { variableMetadata, isPreviewing: true, useIndicatorGrapherConfigs: true, - publishedExplorersBySlug, }) ) }) @@ -325,16 +299,7 @@ adminRouter.get("/grapher/:slug", async (req, res) => { const entity = await Chart.getBySlug(req.params.slug) if (!entity) throw new JsonError("No such chart", 404) - const explorerAdminServer = new ExplorerAdminServer(GIT_CMS_DIR) - const publishedExplorersBySlug = - await explorerAdminServer.getAllPublishedExplorersBySlug() - - res.send( - await renderPreviewDataPageOrGrapherPage( - entity.config, - publishedExplorersBySlug - ) - ) + res.send(await renderPreviewDataPageOrGrapherPage(entity.config)) }) const gitCmsServer = new GitCmsServer({ diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index 61407a38838..8756abe6683 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -2423,14 +2423,7 @@ apiRouter.get("/gdocs/:id", async (req, res) => { | undefined try { - const publishedExplorersBySlug = - await explorerAdminServer.getAllPublishedExplorersBySlugCached() - - const gdoc = await GdocFactory.load( - id, - publishedExplorersBySlug, - contentSource - ) + const gdoc = await GdocFactory.load(id, contentSource) if (!gdoc.published) { await gdoc.save() @@ -2452,24 +2445,22 @@ apiRouter.get("/gdocs/:id", async (req, res) => { apiRouter.put("/gdocs/:id", async (req, res) => { const { id } = req.params const nextGdocJSON: OwidGdocJSON = req.body - const explorers = - await explorerAdminServer.getAllPublishedExplorersBySlugCached() if (isEmpty(nextGdocJSON)) { // Check to see if the gdoc already exists in the database const existingGdoc = await GdocBase.findOneBy({ id }) if (existingGdoc) { - return GdocFactory.load(id, explorers, GdocsContentSource.Gdocs) + return GdocFactory.load(id, GdocsContentSource.Gdocs) } else { - return GdocFactory.create(id, explorers) + return GdocFactory.create(id) } } - const prevGdoc = await GdocFactory.load(id, {}) + const prevGdoc = await GdocFactory.load(id) if (!prevGdoc) throw new JsonError(`No Google Doc with id ${id} found`) const nextGdoc = GdocFactory.fromJSON(nextGdocJSON) - await nextGdoc.loadState(explorers) + await nextGdoc.loadState() // Deleting and recreating these is simpler than tracking orphans over the next code block await GdocXImage.delete({ gdocId: id }) diff --git a/adminSiteServer/app.tsx b/adminSiteServer/app.tsx index 500eb359cd9..579a1e0f202 100644 --- a/adminSiteServer/app.tsx +++ b/adminSiteServer/app.tsx @@ -127,13 +127,9 @@ export class OwidAdminApp { const adminExplorerServer = new ExplorerAdminServer(GIT_CMS_DIR) // Public preview of a Gdoc document app.get("/gdocs/:id/preview", async (req, res) => { - const publishedExplorersBySlug = - await adminExplorerServer.getAllPublishedExplorersBySlugCached() - try { const gdoc = await GdocFactory.load( req.params.id, - publishedExplorersBySlug, GdocsContentSource.Gdocs ) diff --git a/adminSiteServer/mockSiteRouter.tsx b/adminSiteServer/mockSiteRouter.tsx index 7dd809be0d6..4c082e13b10 100644 --- a/adminSiteServer/mockSiteRouter.tsx +++ b/adminSiteServer/mockSiteRouter.tsx @@ -151,18 +151,9 @@ mockSiteRouter.get("/grapher/:slug", async (req, res) => { const entity = await Chart.getBySlug(req.params.slug) if (!entity) throw new JsonError("No such chart", 404) - const explorerAdminServer = new ExplorerAdminServer(GIT_CMS_DIR) - const publishedExplorersBySlug = - await explorerAdminServer.getAllPublishedExplorersBySlug() - // XXX add dev-prod parity for this res.set("Access-Control-Allow-Origin", "*") - res.send( - await renderPreviewDataPageOrGrapherPage( - entity.config, - publishedExplorersBySlug - ) - ) + res.send(await renderPreviewDataPageOrGrapherPage(entity.config)) }) mockSiteRouter.get("/", async (req, res) => { @@ -182,7 +173,7 @@ mockSiteRouter.get("/data-insights/:pageNumberOrSlug?", async (req, res) => { const dataInsights = await GdocDataInsight.getPublishedDataInsights(pageNumber) // calling fetchImageMetadata 20 times makes me sad, would be nice if we could cache this - await Promise.all(dataInsights.map((insight) => insight.loadState({}))) + await Promise.all(dataInsights.map((insight) => insight.loadState())) const totalPageCount = await GdocDataInsight.getTotalPageCount() return renderDataInsightsIndexPage( dataInsights, @@ -223,8 +214,6 @@ mockSiteRouter.get("/charts", async (req, res) => { mockSiteRouter.get("/datapage-preview/:id", async (req, res) => { const variableId = expectInt(req.params.id) const variableMetadata = await getVariableMetadata(variableId) - const publishedExplorersBySlug = - await explorerAdminServer.getAllPublishedExplorersBySlugCached() res.send( await renderDataPageV2({ @@ -232,7 +221,6 @@ mockSiteRouter.get("/datapage-preview/:id", async (req, res) => { variableMetadata, isPreviewing: true, useIndicatorGrapherConfigs: true, - publishedExplorersBySlug, }) ) }) diff --git a/baker/GrapherBaker.tsx b/baker/GrapherBaker.tsx index 37d52ff0ecf..01af44f259a 100644 --- a/baker/GrapherBaker.tsx +++ b/baker/GrapherBaker.tsx @@ -67,7 +67,6 @@ import { getSlugForTopicTag, getTagToSlugMap } from "./GrapherBakingUtils.js" const renderDatapageIfApplicable = async ( grapher: GrapherInterface, isPreviewing: boolean, - publishedExplorersBySlug?: Record, imageMetadataDictionary?: Record ) => { // If we have a single Y variable and that one has a schema version >= 2, @@ -108,7 +107,6 @@ const renderDatapageIfApplicable = async ( isPreviewing: isPreviewing, useIndicatorGrapherConfigs: false, pageGrapher: grapher, - publishedExplorersBySlug, imageMetadataDictionary, }) } @@ -122,13 +120,11 @@ const renderDatapageIfApplicable = async ( */ export const renderDataPageOrGrapherPage = async ( grapher: GrapherInterface, - publishedExplorersBySlug?: Record, imageMetadataDictionary?: Record ): Promise => { const datapage = await renderDatapageIfApplicable( grapher, false, - publishedExplorersBySlug, imageMetadataDictionary ) if (datapage) return datapage @@ -153,7 +149,6 @@ export async function renderDataPageV2({ isPreviewing, useIndicatorGrapherConfigs, pageGrapher, - publishedExplorersBySlug, imageMetadataDictionary = {}, }: { variableId: number @@ -161,7 +156,6 @@ export async function renderDataPageV2({ isPreviewing: boolean useIndicatorGrapherConfigs: boolean pageGrapher?: GrapherInterface - publishedExplorersBySlug?: Record imageMetadataDictionary?: Record }) { const grapherConfigForVariable = @@ -178,7 +172,7 @@ export async function renderDataPageV2({ uniq(variableMetadata.presentation?.faqs?.map((faq) => faq.gdocId)) ) const gdocFetchPromises = faqDocs.map((gdocId) => - getDatapageGdoc(gdocId, isPreviewing, publishedExplorersBySlug) + getDatapageGdoc(gdocId, isPreviewing) ) const gdocs = await Promise.all(gdocFetchPromises) const gdocIdToFragmentIdToBlock: Record = {} @@ -344,14 +338,9 @@ export async function renderDataPageV2({ * Similar to renderDataPageOrGrapherPage(), but for admin previews */ export const renderPreviewDataPageOrGrapherPage = async ( - grapher: GrapherInterface, - publishedExplorersBySlug?: Record + grapher: GrapherInterface ) => { - const datapage = await renderDatapageIfApplicable( - grapher, - true, - publishedExplorersBySlug - ) + const datapage = await renderDatapageIfApplicable(grapher, true) if (datapage) return datapage return renderGrapherPage(grapher) @@ -415,7 +404,7 @@ const bakeGrapherPageAndVariablesPngAndSVGIfChanged = async ( const outPath = `${bakedSiteDir}/grapher/${grapher.slug}.html` await fs.writeFile( outPath, - await renderDataPageOrGrapherPage(grapher, {}, imageMetadataDictionary) + await renderDataPageOrGrapherPage(grapher, imageMetadataDictionary) ) console.log(outPath) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index 60c6a3f5086..c438cff1bee 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -309,6 +309,7 @@ export class SiteBaker { thumbnail: cur.thumbnail || `${BAKED_BASE_URL}/default-thumbnail.jpg`, + tags: [], })) ) // Includes redirects @@ -323,6 +324,7 @@ export class SiteBaker { queryString: "", title: cur.config.title || "", thumbnail: `${BAKED_GRAPHER_EXPORTS_BASE_URL}/${cur.config.slug}.svg`, + tags: [], }, }), {} as Record @@ -468,9 +470,7 @@ export class SiteBaker { // this is a no-op if the gdoc doesn't have an all-chart block await publishedGdoc.loadRelatedCharts() - const publishedExplorersBySlug = - await this.explorerAdminServer.getAllPublishedExplorersBySlugCached() - await publishedGdoc.validate(publishedExplorersBySlug) + await publishedGdoc.validate() if ( publishedGdoc.errors.filter( (e) => e.type === OwidGdocErrorMessageType.Error @@ -667,9 +667,7 @@ export class SiteBaker { } dataInsight.latestDataInsights = latestDataInsights - const publishedExplorersBySlug = - await this.explorerAdminServer.getAllPublishedExplorersBySlugCached() - await dataInsight.validate(publishedExplorersBySlug) + await dataInsight.validate() if ( dataInsight.errors.filter( (e) => e.type === OwidGdocErrorMessageType.Error diff --git a/baker/siteRenderers.tsx b/baker/siteRenderers.tsx index 126d0cff3c8..a43dabcb1da 100644 --- a/baker/siteRenderers.tsx +++ b/baker/siteRenderers.tsx @@ -169,16 +169,12 @@ export const renderGdocsPageBySlug = async ( slug: string, isPreviewing: boolean = false ): Promise => { - const explorerAdminServer = new ExplorerAdminServer(GIT_CMS_DIR) - const publishedExplorersBySlug = - await explorerAdminServer.getAllPublishedExplorersBySlug() - - const gdoc = await GdocFactory.loadBySlug(slug, publishedExplorersBySlug) + const gdoc = await GdocFactory.loadBySlug(slug) if (!gdoc) { throw new Error(`Failed to render an unknown GDocs post: ${slug}.`) } - await gdoc.loadState(publishedExplorersBySlug) + await gdoc.loadState() return renderGdoc(gdoc, isPreviewing) } @@ -267,7 +263,7 @@ export const renderFrontPage = async () => { id: GDOCS_HOMEPAGE_CONFIG_DOCUMENT_ID, }) if (!frontPageConfigGdoc) throw new Error("No front page config found") - await frontPageConfigGdoc.loadState({}) + await frontPageConfigGdoc.loadState() const frontPageConfig: any = frontPageConfigGdoc.content const featuredPosts: { slug: string; position: number }[] = frontPageConfig["featured-posts"] ?? [] @@ -331,8 +327,7 @@ export const renderFrontPage = async () => { export const renderDonatePage = async () => { const faqsGdoc = (await GdocFactory.load( - GDOCS_DONATE_FAQS_DOCUMENT_ID, - {} + GDOCS_DONATE_FAQS_DOCUMENT_ID )) as GdocPost if (!faqsGdoc) throw new Error( diff --git a/datapage/Datapage.ts b/datapage/Datapage.ts index ee015d18908..836f51cdde3 100644 --- a/datapage/Datapage.ts +++ b/datapage/Datapage.ts @@ -78,8 +78,7 @@ export const getDatapageDataV2 = async ( */ export const getDatapageGdoc = async ( googleDocEditLinkOrId: string, - isPreviewing: boolean, - publishedExplorersBySlug?: Record + isPreviewing: boolean ): Promise => { // Get the google doc id from the datapage JSON file and return early if // none found @@ -100,12 +99,9 @@ export const getDatapageGdoc = async ( // support images (imageMetadata won't be set). const datapageGdoc = - isPreviewing && - publishedExplorersBySlug && - OwidGoogleAuth.areGdocAuthKeysSet() + isPreviewing && OwidGoogleAuth.areGdocAuthKeysSet() ? ((await GdocFactory.load( googleDocId, - publishedExplorersBySlug, GdocsContentSource.Gdocs )) as GdocPost) : await GdocPost.findOneBy({ id: googleDocId }) diff --git a/db/db.ts b/db/db.ts index 34090537fb8..652c915c122 100644 --- a/db/db.ts +++ b/db/db.ts @@ -10,6 +10,8 @@ import { GRAPHER_DB_PORT, } from "../settings/serverSettings.js" import { registerExitHandler } from "./cleanup.js" +import { keyBy } from "@ourworldindata/utils" +import { DbChartTagJoin } from "@ourworldindata/types" let typeormDataSource: DataSource export const getConnection = async ( @@ -140,3 +142,65 @@ export const getSlugsWithPublishedGdocsSuccessors = async ( knex ).then((rows) => new Set(rows.map((row: any) => row.slug))) } + +export const getExplorerTags = async ( + knex: Knex +): Promise<{ slug: string; tags: DbChartTagJoin[] }[]> => { + return knexRaw<{ slug: string; tags: string }>( + `-- sql + SELECT + ext.explorerSlug as slug, + CASE + WHEN COUNT(t.id) = 0 THEN JSON_ARRAY() + ELSE JSON_ARRAYAGG(JSON_OBJECT('name', t.name, 'id', t.id)) + END AS tags + FROM + explorer_tags ext + LEFT JOIN tags t ON + ext.tagId = t.id + GROUP BY + ext.explorerSlug`, + knex + ).then((rows) => + rows.map((row) => ({ + slug: row.slug, + tags: JSON.parse(row.tags) as DbChartTagJoin[], + })) + ) +} + +export const getPublishedExplorersBySlug = async ( + knex: Knex +): Promise<{ + [slug: string]: { + slug: string + title: string + subtitle: string + tags: DbChartTagJoin[] + } +}> => { + const tags = await getExplorerTags(knex) + const tagsBySlug = keyBy(tags, "slug") + return knexRaw( + `-- sql + SELECT + slug, + JSON_UNQUOTE(JSON_EXTRACT(config, "$.explorerTitle")) as title, + JSON_UNQUOTE(JSON_EXTRACT(config, "$.explorerSubtitle")) as subtitle + FROM + explorers + WHERE + isPublished = TRUE`, + knex + ).then((rows) => { + const processed = rows.map((row: any) => { + return { + slug: row.slug, + title: row.title, + subtitle: row.subtitle === "null" ? "" : row.subtitle, + tags: tagsBySlug[row.slug]?.tags ?? [], + } + }) + return keyBy(processed, "slug") + }) +} diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 98a6f23b59b..cd70c1e18dd 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -9,6 +9,7 @@ import { ManyToMany, JoinTable, } from "typeorm" +import * as db from "../../db" import { getUrlTarget } from "@ourworldindata/components" import { LinkedChart, @@ -549,9 +550,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { } } - async loadLinkedCharts( - publishedExplorersBySlug: Record - ): Promise { + async loadLinkedCharts(): Promise { const slugToIdMap = await Chart.mapSlugsToIds() const linkedGrapherCharts = await Promise.all( [...this.linkedChartSlugs.grapher.values()].map( @@ -567,12 +566,17 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { title: resolvedTitle, resolvedUrl: `${BAKED_GRAPHER_URL}/${resolvedSlug}`, thumbnail: `${BAKED_GRAPHER_EXPORTS_BASE_URL}/${resolvedSlug}.svg`, + tags: [], } return linkedChart } ) ).then(excludeNullish) + const publishedExplorersBySlug = await db.getPublishedExplorersBySlug( + db.knexInstance() + ) + const linkedExplorerCharts = await Promise.all( [...this.linkedChartSlugs.explorer.values()].map((originalSlug) => { const explorer = publishedExplorersBySlug[originalSlug] @@ -580,9 +584,10 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { const linkedChart: LinkedChart = { // we are assuming explorer slugs won't change originalSlug, - title: explorer?.explorerTitle ?? "", + title: explorer?.title ?? "", resolvedUrl: `${BAKED_BASE_URL}/${EXPLORERS_ROUTE_FOLDER}/${originalSlug}`, thumbnail: `${BAKED_BASE_URL}/default-thumbnail.jpg`, + tags: explorer.tags, } return linkedChart }) @@ -641,9 +646,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { this.content = archieToEnriched(text, this._enrichSubclassContent) } - async validate( - publishedExplorersBySlug: Record - ): Promise { + async validate(): Promise { const filenameErrors: OwidGdocErrorMessage[] = this.filenames.reduce( ( errors: OwidGdocErrorMessage[], @@ -668,6 +671,9 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { ) const chartIdsBySlug = await Chart.mapSlugsToIds() + const publishedExplorersBySlug = await db.getPublishedExplorersBySlug( + db.knexInstance() + ) const linkErrors: OwidGdocErrorMessage[] = this.links.reduce( (errors: OwidGdocErrorMessage[], link): OwidGdocErrorMessage[] => { @@ -696,6 +702,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { }) } } + if (link.linkType === "explorer") { if (!publishedExplorersBySlug[link.target]) { errors.push({ @@ -714,14 +721,12 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { this.errors = [...filenameErrors, ...linkErrors, ...subclassErrors] } - async loadState( - publishedExplorersBySlug: Record - ): Promise { + async loadState(): Promise { await this.loadLinkedDocuments() await this.loadImageMetadata() - await this.loadLinkedCharts(publishedExplorersBySlug) + await this.loadLinkedCharts() await this._loadSubclassAttachments() - await this.validate(publishedExplorersBySlug) + await this.validate() } toJSON(): T { diff --git a/db/model/Gdoc/GdocFactory.ts b/db/model/Gdoc/GdocFactory.ts index 10f96f1114b..78cf0d1a802 100644 --- a/db/model/Gdoc/GdocFactory.ts +++ b/db/model/Gdoc/GdocFactory.ts @@ -54,10 +54,7 @@ export class GdocFactory { .exhaustive() } - static async create( - id: string, - publishedExplorersBySlug: Record - ): Promise { + static async create(id: string): Promise { // Fetch the data from Google Docs and save it to the database // We have to fetch it here because we need to know the type of the Gdoc in this.load() const base = new GdocBase(id) @@ -66,21 +63,14 @@ export class GdocFactory { // Load its metadata and state so that subclass parsing & validation is also done. // This involves a second call to the DB and Google, which makes me sad, but it'll do for now. - const gdoc = await this.load( - id, - publishedExplorersBySlug, - GdocsContentSource.Gdocs - ) + const gdoc = await this.load(id, GdocsContentSource.Gdocs) await gdoc.save() return gdoc } - static async loadBySlug( - slug: string, - publishedExplorersBySlug: Record - ): Promise { + static async loadBySlug(slug: string): Promise { const base = await GdocBase.findOne({ where: { slug, published: true }, }) @@ -89,14 +79,13 @@ export class GdocFactory { `No published Google Doc with slug "${slug}" found in the database` ) } - return this.load(base.id, publishedExplorersBySlug) + return this.load(base.id) } // From an ID, get a Gdoc object with all its metadata and state loaded, in its correct subclass. // If contentSource is Gdocs, use live data from Google, otherwise use the data in the DB. static async load( id: string, - publishedExplorersBySlug: Record, contentSource?: GdocsContentSource ): Promise { const base = await GdocBase.findOne({ @@ -138,7 +127,7 @@ export class GdocFactory { await gdoc.fetchAndEnrichGdoc() } - await gdoc.loadState(publishedExplorersBySlug) + await gdoc.loadState() return gdoc } diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index c3531711b57..d606227e452 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -11,6 +11,7 @@ import { RawBlockText, RefDictionary, } from "./ArchieMlComponents.js" +import { DbChartTagJoin } from "../dbTypes/ChartTags.js" export enum OwidGdocPublicationContext { unlisted = "unlisted", @@ -23,6 +24,7 @@ export interface LinkedChart { resolvedUrl: string title: string thumbnail?: string + tags: DbChartTagJoin[] } export enum OwidGdocType { From 22c4d142067700ea36f3b5392e96570281a90868 Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Mon, 12 Feb 2024 23:46:10 +0000 Subject: [PATCH 07/14] =?UTF-8?q?=F0=9F=8E=89=20explorer=20tile=20text=20w?= =?UTF-8?q?rap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/ExplorerTiles.scss | 1 + site/gdocs/components/ExplorerTiles.tsx | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/site/gdocs/components/ExplorerTiles.scss b/site/gdocs/components/ExplorerTiles.scss index e1a6d3b871a..c32f0e5d269 100644 --- a/site/gdocs/components/ExplorerTiles.scss +++ b/site/gdocs/components/ExplorerTiles.scss @@ -48,6 +48,7 @@ } .explorer-tile__title, .explorer-tile__suffix { + display: inline; margin: 0; } .explorer-tile__title { diff --git a/site/gdocs/components/ExplorerTiles.tsx b/site/gdocs/components/ExplorerTiles.tsx index abd1d674cc2..78c0b4f2ca4 100644 --- a/site/gdocs/components/ExplorerTiles.tsx +++ b/site/gdocs/components/ExplorerTiles.tsx @@ -1,12 +1,14 @@ import { EnrichedBlockExplorerTiles } from "@ourworldindata/types" -import React from "react" +import React, { useContext } from "react" import { useLinkedChart } from "../utils.js" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faArrowRight } from "@fortawesome/free-solid-svg-icons" +import { DocumentContext } from "../OwidGdoc.js" function ExplorerTile({ url }: { url: string }) { const { linkedChart, errorMessage } = useLinkedChart(url) - if (errorMessage) { + const { isPreviewing } = useContext(DocumentContext) + if (errorMessage && isPreviewing) { return

    {errorMessage}

    } if (!linkedChart) { @@ -18,10 +20,11 @@ function ExplorerTile({ url }: { url: string }) { href={linkedChart.resolvedUrl} >
    + {/* TODO: add tag icon img */}

    {linkedChart.title}

    -

    Data Explorer

    +

    Data Explorer

    ) From 37e7c98a75fc2e431acfaacbd37f890b74c3c808 Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Tue, 13 Feb 2024 00:10:51 +0000 Subject: [PATCH 08/14] =?UTF-8?q?=E2=9C=85=20fix=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adminSiteServer/apiRouter.ts | 3 --- adminSiteServer/app.tsx | 2 -- baker/GrapherBaker.tsx | 1 - datapage/Datapage.ts | 1 - site/gdocs/components/ArticleBlock.tsx | 2 +- 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index 8756abe6683..8be002f9e29 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -77,7 +77,6 @@ import { DeployQueueServer } from "../baker/DeployQueueServer.js" import { FunctionalRouter } from "./FunctionalRouter.js" import { escape } from "mysql" import Papa from "papaparse" -import { ExplorerAdminServer } from "../explorerAdminServer/ExplorerAdminServer.js" import { postsTable, setTagsForPost, @@ -92,12 +91,10 @@ import { dataSource } from "../db/dataSource.js" import { createGdocAndInsertOwidGdocPostContent } from "../db/model/Gdoc/archieToGdoc.js" import { Link } from "../db/model/Link.js" import { In } from "typeorm" -import { GIT_CMS_DIR } from "../gitCms/GitCmsConstants.js" import { logErrorAndMaybeSendToBugsnag } from "../serverUtils/errorLog.js" import { GdocFactory } from "../db/model/Gdoc/GdocFactory.js" const apiRouter = new FunctionalRouter() -const explorerAdminServer = new ExplorerAdminServer(GIT_CMS_DIR) // Call this to trigger build and deployment of static charts on change const triggerStaticBuild = async (user: CurrentUser, commitMessage: string) => { diff --git a/adminSiteServer/app.tsx b/adminSiteServer/app.tsx index 579a1e0f202..21e00b4c7b8 100644 --- a/adminSiteServer/app.tsx +++ b/adminSiteServer/app.tsx @@ -31,7 +31,6 @@ import { mockSiteRouter } from "./mockSiteRouter.js" import { GIT_CMS_DIR } from "../gitCms/GitCmsConstants.js" import { GdocsContentSource } from "@ourworldindata/utils" import OwidGdocPage from "../site/gdocs/OwidGdocPage.js" -import { ExplorerAdminServer } from "../explorerAdminServer/ExplorerAdminServer.js" import { GdocFactory } from "../db/model/Gdoc/GdocFactory.js" interface OwidAdminAppOptions { @@ -124,7 +123,6 @@ export class OwidAdminApp { ) }) - const adminExplorerServer = new ExplorerAdminServer(GIT_CMS_DIR) // Public preview of a Gdoc document app.get("/gdocs/:id/preview", async (req, res) => { try { diff --git a/baker/GrapherBaker.tsx b/baker/GrapherBaker.tsx index 01af44f259a..ad992c22d8c 100644 --- a/baker/GrapherBaker.tsx +++ b/baker/GrapherBaker.tsx @@ -54,7 +54,6 @@ import { getMergedGrapherConfigForVariable, } from "../db/model/Variable.js" import { getDatapageDataV2, getDatapageGdoc } from "../datapage/Datapage.js" -import { ExplorerProgram } from "../explorer/ExplorerProgram.js" import { Image } from "../db/model/Image.js" import { logErrorAndMaybeSendToBugsnag } from "../serverUtils/errorLog.js" diff --git a/datapage/Datapage.ts b/datapage/Datapage.ts index 836f51cdde3..5470e246fcc 100644 --- a/datapage/Datapage.ts +++ b/datapage/Datapage.ts @@ -11,7 +11,6 @@ import { getNextUpdateFromVariable, omitUndefinedValues, } from "@ourworldindata/utils" -import { ExplorerProgram } from "../explorer/ExplorerProgram.js" import { GdocPost } from "../db/model/Gdoc/GdocPost.js" import { GdocFactory } from "../db/model/Gdoc/GdocFactory.js" import { OwidGoogleAuth } from "../db/OwidGoogleAuth.js" diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index ed715022fec..b0c0b4a4a9c 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -20,7 +20,7 @@ import { convertHeadingTextToId } from "@ourworldindata/components" import SDGGrid from "./SDGGrid.js" import { BlockErrorBoundary, BlockErrorFallback } from "./BlockErrorBoundary.js" import { match } from "ts-pattern" -import { renderSpans, useLinkedChart } from "../utils.js" +import { renderSpans } from "../utils.js" import Paragraph from "./Paragraph.js" import TableOfContents from "./TableOfContents.js" import urlSlug from "url-slug" From 7cd638b7c06c0479ca6db2f69c79f2fbfafc58cb Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Tue, 13 Feb 2024 18:30:16 -0500 Subject: [PATCH 09/14] =?UTF-8?q?=E2=9C=A8=20explorer=20code=20review=20im?= =?UTF-8?q?provements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adminSiteServer/adminRouter.tsx | 8 +++----- adminSiteServer/apiRouter.ts | 2 +- db/migration/1707502831161-ExplorerTags.ts | 4 ++-- explorer/Explorer.tsx | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/adminSiteServer/adminRouter.tsx b/adminSiteServer/adminRouter.tsx index d7850b2bef3..dd92e267614 100644 --- a/adminSiteServer/adminRouter.tsx +++ b/adminSiteServer/adminRouter.tsx @@ -254,11 +254,9 @@ adminRouter.get(`/${GetAllExplorersRoute}`, async (req, res) => { res.send(await explorerAdminServer.getAllExplorersCommand()) }) -adminRouter.get(`/${GetAllExplorersTagsRoute}`, async (req, res) => { - const explorers = await db.getExplorerTags(db.knexInstance()) - - res.send({ - explorers, +adminRouter.get(`/${GetAllExplorersTagsRoute}`, async (_, res) => { + return res.send({ + explorers: await db.getExplorerTags(db.knexInstance()), }) }) diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index 8be002f9e29..92d637ff4a4 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -2604,7 +2604,7 @@ apiRouter.post("/explorer/:slug/tags", async (req: Request, res: Response) => { if (!explorer) throw new JsonError(`No explorer found for slug ${slug}`, 404) - db.knexInstance().transaction(async (t) => { + await db.knexInstance().transaction(async (t) => { await t.table("explorer_tags").where({ explorerSlug: slug }).delete() for (const tagId of tagIds) { await t.table("explorer_tags").insert({ explorerSlug: slug, tagId }) diff --git a/db/migration/1707502831161-ExplorerTags.ts b/db/migration/1707502831161-ExplorerTags.ts index 2cf8fc8bda6..903ce8aa54c 100644 --- a/db/migration/1707502831161-ExplorerTags.ts +++ b/db/migration/1707502831161-ExplorerTags.ts @@ -2,7 +2,7 @@ import { MigrationInterface, QueryRunner } from "typeorm" export class ExplorerTags1707502831161 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - queryRunner.query(` + await queryRunner.query(` CREATE TABLE explorer_tags ( id INT AUTO_INCREMENT PRIMARY KEY, explorerSlug VARCHAR(150) NOT NULL, @@ -15,7 +15,7 @@ export class ExplorerTags1707502831161 implements MigrationInterface { } public async down(queryRunner: QueryRunner): Promise { - queryRunner.query(` + await queryRunner.query(` DROP TABLE IF EXISTS explorer_tags; `) } diff --git a/explorer/Explorer.tsx b/explorer/Explorer.tsx index 7a973de0524..69f28e1c52a 100644 --- a/explorer/Explorer.tsx +++ b/explorer/Explorer.tsx @@ -799,7 +799,7 @@ export class Explorer Download this dataset From 0a4f98d5ff653119fbba31d516b8d9bebf8cc9ad Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Tue, 13 Feb 2024 18:30:29 -0500 Subject: [PATCH 10/14] =?UTF-8?q?=E2=9C=A8=20use=20explorer=20subtitles=20?= =?UTF-8?q?in=20prominent=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/GdocBase.ts | 1 + packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts | 1 + site/gdocs/components/ProminentLink.tsx | 1 + 3 files changed, 3 insertions(+) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index cd70c1e18dd..f769be134b6 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -585,6 +585,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { // we are assuming explorer slugs won't change originalSlug, title: explorer?.title ?? "", + subtitle: explorer?.subtitle ?? "", resolvedUrl: `${BAKED_BASE_URL}/${EXPLORERS_ROUTE_FOLDER}/${originalSlug}`, thumbnail: `${BAKED_BASE_URL}/default-thumbnail.jpg`, tags: explorer.tags, diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index d606227e452..1cef9c66be0 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -23,6 +23,7 @@ export interface LinkedChart { originalSlug: string resolvedUrl: string title: string + subtitle?: string thumbnail?: string tags: DbChartTagJoin[] } diff --git a/site/gdocs/components/ProminentLink.tsx b/site/gdocs/components/ProminentLink.tsx index 0ce78cba5c4..f2ead43cd08 100644 --- a/site/gdocs/components/ProminentLink.tsx +++ b/site/gdocs/components/ProminentLink.tsx @@ -60,6 +60,7 @@ export const ProminentLink = (props: { href = `${linkedChart?.resolvedUrl}` title = title ?? `${linkedChart?.title} Data Explorer` thumbnail = thumbnail ?? linkedChart?.thumbnail + description = description ?? linkedChart?.subtitle } const Thumbnail = ({ thumbnail }: { thumbnail: string }) => { From 7ad826538a893ff5600fa931d0cee683b6e4e753 Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Tue, 13 Feb 2024 18:32:48 -0500 Subject: [PATCH 11/14] =?UTF-8?q?=E2=9C=A8=20use=20better=20key=20for=20Ex?= =?UTF-8?q?plorerTile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/ExplorerTiles.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/gdocs/components/ExplorerTiles.tsx b/site/gdocs/components/ExplorerTiles.tsx index 78c0b4f2ca4..b1cd1eda03c 100644 --- a/site/gdocs/components/ExplorerTiles.tsx +++ b/site/gdocs/components/ExplorerTiles.tsx @@ -56,8 +56,8 @@ export function ExplorerTiles({ {subtitle}

    - {explorers.map((explorer, i) => ( - + {explorers.map((explorer) => ( + ))}
    From 4b543764df07d28b340e3a1bb6e213d1909224dd Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Wed, 14 Feb 2024 12:40:55 -0500 Subject: [PATCH 12/14] =?UTF-8?q?=E2=9C=85=20fix=20explorer-tiles=20roundt?= =?UTF-8?q?rip=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/enrichedToRaw.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 9716631f298..f1a0806551f 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -425,7 +425,11 @@ export function enrichedBlockToRawBlock( .with({ type: "explorer-tiles" }, (b): RawBlockExplorerTiles => { return { type: "explorer-tiles", - value: b, + value: { + title: b.title, + subtitle: b.subtitle, + explorers: b.explorers, + }, } }) .with({ type: "blockquote" }, (b): RawBlockBlockquote => { From 38875ca130d968c45064b29bfeff5025bf60df8e Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Wed, 14 Feb 2024 16:45:49 -0500 Subject: [PATCH 13/14] =?UTF-8?q?=E2=9C=A8=20enhance=20explorer=20tiles=20?= =?UTF-8?q?CSS,=20add=20tag=20icon=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/src/styles/colors.scss | 1 + site/gdocs/components/ExplorerTiles.scss | 26 ++++++++++++------- site/gdocs/components/ExplorerTiles.tsx | 15 ++++++++--- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/@ourworldindata/components/src/styles/colors.scss b/packages/@ourworldindata/components/src/styles/colors.scss index ca523f2f065..7fe5ff77b2a 100644 --- a/packages/@ourworldindata/components/src/styles/colors.scss +++ b/packages/@ourworldindata/components/src/styles/colors.scss @@ -11,6 +11,7 @@ $amber: #f7c020; $blue-100: #002147; $blue-90: #1d3d63; $blue-60: #577291; +$blue-55: #46688f; $blue-50: #6e87a2; $blue-40: #98a9bd; $blue-30: #a4b6ca; diff --git a/site/gdocs/components/ExplorerTiles.scss b/site/gdocs/components/ExplorerTiles.scss index c32f0e5d269..424d8f7e913 100644 --- a/site/gdocs/components/ExplorerTiles.scss +++ b/site/gdocs/components/ExplorerTiles.scss @@ -22,33 +22,41 @@ color: $accent-vermillion; border-color: $accent-vermillion; } - @include md-down { + @include sm-only { justify-self: start; margin-top: 16px; order: 4; - } - @include sm-only { text-align: center; width: 100%; } } .explorer-tiles-grid { row-gap: var(--grid-gap); + @include md-down { + grid-template-rows: repeat(2, 1fr); + column-gap: var(--grid-gap); + } } .explorer-tile { - height: 130px; background-image: url("/explorer-thumbnail.webp"); background-size: cover; - position: relative; + padding: 16px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + } + .explorer-tile__icon { + background-color: $blue-55; + border-radius: 50%; + width: 40px; + height: 40px; } .explorer-tile__text-container { - position: absolute; - bottom: 16px; - left: 16px; + width: 100%; + margin-top: 18px; } .explorer-tile__title, .explorer-tile__suffix { - display: inline; margin: 0; } .explorer-tile__title { diff --git a/site/gdocs/components/ExplorerTiles.tsx b/site/gdocs/components/ExplorerTiles.tsx index b1cd1eda03c..02e5f837430 100644 --- a/site/gdocs/components/ExplorerTiles.tsx +++ b/site/gdocs/components/ExplorerTiles.tsx @@ -14,13 +14,22 @@ function ExplorerTile({ url }: { url: string }) { if (!linkedChart) { return null } + const icon = linkedChart.tags[0] ? ( + + ) : null + return (
    + {icon}
    - {/* TODO: add tag icon img */}

    {linkedChart.title}

    @@ -46,13 +55,13 @@ export function ExplorerTiles({ {title}
    See all our charts and explorers{" "} -

    +

    {subtitle}

    From 08806e76c5de77eef5b77b088f5550ce4f7ced5f Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Thu, 15 Feb 2024 22:27:21 +0000 Subject: [PATCH 14/14] =?UTF-8?q?=E2=9C=A8=20explorer-tiles=20code=20revie?= =?UTF-8?q?w=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/db.ts | 4 ++-- db/model/Gdoc/GdocBase.ts | 2 +- db/model/Gdoc/rawToEnriched.ts | 2 +- public/explorer-thumbnail.webp | Bin 40772 -> 23468 bytes site/gdocs/components/ExplorerTiles.scss | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/db/db.ts b/db/db.ts index 652c915c122..b94d17ba978 100644 --- a/db/db.ts +++ b/db/db.ts @@ -185,8 +185,8 @@ export const getPublishedExplorersBySlug = async ( `-- sql SELECT slug, - JSON_UNQUOTE(JSON_EXTRACT(config, "$.explorerTitle")) as title, - JSON_UNQUOTE(JSON_EXTRACT(config, "$.explorerSubtitle")) as subtitle + config->>"$.explorerTitle" as title, + config->>"$.explorerSubtitle" as subtitle FROM explorers WHERE diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index f769be134b6..115b7ba8bd4 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -578,7 +578,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { ) const linkedExplorerCharts = await Promise.all( - [...this.linkedChartSlugs.explorer.values()].map((originalSlug) => { + this.linkedChartSlugs.explorer.map((originalSlug) => { const explorer = publishedExplorersBySlug[originalSlug] if (!explorer) return const linkedChart: LinkedChart = { diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index ec3d1f8d785..5bea46de024 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -1817,7 +1817,7 @@ function parseExplorerTiles( if (!raw.value.subtitle) return createError({ message: "Explorer tiles missing subtitle" }) - if (!raw.value.explorers || !raw.value.explorers.length) + if (!raw.value.explorers?.length) return createError({ message: "Explorer tiles missing explorers" }) const parsedExplorerUrls: { url: string }[] = [] diff --git a/public/explorer-thumbnail.webp b/public/explorer-thumbnail.webp index f416d77d3587f9dd8b049749f59341a143ee891a..0974a675b3d871ca7a059762fc59255fe06d7919 100644 GIT binary patch literal 23468 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 literal 40772 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 YbKY*DsPh(2rz$ZD9TJ-ScIbdf6J&!(e*gdg diff --git a/site/gdocs/components/ExplorerTiles.scss b/site/gdocs/components/ExplorerTiles.scss index 424d8f7e913..e592dca19d5 100644 --- a/site/gdocs/components/ExplorerTiles.scss +++ b/site/gdocs/components/ExplorerTiles.scss @@ -43,7 +43,7 @@ padding: 16px; display: flex; flex-wrap: wrap; - justify-content: space-between; + align-content: space-between; } .explorer-tile__icon { background-color: $blue-55;