import { isNothing, isSome } from "@jet/environment"; import * as models from "../../api/models"; import * as serverData from "../../foundation/json-parsing/server-data"; import * as mediaAttributes from "../../foundation/media/attributes"; import * as mediaRelationship from "../../foundation/media/relationships"; import * as color from "../../foundation/util/color-util"; import * as dateUtil from "../../foundation/util/date-util"; import * as appPromotionCommon from "./app-promotions-common"; /** * Creates an app event object or a Date, if the event should not yet be shown, from the given data. * @param data The data blob * @param parentAppData The related parent app of this app event. If not provided will be derived from `data`. * @param hideLockupWhenNotInstalled Whether to hide the lockup chin when the app is installed * @param includeClickAction Whether to generate a click action for the app event * @param offerEnvironment The preferred environment for the offer * @param offerStyle The preferred style of the offer * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed * @param allowMissingParentApp Whether to still create the app event if the parent app is missing * @param baseMetricsOptions The base metrics options * @param allowEndedEvents Whether events in the past are allowed * @param includeLockupClickAction Whether to include the click action for the lockup * @param isArcadePage Whether or not this is presented on the Arcade page * @param allowUnpublishedAppEventPreviews Whether or not to allow app event previews * @returns an AppEvent, or a Date if the event's `promotionStartDate` is in the future. */ export function appEventOrPromotionStartDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, includeClickAction, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, editorialKind, isArcadePage, allowUnpublishedAppEventPreviews) { var _a, _b, _c; if (data.type !== "app-events") { return null; } const promotionStartDateString = mediaAttributes.attributeAsString(data, "promotionStartDate"); if (isNothing(promotionStartDateString) || promotionStartDateString.length === 0) { return null; } const promotionStartDate = new Date(promotionStartDateString); if (isNothing(promotionStartDate)) { return null; } const todayDate = new Date(); const isFutureDate = promotionStartDate.getTime() > todayDate.getTime(); if (isFutureDate && !allowUnpublishedAppEventPreviews) { return promotionStartDate; } // Artwork / video const moduleArtwork = appPromotionCommon.artworkFromData(objectGraph, data, "lockupArtwork"); const moduleVideo = appPromotionCommon.videoFromData(objectGraph, data, "lockupVideo", false, false); if (isNothing(moduleArtwork) && serverData.isNullOrEmpty(moduleVideo)) { return null; } const selectedArtwork = (_a = moduleVideo === null || moduleVideo === void 0 ? void 0 : moduleVideo.preview) !== null && _a !== void 0 ? _a : moduleArtwork; const isArtworkDark = color.isDarkColor(selectedArtwork === null || selectedArtwork === void 0 ? void 0 : selectedArtwork.backgroundColor); const mediaOverlayStyle = isArtworkDark ? "dark" : "light"; const includeBorderInDarkMode = color.isDarkColor(selectedArtwork === null || selectedArtwork === void 0 ? void 0 : selectedArtwork.backgroundColor, 10); // Other required fields const title = mediaAttributes.attributeAsString(data, "name"); let kind = mediaAttributes.attributeAsString(data, "kind"); if (serverData.isDefinedNonNullNonEmpty(editorialKind)) { kind = editorialKind; } const startDateString = mediaAttributes.attributeAsString(data, "startDate"); if (isNothing(title) || title.length === 0 || isNothing(kind) || kind.length === 0 || isNothing(startDateString) || startDateString.length === 0) { return null; } const startDate = new Date(startDateString); if (isNothing(startDate)) { return null; } // Description const detail = (_b = mediaAttributes.attributeAsString(data, "description.standard")) !== null && _b !== void 0 ? _b : ""; // Lockup const resolvedParentAppData = parentAppData !== null && parentAppData !== void 0 ? parentAppData : mediaRelationship.relationshipData(objectGraph, data, "app"); let lockup = null; if (isSome(resolvedParentAppData)) { lockup = appPromotionCommon.lockupFromData(objectGraph, data, resolvedParentAppData, title, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, includeLockupClickAction, null, isArcadePage, true); } if (isNothing(lockup)) { objectGraph.console.warn(`Parent app for event ${data.id} is missing, returning null.`); return null; } // Requirements const requirements = mediaAttributes.attributeAsString(data, "requirement"); // Supplementary optional fields const subtitle = mediaAttributes.attributeAsString(data, "subtitle"); const endDateString = mediaAttributes.attributeAsString(data, "endDate"); let endDate; if (isSome(endDateString) && endDateString.length > 0) { endDate = new Date(endDateString); } const hasEventEnded = endDate !== undefined && endDate.getTime() <= todayDate.getTime(); if (hasEventEnded && !allowEndedEvents) { return null; } // Formatted dates const badgeKindString = (_c = mediaAttributes.attributeAsString(data, "badgeKind")) !== null && _c !== void 0 ? _c : undefined; const badgeKind = badgeKindFromString(badgeKindString, startDate, endDate); const formattedDates = formattedDatesWithKind(objectGraph, badgeKind, startDate, endDate); const appEvent = new models.AppEvent(data.id, moduleArtwork !== null && moduleArtwork !== void 0 ? moduleArtwork : undefined, moduleVideo !== null && moduleVideo !== void 0 ? moduleVideo : undefined, title, subtitle !== null && subtitle !== void 0 ? subtitle : undefined, detail, startDate, endDate, badgeKind, kind, requirements !== null && requirements !== void 0 ? requirements : undefined, lockup, hideLockupWhenNotInstalled, formattedDates, mediaOverlayStyle, includeBorderInDarkMode); // Notifications if (serverData.isDefinedNonNull(resolvedParentAppData)) { const clickOptions = { ...baseMetricsOptions, id: resolvedParentAppData.id, inAppEventId: data.id, relatedSubjectIds: [resolvedParentAppData.id], }; appEvent.notificationConfig = appPromotionCommon.notificationConfigFromData(objectGraph, data, appEvent, clickOptions, true); } // Click action if (includeClickAction && serverData.isDefinedNonNull(resolvedParentAppData)) { appEvent.clickAction = appPromotionCommon.detailPageClickActionFromData(objectGraph, data, resolvedParentAppData, appEvent, baseMetricsOptions, includeLockupClickAction); } return appEvent; } /** * Determines the badge kind from the given string. * @param badgeKindString The raw badge kind string * @param startDate The start date of the app event * @param endDate The end date of the app event, if any * @returns An AppEventBadgeKind */ export function badgeKindFromString(badgeKindString, startDate, endDate) { let badgeKind = (badgeKindString !== null && badgeKindString !== void 0 ? badgeKindString : models.AppEventBadgeKind.live); if (badgeKind === models.AppEventBadgeKind.live && serverData.isDefinedNonNull(endDate)) { // If the event is longer than 6 hours, override with "happening now" so that // the blinking red live dot is less prevalent. const difference = endDate.getTime() - startDate.getTime(); const sixHoursDifference = 1000 * 60 * 60 * 6; if (difference > sixHoursDifference) { badgeKind = models.AppEventBadgeKind.happening; } } return badgeKind; } /** * Generates a list of all the possible date variants for a given app event. The native * client will look at this list and determine which variant to display, based * on the current time. The reason for sending down all variants is that the client will * continue to update the display as time progresses, so it's feasible that the client will * cross over from date variant to the next (eg. TOMORROW 12:00 PM -> TODAY 12:00 PM). * @param badgeKind The kind of badge used * @param startDate The start date of the app event * @param endDate The end date of the app event, if any */ export function formattedDatesWithKind(objectGraph, badgeKind, startDate, endDate) { const formattedDates = []; const startMidnight = dateUtil.convertLocalDateToLocalMidnight(startDate); if (isNothing(startMidnight)) { // This is only possible if `startDate` is "nothing", which means we can't proceed to create formatted dates anyway. return []; } // Event starts 7+ days from now // Example: JAN 15 const introductionDateFormat = objectGraph.loc.string("AppEvents.FormattedDate.SevenDaysOrMore.DateFormat"); const introductionText = objectGraph.loc.uppercased(objectGraph.loc.formatDate(introductionDateFormat, startDate)); const introductionFormattedDate = new models.AppEventFormattedDate(false, undefined, introductionText !== null && introductionText !== void 0 ? introductionText : undefined, undefined, undefined); formattedDates.push(introductionFormattedDate); // Event starts 2-6 days from now // Example: FRI 12:00 PM const sixDaysPrior = new Date(startMidnight); sixDaysPrior.setDate(sixDaysPrior.getDate() - 6); const sixDaysPriorDateFormat = objectGraph.loc.string("AppEvents.FormattedDate.SixDaysOrLess.DateFormat"); const sixDaysPriorText = objectGraph.loc.formatDate(sixDaysPriorDateFormat, startDate); const sixDaysPriorTextUppercased = objectGraph.loc.uppercased(sixDaysPriorText); const sixDaysPriorFormattedDate = new models.AppEventFormattedDate(false, sixDaysPrior, sixDaysPriorTextUppercased !== null && sixDaysPriorTextUppercased !== void 0 ? sixDaysPriorTextUppercased : undefined, undefined, undefined); formattedDates.push(sixDaysPriorFormattedDate); // Event starts tomorrow // Example: TOMORROW 12:00 PM const oneDayPrior = new Date(startMidnight); oneDayPrior.setDate(oneDayPrior.getDate() - 1); const tomorrowSentence = objectGraph.loc.string("AppEvents.FormattedDate.Tomorrow"); const tomorrowText = objectGraph.loc.formatDateInSentence(tomorrowSentence, "j:mm", startDate); const tomorrowTextUppercased = objectGraph.loc.uppercased(tomorrowText); const tomorrowFormattedDate = new models.AppEventFormattedDate(false, oneDayPrior, tomorrowTextUppercased !== null && tomorrowTextUppercased !== void 0 ? tomorrowTextUppercased : undefined, undefined, undefined); formattedDates.push(tomorrowFormattedDate); // Event starts today // If the event starts within 1 hour of midnight, we don't need to show this case // as it will be superceded by the 1 hour countdown // Example: TODAY 12:00 PM, TONIGHT 7:00 PM if (startDate.getHours() > 1 || (startDate.getHours() === 1 && startDate.getMinutes() > 0)) { let todaySentence; if (startDate.getHours() >= 19) { todaySentence = objectGraph.loc.string("AppEvents.FormattedDate.Tonight"); } else { todaySentence = objectGraph.loc.string("AppEvents.FormattedDate.Today"); } const todayText = objectGraph.loc.formatDateInSentence(todaySentence, "j:mm", startDate); const todayTextUppercased = objectGraph.loc.uppercased(todayText); const todayFormattedDate = new models.AppEventFormattedDate(false, startMidnight !== null && startMidnight !== void 0 ? startMidnight : undefined, todayTextUppercased !== null && todayTextUppercased !== void 0 ? todayTextUppercased : undefined, undefined, undefined); formattedDates.push(todayFormattedDate); } // Event is starting in 1 hour or less // Example: STARTS IN 59 MIN / AVAILABLE IN 59 MIN const oneHourPrior = new Date(startDate); oneHourPrior.setHours(oneHourPrior.getHours() - 1); let countdownStringKey; switch (badgeKind) { case models.AppEventBadgeKind.available: countdownStringKey = "AppEvents.FormattedDate.AvailableIn.MinutesCountdown"; break; case models.AppEventBadgeKind.happening: case models.AppEventBadgeKind.live: default: countdownStringKey = "AppEvents.FormattedDate.StartsIn.MinutesCountdown"; break; } const oneHourPriorFormattedDate = new models.AppEventFormattedDate(false, oneHourPrior, undefined, startDate, countdownStringKey); formattedDates.push(oneHourPriorFormattedDate); // Event is happening now // Example: LIVE, HAPPENING NOW, NOW AVAILABLE let liveText; let showLiveIndicator; switch (badgeKind) { case models.AppEventBadgeKind.available: liveText = objectGraph.loc.string("AppEvents.FormattedDate.NowAvailable"); showLiveIndicator = false; break; case models.AppEventBadgeKind.happening: liveText = objectGraph.loc.string("AppEvents.FormattedDate.HappeningNow"); showLiveIndicator = false; break; case models.AppEventBadgeKind.live: default: liveText = objectGraph.loc.string("AppEvents.FormattedDate.Live"); showLiveIndicator = true; break; } const liveFormattedDate = new models.AppEventFormattedDate(showLiveIndicator, startDate, liveText, undefined, undefined); formattedDates.push(liveFormattedDate); // Event has ended // Example: EVENT ENDED if (endDate !== null) { const eventEndedFormattedDate = new models.AppEventFormattedDate(false, endDate, objectGraph.loc.string("AppEvents.FormattedDate.EventEnded"), undefined, undefined); formattedDates.push(eventEndedFormattedDate); } return formattedDates; } //# sourceMappingURL=app-event.js.map