diff --git a/dotcom-rendering/fixtures/manual/trails.ts b/dotcom-rendering/fixtures/manual/trails.ts
index a6df1cfe974..5dd5b39feaf 100644
--- a/dotcom-rendering/fixtures/manual/trails.ts
+++ b/dotcom-rendering/fixtures/manual/trails.ts
@@ -71,7 +71,7 @@ export const trails: [
kickerText: 'Kicker',
},
],
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -91,7 +91,7 @@ export const trails: [
mediaDuration: 378,
dataLinkName: 'news | group-0 | card-@2',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -110,7 +110,7 @@ export const trails: [
kickerText: 'Live',
dataLinkName: 'news | group-0 | card-@3',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -128,7 +128,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@4',
showQuotedHeadline: true,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -146,7 +146,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@5',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -165,7 +165,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@6',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -185,7 +185,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@7',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -204,7 +204,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@8',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -225,7 +225,7 @@ export const trails: [
'UK Covid live: England lockdown to be eased in stages, says PM, amid reports of nationwide mass testing',
dataLinkName: 'news | group-0 | card-@9',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -245,7 +245,7 @@ export const trails: [
'UK to infect up to 90 healthy volunteers with Covid in world first trial',
dataLinkName: 'news | group-0 | card-@10',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -265,7 +265,7 @@ export const trails: [
'Scottish government inadequately prepared for Covid, says watchdog',
dataLinkName: 'news | group-0 | card-@11',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -285,7 +285,7 @@ export const trails: [
'‘Encouraging’ signs for Covid vaccine as over-80s deaths fall in England',
dataLinkName: 'news | group-0 | card-@12',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -305,7 +305,7 @@ export const trails: [
'Contact tracing alone has little impact on curbing Covid spread, report finds',
dataLinkName: 'news | group-0 | card-@1',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -325,7 +325,7 @@ export const trails: [
'Ethnicity and poverty are Covid risk factors, new Oxford modelling tool shows',
dataLinkName: 'news | group-0 | card-@13',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -345,7 +345,7 @@ export const trails: [
'UK Covid: 799 more deaths and 10,625 new cases reported; Scottish schools in phased return from Monday – as it happened',
dataLinkName: 'news | group-0 | card-@14',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -365,7 +365,7 @@ export const trails: [
'QCovid: how improved algorithm can identify more higher-risk adults',
dataLinkName: 'news | group-0 | card-@1',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -383,7 +383,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@15',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -402,7 +402,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@16',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -421,7 +421,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@17',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
@@ -440,7 +440,7 @@ export const trails: [
},
dataLinkName: 'news | group-0 | card-@18',
showQuotedHeadline: false,
- showMainVideo: false,
+ mainVideo: undefined,
isExternalLink: false,
showLivePlayable: false,
},
diff --git a/dotcom-rendering/makefile b/dotcom-rendering/makefile
index a2c8c01ee64..1350d204b83 100644
--- a/dotcom-rendering/makefile
+++ b/dotcom-rendering/makefile
@@ -200,6 +200,7 @@ gen-schema:
@git add src/model/article-schema.json
@git add src/model/front-schema.json
@git add src/model/block-schema.json
+ @git add src/model/tag-front-schema.json
check-stories:
$(call log, "Checking Storybook stories")
diff --git a/dotcom-rendering/src/components/Card/Card.stories.tsx b/dotcom-rendering/src/components/Card/Card.stories.tsx
index eabc96330ae..8773ebe0f5b 100644
--- a/dotcom-rendering/src/components/Card/Card.stories.tsx
+++ b/dotcom-rendering/src/components/Card/Card.stories.tsx
@@ -7,6 +7,7 @@ import {
} from '@guardian/libs';
import { from } from '@guardian/source-foundations';
import React from 'react';
+import type { CardYoutubeVideo } from '../../types/video';
import { Section } from '../Section';
import type { Props as CardProps } from './Card';
import { Card } from './Card';
@@ -43,6 +44,15 @@ const aBasicLink = {
},
};
+const mainVideo: CardYoutubeVideo = {
+ type: 'embedded-playable-video',
+ elementId: 'some-atom-id',
+ videoId: 'abcd-01234',
+ width: 500,
+ height: 300,
+ origin: 'The Guardian',
+};
+
const CardWrapper = ({ children }: { children: React.ReactNode }) => {
return (
{
imagePositionOnMobile="top"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={mainVideo}
/>
@@ -938,7 +948,7 @@ export const WhenVideoWithPlayButton = () => {
imagePositionOnMobile="top"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={mainVideo}
/>
@@ -952,7 +962,7 @@ export const WhenVideoWithPlayButton = () => {
imagePosition="top"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={{ type: 'show-only-icon' }}
/>
@@ -970,7 +980,7 @@ export const WhenVideoWithPlayButton = () => {
imagePositionOnMobile="bottom"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={mainVideo}
/>
@@ -986,7 +996,7 @@ export const WhenVideoWithPlayButton = () => {
imagePosition="left"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={{ type: 'show-only-icon' }}
/>
@@ -1000,7 +1010,7 @@ export const WhenVideoWithPlayButton = () => {
imagePosition="right"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={{ type: 'show-only-icon' }}
/>
@@ -1015,7 +1025,7 @@ export const WhenVideoWithPlayButton = () => {
imagePosition="right"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={{ type: 'show-only-icon' }}
/>
@@ -1036,7 +1046,7 @@ export const WhenVideoWithPlayButton = () => {
imagePositionOnMobile="top"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={mainVideo}
/>
@@ -1052,7 +1062,7 @@ export const WhenVideoWithPlayButton = () => {
imageSize="medium"
mediaDuration={200}
mediaType="Video"
- showMainVideo={true}
+ mainVideo={mainVideo}
/>
diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx
index 33bddb33a58..e09e096a69c 100644
--- a/dotcom-rendering/src/components/Card/Card.tsx
+++ b/dotcom-rendering/src/components/Card/Card.tsx
@@ -14,6 +14,7 @@ import type {
DCRSupportingContent,
} from '../../types/front';
import type { Palette } from '../../types/palette';
+import type { CardYoutubeVideo } from '../../types/video';
import { Avatar } from '../Avatar';
import { CardHeadline } from '../CardHeadline';
import { CardPicture } from '../CardPicture';
@@ -27,6 +28,7 @@ import { SnapCssSandbox } from '../SnapCssSandbox';
import { StarRating } from '../StarRating/StarRating';
import type { Alignment } from '../SupportingContent';
import { SupportingContent } from '../SupportingContent';
+import { YoutubeBlockComponent } from '../YoutubeBlockComponent.importable';
import { AvatarContainer } from './components/AvatarContainer';
import { CardAge } from './components/CardAge';
import { CardBranding } from './components/CardBranding';
@@ -64,7 +66,8 @@ export type Props = {
showClock?: boolean;
mediaType?: MediaType;
mediaDuration?: number;
- showMainVideo?: boolean;
+ /** Note YouTube recommends a minimum width of 480px @see https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-youtube-player-size */
+ mainVideo?: CardYoutubeVideo;
kickerText?: string;
showPulsingDot?: boolean;
starRating?: number;
@@ -276,7 +279,7 @@ export const Card = ({
avatarUrl,
showClock,
mediaDuration,
- showMainVideo,
+ mainVideo,
kickerText,
showPulsingDot,
starRating,
@@ -420,7 +423,7 @@ export const Card = ({
imageType={image.type}
imagePosition={imagePosition}
imagePositionOnMobile={imagePositionOnMobile}
- showPlayIcon={showMainVideo ?? false}
+ mainVideo={mainVideo}
>
{image.type === 'slideshow' &&
image.slideshowImages && (
@@ -442,13 +445,44 @@ export const Card = ({
/>
)}
- {image.type === 'mainMedia' && (
-
+ {mainVideo?.type === 'embedded-playable-video' && (
+
+
+
+
+
)}
+ {image.type === 'mainMedia' &&
+ mainVideo?.type !== 'embedded-playable-video' && (
+
+ )}
{image.type === 'crossword' && (
)}
diff --git a/dotcom-rendering/src/components/Card/components/ImageWrapper.tsx b/dotcom-rendering/src/components/Card/components/ImageWrapper.tsx
index 9dd3ed5e986..e03d4b924cc 100644
--- a/dotcom-rendering/src/components/Card/components/ImageWrapper.tsx
+++ b/dotcom-rendering/src/components/Card/components/ImageWrapper.tsx
@@ -1,6 +1,7 @@
import type { SerializedStyles } from '@emotion/react';
import { css } from '@emotion/react';
import { between, from, until } from '@guardian/source-foundations';
+import type { CardYoutubeVideo } from '../../../types/video';
import { PlayIcon } from './PlayIcon';
export type ImagePositionType = 'left' | 'top' | 'right' | 'bottom' | 'none';
@@ -13,7 +14,7 @@ type Props = {
imageType?: CardImageType;
imagePosition: ImagePositionType;
imagePositionOnMobile: ImagePositionType;
- showPlayIcon: boolean;
+ mainVideo?: CardYoutubeVideo;
};
/**
@@ -59,7 +60,7 @@ export const ImageWrapper = ({
imageType,
imagePosition,
imagePositionOnMobile,
- showPlayIcon,
+ mainVideo,
}: Props) => {
const isHorizontal = imagePosition === 'left' || imagePosition === 'right';
const isHorizontalOnMobile =
@@ -126,12 +127,14 @@ export const ImageWrapper = ({
{(imageType === 'mainMedia' || imageType === 'slideshow') && (
)}
- {imageType === 'mainMedia' && showPlayIcon && (
-
- )}
+ {/* A `mainVideo` has its own controls */}
+ {mainVideo?.type === 'show-only-icon' &&
+ imageType === 'mainMedia' && (
+
+ )}
>
);
diff --git a/dotcom-rendering/src/components/Carousel.importable.tsx b/dotcom-rendering/src/components/Carousel.importable.tsx
index b7b2407f566..09cd921ef22 100644
--- a/dotcom-rendering/src/components/Carousel.importable.tsx
+++ b/dotcom-rendering/src/components/Carousel.importable.tsx
@@ -20,6 +20,7 @@ import type { Branding } from '../types/branding';
import type { DCRContainerPalette } from '../types/front';
import type { OnwardsSource } from '../types/onwards';
import type { TrailType } from '../types/trails';
+import type { CardYoutubeVideo } from '../types/video';
import { Card } from './Card/Card';
import { LI } from './Card/components/LI';
import { FetchCommentCounts } from './FetchCommentCounts.importable';
@@ -431,7 +432,7 @@ type CarouselCardProps = {
discussionId?: string;
/** Only used on Labs cards */
branding?: Branding;
- showMainVideo?: boolean;
+ mainVideo?: CardYoutubeVideo;
mediaDuration?: number;
verticalDividerColour?: string;
};
@@ -447,7 +448,7 @@ const CarouselCard = ({
dataLinkName,
discussionId,
branding,
- showMainVideo,
+ mainVideo,
mediaDuration,
verticalDividerColour,
}: CarouselCardProps) => (
@@ -476,7 +477,7 @@ const CarouselCard = ({
discussionId={discussionId}
branding={branding}
isExternalLink={false}
- showMainVideo={showMainVideo}
+ mainVideo={mainVideo}
mediaDuration={mediaDuration}
/>
@@ -901,7 +902,7 @@ export const Carousel = ({
: undefined
}
branding={branding}
- showMainVideo={trail.showMainVideo}
+ mainVideo={trail.mainVideo}
mediaDuration={trail.mediaDuration}
verticalDividerColour={
carouselColours.borderColour
diff --git a/dotcom-rendering/src/components/DynamicPackage.stories.tsx b/dotcom-rendering/src/components/DynamicPackage.stories.tsx
index 72a1f02869d..05755d612be 100644
--- a/dotcom-rendering/src/components/DynamicPackage.stories.tsx
+++ b/dotcom-rendering/src/components/DynamicPackage.stories.tsx
@@ -354,7 +354,7 @@ export const SpecialReportWithoutPalette = () => (
'inside the firm that helps the super-rich hide their money',
showQuotedHeadline: false,
dataLinkName: 'news | group-0 | card-@1',
- showMainVideo: false,
+ mainVideo: undefined,
showLivePlayable: false,
isExternalLink: false,
webPublicationDate: '2016-04-08T12:15:09.000Z',
diff --git a/dotcom-rendering/src/components/FrontCard.tsx b/dotcom-rendering/src/components/FrontCard.tsx
index 772fc9c5009..ecea93ec5f1 100644
--- a/dotcom-rendering/src/components/FrontCard.tsx
+++ b/dotcom-rendering/src/components/FrontCard.tsx
@@ -45,7 +45,7 @@ export const FrontCard = (props: Props) => {
snapData: trail.snapData,
discussionId: trail.discussionId,
avatarUrl: trail.avatarUrl,
- showMainVideo: trail.showMainVideo,
+ mainVideo: trail.mainVideo,
isExternalLink: trail.isExternalLink,
branding: trail.branding,
slideshowImages: trail.slideshowImages,
diff --git a/dotcom-rendering/src/lib/cardWrappers.tsx b/dotcom-rendering/src/lib/cardWrappers.tsx
index a30364c588d..8238603f359 100644
--- a/dotcom-rendering/src/lib/cardWrappers.tsx
+++ b/dotcom-rendering/src/lib/cardWrappers.tsx
@@ -1,5 +1,6 @@
import { FrontCard } from '../components/FrontCard';
import type { DCRContainerPalette, DCRFrontCard } from '../types/front';
+import type { CardYoutubeVideo } from '../types/video';
type TrailProps = {
trail: DCRFrontCard;
@@ -7,6 +8,13 @@ type TrailProps = {
containerPalette?: DCRContainerPalette;
};
+const onlyKeepPlayIcon = ({
+ mainVideo,
+}: DCRFrontCard): CardYoutubeVideo | undefined =>
+ mainVideo?.type === 'embedded-playable-video'
+ ? { type: 'show-only-icon' }
+ : undefined;
+
/**
* ASCII Art Guide
* ┏━━┓
@@ -266,6 +274,7 @@ export const Card25Media25 = ({
imageSize="small"
headlineSize="medium"
headlineSizeOnMobile="medium"
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
@@ -301,6 +310,7 @@ export const Card25Media25SmallHeadline = ({
imageSize="small"
headlineSize="small"
headlineSizeOnMobile="medium"
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
@@ -343,6 +353,7 @@ export const Card25Media25Tall = ({
: undefined
}
supportingContent={trail.supportingContent?.slice(0, 2)}
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
@@ -377,6 +388,7 @@ export const Card25Media25TallNoTrail = ({
headlineSize="medium"
headlineSizeOnMobile="medium"
supportingContent={trail.supportingContent?.slice(0, 2)}
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
@@ -411,6 +423,7 @@ export const Card25Media25TallSmallHeadline = ({
headlineSize="small"
headlineSizeOnMobile="medium"
supportingContent={trail.supportingContent?.slice(0, 2)}
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
@@ -651,6 +664,7 @@ export const CardDefault = ({
avatarUrl={undefined}
headlineSize="small"
headlineSizeOnMobile="small"
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
@@ -682,6 +696,7 @@ export const CardDefaultMedia = ({
imagePositionOnMobile="none"
headlineSize="small"
headlineSizeOnMobile="small"
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
@@ -713,6 +728,7 @@ export const CardDefaultMediaMobile = ({
imagePositionOnMobile="left"
headlineSize="small"
headlineSizeOnMobile="small"
+ mainVideo={onlyKeepPlayIcon(trail)}
/>
);
};
diff --git a/dotcom-rendering/src/model/article-schema.json b/dotcom-rendering/src/model/article-schema.json
index c442eb6fd06..037dc576373 100644
--- a/dotcom-rendering/src/model/article-schema.json
+++ b/dotcom-rendering/src/model/article-schema.json
@@ -4398,8 +4398,79 @@
"isCommentable"
]
},
- "showMainVideo": {
- "type": "boolean"
+ "mainVideo": {
+ "description": "For displaying embedded, playable videos directly in cards",
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "show-only-icon"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "embedded-playable-video"
+ ]
+ },
+ "elementId": {
+ "type": "string"
+ },
+ "videoId": {
+ "type": "string"
+ },
+ "height": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "duration": {
+ "type": "number"
+ },
+ "origin": {
+ "type": "string"
+ },
+ "images": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "width": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "url",
+ "width"
+ ]
+ }
+ }
+ },
+ "required": [
+ "elementId",
+ "height",
+ "origin",
+ "type",
+ "videoId",
+ "width"
+ ]
+ }
+ ]
}
},
"required": [
diff --git a/dotcom-rendering/src/model/enhanceCards.ts b/dotcom-rendering/src/model/enhanceCards.ts
index 818796d5713..5582078e94d 100644
--- a/dotcom-rendering/src/model/enhanceCards.ts
+++ b/dotcom-rendering/src/model/enhanceCards.ts
@@ -10,9 +10,11 @@ import type {
DCRSlideshowImage,
DCRSupportingContent,
FEFrontCard,
+ FEMediaAtoms,
FESupportingContent,
} from '../types/front';
import type { FETagType, TagType } from '../types/tag';
+import type { CardYoutubeVideo } from '../types/video';
import { enhanceSnaps } from './enhanceSnaps';
/**
@@ -182,6 +184,34 @@ const enhanceTags = (tags: FETagType[]): TagType[] => {
});
};
+const extractVideo = (
+ mediaAtom?: FEMediaAtoms,
+): CardYoutubeVideo | undefined => {
+ if (!mediaAtom) return undefined;
+ const asset = mediaAtom.assets.find(
+ ({ version }) => version === mediaAtom.activeVersion,
+ );
+ if (asset?.platform === 'Youtube') {
+ return {
+ type: 'embedded-playable-video',
+ elementId: mediaAtom.id,
+ videoId: asset.id,
+ duration: mediaAtom.duration,
+ // Size fixed to a 5:3 ratio
+ width: 500,
+ height: 300,
+ origin: mediaAtom.source ?? 'Unknown origin',
+ images: mediaAtom.posterImage.allImages.map(
+ ({ url, fields: { width } }) => ({
+ url,
+ width: Number(width),
+ }),
+ ),
+ };
+ }
+ return undefined;
+};
+
export const enhanceCards = (
collections: FEFrontCard[],
{
@@ -277,7 +307,15 @@ export const enhanceCards = (
mediaDuration:
faciaCard.properties.maybeContent?.elements.mediaAtoms[0]
?.duration,
- showMainVideo: faciaCard.properties.showMainVideo,
+ mainVideo: faciaCard.properties.showMainVideo
+ ? extractVideo(
+ // While the first Media Atom is *not* guaranteed to be the main video
+ // it *happens to be* correct in the majority of cases.
+ // See https://github.com/guardian/frontend/pull/26247 for inspiration
+ faciaCard.properties.maybeContent?.elements
+ .mediaAtoms[0],
+ )
+ : undefined,
isExternalLink: faciaCard.card.cardStyle.type === 'ExternalLink',
embedUri: faciaCard.properties.embedUri ?? undefined,
branding,
diff --git a/dotcom-rendering/src/model/front-schema.json b/dotcom-rendering/src/model/front-schema.json
index f8e322ee624..2058b4f7375 100644
--- a/dotcom-rendering/src/model/front-schema.json
+++ b/dotcom-rendering/src/model/front-schema.json
@@ -2840,10 +2840,114 @@
"FEMediaAtoms": {
"type": "object",
"properties": {
+ "id": {
+ "type": "string"
+ },
+ "activeVersion": {
+ "type": "number"
+ },
+ "assets": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "version": {
+ "type": "number"
+ },
+ "platform": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "platform",
+ "version"
+ ]
+ }
+ },
+ "posterImage": {
+ "type": "object",
+ "properties": {
+ "allImages": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Image"
+ }
+ }
+ },
+ "required": [
+ "allImages"
+ ]
+ },
"duration": {
"type": "number"
+ },
+ "source": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "channelId": {},
+ "defaultHtml": {}
+ },
+ "required": [
+ "activeVersion",
+ "assets",
+ "id",
+ "posterImage"
+ ]
+ },
+ "Image": {
+ "type": "object",
+ "properties": {
+ "index": {
+ "type": "number"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "height": {
+ "type": "string"
+ },
+ "width": {
+ "type": "string"
+ },
+ "isMaster": {
+ "type": "string"
+ },
+ "source": {
+ "type": "string"
+ },
+ "caption": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "height",
+ "width"
+ ]
+ },
+ "mediaType": {
+ "type": "string"
+ },
+ "mimeType": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
}
- }
+ },
+ "required": [
+ "fields",
+ "index",
+ "mediaType",
+ "mimeType",
+ "url"
+ ]
},
"EditionId": {
"enum": [
@@ -3379,8 +3483,79 @@
"isCommentable"
]
},
- "showMainVideo": {
- "type": "boolean"
+ "mainVideo": {
+ "description": "For displaying embedded, playable videos directly in cards",
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "show-only-icon"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "embedded-playable-video"
+ ]
+ },
+ "elementId": {
+ "type": "string"
+ },
+ "videoId": {
+ "type": "string"
+ },
+ "height": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "duration": {
+ "type": "number"
+ },
+ "origin": {
+ "type": "string"
+ },
+ "images": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "width": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "url",
+ "width"
+ ]
+ }
+ }
+ },
+ "required": [
+ "elementId",
+ "height",
+ "origin",
+ "type",
+ "videoId",
+ "width"
+ ]
+ }
+ ]
}
},
"required": [
diff --git a/dotcom-rendering/src/model/tag-front-schema.json b/dotcom-rendering/src/model/tag-front-schema.json
index e8cb77397c1..711f869e032 100644
--- a/dotcom-rendering/src/model/tag-front-schema.json
+++ b/dotcom-rendering/src/model/tag-front-schema.json
@@ -1249,10 +1249,114 @@
"FEMediaAtoms": {
"type": "object",
"properties": {
+ "id": {
+ "type": "string"
+ },
+ "activeVersion": {
+ "type": "number"
+ },
+ "assets": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "version": {
+ "type": "number"
+ },
+ "platform": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "platform",
+ "version"
+ ]
+ }
+ },
+ "posterImage": {
+ "type": "object",
+ "properties": {
+ "allImages": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Image"
+ }
+ }
+ },
+ "required": [
+ "allImages"
+ ]
+ },
"duration": {
"type": "number"
+ },
+ "source": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "channelId": {},
+ "defaultHtml": {}
+ },
+ "required": [
+ "activeVersion",
+ "assets",
+ "id",
+ "posterImage"
+ ]
+ },
+ "Image": {
+ "type": "object",
+ "properties": {
+ "index": {
+ "type": "number"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "height": {
+ "type": "string"
+ },
+ "width": {
+ "type": "string"
+ },
+ "isMaster": {
+ "type": "string"
+ },
+ "source": {
+ "type": "string"
+ },
+ "caption": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "height",
+ "width"
+ ]
+ },
+ "mediaType": {
+ "type": "string"
+ },
+ "mimeType": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
}
- }
+ },
+ "required": [
+ "fields",
+ "index",
+ "mediaType",
+ "mimeType",
+ "url"
+ ]
},
"EditionId": {
"enum": [
diff --git a/dotcom-rendering/src/types/front.ts b/dotcom-rendering/src/types/front.ts
index 6764bb8aeaa..99121028119 100644
--- a/dotcom-rendering/src/types/front.ts
+++ b/dotcom-rendering/src/types/front.ts
@@ -3,10 +3,12 @@ import type { EditionId } from '../lib/edition';
import type { DCRBadgeType } from './badge';
import type { Branding } from './branding';
import type { ServerSideTests, Switches } from './config';
+import type { Image } from './content';
import type { FooterType } from './footer';
import type { FETagType } from './tag';
import type { Territory } from './territory';
import type { FETrailType, TrailType } from './trails';
+import type { CardYoutubeVideo } from './video';
export interface FEFrontType {
pressedPage: FEPressedPageType;
@@ -132,8 +134,17 @@ export type DCRContainerPalette =
// TODO: These may need to be declared differently than the front types in the future
export type DCRContainerType = FEContainerType;
-interface FEMediaAtoms {
+export interface FEMediaAtoms {
+ id: string;
+ activeVersion: number;
+ assets: Array<{ id: string; version: number; platform: string }>;
+ posterImage: { allImages: Image[] };
duration?: number;
+ source?: string;
+ title?: string;
+ // fields that we’re not interested in but are still passed…
+ channelId?: unknown;
+ defaultHtml?: unknown;
}
export type FEFrontCard = {
@@ -289,7 +300,7 @@ export type DCRFrontCard = {
avatarUrl?: string;
mediaType?: MediaType;
mediaDuration?: number;
- showMainVideo: boolean;
+ mainVideo?: CardYoutubeVideo;
isExternalLink: boolean;
embedUri?: string;
branding?: Branding;
diff --git a/dotcom-rendering/src/types/trails.ts b/dotcom-rendering/src/types/trails.ts
index 2331e325e11..b6596f8227c 100644
--- a/dotcom-rendering/src/types/trails.ts
+++ b/dotcom-rendering/src/types/trails.ts
@@ -1,5 +1,6 @@
import type { Branding } from './branding';
import type { DCRSnapType, DCRSupportingContent } from './front';
+import type { CardYoutubeVideo } from './video';
type MediaType = 'Video' | 'Audio' | 'Gallery';
@@ -29,7 +30,7 @@ interface BaseTrailType {
isClosedForComments: boolean;
discussionId?: string;
};
- showMainVideo?: boolean;
+ mainVideo?: CardYoutubeVideo;
}
export interface TrailType extends BaseTrailType {
diff --git a/dotcom-rendering/src/types/video.ts b/dotcom-rendering/src/types/video.ts
new file mode 100644
index 00000000000..1e179f065e6
--- /dev/null
+++ b/dotcom-rendering/src/types/video.ts
@@ -0,0 +1,13 @@
+/** For displaying embedded, playable videos directly in cards */
+export type CardYoutubeVideo =
+ | { type: 'show-only-icon' }
+ | {
+ type: 'embedded-playable-video';
+ elementId: string;
+ videoId: string;
+ height: number;
+ width: number;
+ duration?: number;
+ origin: string;
+ images?: { url: string; width: number }[];
+ };