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
1,986 changes: 1,986 additions & 0 deletions data/arabic/articles/clypd2823v5o.json

Large diffs are not rendered by default.

1,159 changes: 1,159 additions & 0 deletions data/arabic/topics/cz9mm6r1q5et.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { MediaBlock } from '#app/components/MediaLoader/types';
import type { Summary } from '#app/models/types/curationData';

export type InSituPlaybackIneligibilityReason =
| 'not-first-promo'
| 'not-media-article-promo'
| 'missing-media';

type EligibleInSituPlayback = {
isEligible: true;
mediaBlocks: MediaBlock[];
};

type IneligibleInSituPlayback = {
isEligible: false;
reason: InSituPlaybackIneligibilityReason;
};

type InSituPlaybackEligibility =
| EligibleInSituPlayback
| IneligibleInSituPlayback;

type Props = {
promo: Summary;
promoIndex: number;
mediaBlocks?: MediaBlock[] | null;
};

const isOptimoArticlePromo = (link?: string) =>
/\/articles\/c[a-zA-Z0-9]{10,}o/.test(link ?? '');

export default ({
promo,
promoIndex,
mediaBlocks,
}: Props): InSituPlaybackEligibility => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is this a blanket rule for every hieirachicalGrid view the first promo will show as in-situ playback.

It's not just if its the first curation on the page?

(would be uncomfortable with the latter as it starts to make the rendering very unpredictable)

if (promoIndex !== 0) {
return { isEligible: false, reason: 'not-first-promo' };
}

if (promo.type !== 'video' || !isOptimoArticlePromo(promo.link)) {
return { isEligible: false, reason: 'not-media-article-promo' };
}

if (!mediaBlocks?.length) {
return { isEligible: false, reason: 'missing-media' };
}

