From cf02622f476e649584e10db654fb9fa2152112e7 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 28 Jun 2023 11:31:03 +0100 Subject: [PATCH] feat(Card): Support mainVideo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settings comes from the facia-tool via Frontend, when “Show Video” is enabled for a trail in a container, it should be displayed as a playable video inline instead or redirecting to the article itself. Currently, only Youtube Atoms are supported. There is currently no guarantee that the first atom will be the expected video, but it happens to be the case the vast majority of the time A minimum Card width of one third or three columns is required for the YouTube atom to be embedded, similar to the existing Frontend logic: https://github.com/guardian/frontend/blob/1c4555a9f/common/app/layout/cards/CardType.scala#L20-L23 --- dotcom-rendering/fixtures/manual/trails.ts | 40 ++-- dotcom-rendering/makefile | 1 + .../src/components/Card/Card.stories.tsx | 28 ++- dotcom-rendering/src/components/Card/Card.tsx | 52 ++++- .../Card/components/ImageWrapper.tsx | 19 +- .../src/components/Carousel.importable.tsx | 9 +- .../src/components/DynamicPackage.stories.tsx | 2 +- dotcom-rendering/src/components/FrontCard.tsx | 2 +- dotcom-rendering/src/lib/cardWrappers.tsx | 16 ++ .../src/model/article-schema.json | 75 +++++++- dotcom-rendering/src/model/enhanceCards.ts | 40 +++- dotcom-rendering/src/model/front-schema.json | 181 +++++++++++++++++- .../src/model/tag-front-schema.json | 106 +++++++++- dotcom-rendering/src/types/front.ts | 15 +- dotcom-rendering/src/types/trails.ts | 3 +- dotcom-rendering/src/types/video.ts | 13 ++ 16 files changed, 540 insertions(+), 62 deletions(-) create mode 100644 dotcom-rendering/src/types/video.ts 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 }[]; + };