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 }[]; + };