return { isEligible: true, mediaBlocks };
};
17 changes: 17 additions & 0 deletions src/app/components/Curation/HierarchicalGrid/index.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ const styles = {
},
},
}),
inSituPromo: () =>
css({
position: 'relative',
}),
inSituMedia: ({ spacings }: Theme) =>
css({
marginBottom: `${spacings.FULL}rem`,
'.media-container': {
margin: 0,
},
}),
headlineLink: () =>
css({
'&::before': {
display: 'none',
},
}),
list: ({ mq, spacings, isLite }: Theme) =>
css({
padding: 0,
Expand Down
186 changes: 111 additions & 75 deletions src/app/components/Curation/HierarchicalGrid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler';
import VisuallyHiddenText from '../../VisuallyHiddenText';
import formatDuration from '../../../lib/utilities/formatDuration';
import Promo from '../../../legacy/components/Promo';
import MediaLoader from '../../MediaLoader';
import { DESKTOP, TABLET, MOBILE, SMALL } from './dataStructures';
import { styles } from './index.styles';
import { ServiceContext } from '../../../contexts/ServiceContext';
import { CurationGridProps } from '../types';
import { Summary } from '../../../models/types/curationData';
import { RequestContext } from '../../../contexts/RequestContext';
import LiveLabel from '../../LiveLabel';
import getInSituPlaybackEligibility from './getInSituPlaybackEligibility';

const getStyles = (promoCount: number, i: number, mq: Theme['mq']) => {
return css({
Expand Down Expand Up @@ -44,9 +46,9 @@ const HiearchicalGrid = ({
const videoTranslation = path(['media', 'video'], translations);
const photoGalleryTranslation = path(['media', 'photogallery'], translations);
const durationTranslation = path(['media', 'duration'], translations);
if (!summaries || summaries.length < 3) return null;
const promoItems = summaries?.slice(0, 12) ?? [];

const promoItems = summaries.slice(0, 12);
if (!summaries || summaries.length < 3) return null;

const buildPromoEventTrackingData = (promo: Summary, i: number) => {
const itemTracker = {
Expand Down Expand Up @@ -93,6 +95,82 @@ const HiearchicalGrid = ({
const clickTrackerHandler = getClickTrackerHandler(
promoEventTrackingData,
);
const inSituPlaybackEligibility = getInSituPlaybackEligibility({
promo,
promoIndex: i,
mediaBlocks: promo.inSituMedia,
});
const eligibleInSituMediaBlocks =
!isAmp && inSituPlaybackEligibility.isEligible
? inSituPlaybackEligibility.mediaBlocks
: null;
const inSituMediaBlocks = eligibleInSituMediaBlocks;
Comment on lines +98 to +107
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a maintainability standpoint this logic definitely belongs in the BFF as it is pure business logic

const linkCss = inSituMediaBlocks ? styles.headlineLink : undefined;
const promoText = (
<>
<Promo.Heading
as={`h${headingLevel}`}
css={(theme: Theme) => ({
color: theme.palette.GREY_10,
...(i === 0 && theme.fontSizes.paragon),
})}
>
{isMedia ? (
<Promo.A
href={promo.link}
aria-labelledby={promo.id}
css={linkCss}
{...clickTrackerHandler}
>
<span id={promo.id} role="text">
<VisuallyHiddenText data-testid="visually-hidden-text">
{typeTranslated}
</VisuallyHiddenText>
<Promo.MediaIcon
className="inline-icon"
type={promo.type}
css={styles.inlineIcon}
/>
{promo.title}
{showDuration && (
<VisuallyHiddenText>
{durationString}
</VisuallyHiddenText>
)}
</span>
</Promo.A>
) : (
<Promo.A
href={promo.link}
css={linkCss}
{...clickTrackerHandler}
>
{isLive ? (
<LiveLabel
{...(isFirstPromo
? {
className: 'first-promo',
}
: undefined)}
>
{promo.title}
</LiveLabel>
) : (
promo.title
)}
</Promo.A>
)}
</Promo.Heading>
<Promo.Body className="promo-paragraph" css={styles.body}>
{promo.description}
</Promo.Body>
{!isLive ? (
<Promo.Timestamp className="promo-timestamp">
{promo.lastPublished}
</Promo.Timestamp>
) : null}
</>
);

return (
<li
Expand All @@ -102,79 +180,37 @@ const HiearchicalGrid = ({
getStyles(promoItems.length, i, mq),
]}
>
<Promo className="">
<Promo.Image
useLargeImages={useLargeImages}
src={promo.imageUrl || null}
alt={promo.imageAlt}
lazyLoad={lazyLoadImages}
fetchPriority={fetchpriority}
isAmp={isAmp}
isPortraitImage={promo.isPortraitImage}
>
{isMedia && (
<Promo.MediaIcon type={promo.type}>
{showDuration ? promo.duration : ''}
</Promo.MediaIcon>
)}
</Promo.Image>
<Promo.Heading
as={`h${headingLevel}`}
css={(theme: Theme) => ({
color: theme.palette.GREY_10,
...(i === 0 && theme.fontSizes.paragon),
})}
>
{isMedia ? (
<Promo.A
href={promo.link}
aria-labelledby={promo.id}
{...clickTrackerHandler}
>
<span id={promo.id} role="text">
<VisuallyHiddenText data-testid="visually-hidden-text">
{typeTranslated}
</VisuallyHiddenText>
<Promo.MediaIcon
className="inline-icon"
type={promo.type}
css={styles.inlineIcon}
/>
{promo.title}
{showDuration && (
<VisuallyHiddenText>
{durationString}
</VisuallyHiddenText>
)}
</span>
</Promo.A>
) : (
<Promo.A href={promo.link} {...clickTrackerHandler}>
{isLive ? (
<LiveLabel
{...(isFirstPromo
? {
className: 'first-promo',
}
: undefined)}
>
{promo.title}
</LiveLabel>
) : (
promo.title
)}
</Promo.A>
)}
</Promo.Heading>
<Promo.Body className="promo-paragraph" css={styles.body}>
{promo.description}
</Promo.Body>
{!isLive ? (
<Promo.Timestamp className="promo-timestamp">
{promo.lastPublished}
</Promo.Timestamp>
) : null}
</Promo>
{inSituMediaBlocks ? (
<div css={styles.inSituPromo}>
<div css={styles.inSituMedia}>
<MediaLoader
blocks={inSituMediaBlocks}
showMetadata={false}
uniqueId={`in-situ-${promo.id || i}`}
/>
</div>
<div className="promo-text">{promoText}</div>
</div>
) : (
<Promo className="">
<Promo.Image
useLargeImages={useLargeImages}
src={promo.imageUrl || null}
alt={promo.imageAlt}
lazyLoad={lazyLoadImages}
fetchPriority={fetchpriority}
isAmp={isAmp}
isPortraitImage={promo.isPortraitImage}
>
{isMedia && (
<Promo.MediaIcon type={promo.type}>
{showDuration ? promo.duration : ''}
</Promo.MediaIcon>
)}
</Promo.Image>
{promoText}
</Promo>
)}
</li>
);
})}
Expand Down
6 changes: 4 additions & 2 deletions src/app/components/MediaLoader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ type Props = {
blocks: MediaBlock[];
className?: string;
embedded?: boolean;
showMetadata?: boolean;
uniqueId?: string;
eventMapping?: EventMapping;
};
Expand All @@ -223,6 +224,7 @@ const MediaLoader = ({
blocks,
className,
embedded,
showMetadata = true,
uniqueId,
eventMapping,
}: Props) => {
Expand Down Expand Up @@ -297,8 +299,8 @@ const MediaLoader = ({
return (
<>
{
// Prevents the av-embeds route itself rendering the Metadata component
!embedded && (
// Prevents embedded and in-page promo players rendering page-level media metadata
showMetadata && !embedded && (
<Metadata blocks={blocks} embedURL={playerConfig?.externalEmbedUrl} />
)
}
Expand Down
2 changes: 2 additions & 0 deletions src/app/models/types/curationData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { OEmbedData } from '#app/components/Embeds/types';
import {
MediaCollection,
MediaBlock,
PortraitClipMediaBlock,
} from '#app/components/MediaLoader/types';
import { RadioScheduleData } from '#app/models/types/radioSchedule';
Expand Down Expand Up @@ -31,6 +32,7 @@ export interface Summary extends BaseSummary {
eventTrackingData?: EventTrackingData;
visualProminence?: VisualProminence | string;
isPortraitImage?: boolean;
inSituMedia?: MediaBlock[];
}

export const VISUAL_STYLE = {
Expand Down
10 changes: 9 additions & 1 deletion ws-nextjs-app/pages/[service]/homepage/handleHomepageRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ROUTING_INFORMATION } from '#app/lib/logger.const';
import PageDataParams from '#app/models/types/pageDataParams';
import handleError from '#app/routes/utils/handleError';
import getPageData from '../../../utilities/pageRequests/getPageData';
import enrichCurationsWithInSituPlayback from '../../../utilities/enrichCurationsWithInSituPlayback';

const logger = nodeLogger(__filename);

Expand Down Expand Up @@ -83,14 +84,21 @@ export default async (context: GetServerSidePropsContext) => {
pageType: HOME_PAGE,
});

const curations = await enrichCurationsWithInSituPlayback({
curations: pageData.curations ?? null,
service,
variant: variant || null,
rendererEnv,
});

return {
props: {
id: resolvedUrlWithoutQuery,
pageData: {
title: pageData.title ?? null,
seoTitle: pageData.seoTitle ?? null,
metadata: { ...pageData.metadata, type: HOME_PAGE },
curations: pageData.curations ?? null,
curations,
description: pageData.description ?? null,
seoDescription: pageData.seoDescription ?? null,
},
Expand Down
Loading
Loading