import { LightningElement, api } from "lwc"; import cx from "classnames"; import { isSlotEmpty } from "dxUtils/slot"; import { toJson } from "dxUtils/normalizers"; import { track } from "dxUtils/analytics"; import svgs from "./svgs"; import ImageAndLabel from "dx/imageAndLabel"; import { dispatchDarkModeToggleEvent, isDarkModeEnabled } from "dx/darkModeManager"; export default class FeaturedContentHeader extends LightningElement { @api label?: string | null = null; @api labelAsPrimaryHeading: boolean = false; @api body!: string; @api ctaPrimaryLabel?: string; @api ctaPrimaryHref?: string; @api ctaPrimarySymbol?: string; @api ctaPrimaryTarget?: string; @api ctaSecondaryLabel?: string; @api ctaSecondaryHref?: string; @api ctaSecondarySymbol?: string; @api ctaSecondaryTarget?: string; @api imgSrc?: string; @api imgAlt?: string = "Featured content image"; @api imgPlacement?: "inline" | "below" = "inline"; @api href?: string; @api target?: string | null = null; @api header!: string; @api backgroundImg?: string; @api backgroundImgId: | "trees" | "codey" | "blog" | "moon" | "trial" | "big-moon" | null = null; @api noSwoop: boolean = false; @api dark: boolean = false; @api icon: string = ""; @api swoopUnderColor: string = "var(--dx-g-fch-swoop-under-color)"; @api enableDarkModeToggle: boolean = false; private _authors?: Array; private isSlotEmpty: boolean = true; private isLinkHovered: boolean = false; private progressPodcastPlayed = { percent25: false, percent50: false, percent75: false, percent100: false }; // ! Move this map away from the component and probably the filter too. @api get authors() { if (this._authors && this._authors.length) { return this._authors!.map((author, index) => ({ ...author, // @ts-ignore we're doing some hacky stuff to get at LWC internals here imgSrc: author.image_src, key: index })); } return undefined; } set authors(value: any) { this._authors = (toJson(value) as Array)?.filter( (author) => author.name ); } private get classname() { return cx( "container", this.imgSrc && `img-placement_${this.imgPlacement}`, this.dark && "variant_intense", this.isLinkHovered && "link-hovered", !this.isSlotEmpty && "has-slotted", this.backgroundImgId && `custom-bg-${this.backgroundImgId}` ); } private get hasButtons() { return this.ctaPrimaryLabel && this.ctaSecondaryLabel; } private get style() { return cx( this.backgroundImg && `--background-image: url(${this.backgroundImg});` ); } private get isDarkModeEnabled() { return this.enableDarkModeToggle && isDarkModeEnabled(); } private get hasPlainImage() { return this.imgSrc && !this.href; } private get hasLinkedImage() { return this.imgSrc && this.href; } private get hasBackgroundImage() { return this.backgroundImgId !== null; } private get hasIcon(): boolean { return this.icon !== "" && this.backgroundImgId === "big-moon"; } private get backgroundImageClass() { return cx( "bg-image-container", this.backgroundImgId && `bg-${this.backgroundImgId}` ); } private setLinkHovered() { this.isLinkHovered = true; } private setLinkInactive() { this.isLinkHovered = false; } private onDarkModeButtonClick() { dispatchDarkModeToggleEvent(); } private onSlotChange(e: any): void { this.isSlotEmpty = isSlotEmpty(e); const slot = e.target; const slotElements = slot.assignedElements(); if (slotElements.length) { const slotContentElement = slotElements[0]; const playerElement = slotContentElement?.shadowRoot?.querySelector( "audio" ) as HTMLAudioElement; if (playerElement) { playerElement.addEventListener("play", (event: Event) => { const playerState = this.handlePlayerState(playerElement); this.trackAnalytics( event, "play", playerState.timePlayed, playerState.percentagePlayed ); }); playerElement.addEventListener("pause", (event: Event) => { const playerState = this.handlePlayerState(playerElement); this.trackAnalytics( event, "pause", playerState.timePlayed, playerState.percentagePlayed ); }); playerElement.addEventListener("timeupdate", (event: Event) => { const playerState = this.handlePlayerState(playerElement); if ( (playerState.percentagePlayed === 25 && !this.progressPodcastPlayed.percent25) || (playerState.percentagePlayed === 50 && !this.progressPodcastPlayed.percent50) || (playerState.percentagePlayed === 75 && !this.progressPodcastPlayed.percent75) || (playerState.percentagePlayed === 100 && !this.progressPodcastPlayed.percent100) ) { this.trackAnalytics( event, "progress", playerState.timePlayed, playerState.percentagePlayed ); } }); } } } renderedCallback(): void { // Setting the svgs with innerHTML prevents their IDs from being rewritten // which is necessary to preserve the svg masking and other effects if (this.backgroundImgId && this.backgroundImgId in svgs) { const bgSvg = this.template?.querySelector(".bg-image-container"); if (bgSvg) { // eslint-disable-next-line @lwc/lwc/no-inner-html bgSvg.innerHTML = ` ${svgs[this.backgroundImgId as keyof typeof svgs].desktop} ${svgs[this.backgroundImgId as keyof typeof svgs].mobile} `; } } } private trackAnalytics( event: Event, action: "play" | "pause" | "progress", timePlayed: number, percentagePlayed: number ) { let trackEvent = null; switch (action) { case "play": trackEvent = "custEv_podcastPlay"; break; case "pause": trackEvent = "custEv_podcastPause"; break; case "progress": trackEvent = "custEv_podcastProgress"; break; default: trackEvent = null; } if (!trackEvent) { return; } track(event.target!, trackEvent, { media_name: this.header, media_action: action, media_episode: this.header.split(":")[0], media_percentage_played: percentagePlayed, media_seconds_played: timePlayed, media_type: "podcast" }); if (Math.trunc(percentagePlayed) === 25) { this.progressPodcastPlayed.percent25 = true; } else if (Math.trunc(percentagePlayed) === 50) { this.progressPodcastPlayed.percent50 = true; } else if (Math.trunc(percentagePlayed) === 75) { this.progressPodcastPlayed.percent75 = true; } else if (Math.trunc(percentagePlayed) === 100) { this.progressPodcastPlayed.percent100 = true; } } private handlePlayerState(player: HTMLAudioElement) { let timePlayed = 0; let percentagePlayed = 0; if (player) { timePlayed = player.currentTime / 1000; percentagePlayed = Math.trunc( (player.currentTime * 100) / player.duration ); } return { timePlayed, percentagePlayed }; } }