Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ _templates
# favicons
/public/favicons/*

# og images
/public/og/*

/files/**/out/

#Python
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import { useConfig } from "@databiosphere/findable-ui/lib/hooks/useConfig";
import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography";
import { JSX } from "react";
import { SiteConfig } from "../../../../../../../../../site-config/common/entities";
import { StyledBackPageHeroActions } from "./backPageHeroActions.styles";

export interface BackPageHeroActionsProps {
Expand All @@ -21,7 +20,7 @@ export const BackPageHeroActions = ({
callToActionProps,
linkProps,
}: BackPageHeroActionsProps): JSX.Element => {
const { config } = useConfig() as { config: SiteConfig };
const { config } = useConfig() as { config: { portalURL?: string } };
const { getURL, label, ...otherProps } = linkProps || {};
const linkUrl = getURL?.(config.portalURL);
return (
Expand Down
70 changes: 70 additions & 0 deletions app/components/common/OgMeta/ogMeta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import NextHead from "next/head";
import { useRouter } from "next/router";
import { JSX } from "react";
import type { OgMetaProps } from "./types";

/**
* Builds the canonical path from the router's asPath, stripping query and hash.
* @param asPath - The router's asPath value.
* @returns clean path.
*/
function buildPath(asPath: string): string {
return asPath.split("?")[0].split("#")[0];
}

/**
* Builds the OG title from the page title and app title.
* @param appTitle - The application title.
* @param pageTitle - The page-specific title.
* @returns formatted title.
*/
function buildTitle(appTitle: string, pageTitle?: string | null): string {
if (pageTitle && pageTitle !== appTitle) {
return `${pageTitle} - ${appTitle}`;
}
return appTitle;
}

/**
* Renders Open Graph and Twitter meta tags for rich link sharing.
* @param props - The component props.
* @param props.appTitle - The application title.
* @param props.browserURL - The site's base URL.
* @param props.defaultDescription - Fallback description when no page description is provided.
* @param props.pageDescription - Page-specific description.
* @param props.pageTitle - Page-specific title.
* @returns head element with meta tags.
*/
export const OgMeta = ({
appTitle,
browserURL,
defaultDescription,
pageDescription,
pageTitle,
}: OgMetaProps): JSX.Element => {
const { asPath } = useRouter();
const description = pageDescription || defaultDescription;
const image = `${browserURL}/og/og-image.png`;
const path = buildPath(asPath);
const title = buildTitle(appTitle, pageTitle);
const url = `${browserURL}${path}`;
return (
<NextHead>
<meta key="description" content={description} name="description" />
<meta
key="og:description"
content={description}
property="og:description"
/>
<meta key="og:image" content={image} property="og:image" />
<meta key="og:image:height" content="512" property="og:image:height" />
<meta key="og:image:width" content="512" property="og:image:width" />
<meta key="og:site_name" content={appTitle} property="og:site_name" />
<meta key="og:title" content={title} property="og:title" />
<meta key="og:type" content="website" property="og:type" />
<meta key="og:url" content={url} property="og:url" />
<meta key="twitter:card" content="summary" name="twitter:card" />
<meta key="twitter:image" content={image} name="twitter:image" />
</NextHead>
);
};
7 changes: 7 additions & 0 deletions app/components/common/OgMeta/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface OgMetaProps {
appTitle: string;
browserURL: string;
defaultDescription: string;
pageDescription?: string | null;
pageTitle?: string | null;
}
2 changes: 1 addition & 1 deletion app/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { setConfig } from "@databiosphere/findable-ui/lib/config/config";
import { SiteConfig } from "@databiosphere/findable-ui/lib/config/entities";
import anvilCatalogDev from "../../site-config/anvil-catalog/dev/config";
import anvilCatalogProd from "../../site-config/anvil-catalog/prod/config";
import anvilCmgCCDev from "../../site-config/anvil-cmg/cc-dev/config";
import anvilCmgDev from "../../site-config/anvil-cmg/dev/config";
import anvilCmgProd from "../../site-config/anvil-cmg/prod/config";
import anvilCmgTempdev from "../../site-config/anvil-cmg/tempdev/config";
import { SiteConfig } from "../../site-config/common/entities";
import hcaDcpCCMaDev from "../../site-config/hca-dcp/cc-ma-dev/config";
import hcaDcpDev from "../../site-config/hca-dcp/dev/config";
import hcaDcpMaDev from "../../site-config/hca-dcp/ma-dev/config";
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/beta-announcement.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "Beta Announcement"
pageDescription: "AnVIL Data Explorer beta launch announcement and new features."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/ga-announcement.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "GA Announcement"
pageDescription: "AnVIL Data Explorer general availability announcement."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/guides.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "Guides"
pageDescription: "Guides for downloading and accessing data from the AnVIL Data Explorer."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/guides/data-download-options.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "Data Download Options"
pageDescription: "Overview of data download options available in the AnVIL Data Explorer."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/guides/data-download-via-curl.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "Data Download via curl"
pageDescription: "Download datasets using the curl command line tool."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/guides/individual-file-download.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "Individual File Download"
pageDescription: "Download individual files directly from the AnVIL Data Explorer."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/guides/tsv-file-manifest-download.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "TSV File Manifest Download"
pageDescription: "Export a TSV file manifest with download URLs from the AnVIL Data Explorer."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/privacy.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "Privacy Policy"
pageDescription: "Privacy policy for this data explorer."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/anvil-cmg/terms-of-service.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageTitle: "Terms of Service"
pageDescription: "Terms of service for this data explorer."
---

<Breadcrumbs
breadcrumbs={[
{ path: "/datasets", text: "AnVIL Data Explorer" },
Expand Down
9 changes: 5 additions & 4 deletions app/content/common/contentPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import matter from "gray-matter";
import { GetStaticPropsContext, GetStaticPropsResult } from "next";
import { serialize } from "next-mdx-remote/serialize";
import { MDX_SCOPE } from "./constants";
import { ContentProps } from "./entities";
import { ContentFrontmatter, ContentProps } from "./entities";
import {
getContentPathname,
getMarkdownPathname,
Expand All @@ -13,8 +13,7 @@ import {
} from "./utils";

export async function getContentStaticProps(
context: GetStaticPropsContext,
pageTitle: string
context: GetStaticPropsContext
): Promise<GetStaticPropsResult<ContentProps>> {
const slug = getSlug(context);
const contentPathname = getContentPathname();
Expand All @@ -27,7 +26,8 @@ export async function getContentStaticProps(
}
const markdownPathname = getMarkdownPathname(contentPathname, slug);
const markdownWithMeta = fs.readFileSync(markdownPathname, "utf-8");
const { content } = matter(markdownWithMeta);
const { content, data } = matter(markdownWithMeta);
const { pageDescription, pageTitle } = data as ContentFrontmatter;
const mdxSource = await serialize(content, {
mdxOptions: {
development: process.env.NODE_ENV === "development",
Expand All @@ -40,6 +40,7 @@ export async function getContentStaticProps(
props: {
layoutStyle: LAYOUT_STYLE_NO_CONTRAST_DEFAULT,
mdxSource,
pageDescription: pageDescription ?? null,
pageTitle,
slug,
},
Expand Down
6 changes: 6 additions & 0 deletions app/content/common/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ export interface AnchorProps {
href: string;
}

export interface ContentFrontmatter {
pageDescription?: string;
pageTitle: string;
}

export interface ContentProps {
layoutStyle?: LayoutStyle;
mdxSource: MDXRemoteSerializeResult | null;
pageDescription?: string | null;
pageTitle: string;
slug: string[] | null;
}
5 changes: 5 additions & 0 deletions app/content/lungmap/apis.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageDescription: "REST API reference for querying LungMAP project, sample, and file metadata."
pageTitle: "APIs"
---

<Breadcrumbs
breadcrumbs={[
{ path: "/projects", text: "LungMAP Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/lungmap/metadata.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageDescription: "Metadata schema reference for LungMAP projects, based on the HCA Metadata Schema."
pageTitle: "Metadata Dictionary"
---

<Breadcrumbs
breadcrumbs={[
{ path: "/projects", text: "LungMAP Data Explorer" },
Expand Down
5 changes: 5 additions & 0 deletions app/content/lungmap/privacy.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
pageDescription: "Privacy notice for the LungMAP Data Browser."
pageTitle: "Privacy Policy"
---

<Breadcrumbs
breadcrumbs={[
{ path: "/projects", text: "LungMAP Data Explorer" },
Expand Down
44 changes: 43 additions & 1 deletion pages/[entityListType]/[...params].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ interface PageUrl extends ParsedUrlQuery {
export interface EntityDetailPageProps extends AzulEntityStaticResponse {
entityListType: string;
override?: Override;
pageDescription?: string | null;
pageTitle?: string | null;
}

/**
Expand Down Expand Up @@ -264,7 +266,11 @@ export const getStaticProps: GetStaticProps<AzulEntityStaticResponse> = async ({

if (!entityConfig || !entityId) return { notFound: true };

const props: EntityDetailPageProps = { entityListType };
const { label } = entityConfig;
const props: EntityDetailPageProps = {
entityListType,
pageTitle: typeof label === "string" ? label : null,
};

// Process entity override props.
processEntityOverrideProps(entityConfig, entityListType, entityId, props);
Expand All @@ -280,13 +286,49 @@ export const getStaticProps: GetStaticProps<AzulEntityStaticResponse> = async ({
props
);

props.pageTitle = buildEntityPageTitle(
entityConfig,
props.data,
entityTab,
entityId
);

return {
props,
};
};

export default EntityDetailPage;

/**
* Builds the entity detail page title from the fetched entity data plus the
* active detail tab. Falls back to the entity id when no entity title is
* available.
* @param entityConfig - Entity config providing getTitle and tab definitions.
* @param data - Fetched entity data (may be undefined).
* @param entityTab - The active detail tab route.
* @param entityId - The entity id used as a fallback when no title is found.
* @returns Formatted page title.
*/
function buildEntityPageTitle(
entityConfig: EntityConfig,
data: AzulEntityStaticResponse["data"],
entityTab: string | undefined,
entityId: string
): string {
const {
detail: { tabs },
getTitle,
} = entityConfig;

const entityTitle = getTitle?.(data);
const { label } = tabs.find(({ route }) => route === entityTab) || {};

const detailTitle = entityTitle || entityId;

return typeof label === "string" ? `${label} — ${detailTitle}` : detailTitle;
}

/**
* Returns the catalog prefix for the given default catalog.
* @param defaultCatalog - Default catalog.
Expand Down
13 changes: 7 additions & 6 deletions pages/[entityListType]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ interface PageUrl extends ParsedUrlQuery {

interface ListPageProps extends AzulEntitiesStaticResponse {
entityListType: string;
pageTitle?: string;
pageDescription?: string | null;
pageTitle?: string | null;
}

/**
Expand Down Expand Up @@ -98,12 +99,12 @@ export const getStaticProps: GetStaticProps<
const { exploreMode, label } = entityConfig;
const { fetchAllEntities } = getEntityService(entityConfig, undefined); // Determine the type of fetch, either from an API endpoint or a TSV.

let pageTitle;
if (typeof label === "string") {
pageTitle = label;
}
const pageTitle = typeof label === "string" ? label : null;
const pageDescription = pageTitle
? `Browse and explore ${pageTitle.toLowerCase()}.`
: null;

const props: ListPageProps = { entityListType, pageTitle };
const props: ListPageProps = { entityListType, pageDescription, pageTitle };

// Seed database.
if (exploreMode === EXPLORE_MODE.CS_FETCH_CS_FILTERING) {
Expand Down
Loading
Loading