From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- .../tmp/src/common/today/article-request.js | 86 ++ .../app-store/tmp/src/common/today/article.js | 1572 ++++++++++++++++++++ .../category-detail-motion-16x9.js | 34 + .../category-detail-static-16x9.js | 49 + .../today/artwork/media-configurations/day-card.js | 51 + .../artwork/media-configurations/event-card.js | 51 + .../artwork/media-configurations/general-card.js | 51 + .../today/artwork/media-configurations/index.js | 85 ++ .../media-configurations/list-card-motion-21x9.js | 116 ++ .../media-configurations/list-card-static-21x9.js | 92 ++ .../artwork/media-configurations/media-card.js | 51 + .../media-configurations/store-front-video-4x3.js | 22 + .../media-configurations/store-front-video.js | 25 + .../media-configurations/story-card-motion-16x9.js | 34 + .../media-configurations/story-card-static-16x9.js | 49 + .../story-centered-motion-16x9.js | 34 + .../story-centered-static-16x9.js | 49 + .../universal-a-motion-16x9.js | 146 ++ .../universal-a-static-16x9.js | 107 ++ .../src/common/today/artwork/today-artwork-util.js | 267 ++++ .../common/today/cards/today-ad-card-builder.js | 232 +++ .../today/cards/today-app-event-card-builder.js | 112 ++ .../common/today/cards/today-base-card-builder.js | 244 +++ .../today/cards/today-branded-card-builder.js | 58 + .../cards/today-full-bleed-image-card-builder.js | 37 + .../cards/today-in-app-purchase-card-builder.js | 62 + .../common/today/cards/today-list-card-builder.js | 71 + .../common/today/cards/today-river-card-builder.js | 62 + .../today/cards/today-short-image-card-builder.js | 42 + .../today/cards/today-single-app-card-builder.js | 81 + .../common/today/cards/today-video-card-builder.js | 33 + .../tmp/src/common/today/onboarding-cards.js | 23 + .../today/routable-article-page-url-utils.js | 32 + .../src/common/today/today-card-overlay-util.js | 111 ++ .../tmp/src/common/today/today-card-util.js | 785 ++++++++++ .../tmp/src/common/today/today-controller-util.js | 534 +++++++ .../src/common/today/today-horizontal-card-util.js | 352 +++++ .../tmp/src/common/today/today-parse-util.js | 1047 +++++++++++++ .../app-store/tmp/src/common/today/today-types.js | 118 ++ 39 files changed, 7007 insertions(+) create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/article.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js (limited to 'node_modules/@jet-app/app-store/tmp/src/common/today') diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js b/node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js new file mode 100644 index 0000000..4371605 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js @@ -0,0 +1,86 @@ +import { defaultAdditionalPlatformsForClient, Request } from "../../foundation/media/data-fetching"; +import { shouldFetchCustomAttributes } from "../product-page/product-page-variants"; +import { appEventsAreEnabled, appOfferItemsAreEnabled } from "../app-promotions/app-promotions-common"; +import { shouldUsePrerenderedIconArtwork } from "../content/content"; +import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types"; +function buildAttributesForArticlePageRequest(objectGraph) { + const attributes = [ + "screenshotsByType", + "videoPreviewsByType", + "requiredCapabilities", + "minimumOSVersion", + "editorialArtwork", + "editorialVideo", + "editorialClientParams", + "shortEditorialNotes", + "enrichedEditorialNotes", + ]; + if (objectGraph.bag.enableUpdatedAgeRatings) { + attributes.push("ageRating"); + } + if (objectGraph.appleSilicon.isSupportEnabled) { + attributes.push("macRequiredCapabilities"); + } + if (objectGraph.client.isMac) { + attributes.push("hasMacIPAPackage"); + } + if (objectGraph.client.isVision) { + attributes.push("compatibilityControllerRequirement"); + } + if (shouldUsePrerenderedIconArtwork(objectGraph)) { + attributes.push("iconArtwork"); + } + return attributes; +} +/** + * Create a Media API request for an `editorial-item` + * + * This corresponsd to an "Article" or "Story" page + */ +export function buildArticlePageRequest(objectGraph, intent, isIncomingURL) { + const request = new Request(objectGraph) + .withIdOfType(intent.id, "editorial-items") + .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph)) + .includingAttributes(buildAttributesForArticlePageRequest(objectGraph)) + .includingRelationships(["canvas"]) + .includingRelationshipsForUpsell(true) + .includingMacOSCompatibleIOSAppsWhenSupported(true) + .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph)); + if (!isIncomingURL) { + request.includingAgeRestrictions(); + } + if (appEventsAreEnabled(objectGraph)) { + request.enablingFeature("appEvents"); + request.includingScopedAttributes("app-events", AppEventsAttributes); + request.includingScopedRelationships("app-events", ["app"]); + request.includingScopedRelationships("editorial-item-shelves", ["app-events"]); + request.includingScopedAvailableIn("app-events", ["past"]); + } + if (appOfferItemsAreEnabled(objectGraph)) { + request.enablingFeature("offerItems"); + request.includingScopedRelationships("offer-items", ["salables"]); + request.includingAssociateKeys("editorial-items", ["editorial-cards"]); + request.includingMetaKeys("offer-items:salables", ["discountOffer"]); + request.includingScopedAttributes("offer-items", [ + "title", + "subtitle", + "additionalTerms", + "redemptionExpirationDate", + ]); + } + if (objectGraph.client.isVision) { + request.enablingFeature("supportsCustomTextColor"); + request.includingScopedAttributes("editorial-items", ["enrichedEditorialNotes"]); + } + if (objectGraph.client.isWeb) { + request.includingAttributes([ + // Publication date is used as part of SEO meta-data + "lastPublishedDate", + ]); + } + if (preprocessor.GAMES_TARGET) { + request.includingScopedAttributes("apps", ["isEligibleForGamesApp"]); + } + return request; +} +//# sourceMappingURL=article-request.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/article.js b/node_modules/@jet-app/app-store/tmp/src/common/today/article.js new file mode 100644 index 0000000..a67fde4 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/article.js @@ -0,0 +1,1572 @@ +/** + * Created by keithpk on 3/21/17. + */ +import { isNothing, isSome } from "@jet/environment"; +import * as validation from "@jet/environment/json/validation"; +import * as models from "../../api/models"; +import * as serverData from "../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../foundation/media/attributes"; +import * as mediaAugment from "../../foundation/media/augment"; +import * as mediaDataStructure from "../../foundation/media/data-structure"; +import * as mediaNetwork from "../../foundation/media/network"; +import * as mediaRelationships from "../../foundation/media/relationships"; +import * as urls from "../../foundation/network/urls"; +import * as color from "../../foundation/util/color-util"; +import { PageID } from "../../gameservicesui/src/common/id-builder"; +import * as gamesComponentBuilder from "../../gameservicesui/src/editorial-page/editorial-component-builder"; +import * as appPromotionsShelf from "../app-promotions/app-promotions-shelf"; +import * as arcadeCommon from "../arcade/arcade-common"; +import * as arcadeUpsell from "../arcade/arcade-upsell"; +import * as breakoutsCommon from "../arcade/breakouts-common"; +import * as videoDefaults from "../constants/video-constants"; +import * as artworkBuilder from "../content/artwork/artwork"; +import * as content from "../content/content"; +import { EditorialMediaPlacement } from "../editorial-pages/editorial-media-util"; +import { buildSmallStoryCardShelf } from "../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-collection-shelf-builder"; +import { buildStoryCard } from "../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-utils"; +import { createBaseShelfToken } from "../editorial-pages/editorial-page-shelf-token"; +import { CollectionShelfDisplayStyle } from "../editorial-pages/editorial-page-types"; +import * as externalDeepLink from "../linking/external-deep-link"; +import * as links from "../linking/os-update-links"; +import * as lockups from "../lockups/lockups"; +import * as metricsHelpersClicks from "../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../metrics/helpers/location"; +import * as metricsHelpersMedia from "../metrics/helpers/media"; +import * as metricsHelpersPage from "../metrics/helpers/page"; +import * as metricsHelpersUtil from "../metrics/helpers/util"; +import * as sharing from "../sharing"; +import { crossLinkSubtitleFromData, defaultTodayCardConfiguration, fallbackWatchTodayCardFromData, todayCardFromData, } from "./today-card-util"; +import * as todayHorizontalCardUtil from "./today-horizontal-card-util"; +import { todayCardPreviewUrlForTodayCard } from "./today-parse-util"; +import { HeroMediaDisplayContext, TodayCardDisplayStyle, TodayParseContext, } from "./today-types"; +export const iAPBackgroundColor = color.named("componentBackgroundStandout"); +const appShowcaseBackgroundColor = color.named("componentBackgroundStandout"); +const arcadeShowcaseShelfBackgroundColor = color.named("componentBackgroundStandout"); +/** + * Resolves the article module's app media platform to an `AppPlatform` to use for screenshots. + * @param {AppMediaPlatform} appMediaPlatform The server-dictated media platform to use for the module. + * @returns {AppPlatform} The app platform that is appropriate for this media platform, taking into account our device. + */ +function appPlatformFromAppMediaPlatform(objectGraph, appMediaPlatform) { + switch (appMediaPlatform) { + case "Watch": + return "watch"; + case "iOS": + if (objectGraph.client.isPad) { + return "pad"; + } + else { + return "phone"; + } + case "tvOS": + return "tv"; + case "Messages": + return "messages"; + case "visionOS": + return "vision"; + default: + return null; + } +} +export class ArticleParseContext { + constructor() { + // The index of the current module + this.index = 0; + // The reco metrics from the shelf on the today page + this.todayShelfRecoMetricsData = {}; + /// Whether there are any focusable elements (for touch mode) + this.hasFocusableElements = false; + /// Whether there are any non-focusable elements (for touch mode) + this.hasNonFocusableElements = false; + /// Whether there is a resilient deep link. + this.isResilientDeepLink = false; + /// Whether or not to allow app event previews, used by editorial to preview app event stories before they are published + this.allowUnpublishedAppEventPreviews = false; + } +} +function todayCardConfigFromArticleContext(objectGraph, articleContext) { + if (!serverData.isDefinedNonNull(articleContext)) { + return null; + } + if (isSome(articleContext.todayCardConfig)) { + return articleContext.todayCardConfig; + } + const config = defaultTodayCardConfiguration(objectGraph); + config.enableListCardToMultiAppFallback = false; + config.clientIdentifierOverride = articleContext.clientIdentifierOverride; + config.useOTDTextStyle = false; + config.allowUnpublishedAppEventPreviews = articleContext.allowUnpublishedAppEventPreviews; + config.currentRowIndex = undefined; + switch (objectGraph.client.deviceType) { + case "mac": + config.prevailingCropCodes = { defaultCrop: "en" }; + config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid; + config.heroDisplayContext = HeroMediaDisplayContext.Article; + break; + case "tv": + config.prevailingCropCodes = { + "defaultCrop": "ek", + "editorialArtwork.storyCenteredStatic16x9": "SCS.ApDHXL01", + }; + config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid; + config.heroDisplayContext = HeroMediaDisplayContext.Article; + break; + case "web": + config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.River; + config.prevailingCropCodes = { + "defaultCrop": "sr", + "editorialArtwork.dayCard": "grav.west", + }; + break; + default: + break; + } + return config; +} +export function articlePageFromResponse(objectGraph, articleResponse, context) { + return validation.context("articlePageWithResponse", () => { + var _a; + const articleData = mediaDataStructure.dataFromDataContainer(objectGraph, articleResponse); + context.metricsPageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "editorialItem", articleData.id, articleResponse); + context.metricsLocationTracker = metricsHelpersLocation.newLocationTracker(); + context.pageId = articleData.id; + // Bridge over article contexts to today's metrics context and card config + const todayParseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker, context.refreshController); + const todayCardConfig = todayCardConfigFromArticleContext(objectGraph, context); + // Render the top card + let todayCard = todayCardFromData(objectGraph, articleData, todayCardConfig, todayParseContext); + let editorialStoryCard = null; + const todayCardMedia = todayCard === null || todayCard === void 0 ? void 0 : todayCard.media; + if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) { + editorialStoryCard = buildStoryCard(objectGraph, articleData, EditorialMediaPlacement.StoryDetail, { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }, CollectionShelfDisplayStyle.StoryMedium, false); + todayCard = null; + } + if (isNothing(todayCard)) { + todayCard = fallbackWatchTodayCardFromData(objectGraph, articleData, todayCardConfig, todayParseContext); + } + // Get the title for metrics purposes. + const title = (_a = todayCard === null || todayCard === void 0 ? void 0 : todayCard.title) !== null && _a !== void 0 ? _a : editorialStoryCard === null || editorialStoryCard === void 0 ? void 0 : editorialStoryCard.title; + const editorialItemKind = mediaAttributes.attributeAsString(articleData, "kind"); + // Configure subtitle for cross link + context.crossLinkSubtitle = crossLinkSubtitleFromData(objectGraph, articleData); + // Bridge today config back into articles, now that cards are created. + // Now we've created the card, reference the clientIdentifierOverride it used for the rest of the article. + context.clientIdentifierOverride = todayCardConfig.clientIdentifierOverride; + // Start a metrics location + metricsHelpersLocation.pushContentLocation(objectGraph, { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + targetType: "article", + id: context.pageId, + idType: "its_id", + }, title); + // Render the article itself + const shelves = renderArticle(objectGraph, articleData, todayCardMedia, context); + const lastShelf = shelves[shelves.length - 1]; + // Sharing + const shareAction = objectGraph.client.isTV || + objectGraph.client.isWeb || + context.isResilientDeepLink || + preprocessor.GAMES_TARGET || + editorialItemKind === "OfferItem" + ? null + : shareSheetActionFromData(objectGraph, articleData, todayCardConfig); + if (serverData.isDefinedNonNull(shareAction)) { + // Add click event + metricsHelpersClicks.addClickEventToAction(objectGraph, shareAction, { + targetType: "button", + id: context.pageId, + actionType: "share", + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }); + const isLastModuleFullWidth = isArticleShelfFullWidth(objectGraph, lastShelf, context.module); + const shareButtonShelf = createShareShelf(objectGraph, shareAction, context, isLastModuleFullWidth); + if (shareButtonShelf) { + shelves.push(shareButtonShelf); + } + } + const page = new models.ArticlePage(todayCard, shelves, shareAction); + page.editorialStoryCard = editorialStoryCard; + page.title = todayCard === null || todayCard === void 0 ? void 0 : todayCard.title; + page.subtitle = todayCard === null || todayCard === void 0 ? void 0 : todayCard.inlineDescription; + addFooterLockupForPageIfNeeded(objectGraph, page, articleData, context); + if (objectGraph.client.isTV) { + if (context.hasFocusableElements && !context.hasNonFocusableElements) { + page.touchMode = "focus"; + } + else if (!context.hasFocusableElements && context.hasNonFocusableElements) { + page.touchMode = "pan"; + } + else { + page.touchMode = "auto"; + } + } + // Map whether the article should terminate on close. + page.shouldTerminateOnClose = context.isResilientDeepLink; + metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, context.metricsPageInformation, (fields) => { + let additionalValue = title; + if ((todayCard === null || todayCard === void 0 ? void 0 : todayCard.media) instanceof models.TodayCardMediaBrandedSingleApp && + (todayCard === null || todayCard === void 0 ? void 0 : todayCard.overlay) instanceof models.TodayCardLockupOverlay) { + const lockupOverlay = todayCard === null || todayCard === void 0 ? void 0 : todayCard.overlay; + additionalValue = lockupOverlay.lockup.title; + } + if (!additionalValue) { + return; + } + let pageDetails = serverData.asString(serverData.asJSONValue(fields["pageDetails"]), "coercible"); + pageDetails = pageDetails || serverData.asString(serverData.asJSONValue(fields["pageId"])); + if (pageDetails) { + fields["pageDetails"] = `${pageDetails}_${additionalValue}`; + } + else { + fields["pageDetails"] = `unknown_${additionalValue}`; + } + }); + page.canonicalURL = mediaAttributes.attributeAsString(articleData, "url"); + if (isSome(articleData)) { + const articleUrl = mediaAttributes.attributeAsString(articleData, "url"); + if (isSome(articleUrl)) { + page.viewArticleAction = new models.ExternalUrlAction(articleUrl, true); + } + } + return page; + }); +} +function renderArticle(objectGraph, articleData, cardMedia, context) { + return validation.context("renderArticle", () => { + var _a; + const shelves = []; + const canvas = (_a = mediaRelationships.relationshipCollection(articleData, "canvas")) !== null && _a !== void 0 ? _a : []; + for (const storyModule of canvas) { + context.module = mediaAttributes.attributeAsString(storyModule, "displayType"); + context.subStyle = null; + const shelfIndex = shelves.length; + const shelvesToRender = renderModule(objectGraph, storyModule, articleData, context, shelfIndex); + if (shelvesToRender.length > 0) { + for (const shelf of shelvesToRender) { + shelf.title = context.titleForNextShelf; + if (objectGraph.client.isTV) { + // Skip unsupported tvOS shelves + if (shelf.contentType === "editorialLink") { + continue; + } + } + else if (objectGraph.client.isWatch) { + // Skip unsupported watchOS shelves + if (shelf.contentType === "editorialLink") { + continue; + } + } + shelves.push(shelf); + context.titleForNextShelf = null; + } + } + context.index++; + metricsHelpersLocation.nextPosition(context.metricsLocationTracker); + } + // If we we're showing the fallback list card type on the today page, we're going to show + // the lockups as a list shelf underneath so we can still display the list contents. + // If we're on watchOS, we also want to hit this codepath so that lockup lists do not + // show as empty pages. + if ((context.showingFallbackMediaInline || + objectGraph.client.isWatch || + objectGraph.client.isVision || + objectGraph.client.isWeb || + preprocessor.GAMES_TARGET) && + shelves.length === 0) { + const fallbackShelf = createFallbackListShelf(objectGraph, cardMedia); + if (serverData.isDefinedNonNull(fallbackShelf)) { + shelves.push(fallbackShelf); + } + } + return shelves; + }); +} +// region Data Augmenting +/** + * Article specific entrypoint for page response augmenting. See `augment.ts`. + * @param response Response to augment. + */ +export async function fetchAdditionalDataForInitialResponse(objectGraph, response) { + return await mediaAugment.fetchAugmentedData(objectGraph, response, findAdditionalDataKeysForArticleResponse, fetchDataForArticleDataKey); +} +/** + * Determine the set of data, expressed as an set of `ArticleAdditionalDataKey`s, that need to be fetched for given article response to be displayed. + * This is equivalent to `AbstractMediaApiPageBuilder.additionalDataKeysNeededForData`, but for article builder which doesn't adopt the `builder` API. + * + * @param articleResponse Initial response to determine additional data requirements for. + * @returns {Set} Additional data needed expressed as set of `ArticleAdditionalDataKey` + */ +function findAdditionalDataKeysForArticleResponse(objectGraph, articleResponse) { + /** + * Keys for requested requirements determined by: + * - Modules in canvas only now :) + */ + const allAdditionalDataKeySet = new Set(); + // Requirements based on canvas items: + const articleData = mediaDataStructure.dataFromDataContainer(objectGraph, articleResponse); + const canvasModules = mediaRelationships.relationshipCollection(articleData, "canvas"); + for (const storyModule of canvasModules) { + // Determine additional requests and add to `allRequirementsSet` + const dataKeysForModule = additionalDataKeysForArticleModule(objectGraph, storyModule, articleData); + if (serverData.isDefinedNonNullNonEmpty(dataKeysForModule)) { + for (const requirement of dataKeysForModule) { + allAdditionalDataKeySet.add(requirement); + } + } + } + return allAdditionalDataKeySet; +} +/** + * Builds a promise that will fetch data fulfilling given requirement. Note that these promises will return `null` when they fail, + * and their failure should not cause the entire page to fail. + * This is equivalent to `AbstractMediaApiPageBuilder.fetchAdditionalDataForKey`, but for article builder which doesn't adopt the `builder` API. + * + * @param dataKey Corresponding data key to fetch data for. + */ +// eslint-disable-next-line @typescript-eslint/promise-function-async +function fetchDataForArticleDataKey(objectGraph, dataKey) { + let request; + if (dataKey === "upsellForNonacquisitionCanvas") { + // Use `editorialItem` matching context of that would've otherwise been joined if this story was an acquisition story. + request = arcadeCommon.arcadeUpsellRequest(objectGraph, models.marketingItemContextFromString("editorialItemCanvas")); + } + if (dataKey === "arcadeIcons") { + // Require 10 for now. + request = arcadeCommon.arcadeAppsRequestForIcons(objectGraph, 10); + } + if (serverData.isNull(request)) { + return null; + } + // Failable data fetch, either resolving to valid response or `null`. + return mediaNetwork.fetchData(objectGraph, request).catch(() => null); +} +/** + * Determine the requirements for single article module as determined by it's type. + * @param storyModule The module to fetch additional requirements for. + * @param articleData The article that contains `storyModule` in its canvas. + * @returns {ArticleAdditionalDataKey[] | undefined} Set of data keys if any are needed for rendering given module. + */ +export function additionalDataKeysForArticleModule(objectGraph, storyModule, articleData) { + // Only `AppMarker` has additional requirements. + const moduleType = mediaAttributes.attributeAsString(storyModule, "displayType"); + if (moduleType !== "AppMarker") { + return null; + } + const markerType = mediaAttributes.attributeAsString(storyModule, "appMarkerType"); + // In story Arcade acquisition module dropping from stories + // Editorial wants to use the acquisition module in non-acquisition stories, but the `upsell` relationship is only joined for EIs marked with the acquisition flag. + // When an article is missing the upsell relationship, we'll fetch it separately if we have modules that need it... + const articleDataIsMissingUpsell = serverData.isNull(arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData)); + /** + * Acquisition AppMarker, i.e. `ArcadeShowcase` needs: + * 1. Upsell data for text data, e.g. editorial notes and breakoutCallToAction label, provided this data isn't already provided as part of original page. + * 2. Assortment of Arcade App Icons (iOS Only). + */ + const additionalDataKeysForModule = []; + if (markerType === "Acquisition") { + // iOS needs icon dependency + if (objectGraph.host.isiOS || objectGraph.client.isVision) { + additionalDataKeysForModule.push("arcadeIcons"); + } + // All platform needs upsell to render acquisition modules, add it if missing. + if (articleDataIsMissingUpsell) { + additionalDataKeysForModule.push("upsellForNonacquisitionCanvas"); + } + } + return additionalDataKeysForModule; +} +// endregion +/** + * Create a shelf model representing a single module within article pages. + * @param storyModule Module server data to build shelf and contents from. + * @param articleData The data for article that contains `storyModule` above. + * @param context Global parse context updated while entire sets of modules are being parsed. + * @returns an array of `Shelf` or `null` if building fails for given module. + */ +function renderModule(objectGraph, storyModule, articleData, context, shelfIndex) { + return validation.catchingContext(`module: ${context.module}`, () => { + var _a; + const shelves = []; + switch (context.module) { + case "Header": { + context.titleForNextShelf = mediaAttributes.attributeAsString(storyModule, "editorialCopy"); + break; + } + case "TextBlock": { + const textBlockShelf = createParagraph(objectGraph, storyModule, context); + if (isSome(textBlockShelf)) { + shelves.push(textBlockShelf); + context.hasNonFocusableElements = true; + } + break; + } + case "CollectionLockup": { + const appListShelf = createAppList(objectGraph, storyModule, context); + if (isSome(appListShelf)) { + shelves.push(appListShelf); + context.hasFocusableElements = true; + } + break; + } + case "InlineImage": { + const inlineImageShelf = createImage(objectGraph, storyModule, context); + if (isSome(inlineImageShelf)) { + shelves.push(inlineImageShelf); + context.hasNonFocusableElements = true; + } + break; + } + case "AppLockup": { + const appLockupShelf = createAppLockup(objectGraph, storyModule, context); + if (isSome(appLockupShelf)) { + shelves.push(appLockupShelf); + context.hasFocusableElements = true; + } + break; + } + case "TipBlock": { + const tipShelf = createTip(objectGraph, storyModule, context); + if (isSome(tipShelf)) { + shelves.push(tipShelf); + context.hasNonFocusableElements = true; + } + break; + } + case "PullQuote": { + const pullQuoteShelf = createPullQuote(objectGraph, storyModule, context); + if (isSome(pullQuoteShelf)) { + shelves.push(pullQuoteShelf); + context.hasNonFocusableElements = true; + } + break; + } + case "HorizontalRule": { + const horizontalRuleShelf = createHorizontalRule(objectGraph, storyModule, context); + if (isSome(horizontalRuleShelf)) { + shelves.push(horizontalRuleShelf); + context.hasNonFocusableElements = true; + } + break; + } + case "InlineVideo": { + const inlineVideoShelf = createVideo(objectGraph, storyModule, context); + if (isSome(inlineVideoShelf)) { + shelves.push(inlineVideoShelf); + context.hasFocusableElements = true; + } + break; + } + case "AppMedia": { + const appMediaShelf = createAppMedia(objectGraph, storyModule, context); + if (isSome(appMediaShelf)) { + shelves.push(appMediaShelf); + context.hasFocusableElements = true; + } + break; + } + case "LinkBlock": { + const linkBlockShelf = createLink(objectGraph, storyModule, context); + if (isSome(linkBlockShelf)) { + shelves.push(linkBlockShelf); + context.hasFocusableElements = true; + } + break; + } + case "TextList": { + const textListShelf = createTextList(objectGraph, storyModule, context); + if (isSome(textListShelf)) { + shelves.push(textListShelf); + context.hasNonFocusableElements = true; + } + break; + } + case "IAPLockup": { + const iapLockupShelf = createIAPLockup(objectGraph, storyModule, context); + if (isSome(iapLockupShelf)) { + shelves.push(iapLockupShelf); + context.hasFocusableElements = true; + } + break; + } + case "AppMarker": { + const appMarkerShelf = createAppMarker(objectGraph, storyModule, articleData, context); + if (isSome(appMarkerShelf)) { + shelves.push(appMarkerShelf); + context.hasFocusableElements = true; + } + break; + } + case "StoryList": { + const storyListShelf = createStoryCards(objectGraph, storyModule, context, shelfIndex); + if (isSome(storyListShelf)) { + shelves.push(storyListShelf); + context.hasFocusableElements = true; + } + break; + } + case "AppEventLockup": { + const appEventShelf = createAppEventLockup(objectGraph, storyModule, context); + if (isSome(appEventShelf)) { + shelves.push(appEventShelf); + context.hasFocusableElements = true; + } + break; + } + case "OfferItemLockup": { + const offerItemShelves = createOfferItemLockup(objectGraph, storyModule, context); + if (isSome(offerItemShelves)) { + shelves.push(...offerItemShelves); + context.hasFocusableElements = true; + } + break; + } + default: { + objectGraph.console.log(`Unknown module: ${context.module}`); + } + } + for (const shelf of shelves) { + const existingShelfPresentationHints = (_a = shelf.presentationHints) !== null && _a !== void 0 ? _a : {}; + shelf.presentationHints = { + ...existingShelfPresentationHints, + isArticleContext: true, + }; + } + return shelves; + }); +} +const FULL_WIDTH_MODULES = ["AppLockup", "InlineImage", "InlineVideo", "AppMarker"]; +/** + * Determines whether the provided parameters signifies a full-width article + * module. + * @param shelf The shelf in question. + * @param type The type of article module. + * @returns Whether or not the given shelf for the article type is full width. + */ +function isArticleShelfFullWidth(objectGraph, shelf, type) { + if (shelf && type) { + const itemCount = shelf.items.length; + if (itemCount > 0 && FULL_WIDTH_MODULES.indexOf(type) !== -1) { + const lastItem = shelf.items[itemCount - 1]; + switch (shelf.contentType) { + case "framedArtwork": { + const framedArt = lastItem; + return framedArt && framedArt.isFullWidth; + } + case "framedVideo": { + const framedVideo = lastItem; + return framedVideo && framedVideo.isFullWidth; + } + default: { + return true; + } + } + } + } + return false; +} +// region Footer Lockup +/** + * Adds either a `footerLockup` or `arcadeFooterLockup` property on `ArticlePage` model, based on type of article. + * @param page Page to add footer to if needed. + * @param articleData Original data of article being rendered. + * @param context Parse context for article builder. + */ +function addFooterLockupForPageIfNeeded(objectGraph, page, articleData, context) { + // App Lockup for Articles about single specific app. + const footerProductData = productDataFromArticle(objectGraph, articleData); + if (footerProductData) { + const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, articleData); + page.footerLockup = productFooterLockupFromData(objectGraph, footerProductData, context, externalDeepLinkUrl); + return; + } + // Arcade Lockup for Acquisition Story for supported platforms + const isArcadeAcquisitionEI = mediaAttributes.attributeAsBooleanOrFalse(articleData, "isAcquisition"); + const platformSupportsArcadeFooterLockup = objectGraph.host.isiOS || objectGraph.host.isMac; + const additionalDataIsAvailable = serverData.isDefinedNonNull(context.additionalData); + if (additionalDataIsAvailable && isArcadeAcquisitionEI && platformSupportsArcadeFooterLockup) { + const upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData); + page.arcadeFooterLockup = arcadeFooterLockupFromData(objectGraph, upsellData, context); + } +} +/** + * Find platform data from editorial item to enhance sharing and display in footer lockup + * At the moment, only single app editorials get footer lockups and have enhanced sharing. + * + * @param editorialItem Item to find footer content for + * @returns content to display in footer lockup, or null if no content should be displayed + */ +export function productDataFromArticle(objectGraph, editorialItem) { + const relatedContent = mediaRelationships.relationshipCollection(editorialItem, "card-contents"); + if (relatedContent.length !== 1) { + return null; + } + const contentData = relatedContent[0]; + if (!contentData) { + return null; + } + switch (contentData.type) { + case "apps": + case "app-bundles": + return contentData; + default: + return null; + } +} +/** + * Creates a footer lockup with a data for a specific app. + * Cover method over `lockupFromData` to override `offerStyle`. + * + * @param data MAPI data to build footer with. + * @param context Parse context + * @param externalDeepLinkUrl promotional deep link url to use on the lockup's offer. + * @returns A new `Lockup` object for footer lockups. + */ +function productFooterLockupFromData(objectGraph, data, context, externalDeepLinkUrl) { + const lockupOptions = { + offerStyle: footerLockupOfferStyle(objectGraph), + metricsOptions: { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }, + clientIdentifierOverride: context.clientIdentifierOverride, + externalDeepLinkUrl: externalDeepLinkUrl, + crossLinkSubtitle: context.crossLinkSubtitle, + artworkUseCase: 0 /* content.ArtworkUseCase.Default */, + canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "smallLockup"), + }; + return lockups.lockupFromData(objectGraph, data, lockupOptions); +} +/** + * Creates a footer lockup representing the Arcade subscription service. + * @param upsellData Contains both editorial and iAP data for Arcade + * @param context Parse context. + */ +function arcadeFooterLockupFromData(objectGraph, upsellData, context) { + const metricsOptions = { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }; + return lockups.arcadeLockupFromData(objectGraph, upsellData, metricsOptions, models.marketingItemContextFromString("editorialItem"), "infer", null); +} +/** + * Determines the offer style to use for the footer lockup. + */ +function footerLockupOfferStyle(objectGraph) { + switch (objectGraph.client.deviceType) { + case "mac": + return "white"; + default: + return "infer"; + } +} +// endregion +function createFallbackListShelf(objectGraph, cardMedia) { + if (cardMedia instanceof models.TodayCardMediaList || cardMedia instanceof models.TodayCardMediaRiver) { + const fallbackShelf = new models.Shelf("smallLockup"); + fallbackShelf.items = cardMedia.lockups; + if (objectGraph.client.isWeb) { + fallbackShelf.presentationHints = { + ...fallbackShelf.presentationHints, + isArticleContext: true, + }; + } + return fallbackShelf; + } + return null; +} +function shareSheetActionFromData(objectGraph, editorialItem, todayCardConfig) { + const productData = productDataFromArticle(objectGraph, editorialItem); + /* + * Determine title + */ + let title = null; + const name = content.notesFromData(objectGraph, editorialItem, "name"); + const short = content.notesFromData(objectGraph, editorialItem, "short"); + // Prefer "name: short" + if (name && short) { + title = objectGraph.loc + .string("ShareSheet.TitleSubtitle.Format", "{title}: {subtitle}") + .replace("{title}", name) + .replace("{subtitle}", short); + } + // Followed by name + if (!title && name) { + title = name; + } + // Followed by short + if (!title && short) { + title = short; + } + // Followed by product name + if (!title && productData) { + const productTitle = mediaAttributes.attributeAsString(productData, "name"); + const cardDisplayStyle = mediaAttributes.attributeAsString(editorialItem, "cardDisplayStyle"); + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.GameOfTheDay: { + title = objectGraph.loc.string("SHARE_SHEET_GAME_OF_DAY_TITLE_FORMAT").replace("{title}", productTitle); + break; + } + case TodayCardDisplayStyle.AppOfTheDay: { + title = objectGraph.loc.string("SHARE_SHEET_APP_OF_DAY_TITLE_FORMAT").replace("{title}", productTitle); + break; + } + default: { + objectGraph.console.log(`No title for article with unknown style: ${cardDisplayStyle}`); + break; + } + } + } + const url = mediaAttributes.attributeAsString(editorialItem, "url"); + let articleArtwork; + const cardDisplayStyle = mediaAttributes.attributeAsString(editorialItem, "cardDisplayStyle"); + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.Grid: + case TodayCardDisplayStyle.List: + case TodayCardDisplayStyle.River: + articleArtwork = artworkBuilder.createArtworkForResource(objectGraph, "resource://ShareCollectionThumbnail", 40, 40); + break; + default: + articleArtwork = null; + break; + } + // Create share sheet model (bail out if unable to do so) + const shareData = sharing.shareSheetDataForArticle(objectGraph, title, url, null, articleArtwork, editorialItem); + if (!serverData.isDefinedNonNull(shareData)) { + return null; + } + const activities = sharing.shareSheetActivitiesForArticle(objectGraph, url, todayCardPreviewUrlForTodayCard(objectGraph, editorialItem.id, todayCardConfig), editorialItem.id); + return new models.ShareSheetAction(shareData, activities); +} +function createShareShelf(objectGraph, shareAction, context, isLastModuleFullWidth) { + if (!serverData.isDefinedNonNull(shareAction) || + objectGraph.client.isVision || + preprocessor.GAMES_TARGET || + objectGraph.client.isCompanionVisionApp) { + return null; + } + // Create share button + const shareButton = new models.RoundedButton("share", objectGraph.loc.string("SHARE_STORY"), !isLastModuleFullWidth, shareAction); + // Add share shelf + const shareButtonShelf = new models.Shelf("roundedButton"); + shareButtonShelf.items = [shareButton]; + return shareButtonShelf; +} +function createParagraph(objectGraph, module, context) { + const text = mediaAttributes.attributeAsString(module, "editorialCopy"); + if (!text) { + return null; + } + const paragraph = new models.Paragraph(text, "text/x-apple-as3-nqml", "article"); + // Setup impressions + addImpressionsFieldsToModel(objectGraph, paragraph, context); + const shelf = new models.Shelf("paragraph"); + shelf.items = [paragraph]; + return shelf; +} +function createImage(objectGraph, module, context) { + const displayStyle = mediaAttributes.attributeAsString(module, "inlineImageDisplayType"); + const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork"); + // If the displayStyle is FullWidth want to 'allowTransparency' so that images blend into the page in both + // light and dark mode. Previously editorial would bake white backgrounds into images they wanted to 'blend' + // with the page + const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, { + useCase: 13 /* content.ArtworkUseCase.ArticleImage */, + allowingTransparency: displayStyle === "FullWidth" && !objectGraph.client.isVision, + withJoeColorPlaceholder: objectGraph.client.isVision, + }); + if (!artwork) { + return null; + } + const frame = new models.FramedArtwork(artwork, false, "text/x-apple-as3-nqml"); + // Get the optional caption + frame.caption = mediaAttributes.attributeAsString(module, "editorialCopy"); + context.subStyle = displayStyle; + if (displayStyle) { + switch (displayStyle) { + case "BoundingBox": { + frame.isFullWidth = false; + frame.hasRoundedCorners = true; + break; + } + case "FullWidth": + default: { + frame.isFullWidth = true; + frame.hasRoundedCorners = false; + break; + } + } + } + // Setup impressions + addImpressionsFieldsToModel(objectGraph, frame, context); + const shelf = new models.Shelf("framedArtwork"); + shelf.items = [frame]; + return shelf; +} +function createTip(objectGraph, module, context) { + const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork"); + const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, { + useCase: 13 /* content.ArtworkUseCase.ArticleImage */, + }); + if (!artwork) { + return null; + } + const caption = mediaAttributes.attributeAsString(module, "editorialCopy"); + const ordinal = mediaAttributes.attributeAsString(module, "tipNumber"); + // Create the tip image + const frame = new models.FramedArtwork(artwork, false, "text/x-apple-as3-nqml"); + frame.isFullWidth = false; + frame.hasRoundedCorners = true; + frame.caption = caption; + frame.ordinal = ordinal; + // Setup impressions + addImpressionsFieldsToModel(objectGraph, frame, context); + // Create the shelf + const shelf = new models.Shelf("framedArtwork"); + shelf.items = [frame]; + return shelf; +} +function createPullQuote(objectGraph, module, context) { + const text = mediaAttributes.attributeAsString(module, "quote"); + const attribution = mediaAttributes.attributeAsString(module, "quoteAttribution"); + // Get the optional artwork + const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork"); + const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, { + useCase: 13 /* content.ArtworkUseCase.ArticleImage */, + }); + const fullWidth = mediaAttributes.attributeAsString(module, "pullQuoteDisplayType") === "FullWidth"; + // Create the quote + const quote = new models.Quote(text, attribution, artwork, fullWidth); + // Setup impressions + addImpressionsFieldsToModel(objectGraph, quote, context); + // Create the shelf + const shelf = new models.Shelf("quote"); + shelf.items = [quote]; + return shelf; +} +function createHorizontalRule(objectGraph, module, context) { + const lineStyle = mediaAttributes.attributeAsString(module, "lineStyle"); + const fullWidth = mediaAttributes.attributeAsString(module, "displayStyle") === "FullWidth"; + let ruleColor = color.named("defaultLine"); + if (objectGraph.client.isVision && (lineStyle === "Dotted" || lineStyle === "Dashed")) { + ruleColor = color.white; + } + // Parse the customColor from Media API. This can only be a dynamic color. + const apiColor = mediaAttributes.attributeAsDictionary(module, "customColor"); + const lightColor = color.fromHex(serverData.asString(apiColor, "lightMode")); + const darkColor = color.fromHex(serverData.asString(apiColor, "darkMode")); + if (!serverData.isNullOrEmpty(lightColor) && !serverData.isNullOrEmpty(darkColor)) { + ruleColor = color.dynamicWith(lightColor, darkColor); + } + const horizontalRule = new models.HorizontalRule(lineStyle, ruleColor, fullWidth); + // Create the Shelf + const shelf = new models.Shelf("horizontalRule"); + shelf.items = [horizontalRule]; + return shelf; +} +function createVideo(objectGraph, module, context) { + // Get the preview artwork + const artworkData = mediaAttributes.attributeAsDictionary(module, "video.previewFrame"); + const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, { + useCase: 13 /* content.ArtworkUseCase.ArticleImage */, + }); + if (!artwork) { + return null; + } + // Get the video URL + const videoUrl = mediaAttributes.attributeAsString(module, "video.video"); + if (!videoUrl) { + return null; + } + const videoDisplayType = mediaAttributes.attributeAsString(module, "inlineVideoDisplayType"); + const isFullWidth = videoDisplayType === "FullWidth"; + // Create the video + const video = new models.Video(videoUrl, artwork, videoDefaults.defaultVideoConfiguration(objectGraph)); + metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, video, { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + id: context.pageId, + }); + const videoModule = new models.FramedVideo(video, isFullWidth, "text/x-apple-as3-nqml"); + // Get the optional caption + videoModule.caption = mediaAttributes.attributeAsString(module, "editorialCopy"); + // Setup impressions + addImpressionsFieldsToModel(objectGraph, videoModule, context); + // Create the shelf + const shelf = new models.Shelf("framedVideo"); + shelf.items = [videoModule]; + return shelf; +} +function createAppLockup(objectGraph, module, context) { + const contentData = contentFromModule(objectGraph, module, context); + if (!contentData) { + return null; + } + // Shelf to generate. Either lockup, app showcase, or app event shelf + let shelf = null; + // If we have an app-events relationship, we want to use this as the priority. This sometimes exists on the + // AppLockup type, rather than as the AppEventLockup type, so that older clients can still render this + // item by falling back to the AppLockup type. + const appEventsDataItems = mediaRelationships.relationshipCollection(module, "app-events"); + if (serverData.isDefinedNonNullNonEmpty(appEventsDataItems)) { + shelf = appPromotionsShelf.appEventsShelfForArticle(objectGraph, appEventsDataItems, context.metricsPageInformation, context.metricsLocationTracker, context); + if (serverData.isDefinedNonNull(shelf)) { + return shelf; + } + } + // Set the display style + const displayStyle = mediaAttributes.attributeAsString(module, "appLockupSize"); + context.subStyle = displayStyle; + let shelfStyle; + let isLockup = false; + if (displayStyle) { + switch (displayStyle) { + case "Small": { + shelfStyle = "smallLockup"; + isLockup = true; + break; + } + case "Medium": { + shelfStyle = "mediumLockup"; + isLockup = true; + break; + } + case "Large": + default: { + if (objectGraph.client.isWatch || + objectGraph.client.isTV || + objectGraph.client.isVision || + preprocessor.GAMES_TARGET) { + // Per design, on watchOS we always show a lockup for app showcases. + // Watch App Store treats all lockup sizes the same -- let's pick small. + shelfStyle = "smallLockup"; + isLockup = true; + } + else { + shelfStyle = "appShowcase"; + } + break; + } + } + } + // Determine the deep link URL, if there is one. + const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, module); + // Create the appropriate shelf item + if (isLockup) { + const lockupShelf = new models.Shelf(shelfStyle); + const metricsOptions = { + metricsOptions: { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }, + clientIdentifierOverride: context.clientIdentifierOverride, + externalDeepLinkUrl: externalDeepLinkUrl, + artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfStyle), + }; + let lockup; + if (preprocessor.GAMES_TARGET) { + const shelfID = new PageID(context.pageId).shelfID(module.id); + lockup = gamesComponentBuilder.makeArticleGameLockup(objectGraph, contentData, shelfID); + } + else { + lockup = lockups.lockupFromData(objectGraph, contentData, metricsOptions); + } + if (isNothing(lockup)) { + return null; + } + lockupShelf.items = [lockup]; + shelf = lockupShelf; + } + else { + // On all platforms, the AppLockup platform generates a AppShowcase when display style is large. + shelf = createAppShowcase(objectGraph, module, context); + } + return shelf; +} +function createAppShowcase(objectGraph, module, context) { + // Create the shelf + const shelf = new models.Shelf("appShowcase"); + // Parameterize by platform: + // tvOS populates the `screenshots` field to display alongside video. + const showcaseHasScreenshots = objectGraph.client.isTV; + // Only non-tvOS has shelf background color + const shelfHasBackgroundColor = objectGraph.client.deviceType !== "tv"; + const contentData = contentFromModule(objectGraph, module, context); + const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, module); + const lockup = lockups.lockupFromData(objectGraph, contentData, { + offerStyle: "colored", + metricsOptions: { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }, + clientIdentifierOverride: context.clientIdentifierOverride, + externalDeepLinkUrl: externalDeepLinkUrl, + crossLinkSubtitle: context.crossLinkSubtitle, + artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */, + }); + const showcase = new models.AppShowcase("large", lockup); + showcase.description = lockups.subtitleFromData(objectGraph, contentData); + // Add Video + // Configure the video for the showcase, if the module demands it. + let showcaseVideo = null; + const videoType = mediaAttributes.attributeAsString(module, "appLockupVideo"); + switch (videoType) { + case "AppTrailer": { + const allAppVideos = content.videoPreviewsFromData(objectGraph, contentData); + if (allAppVideos && allAppVideos.length > 0) { + showcaseVideo = allAppVideos[0]; + } + break; + } + default: + break; + } + if (showcaseVideo) { + metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, showcaseVideo, { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + id: context.pageId, + }); + showcase.video = showcaseVideo; + } + // Add Screenshots for AppShowcase if necessary + if (showcaseHasScreenshots) { + showcase.screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */, [content.currentAppPlatform(objectGraph)]); + } + // Configure background if necessary. + if (shelfHasBackgroundColor) { + shelf.background = { + type: "color", + color: appShowcaseBackgroundColor, + }; + } + shelf.items = [showcase]; + return shelf; +} +function createIAPLockup(objectGraph, module, context) { + const contentData = contentFromModule(objectGraph, module, context); + if (!contentData) { + return null; + } + // Create the lockup + const lockup = lockups.inAppPurchaseLockupFromData(objectGraph, contentData, { + metricsOptions: { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }, + clientIdentifierOverride: context.clientIdentifierOverride, + artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */, + }); + if (!lockup) { + return null; + } + const showcase = new models.InAppPurchaseShowcase(lockup); + // Create the shelf + const shelf = new models.Shelf("inAppPurchaseShowcase"); + shelf.background = { + type: "color", + color: iAPBackgroundColor, + }; + shelf.items = [showcase]; + return shelf; +} +function createAppList(objectGraph, module, context) { + const showOrdinals = mediaAttributes.attributeAsBooleanOrFalse(module, "showOrdinals"); + const ordinalDirection = mediaAttributes.attributeAsString(module, "collectionLockupDisplayType") === "OrdinalDesc" + ? "descending" + : "ascending"; + // Set the display style + const displayStyle = mediaAttributes.attributeAsString(module, "collectionLockupSize"); + context.subStyle = displayStyle; + let style; + if (displayStyle) { + switch (displayStyle) { + case "Large": { + style = "largeLockup"; + break; + } + case "Medium": { + style = "mediumLockup"; + break; + } + case "Small": + default: { + style = "smallLockup"; + break; + } + } + } + // Construct the lockup options + const lockupOptions = { + metricsOptions: { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }, + clientIdentifierOverride: context.clientIdentifierOverride, + artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, style), + canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, style), + }; + // Check if we have content + const contents = mediaRelationships.relationshipCollection(module, "contents"); + if (isNothing(contents)) { + return null; + } + let childLockups = []; + if (preprocessor.GAMES_TARGET) { + const shelfID = new PageID(context.pageId).shelfID(module.id); + childLockups = gamesComponentBuilder.makeArticleGameLockups(objectGraph, contents, shelfID); + } + else { + childLockups = lockups.lockupsFromData(objectGraph, contents, { + includeOrdinals: showOrdinals, + ordinalDirection: ordinalDirection, + lockupOptions: lockupOptions, + }); + } + if (!childLockups || childLockups.length === 0) { + return null; + } + // Create the shelf + const shelf = new models.Shelf(style); + shelf.items = childLockups; + return shelf; +} +function createAppMedia(objectGraph, module, context) { + const contentData = contentFromModule(objectGraph, module, context); + if (!contentData) { + return null; + } + // Set the display style + const mediaOption = mediaAttributes.attributeAsString(module, "appMediaOption"); + const appMediaPlatform = mediaAttributes.attributeAsString(module, "appMediaPlatform"); + context.subStyle = mediaOption; + switch (mediaOption) { + case "Screenshots": { + let shelf = null; + // I'm so sorry, but making this split makes the macOS client code infinitely better because we are able + // to reuse the same product media view and component contract that is used on product page screenshots/trailers. + // Really, iOS should be reworked such that its module & product page implementation has a single source, + // but this has serious design obstacles that need to be worked through. + if (objectGraph.client.isMac) { + shelf = new models.Shelf("productMedia"); + const productMedia = content.productMediaFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */); + if (serverData.isDefinedNonNull(productMedia) && productMedia.length) { + shelf.items = productMedia; + } + } + else { + shelf = new models.Shelf("screenshots"); + if (serverData.isNull(appMediaPlatform)) { + /** + * The server did not tell us which app platform to use, so we need to infer based on various keys in + * the response. These parameters are only fully baked into product-dv responses, so we we need to do + * the more expensive product-dv lookup in order to correctly infer the default screenshots to use for + * the shelf. + */ + const screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */); + if (screenshots && screenshots.length > 0) { + shelf.items = [screenshots[0]]; + } + } + else { + /** + * Server tells us which platform to use -- dictated by `appMediaPlatform`. Selectively do a lookup for + * just those screenshots. + */ + const desiredAppPlatform = appPlatformFromAppMediaPlatform(objectGraph, appMediaPlatform); + if (desiredAppPlatform) { + const screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */, [desiredAppPlatform]); + if (screenshots && screenshots.length) { + shelf.items = [screenshots[0]]; + } + } + } + } + if (serverData.isDefinedNonNull(shelf) && shelf.items.length === 0) { + return null; + } + return shelf; + } + case "AppTrailers": + const trailersShelf = new models.Shelf("framedVideo"); + const videoPreviews = content.videoPreviewsFromData(objectGraph, contentData); + if (videoPreviews && videoPreviews.length > 0) { + const video = videoPreviews[0]; + metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, video, { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + id: context.pageId, + }); + const firstTrailer = new models.FramedVideo(video, false, "text/plain", null, null, true); + trailersShelf.items = [firstTrailer]; + return trailersShelf; + } + else { + return null; + } + default: { + return null; + } + } +} +function createLink(objectGraph, module, context) { + if (objectGraph.client.isTV || objectGraph.client.isWatch) { + return null; + } + const urlString = mediaAttributes.attributeAsString(module, "url"); + if (!urlString) { + return null; + } + const url = new urls.URL(urlString); + const linkTitle = mediaAttributes.attributeAsString(module, "urlTitle"); + let text = mediaAttributes.attributeAsString(module, "editorialCopy"); + if (!text) { + text = url.host; + } + const mediaHosts = [ + "itunes.apple.com", + "apps.apple.com", + "music.apple.com", + "books.apple.com", + "podcasts.apple.com", + "watch-app.cdn-apple.com", + "tv.apple.com", + ]; + let linkPresentationEnabled = false; + for (const mediaHost of mediaHosts) { + if (url.host.endsWith(mediaHost)) { + linkPresentationEnabled = true; + } + } + const action = new models.ExternalUrlAction(urlString); + metricsHelpersClicks.addClickEventToAction(objectGraph, action, { + targetType: "link", + pageInformation: context.metricsPageInformation, + id: `${context.index}`, + locationTracker: context.metricsLocationTracker, + }); + const link = new models.EditorialLink(linkTitle, text, action, linkPresentationEnabled); + // Setup impressions + addImpressionsFieldsToModel(objectGraph, link, context); + const shelf = new models.Shelf("editorialLink"); + shelf.items = [link]; + return shelf; +} +function createTextList(objectGraph, module, context) { + const listEntries = mediaAttributes.attributeAsArrayOrEmpty(module, "editorialCopy"); + if (!listEntries.length) { + return null; + } + const type = mediaAttributes.attributeAsString(module, "textListDisplayType"); + context.subStyle = type; + let isBulleted = false; + switch (type) { + case "Bulleted": { + isBulleted = true; + break; + } + default: { + isBulleted = false; + break; + } + } + let text; + if (isBulleted) { + text = "
    "; + } + else { + text = "
      "; + } + for (const textEntry of listEntries) { + const listItemJSONString = JSON.stringify(textEntry); + // rdar://104446319 - We must use `parse` on our JSON string to convert back to + // a raw string object as this ensures leading/trailing quotation marks are *not* escaped + const listItem = JSON.parse(listItemJSONString); + text = `${text}
    1. ${listItem}
    2. `; + } + if (isBulleted) { + text = `${text}
`; + } + else { + text = `${text}`; + } + const paragraph = new models.Paragraph(text, "text/x-apple-as3-nqml", "article"); + // Setup impressions + addImpressionsFieldsToModel(objectGraph, paragraph, context); + const shelf = new models.Shelf("paragraph"); + shelf.items = [paragraph]; + return shelf; +} +function createStoryCards(objectGraph, module, context, shelfIndex) { + if (objectGraph.client.isVision) { + const shelfToken = createBaseShelfToken(objectGraph, undefined, module, false, shelfIndex, context.metricsPageInformation, context.metricsLocationTracker); + const shelf = buildSmallStoryCardShelf(objectGraph, shelfToken); + shelf.isHorizontal = true; + return shelf; + } + const cards = mediaRelationships.relationshipCollection(module, "contents"); + if (!cards) { + return null; + } + const title = mediaAttributes.attributeAsString(module, "name"); + const subtitle = mediaAttributes.attributeAsString(module, "tagline"); + let shelf = null; + if (objectGraph.client.isiOS && objectGraph.featureFlags.isEnabled("mini_today_cards_article")) { + const todayParseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker); + shelf = todayHorizontalCardUtil.shelfForMiniTodayCards(objectGraph, cards, title, subtitle, todayParseContext); + } + else { + const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb; + const resolvedContentType = isSmallStoryCardsSupported ? "smallStoryCard" : "todayBrick"; + shelf = todayHorizontalCardUtil.shelfForHorizontalCardItems(objectGraph, cards, resolvedContentType, title, subtitle, context, null); + if (isSmallStoryCardsSupported) { + // Only specific small story cards are supported and will crash otherwise, filter those here preemptively. + // rdar://91965501 (MAS Crashing - Earth Day Landing Page - 4/19) + if (Array.isArray(shelf.items)) { + shelf.items = shelf.items.filter((item) => { + if (!(item instanceof models.TodayCard)) { + return true; + } + return todayHorizontalCardUtil.isHorizontalCardSupportedForKind(objectGraph, item.media.kind, resolvedContentType); + }); + } + } + } + return shelf; +} +function createAppEventLockup(objectGraph, module, context) { + const contentData = contentFromModule(objectGraph, module, context); + if (!contentData) { + return null; + } + return appPromotionsShelf.appEventsShelfForArticle(objectGraph, [contentData], context.metricsPageInformation, context.metricsLocationTracker, context); +} +function createOfferItemLockup(objectGraph, module, context) { + if (!objectGraph.client.isiOS) { + return []; + } + const offerItem = mediaRelationships.relationshipData(objectGraph, module, "contents"); + if (serverData.isNullOrEmpty(offerItem)) { + return null; + } + // Offer detail Paragraph + const offerParagraph = mediaAttributes.attributeAsString(module, "editorialCopy"); + const paragraph = new models.Paragraph(offerParagraph, "text/x-apple-as3-nqml", "article"); + const paragraphShelf = new models.Shelf("paragraph"); + paragraphShelf.items = [paragraph]; + // Winback Offer Card + const offerItemShelf = appPromotionsShelf.appEventsShelfForArticle(objectGraph, [offerItem], context.metricsPageInformation, context.metricsLocationTracker, context); + return [paragraphShelf, offerItemShelf]; +} +/** + * Ingests EI canvas modules of form: + * { + * id: , + * type: "editorial-item-shelves", + * attributes: { + * displayType: "AppMarker", + * appMarkerType: + * } + * } + * to generate an shelf for AppMarker model. + */ +function createAppMarker(objectGraph, appMarkerModule, articleData, context) { + const markerType = mediaAttributes.attributeAsString(appMarkerModule, "appMarkerType"); + context.subStyle = markerType; + let shelf = null; + switch (markerType) { + case "OSUpgrade": + shelf = createOSUpgradeClientControlButton(objectGraph, appMarkerModule, context); + break; + case "Acquisition": + shelf = createArcadeShowcase(objectGraph, appMarkerModule, articleData, context); + break; + default: + break; + } + return shelf; +} +/** + * Ingests EI canvas modules of form: + * { + * id: , + * type: "editorial-item-shelves", + * attributes: { + * displayType: "AppMarker", + * appMarkerType: "OSUpgrade" + * } + * } + * to generate an shelf with an button that links to preferences updates. + */ +function createOSUpgradeClientControlButton(objectGraph, osUpgradeModule, context) { + const deviceType = objectGraph.client.deviceType; + if (deviceType !== "mac") { + return null; // Early exit - Only MAS utilizes OS Upgrade Client Control Button currently. + } + const installUpdateUrl = links.osUpdateUrl(deviceType); + if (installUpdateUrl === null) { + return null; + } + // Action to Preferences + const openUpdatesAction = new models.ExternalUrlAction(installUpdateUrl); + // Action to open preferences is configured as `link` + metricsHelpersClicks.addClickEventToAction(objectGraph, openUpdatesAction, { + targetType: "link", + pageInformation: context.metricsPageInformation, + id: `${context.index}`, + locationTracker: context.metricsLocationTracker, + }); + // Shelf model + const upgradeControlText = objectGraph.loc.string("CLIENT_CONTROL_OS_UPGRADE_TITLE", "CHECK FOR UPDATE"); + const upgradeControl = new models.ClientControlButton(upgradeControlText, openUpdatesAction); + // Add impressions + addImpressionsFieldsToModel(objectGraph, upgradeControl, context); + const shelf = new models.Shelf("clientControlButton"); + shelf.items = [upgradeControl]; + return shelf; +} +/** + * Ingests EI canvas modules of form: + * { + * id: , + * type: "editorial-item-shelves", + * } + * with additional data: + * - Upsell data on `context.additionalData` + * - Icon Artwork data (iOS only) on `context.additionalData` + * + * to generate an shelf that promotes Arcade service. + * + * @param arcadeShowcaseModule Arcade showcase module + * @param articleData The data backing the article containing the module. Used for top-level relationship. + * @param context Parse context for this page parsing. This context contains the additional requirements data. + */ +function createArcadeShowcase(objectGraph, arcadeShowcaseModule, articleData, context) { + const supportedOnPlatform = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.client.isVision; + if (!supportedOnPlatform) { + return null; + } + // Default to upsell on relation, falling back to upsell that may have been fetched separately for orphaned acquisition modules. + let upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData); + if (!upsellData && context.additionalData) { + const upsellResponse = context.additionalData.get("upsellForNonacquisitionCanvas"); + upsellData = arcadeCommon.upsellFromContentsOfUpsellResponse(objectGraph, upsellResponse); + } + if (!serverData.isDefinedNonNull(upsellData)) { + return null; + } + const baseMetricsOptions = { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }; + // Flow to See All games if subscribed + const subscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, context.metricsPageInformation, context.metricsLocationTracker, objectGraph.client.isVision); + if (preprocessor.GAMES_TARGET) { + subscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore"); + } + else { + subscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE"); + } + // Flow to Arcade Subscribe page if unsubscribed. + let unsubscribedAction; + const unsubscribedActionTitle = breakoutsCommon.callToActionLabelFromData(objectGraph, upsellData.marketingItemData); + if (serverData.isDefinedNonNullNonEmpty(unsubscribedActionTitle)) { + // We support an inline offer here instead, when the pricing token is there. + unsubscribedAction = arcadeUpsell.arcadeOfferButtonActionFromData(objectGraph, upsellData.marketingItemData, models.marketingItemContextFromString("editorialItemCanvas"), baseMetricsOptions); + if (serverData.isDefinedNonNull(unsubscribedAction)) { + unsubscribedAction.title = unsubscribedActionTitle; + } + } + else { + // If Upsell EI is misconfigured and missing `breakoutCallToActionLabel`, default to opening Arcade app for unsubscribed state. + unsubscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, context.metricsPageInformation, context.metricsLocationTracker, objectGraph.client.isVision); + if (preprocessor.GAMES_TARGET) { + unsubscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore"); + } + else { + unsubscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE"); + } + } + const arcadeShowcase = new models.ArcadeShowcase(unsubscribedAction, subscribedAction); + const unsubscribedDescription = arcadeUpsell.descriptionFromData(objectGraph, upsellData.marketingItemData); + arcadeShowcase.unsubscribedDescription = unsubscribedDescription; + const offerDisplayProperties = new models.OfferDisplayProperties("arcade", objectGraph.bag.arcadeAppAdamId, null, "colored", null, "dark", null, null, null, null, null, null, null, null, null, null, null, null, objectGraph.bag.arcadeProductFamilyId); + if (preprocessor.GAMES_TARGET) { + offerDisplayProperties.titles["subscribed"] = objectGraph.loc.string("OfferButton.Arcade.Title.Explore"); + } + else { + offerDisplayProperties.titles["subscribed"] = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE"); + } + arcadeShowcase.offerDisplayProperties = offerDisplayProperties; + const showcaseMetricsOptions = { + ...baseMetricsOptions, + targetType: "arcadeShowcase", + title: unsubscribedActionTitle, + id: arcadeShowcaseModule.id, + kind: "arcadeShowcase", + softwareType: null, + displaysArcadeUpsell: true, + }; + metricsHelpersImpressions.addImpressionFields(objectGraph, arcadeShowcase, showcaseMetricsOptions); + // Build Artwork for iOS only + if (objectGraph.host.isiOS || objectGraph.client.isVision) { + // Context should have additional data to source icons. + if (serverData.isNull(context.additionalData)) { + return null; + } + const iconResponse = context.additionalData.get("arcadeIcons"); + if (serverData.isDefinedNonNullNonEmpty(iconResponse)) { + const iconMetricsOptions = { + pageInformation: context.metricsPageInformation, + locationTracker: context.metricsLocationTracker, + }; + const iconsDataCollection = mediaDataStructure.dataCollectionFromResultsListContainer(iconResponse); + arcadeShowcase.iconArtworks = content.impressionableAppIconsFromDataCollection(objectGraph, iconsDataCollection, iconMetricsOptions, { + useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */, + }); + } + } + const shelf = new models.Shelf("arcadeShowcase"); + shelf.items = [arcadeShowcase]; + const shelfHasBackgroundColor = objectGraph.host.isiOS || objectGraph.client.isVision; + if (shelfHasBackgroundColor) { + shelf.background = { + type: "color", + color: arcadeShowcaseShelfBackgroundColor, + }; + } + return shelf; +} +// endregion +function contentFromModule(objectGraph, module, context) { + const contents = mediaRelationships.relationshipData(objectGraph, module, "contents"); + if (!contents) { + return null; + } + return contents; +} +function addImpressionsFieldsToModel(objectGraph, model, context, impressionData) { + if (!model) { + return; + } + let impressionType = context.module; + if (context.subStyle) { + impressionType = impressionType + "_" + context.subStyle; + } + if (serverData.isNull(impressionData)) { + impressionData = { + id: `${context.index}`, + impressionIndex: context.index, + idType: "sequential", + impressionType: impressionType, + kind: "iosModule", + }; + } + model.impressionMetrics = new models.ImpressionMetrics(metricsHelpersUtil.sanitizedMetricsDictionary(impressionData)); +} +//# sourceMappingURL=article.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js new file mode 100644 index 0000000..b209cba --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js @@ -0,0 +1,34 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const categoryDetailMotion16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const categoryDetailMotion16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.top, + expandedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const categoryDetailMotion16x9Metrics_mini = { + maxWidth: 250.0, + ltr: categoryDetailMotion16x9Layout_mini, + rtl: categoryDetailMotion16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const categoryDetailMotion16x9Metrics_base = { + ltr: categoryDetailMotion16x9Layout_base, + rtl: categoryDetailMotion16x9Layout_base, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, +}; +export const categoryDetailMotion16x9 = { + objectPath: "editorialVideo.categoryDetailMotion16x9", + cardArtLayoutMetrics: [categoryDetailMotion16x9Metrics_mini, categoryDetailMotion16x9Metrics_base], + sourceWidth: 800, + sourceHeight: 450, + type: "video", + crops: ["sr"], +}; +//# sourceMappingURL=category-detail-motion-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js new file mode 100644 index 0000000..ab3c57f --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js @@ -0,0 +1,49 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const categoryDetailStatic16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const categoryDetailStatic16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const categoryDetailStatic16x9Layout_700w = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const categoryDetailStatic16x9Metrics_mini = { + maxWidth: 250.0, + ltr: categoryDetailStatic16x9Layout_mini, + rtl: categoryDetailStatic16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const categoryDetailStatic16x9Metrics_base = { + maxWidth: 704, + ltr: categoryDetailStatic16x9Layout_base, + rtl: categoryDetailStatic16x9Layout_base, + collapsedSize: { type: "fractionalHeight", height: 1.08 }, +}; +const categoryDetailStatic16x9Metrics_700w = { + ltr: categoryDetailStatic16x9Layout_700w, + rtl: categoryDetailStatic16x9Layout_700w, +}; +export const categoryDetailStatic16x9 = { + objectPath: "editorialArtwork.categoryDetailStatic16x9", + cardArtLayoutMetrics: [ + categoryDetailStatic16x9Metrics_mini, + categoryDetailStatic16x9Metrics_base, + categoryDetailStatic16x9Metrics_700w, + ], + crops: ["sr"], + sourceWidth: 800, + sourceHeight: 450, + type: "image", +}; +//# sourceMappingURL=category-detail-static-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js new file mode 100644 index 0000000..4688784 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js @@ -0,0 +1,51 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const dayCardLayout_mini = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: { + top: 0, + left: -8, + bottom: 0, + right: 0, + }, + expandedLayoutInsets: EdgeInsetsZero, +}; +const dayCardLayout_base = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const dayCardLayout_700w = { + collapsedContentMode: ArtworkContentMode.topLeft, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const dayCardMetrics_mini = { + maxWidth: 250, + ltr: dayCardLayout_mini, + rtl: dayCardLayout_mini, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const dayCardMetrics_base = { + maxWidth: 704, + ltr: dayCardLayout_base, + rtl: dayCardLayout_base, + collapsedSize: { type: "fractionalHeight", height: 1.062 }, +}; +const dayCardMetrics_700w = { + ltr: dayCardLayout_700w, + rtl: dayCardLayout_700w, + collapsedSize: { type: "fractionalWidth", width: 1.0 }, +}; +export const dayCard = { + objectPath: "editorialArtwork.dayCard", + cardArtLayoutMetrics: [dayCardMetrics_mini, dayCardMetrics_base, dayCardMetrics_700w], + crops: ["sr"], + sourceWidth: 800, + sourceHeight: 490, + type: "image", +}; +//# sourceMappingURL=day-card.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js new file mode 100644 index 0000000..3317d1b --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js @@ -0,0 +1,51 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const eventCardLayout_mini = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: { + top: 0, + left: -8, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.bottomLeft, + expandedLayoutInsets: EdgeInsetsZero, +}; +const eventCardLayout_base = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.bottomLeft, + expandedLayoutInsets: EdgeInsetsZero, +}; +const eventCardLayout_700w = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const eventCardMetrics_mini = { + maxWidth: 250, + ltr: eventCardLayout_mini, + rtl: eventCardLayout_mini, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const eventCardMetrics_base = { + maxWidth: 704, + ltr: eventCardLayout_base, + rtl: eventCardLayout_base, + collapsedSize: { type: "fractionalHeight", height: 1.062 }, +}; +const eventCardMetrics_700w = { + ltr: eventCardLayout_700w, + rtl: eventCardLayout_700w, + collapsedSize: { type: "fractionalWidth", width: 1.0 }, +}; +export const eventCard = { + objectPath: "editorialArtwork.eventCard", + cardArtLayoutMetrics: [eventCardMetrics_mini, eventCardMetrics_base, eventCardMetrics_700w], + crops: ["sr"], + sourceWidth: 800, + sourceHeight: 490, + type: "image", +}; +//# sourceMappingURL=event-card.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js new file mode 100644 index 0000000..f50840f --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js @@ -0,0 +1,51 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const generalCardLayout_mini = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: { + top: 0, + left: -8, + bottom: 0, + right: 0, + }, + expandedLayoutInsets: EdgeInsetsZero, +}; +const generalCardLayout_base = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const generalCardLayout_700w = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const generalCardMetrics_mini = { + maxWidth: 250, + ltr: generalCardLayout_mini, + rtl: generalCardLayout_mini, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const generalCardMetrics_base = { + maxWidth: 704, + ltr: generalCardLayout_base, + rtl: generalCardLayout_base, + collapsedSize: { type: "fractionalHeight", height: 1.062 }, +}; +const generalCardMetrics_700w = { + ltr: generalCardLayout_700w, + rtl: generalCardLayout_700w, + collapsedSize: { type: "fractionalWidth", width: 1.0 }, +}; +export const generalCard = { + objectPath: "editorialArtwork.generalCard", + cardArtLayoutMetrics: [generalCardMetrics_mini, generalCardMetrics_base, generalCardMetrics_700w], + crops: ["MC.ApSCFB01"], + sourceWidth: 800, + sourceHeight: 490, + type: "image", +}; +//# sourceMappingURL=general-card.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js new file mode 100644 index 0000000..5efa2cb --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js @@ -0,0 +1,85 @@ +import { isSome } from "@jet/environment"; +import { dayCard } from "./day-card"; +import { eventCard } from "./event-card"; +import { generalCard } from "./general-card"; +import { listCardMotion21x9 } from "./list-card-motion-21x9"; +import { listCardStatic21x9 } from "./list-card-static-21x9"; +import { mediaCard } from "./media-card"; +import { storeFrontVideo } from "./store-front-video"; +import { storeFrontVideo4x3 } from "./store-front-video-4x3"; +import { storyCardMotion16x9 } from "./story-card-motion-16x9"; +import { storyCardStatic16x9 } from "./story-card-static-16x9"; +import { storyCenteredMotion16x9 } from "./story-centered-motion-16x9"; +import { storyCenteredStatic16x9 } from "./story-centered-static-16x9"; +import { storySearchStatic16x9 } from "./story-search-static-16x9"; +import { universalAMotion16x9 } from "./universal-a-motion-16x9"; +import { universalAStatic16x9 } from "./universal-a-static-16x9"; +import { categoryDetailMotion16x9 } from "./category-detail-motion-16x9"; +import { categoryDetailStatic16x9 } from "./category-detail-static-16x9"; +/** + * Hero position on iPad requires the new artwork flavor to be considered valid + */ +export function universalAConfigurations(objectGraph, isHeroPosition) { + return isHeroPosition + ? [universalAMotion16x9Configuration(objectGraph), universalAStatic16x9Configuration(objectGraph)] + : []; +} +export function dayCardConfiguration(objectGraph) { + return dayCard; +} +export function eventCardConfiguration(objectGraph) { + return eventCard; +} +export function generalCardConfiguration(objectGraph, cropCodeOverrides) { + return applyOverrides(generalCard, cropCodeOverrides); +} +export function listCardMotion21x9Configuration(objectGraph) { + return listCardMotion21x9; +} +export function listCardStatic21x9Configuration(objectGraph) { + return listCardStatic21x9; +} +export function mediaCardConfiguration(objectGraph, cropCodeOverrides) { + return applyOverrides(mediaCard, cropCodeOverrides); +} +export function storeFrontVideoConfiguration(objectGraph) { + return storeFrontVideo; +} +export function storeFrontVideo4x3Configuration(objectGraph) { + return storeFrontVideo4x3; +} +export function storyCardMotion16x9Configuration(objectGraph) { + return storyCardMotion16x9; +} +export function storyCardStatic16x9Configuration(objectGraph) { + return storyCardStatic16x9; +} +export function storyCenteredMotion16x9Configuration(objectGraph) { + return storyCenteredMotion16x9; +} +export function storyCenteredStatic16x9Configuration(objectGraph, cropCodeOverrides) { + return applyOverrides(storyCenteredStatic16x9, cropCodeOverrides); +} +export function categoryDetailMotion16x9Configuration(objectGraph) { + return categoryDetailMotion16x9; +} +export function categoryDetailStatic16x9Configuration(objectGraph, cropCodeOverrides) { + return applyOverrides(categoryDetailStatic16x9, cropCodeOverrides); +} +export function storySearchStatic16x9Configuration(objectGraph) { + return storySearchStatic16x9; +} +function universalAMotion16x9Configuration(objectGraph) { + return universalAMotion16x9; +} +function universalAStatic16x9Configuration(objectGraph) { + return universalAStatic16x9; +} +function applyOverrides(mediaConfiguration, cropCodeOverrides) { + const updatedMediaConfiguration = { ...mediaConfiguration }; + if (isSome(cropCodeOverrides)) { + updatedMediaConfiguration.crops = cropCodeOverrides; + } + return updatedMediaConfiguration; +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js new file mode 100644 index 0000000..8570df2 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js @@ -0,0 +1,116 @@ +import { ArtworkContentMode, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const expandedContentMode = ArtworkContentMode.topRight; +const expandedLayoutInsets = { + top: -108, + left: 495, + bottom: 0, + right: 0, +}; +const expandedContentMode_rtl = ArtworkContentMode.topLeft; +const expandedLayoutInsets_rtl = { + top: -108, + left: -495, + bottom: 0, + right: 0, +}; +const expandedSize = { type: "absolute", width: 1124, height: 482 }; +const listCardMotion16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: { + top: -117, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode, + expandedLayoutInsets, +}; +const listCardMotion16x9Layout_mini_rtl = { + collapsedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: { + top: -117, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: expandedContentMode_rtl, + expandedLayoutInsets: expandedLayoutInsets_rtl, +}; +const listCardMotion16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: { + top: -165, + left: 524, + bottom: 0, + right: 0, + }, + expandedContentMode, + expandedLayoutInsets, +}; +const listCardMotion16x9Layout_base_rtl = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: { + top: -165, + left: -524, + bottom: 0, + right: 0, + }, + expandedContentMode: expandedContentMode_rtl, + expandedLayoutInsets: expandedLayoutInsets_rtl, +}; +const listCardMotion16x9Layout_extraWide = { + collapsedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: { + top: -55, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode, + expandedLayoutInsets, +}; +const listCardMotion16x9Layout_extraWide_rtl = { + collapsedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: { + top: -55, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: expandedContentMode_rtl, + expandedLayoutInsets: expandedLayoutInsets_rtl, +}; +const listCardMotion16x9Metrics_mini = { + maxWidth: 250.0, + ltr: listCardMotion16x9Layout_mini, + rtl: listCardMotion16x9Layout_mini_rtl, + collapsedSize: { type: "absolute", width: 914, height: 392 }, + expandedSize, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const listCardMotion16x9Metrics_base = { + ltr: listCardMotion16x9Layout_base, + rtl: listCardMotion16x9Layout_base_rtl, + collapsedSize: { type: "absolute", width: 1188, height: 509 }, + expandedSize, +}; +const listCardMotion16x9Metrics_extraWide = { + ltr: listCardMotion16x9Layout_extraWide, + rtl: listCardMotion16x9Layout_extraWide_rtl, + collapsedSize: { type: "absolute", width: 1456, height: 624 }, + expandedSize, + priority: TodayCardArtworkSizedLayoutMetricsPriority.ExtraWide, +}; +export const listCardMotion21x9 = { + objectPath: "editorialVideo.listCardMotion21x9", + cardArtLayoutMetrics: [ + listCardMotion16x9Metrics_mini, + listCardMotion16x9Metrics_base, + listCardMotion16x9Metrics_extraWide, + ], + crops: [], + sourceWidth: 1208, + sourceHeight: 518, + type: "video", +}; +//# sourceMappingURL=list-card-motion-21x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js new file mode 100644 index 0000000..8adecbe --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js @@ -0,0 +1,92 @@ +import { ArtworkContentMode, EdgeInsetsZero, Size, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const listCardStatic21x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.bottomRight, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.topRight, + expandedLayoutInsets: EdgeInsetsZero, +}; +const listCardStatic21x9Layout_mini_rtl = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.topLeft, + expandedLayoutInsets: EdgeInsetsZero, +}; +const listCardStatic21x9Layout_base = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: { + top: -49, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.right, + expandedLayoutInsets: EdgeInsetsZero, +}; +const listCardStatic21x9Layout_base_rtl = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: { + top: -49, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.left, + expandedLayoutInsets: EdgeInsetsZero, +}; +const listCardStatic16x9Layout_extraWide = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.bottomRight, + expandedLayoutInsets: { + top: 50, + left: 495, + bottom: 0, + right: 0, + }, +}; +const listCardStatic16x9Layout_extraWide_rtl = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.bottomRight, + expandedLayoutInsets: { + top: 50, + left: -495, + bottom: 0, + right: 0, + }, +}; +const listCardStatic21x9Metrics_mini = { + maxWidth: 250.0, + ltr: listCardStatic21x9Layout_mini, + rtl: listCardStatic21x9Layout_mini_rtl, + sourceCropOverrideLTR: "LCS.ApLCS01", + sourceCropOverrideRTL: "LCS.ApLCS02", + sourceSizeOverride: new Size(550, 264), + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const listCardStatic21x9Metrics_base = { + ltr: listCardStatic21x9Layout_base, + rtl: listCardStatic21x9Layout_base_rtl, + collapsedSize: { type: "absolute", width: 672, height: 279 }, +}; +const listCardMotion16x9Metrics_extraWide = { + ltr: listCardStatic16x9Layout_extraWide, + rtl: listCardStatic16x9Layout_extraWide_rtl, + expandedSize: { type: "absolute", width: 1124, height: 482 }, + sourceCropOverrideLTR: "LCS.ApLCXW01", + sourceCropOverrideRTL: "LCS.ApLCXW01", + priority: TodayCardArtworkSizedLayoutMetricsPriority.ExtraWide, +}; +export const listCardStatic21x9 = { + objectPath: "editorialArtwork.listCardStatic21x9", + cardArtLayoutMetrics: [ + listCardStatic21x9Metrics_mini, + listCardStatic21x9Metrics_base, + listCardMotion16x9Metrics_extraWide, + ], + crops: ["LCS.ApLCL01", "LCS.ApLCL02"], + sourceWidth: 688, + sourceHeight: 286, + type: "image", +}; +//# sourceMappingURL=list-card-static-21x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js new file mode 100644 index 0000000..3b8ff21 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js @@ -0,0 +1,51 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const mediaCardLayout_mini = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: { + top: 0, + left: -8, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.bottomLeft, + expandedLayoutInsets: EdgeInsetsZero, +}; +const mediaCardLayout_base = { + collapsedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.bottomLeft, + expandedLayoutInsets: EdgeInsetsZero, +}; +const mediaCardLayout_700w = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + expandedContentMode: ArtworkContentMode.bottomLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const mediaCardMetrics_mini = { + maxWidth: 250, + ltr: mediaCardLayout_mini, + rtl: mediaCardLayout_mini, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const mediaCardMetrics_base = { + maxWidth: 704, + ltr: mediaCardLayout_base, + rtl: mediaCardLayout_base, + collapsedSize: { type: "fractionalHeight", height: 1.062 }, +}; +const mediaCardMetrics_700w = { + ltr: mediaCardLayout_700w, + rtl: mediaCardLayout_700w, + collapsedSize: { type: "fractionalWidth", width: 1.0 }, +}; +export const mediaCard = { + objectPath: "editorialArtwork.mediaCard", + cardArtLayoutMetrics: [mediaCardMetrics_mini, mediaCardMetrics_base, mediaCardMetrics_700w], + crops: ["MC.ApSCFB01"], + sourceWidth: 800, + sourceHeight: 490, + type: "image", +}; +//# sourceMappingURL=media-card.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js new file mode 100644 index 0000000..653df0e --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js @@ -0,0 +1,22 @@ +import { ArtworkContentMode, EdgeInsetsZero } from "../../../../api/models"; +const storeFrontVideo4x3Layout = { + collapsedContentMode: ArtworkContentMode.center, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storeFrontVideo4x3Metrics = { + ltr: storeFrontVideo4x3Layout, + rtl: storeFrontVideo4x3Layout, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, + expandedSize: { type: "fractionalHeight", height: 1.0 }, +}; +export const storeFrontVideo4x3 = { + objectPath: "editorialVideo.storeFrontVideo4x3", + cardArtLayoutMetrics: [storeFrontVideo4x3Metrics], + crops: [], + sourceWidth: 656, + sourceHeight: 492, + type: "video", +}; +//# sourceMappingURL=store-front-video-4x3.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js new file mode 100644 index 0000000..472e52c --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js @@ -0,0 +1,25 @@ +import { ArtworkContentMode, EdgeInsetsZero, } from "../../../../api/models"; +const storeFrontVideoLayout = { + collapsedContentMode: ArtworkContentMode.center, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storeFrontVideoMetrics = { + ltr: storeFrontVideoLayout, + rtl: storeFrontVideoLayout, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, + expandedSize: { type: "fractionalHeight", height: 1.0 }, +}; +/** + * This configuration is in 16x9 aspect ratio. + */ +export const storeFrontVideo = { + objectPath: "editorialVideo.storeFrontVideo", + cardArtLayoutMetrics: [storeFrontVideoMetrics], + crops: [], + sourceWidth: 875, + sourceHeight: 492, + type: "video", +}; +//# sourceMappingURL=store-front-video.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js new file mode 100644 index 0000000..b0e4530 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js @@ -0,0 +1,34 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const storyCardMotion16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCardMotion16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.top, + expandedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCardMotion16x9Metrics_mini = { + maxWidth: 250.0, + ltr: storyCardMotion16x9Layout_mini, + rtl: storyCardMotion16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const storyCardMotion16x9Metrics_base = { + ltr: storyCardMotion16x9Layout_base, + rtl: storyCardMotion16x9Layout_base, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, +}; +export const storyCardMotion16x9 = { + objectPath: "editorialVideo.storyCardMotion16x9", + cardArtLayoutMetrics: [storyCardMotion16x9Metrics_mini, storyCardMotion16x9Metrics_base], + sourceWidth: 800, + sourceHeight: 450, + type: "video", + crops: ["sr"], +}; +//# sourceMappingURL=story-card-motion-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js new file mode 100644 index 0000000..c4e7220 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js @@ -0,0 +1,49 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const storyCardStatic16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCardStatic16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCardStatic16x9Layout_700w = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCardStatic16x9Metrics_mini = { + maxWidth: 250.0, + ltr: storyCardStatic16x9Layout_mini, + rtl: storyCardStatic16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const storyCardStatic16x9Metrics_base = { + maxWidth: 704, + ltr: storyCardStatic16x9Layout_base, + rtl: storyCardStatic16x9Layout_base, + collapsedSize: { type: "fractionalHeight", height: 1.08 }, +}; +const storyCardStatic16x9Metrics_700w = { + ltr: storyCardStatic16x9Layout_700w, + rtl: storyCardStatic16x9Layout_700w, +}; +export const storyCardStatic16x9 = { + objectPath: "editorialArtwork.storyCardStatic16x9", + cardArtLayoutMetrics: [ + storyCardStatic16x9Metrics_mini, + storyCardStatic16x9Metrics_base, + storyCardStatic16x9Metrics_700w, + ], + crops: ["sr"], + sourceWidth: 800, + sourceHeight: 450, + type: "image", +}; +//# sourceMappingURL=story-card-static-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js new file mode 100644 index 0000000..273503b --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js @@ -0,0 +1,34 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const storyCenteredMotion16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCenteredMotion16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.top, + expandedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCenteredMotion16x9Metrics_mini = { + maxWidth: 250.0, + ltr: storyCenteredMotion16x9Layout_mini, + rtl: storyCenteredMotion16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const storyCenteredMotion16x9Metrics_base = { + ltr: storyCenteredMotion16x9Layout_base, + rtl: storyCenteredMotion16x9Layout_base, + collapsedSize: { type: "fractionalHeight", height: 1.0 }, +}; +export const storyCenteredMotion16x9 = { + objectPath: "editorialVideo.storyCenteredMotion16x9", + cardArtLayoutMetrics: [storyCenteredMotion16x9Metrics_mini, storyCenteredMotion16x9Metrics_base], + sourceWidth: 800, + sourceHeight: 450, + type: "video", + crops: ["sr"], +}; +//# sourceMappingURL=story-centered-motion-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js new file mode 100644 index 0000000..404d58c --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js @@ -0,0 +1,49 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const storyCenteredStatic16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCenteredStatic16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.bottom, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCenteredStatic16x9Layout_700w = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + expandedContentMode: ArtworkContentMode.bottom, + collapsedLayoutInsets: EdgeInsetsZero, + expandedLayoutInsets: EdgeInsetsZero, +}; +const storyCenteredStatic16x9Metrics_mini = { + maxWidth: 250.0, + ltr: storyCenteredStatic16x9Layout_mini, + rtl: storyCenteredStatic16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const storyCenteredStatic16x9Metrics_base = { + maxWidth: 704, + ltr: storyCenteredStatic16x9Layout_base, + rtl: storyCenteredStatic16x9Layout_base, + collapsedSize: { type: "fractionalHeight", height: 1.08 }, +}; +const storyCenteredStatic16x9Metrics_700w = { + ltr: storyCenteredStatic16x9Layout_700w, + rtl: storyCenteredStatic16x9Layout_700w, +}; +export const storyCenteredStatic16x9 = { + objectPath: "editorialArtwork.storyCenteredStatic16x9", + cardArtLayoutMetrics: [ + storyCenteredStatic16x9Metrics_mini, + storyCenteredStatic16x9Metrics_base, + storyCenteredStatic16x9Metrics_700w, + ], + crops: ["sr"], + sourceWidth: 800, + sourceHeight: 450, + type: "image", +}; +//# sourceMappingURL=story-centered-static-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js new file mode 100644 index 0000000..f16d51f --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js @@ -0,0 +1,146 @@ +import { ArtworkContentMode, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const expandedSize = { type: "absolute", width: 1200, height: 675 }; +const expandedLayoutInsets = { + top: -130, + left: 0, + bottom: 0, + right: 0, +}; +const universalAMotion16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: { + top: -16, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: { + top: -116, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Layout_700w = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: { + top: -116, + left: -70, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Layout_700w_rtl = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: { + top: -116, + left: 70, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Layout_920w = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: { + top: -148, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Layout_920w_rtl = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: { + top: -148, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Layout_max = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: { + top: -170, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Layout_max_rtl = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: { + top: -170, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets, +}; +const universalAMotion16x9Metrics_mini = { + maxWidth: 250.0, + ltr: universalAMotion16x9Layout_mini, + rtl: universalAMotion16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + expandedSize, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const universalAMotion16x9Metrics_base = { + maxWidth: 699.0, + ltr: universalAMotion16x9Layout_base, + rtl: universalAMotion16x9Layout_base, + collapsedSize: { type: "absolute", width: 1094, height: 614 }, + expandedSize, +}; +const universalAMotion16x9Metrics_700w = { + maxWidth: 899.0, + ltr: universalAMotion16x9Layout_700w, + rtl: universalAMotion16x9Layout_700w_rtl, + collapsedSize: { type: "absolute", width: 1092, height: 614 }, + expandedSize, +}; +const universalAMotion16x9Metrics_920w = { + maxWidth: 1090.0, + ltr: universalAMotion16x9Layout_920w, + rtl: universalAMotion16x9Layout_920w_rtl, + collapsedSize: { type: "absolute", width: 1392, height: 783 }, + expandedSize, +}; +const universalAMotion16x9Metrics_max = { + ltr: universalAMotion16x9Layout_max, + rtl: universalAMotion16x9Layout_max_rtl, + collapsedSize: { type: "absolute", width: 1600, height: 900 }, + expandedSize, +}; +export const universalAMotion16x9 = { + objectPath: "editorialVideo.universalAMotion16x9", + cardArtLayoutMetrics: [ + universalAMotion16x9Metrics_mini, + universalAMotion16x9Metrics_base, + universalAMotion16x9Metrics_700w, + universalAMotion16x9Metrics_920w, + universalAMotion16x9Metrics_max, + ], + crops: ["UAS.ApXWC01"], + sourceWidth: 1600, + sourceHeight: 900, + type: "video", +}; +//# sourceMappingURL=universal-a-motion-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js new file mode 100644 index 0000000..f0d34ce --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js @@ -0,0 +1,107 @@ +import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models"; +const universalAStatic16x9Layout_mini = { + collapsedContentMode: ArtworkContentMode.top, + collapsedLayoutInsets: { + top: 16, + left: 0, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.top, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Layout_base = { + collapsedContentMode: ArtworkContentMode.scaleAspectFill, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.scaleAspectFill, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Layout_700w = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: { + top: 0, + left: -70, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Layout_700w_rtl = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: { + top: 0, + left: 70, + bottom: 0, + right: 0, + }, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Layout_920w = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Layout_920w_rtl = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Layout_max = { + collapsedContentMode: ArtworkContentMode.topLeft, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Layout_max_rtl = { + collapsedContentMode: ArtworkContentMode.topRight, + collapsedLayoutInsets: EdgeInsetsZero, + expandedContentMode: ArtworkContentMode.center, + expandedLayoutInsets: EdgeInsetsZero, +}; +const universalAStatic16x9Metrics_mini = { + maxWidth: 250.0, + ltr: universalAStatic16x9Layout_mini, + rtl: universalAStatic16x9Layout_mini, + collapsedSize: { type: "absolute", width: 455, height: 256 }, + priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini, +}; +const universalAStatic16x9Metrics_base = { + maxWidth: 699.0, + ltr: universalAStatic16x9Layout_base, + rtl: universalAStatic16x9Layout_base, +}; +const universalAStatic16x9Metrics_700w = { + maxWidth: 899.0, + ltr: universalAStatic16x9Layout_700w, + rtl: universalAStatic16x9Layout_700w_rtl, +}; +const universalAStatic16x9Metrics_920w = { + maxWidth: 1090.0, + ltr: universalAStatic16x9Layout_920w, + rtl: universalAStatic16x9Layout_920w_rtl, + collapsedSize: { type: "absolute", width: 1400, height: 527 }, +}; +const universalAStatic16x9Metrics_max = { + ltr: universalAStatic16x9Layout_max, + rtl: universalAStatic16x9Layout_max_rtl, + collapsedSize: { type: "absolute", width: 1600, height: 604 }, +}; +export const universalAStatic16x9 = { + objectPath: "editorialArtwork.universalAStatic16x9", + cardArtLayoutMetrics: [ + universalAStatic16x9Metrics_mini, + universalAStatic16x9Metrics_base, + universalAStatic16x9Metrics_700w, + universalAStatic16x9Metrics_920w, + universalAStatic16x9Metrics_max, + ], + crops: ["UAS.ApXWC01"], + sourceWidth: 1600, + sourceHeight: 604, + type: "image", +}; +//# sourceMappingURL=universal-a-static-16x9.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js new file mode 100644 index 0000000..4705bef --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js @@ -0,0 +1,267 @@ +import { isNothing, isSome } from "@jet/environment"; +import { Video } from "../../../api/models"; +import { asDictionary, asNumber, asString } from "../../../foundation/json-parsing/server-data"; +import { editorialCardFromData } from "../../../foundation/media/associations"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import { autoPlayControls, standardControls } from "../../constants/video-constants"; +import { artworkFromApiArtwork, joeColorSetFromData } from "../../content/content"; +import { cardDisplayStyleFromData } from "../today-card-util"; +import { TodayCardDisplayStyle } from "../today-types"; +import { categoryDetailMotion16x9Configuration, categoryDetailStatic16x9Configuration, dayCardConfiguration, eventCardConfiguration, generalCardConfiguration, listCardMotion21x9Configuration, listCardStatic21x9Configuration, mediaCardConfiguration, storeFrontVideo4x3Configuration, storeFrontVideoConfiguration, storyCardMotion16x9Configuration, storyCardStatic16x9Configuration, storyCenteredMotion16x9Configuration, storyCenteredStatic16x9Configuration, storySearchStatic16x9Configuration, universalAConfigurations, } from "./media-configurations"; +/** + * Retrieves the artwork details for a today card. This function will determine the + * appropriate media configuration to use based on the card display style and the + * media data available in the card response. + * + * @param objectGraph - The object graph of the app store. + * @param data - The media data. + * @param cardConfig - The configuration for the today card. + * @returns The artwork details for the today card, or undefined if no artwork is available. + */ +export function todayCardArtworkDetails(objectGraph, data, cardConfig) { + var _a; + // Watch uses different artwork lookup logic + const isWebViewingWatch = objectGraph.client.isWeb && ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform) === "watch"; + if (objectGraph.client.isWatch || isWebViewingWatch) { + return todayCardArtworkDetailsForWatch(objectGraph, data, cardConfig); + } + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle); + // Determine our preferred ranking of media flavors for the display style, + // then get the highest ranked media available on the card response. + const mediaSearchOrder = cardConfig.isHorizontalShelfContext + ? horizontalShelfMediaSearchOrderForCardDisplayStyle(objectGraph, cardConfig) + : mediaSearchOrderForCardDisplayStyle(objectGraph, cardDisplayStyle, cardConfig); + let mediaConfigurationForCard; + let isUsingEditorialCardOverride = false; + const editorialCardData = editorialCardFromData(data); + if (mediaAttributes.hasAttributes(editorialCardData)) { + mediaConfigurationForCard = mediaSearchOrder.find((conf) => { + return isSome(mediaAttributes.attributeAsDictionary(editorialCardData, conf.objectPath)); + }); + isUsingEditorialCardOverride = isSome(mediaConfigurationForCard); + } + if (isNothing(mediaConfigurationForCard)) { + mediaConfigurationForCard = mediaSearchOrder.find((conf) => { + return isSome(mediaAttributes.attributeAsDictionary(data, conf.objectPath)); + }); + } + if (!mediaConfigurationForCard) { + return undefined; + } + const mediaData = isUsingEditorialCardOverride + ? mediaAttributes.attributeAsDictionary(editorialCardData, mediaConfigurationForCard.objectPath) + : mediaAttributes.attributeAsDictionary(data, mediaConfigurationForCard.objectPath); + if (mediaConfigurationForCard.type === "image") { + // Return an Artwork and cardArtLayout + const artworks = mediaConfigurationForCard.crops.map((crop) => { + let cropCodeToUse = crop; + if (cardConfig.isSearchContext && isSome(cardConfig.prevailingCropCodes)) { + cropCodeToUse = cardConfig.prevailingCropCodes.defaultCrop; + } + else if (isSome(cardConfig.prevailingCropCodes) && + isSome(cardConfig.prevailingCropCodes[mediaConfigurationForCard.objectPath])) { + cropCodeToUse = cardConfig.prevailingCropCodes[mediaConfigurationForCard.objectPath]; + } + const artwork = artworkFromApiArtwork(objectGraph, mediaData, { + withJoeColorPlaceholder: true, + cropCode: cropCodeToUse, + useCase: 15 /* ArtworkUseCase.TodayCardMedia */, + overrideHeight: mediaConfigurationForCard.sourceHeight, + overrideWidth: mediaConfigurationForCard.sourceWidth, + }); + return artwork; + }); + const joeColors = joeColorSetFromData(mediaData); + return { + artworks: artworks, + videos: [], + artworkLayoutsWithMetrics: mediaConfigurationForCard.cardArtLayoutMetrics, + joeColors: joeColors, + }; + } + else { + // Return a Video and cardArtLayout + const previewArtworkData = asDictionary(mediaData, "previewFrame"); + // All TodayCardArtworkLayouts are specified in landscape aspect ratio, + // but videos can come back in either landscape or portrait. If we + // receive a portrait video, we need to flip the TodayCardArtworkLayout + const previewArtworkWidth = asNumber(previewArtworkData, "width"); + const previewArtworkHeight = asNumber(previewArtworkData, "height"); + const isPortrait = previewArtworkHeight >= previewArtworkWidth; + let overrideHeight = mediaConfigurationForCard.sourceHeight; + let overrideWidth = mediaConfigurationForCard.sourceWidth; + if (isPortrait) { + [overrideHeight, overrideWidth] = [overrideWidth, overrideHeight]; + } + const previewArtwork = artworkFromApiArtwork(objectGraph, previewArtworkData, { + withJoeColorPlaceholder: true, + cropCode: mediaConfigurationForCard.crops[0], + useCase: 15 /* ArtworkUseCase.TodayCardMedia */, + overrideHeight: overrideHeight, + overrideWidth: overrideWidth, + }); + if (isNothing(previewArtwork)) { + return undefined; + } + let playbackControls = standardControls(objectGraph); + if (isSome(cardConfig.videoPlaybackControls)) { + playbackControls = cardConfig.videoPlaybackControls; + } + if (objectGraph.client.isMac) { + playbackControls.scrubber = true; + } + let autoplayPlaybackControls = autoPlayControls(objectGraph); + if (isSome(cardConfig.videoPlaybackControls)) { + autoplayPlaybackControls = cardConfig.videoAutoplayPlaybackControls; + } + const videoUrl = asString(mediaData, "video"); + if (isNothing(videoUrl)) { + return undefined; + } + const video = new Video(videoUrl, previewArtwork, { + playbackControls: playbackControls, + autoPlayPlaybackControls: autoplayPlaybackControls, + canPlayFullScreen: cardConfig.enableFullScreenVideo, + }); + const joeColors = joeColorSetFromData(previewArtworkData); + return { + artworks: [], + videos: [video], + artworkLayoutsWithMetrics: mediaConfigurationForCard.cardArtLayoutMetrics, + joeColors: joeColors, + }; + } +} +function mediaSearchOrderForCardDisplayStyle(objectGraph, cardDisplayStyle, cardConfig) { + // Hero position, non-list card display styles, on iPad requires the UniversalA flavor artwork to be considered valid, + // so we'll only check those 2 configurations in this scenario. + const listCardDisplayStyles = new Set([TodayCardDisplayStyle.List, TodayCardDisplayStyle.NumberedList]); + if (objectGraph.client.isPad && cardConfig.isHeroCard && !listCardDisplayStyles.has(cardDisplayStyle)) { + return universalAConfigurations(objectGraph, cardConfig.isHeroCard); + } + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.AppEventCard: + return [ + storyCardMotion16x9Configuration(objectGraph), + storyCardStatic16x9Configuration(objectGraph), + storyCenteredMotion16x9Configuration(objectGraph), + storyCenteredStatic16x9Configuration(objectGraph), + ...universalAConfigurations(objectGraph, cardConfig.isHeroCard), + eventCardConfiguration(objectGraph), + ]; + case TodayCardDisplayStyle.AppOfTheDay: + case TodayCardDisplayStyle.GameOfTheDay: + return [ + storyCardMotion16x9Configuration(objectGraph), + storyCardStatic16x9Configuration(objectGraph), + storyCenteredMotion16x9Configuration(objectGraph), + storyCenteredStatic16x9Configuration(objectGraph), + ...universalAConfigurations(objectGraph, cardConfig.isHeroCard), + dayCardConfiguration(objectGraph), + ]; + case TodayCardDisplayStyle.List: + case TodayCardDisplayStyle.NumberedList: + return [listCardMotion21x9Configuration(objectGraph), listCardStatic21x9Configuration(objectGraph)]; + case TodayCardDisplayStyle.ShortImage: + if (cardConfig.isSearchContext) { + return [ + generalCardConfiguration(objectGraph), + storyCardMotion16x9Configuration(objectGraph), + storyCardStatic16x9Configuration(objectGraph), + storyCenteredMotion16x9Configuration(objectGraph), + storyCenteredStatic16x9Configuration(objectGraph), + ...universalAConfigurations(objectGraph, cardConfig.isHeroCard), + storeFrontVideoConfiguration(objectGraph), + storeFrontVideo4x3Configuration(objectGraph), + mediaCardConfiguration(objectGraph), + ]; + } + else { + return [ + storyCardMotion16x9Configuration(objectGraph), + storyCardStatic16x9Configuration(objectGraph), + storyCenteredMotion16x9Configuration(objectGraph), + storyCenteredStatic16x9Configuration(objectGraph), + ...universalAConfigurations(objectGraph, cardConfig.isHeroCard), + storeFrontVideoConfiguration(objectGraph), + storeFrontVideo4x3Configuration(objectGraph), + generalCardConfiguration(objectGraph), + mediaCardConfiguration(objectGraph), + ]; + } + case TodayCardDisplayStyle.FullBleedImage: + const fullBleedConfigurations = [ + storyCardMotion16x9Configuration(objectGraph), + storyCardStatic16x9Configuration(objectGraph), + categoryDetailMotion16x9Configuration(objectGraph), + categoryDetailStatic16x9Configuration(objectGraph), + storyCenteredMotion16x9Configuration(objectGraph), + storyCenteredStatic16x9Configuration(objectGraph), + ...universalAConfigurations(objectGraph, cardConfig.isHeroCard), + storeFrontVideoConfiguration(objectGraph), + storeFrontVideo4x3Configuration(objectGraph), + mediaCardConfiguration(objectGraph), + generalCardConfiguration(objectGraph), + ]; + if (objectGraph.client.isVision && isSome(cardConfig.isSearchContext) && cardConfig.isSearchContext) { + fullBleedConfigurations.unshift(storySearchStatic16x9Configuration(objectGraph)); + } + return fullBleedConfigurations; + case TodayCardDisplayStyle.SingleApp: + case TodayCardDisplayStyle.Video: + default: + const configurations = [ + storyCardMotion16x9Configuration(objectGraph), + storyCardStatic16x9Configuration(objectGraph), + storyCenteredMotion16x9Configuration(objectGraph), + storyCenteredStatic16x9Configuration(objectGraph), + ...universalAConfigurations(objectGraph, cardConfig.isHeroCard), + storeFrontVideoConfiguration(objectGraph), + storeFrontVideo4x3Configuration(objectGraph), + mediaCardConfiguration(objectGraph), + generalCardConfiguration(objectGraph), + ]; + if (objectGraph.client.isVision && isSome(cardConfig.isSearchContext) && cardConfig.isSearchContext) { + configurations.unshift(storySearchStatic16x9Configuration(objectGraph)); + } + return configurations; + } +} +function horizontalShelfMediaSearchOrderForCardDisplayStyle(objectGraph, cardConfig) { + const searchOrder = []; + if (objectGraph.client.isiOS || objectGraph.client.isWeb) { + searchOrder.push(storyCenteredStatic16x9Configuration(objectGraph, ["SCS.ApDPCS01"])); + } + if (cardConfig.horizontalCardContentType === "largeStoryCard") { + searchOrder.push(storeFrontVideoConfiguration(objectGraph)); + } + const shouldUseHorizontalCardCrop = objectGraph.client.isiOS || objectGraph.client.isMac; + searchOrder.push(mediaCardConfiguration(objectGraph, shouldUseHorizontalCardCrop ? ["sr"] : undefined), generalCardConfiguration(objectGraph, shouldUseHorizontalCardCrop ? ["sr"] : undefined)); + return searchOrder; +} +/** + * Watch uses a more straightforward artwork lookup than other platforms. + * @param objectGraph The AppStoreObjectGraph + * @param data MAPI data for a Today Card + * @param cardConfig The TodayCardConfiguration + * @returns TodayCardArtworkDetails appropriate for display on Apple Watch + */ +function todayCardArtworkDetailsForWatch(objectGraph, data, cardConfig) { + var _a, _b; + const mediaData = mediaAttributes.attributeAsDictionary(data, "editorialArtwork.subscriptionHero"); + const artwork = artworkFromApiArtwork(objectGraph, mediaData, { + withJoeColorPlaceholder: true, + cropCode: (_b = (_a = cardConfig.prevailingCropCodes) === null || _a === void 0 ? void 0 : _a.defaultCrop) !== null && _b !== void 0 ? _b : "SH.ApHXS01", + useCase: 15 /* ArtworkUseCase.TodayCardMedia */, + }); + const joeColors = joeColorSetFromData(mediaData); + // Don't return an array containing `undefined` + const artworks = isSome(artwork) ? [artwork] : []; + return { + artworks: artworks, + videos: [], + artworkLayoutsWithMetrics: [], + joeColors: joeColors, + }; +} +//# sourceMappingURL=today-artwork-util.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js new file mode 100644 index 0000000..dfe9836 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js @@ -0,0 +1,232 @@ +import { isSome } from "@jet/environment"; +import * as validation from "@jet/environment/json/validation"; +import { MediumAdLockupWithScreenshotsBackground, CondensedAdLockupWithIconBackground, TodayCardMediaSingleLockup, TodayCardMediaMediumLockupWithScreenshots, TodayCardMediaMediumLockupWithAlignedRegion, MediumAdLockupWithAlignedRegionBackground, } from "../../../api/models"; +import { asString, isDefinedNonNull, isNull } from "../../../foundation/json-parsing/server-data"; +import { isColorEqualToColor } from "../../../foundation/util/color-util"; +import { todayAdStyle } from "../../ads/ad-common"; +import { recordLockupFromDataFailed } from "../../ads/ad-incident-recorder"; +import { iconFromData } from "../../content/content"; +import { getTemplateTypeForMediumAdFromLockupWithCustomCreative, getTemplateTypeForMediumAdFromLockupWithScreenshots, isCppDeeplinkEnabledForAdvert, performAdOverridesForCard, } from "../../lockups/ad-lockups"; +import * as lockups from "../../lockups/lockups"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import { productVariantDataForData } from "../../product-page/product-page-variants"; +import { popTodayCardLocation, pushTodayCardLocation } from "../today-card-util"; +import { createTodayBaseCard } from "./today-base-card-builder"; +import { getSelectedCustomCreativeId } from "../../search/custom-creative"; +/** + * Create TodayCard displaying an ad fetched from Ad platforms + * + * @param objectGraph The dependency graph for the App Store + * @param adData The media api data to build the ad card from + * @param adIncidentRecorder The incident recorder for the ad, for when an issues arises + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @returns The newly created TodayCard, using the full bleed image media. + */ +export function createTodayAdCard(objectGraph, adData, adIncidentRecorder, cardConfig, context) { + return validation.context("createTodayAdCard", () => { + var _a, _b, _c, _d; + if (isNull(adData)) { + return null; // No task for the position + } + const adCard = createTodayBaseCard(objectGraph, adData, cardConfig, context); + pushTodayCardLocation(objectGraph, adData, cardConfig, context, asString(adData.attributes.name)); + const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, adData, asString(adData.attributes.name), { + targetType: "todayCard", + pageInformation: context.pageInformation, + locationTracker: context.locationTracker, + isAdvert: true, + rowIndex: cardConfig.currentRowIndex, + displayStyle: cardConfig.metricsDisplayStyle, + }); + const productVariantData = productVariantDataForData(objectGraph, adData); + metricsOptions.productVariantData = productVariantData; + metricsOptions.adSlotOverride = context.parsedCardCount; + metricsOptions.kind = "adItem"; + const clickOptions = metricsOptions; + // Set up iAdInfo + metricsOptions.pageInformation.iAdInfo.apply(objectGraph, adData); + // Configure the action + let clientIdentifierOverride; + if (isDefinedNonNull(cardConfig)) { + clientIdentifierOverride = cardConfig.clientIdentifierOverride; + } + const customCreativeId = getSelectedCustomCreativeId(adData); + const isCustomCreative = isSome(customCreativeId); + adCard.style = "dark"; + switch (todayAdStyle(objectGraph)) { + case "singleLockup": + // Hardcode the condensed ad template type. This is not dependent on media count. + (_a = metricsOptions.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType("APPLOCKUP"); + break; + case "mediumLockup": + if (isCustomCreative) { + (_b = metricsOptions.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.setTemplateType(getTemplateTypeForMediumAdFromLockupWithCustomCreative()); + } + break; + default: + break; + } + let lockup = createAdCardLockup(objectGraph, adData, cardConfig, context); + if (isNull(lockup)) { + recordLockupFromDataFailed(objectGraph, adIncidentRecorder, adData); + popTodayCardLocation(context); + // Configure impressions + metricsHelpersImpressions.addImpressionsFieldsToTodayCard(objectGraph, adCard, metricsOptions, null, null, false, false); + return null; + } + if (objectGraph.props.enabled("advertSlotReporting")) { + (_c = lockup.searchAdOpportunity) === null || _c === void 0 ? void 0 : _c.setTemplateType("APPLOCKUP"); + } + else { + (_d = lockup.searchAd) === null || _d === void 0 ? void 0 : _d.setTemplateType("APPLOCKUP"); + } + switch (todayAdStyle(objectGraph)) { + case "singleLockup": + const condensedAdLockupWithIconBackground = new CondensedAdLockupWithIconBackground(lockup, lockup.icon); + adCard.media = new TodayCardMediaSingleLockup(condensedAdLockupWithIconBackground); + adCard.media.impressionMetrics = lockup.impressionMetrics; + break; + case "mediumLockup": + if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) { + if (isCustomCreative && objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) { + adCard.media = createMediumLockupWithAlignedRegion(objectGraph, lockup, adData, metricsOptions.pageInformation); + // After the media is created, we need to recreate the lockup to ensure all instrumentation + // has the updated template type, which is derived from the final set of media in + // `createMediumLockupWithAlignedRegion`. + lockup = createAdCardLockup(objectGraph, adData, cardConfig, context); + // Preserve SearchAd metadata + lockup.searchAdOpportunity = adCard.media.mediumAdLockupWithAlignedRegionBackground.lockup.searchAdOpportunity; + lockup.searchAd = adCard.media.mediumAdLockupWithAlignedRegionBackground.lockup.searchAd; + // The new lockup with the correct metrics should be set on the media. + adCard.media.mediumAdLockupWithAlignedRegionBackground.lockup = lockup; + adCard.media.impressionMetrics = lockup.impressionMetrics; + break; + } + else { + adCard.media = createMediumScreenshotsCardMedia(adCard, objectGraph, lockup, adData, cardConfig, context, metricsOptions); + } + } + else { + adCard.media = createMediumScreenshotsCardMedia(adCard, objectGraph, lockup, adData, cardConfig, context, metricsOptions); + } + break; + default: + return null; + } + popTodayCardLocation(context); + // CPP deeplinks used for ads have a requirement to not be used when targeting criteria is too narrow. + const isCppDeeplinkingEnabled = isCppDeeplinkEnabledForAdvert(adData); + // It's important the click action is created after the template type is set, to ensure it's in the metrics. + adCard.clickAction = lockups.actionFromData(objectGraph, adData, clickOptions, clientIdentifierOverride, undefined, isCppDeeplinkingEnabled); + performAdOverridesForCard(objectGraph, adData, adCard, metricsOptions); + // Configure impressions + metricsHelpersImpressions.addImpressionsFieldsToTodayCard(objectGraph, adCard, metricsOptions, null, null, false, false); + return adCard; + }); +} +function createMediumScreenshotsCardMedia(adCard, objectGraph, lockup, adData, cardConfig, context, metricsOptions) { + adCard.media = createMediumLockupWithScreenshots(objectGraph, lockup, objectGraph.bag.todayAdMediumLockupScreenshotAnimationEnabled, metricsOptions.pageInformation, adData); + // After the media is created, we need to recreate the lockup to ensure all instrumentation + // has the updated template type, which is derived from the final set of media in + // `createMediumLockupWithScreenshots`. + lockup = createAdCardLockup(objectGraph, adData, cardConfig, context); + if (isNull(lockup)) { + return null; + } + // Preserve SearchAd metadata + lockup.searchAdOpportunity = adCard.media.mediumAdLockupWithScreenshotsBackground.lockup.searchAdOpportunity; + lockup.searchAd = adCard.media.mediumAdLockupWithScreenshotsBackground.lockup.searchAd; + // The new lockup with the correct metrics should be set on the media. + adCard.media.mediumAdLockupWithScreenshotsBackground.lockup = lockup; + adCard.media.impressionMetrics = lockup.impressionMetrics; + return adCard.media; +} +function createAdCardLockup(objectGraph, data, cardConfig, context) { + const offerEnvironment = "ad"; + const offerStyle = "transparent"; + return lockups.mixedMediaAdLockupFromData(objectGraph, data, { + offerEnvironment: offerEnvironment, + offerStyle: offerStyle, + metricsOptions: { + pageInformation: context.pageInformation, + locationTracker: context.locationTracker, + isAdvert: true, + adSlotOverride: context.parsedCardCount, + disableFastImpressionsForAds: true, + }, + clientIdentifierOverride: cardConfig.clientIdentifierOverride, + crossLinkSubtitle: cardConfig.crossLinkSubtitle, + artworkUseCase: 1 /* ArtworkUseCase.LockupIconSmall */, + canDisplayArcadeOfferButton: cardConfig.canDisplayArcadeOfferButton, + }, { + allowsAutoPlay: true, + looping: true, + canPlayFullScreen: false, + playbackControls: {}, + }, null, false); +} +function createMediumLockupWithAlignedRegion(objectGraph, lockup, adData, pageInformation) { + var _a, _b, _c; + const alignedRegionArtwork = lockup.alignedRegionArtwork; + const templateString = getTemplateTypeForMediumAdFromLockupWithCustomCreative(); + (_a = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType(templateString); + if (objectGraph.props.enabled("advertSlotReporting")) { + (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType(templateString); + } + else { + (_c = lockup.searchAd) === null || _c === void 0 ? void 0 : _c.setTemplateType(templateString); + } + const mediumContainer = new MediumAdLockupWithAlignedRegionBackground(lockup, alignedRegionArtwork); + return new TodayCardMediaMediumLockupWithAlignedRegion(mediumContainer); +} +/** + * Creates the media for a medium Today ad, with the appropriate media. + * Also applies information about the created media template to a provided MetricsPageInformation. + * @param objectGraph The object graph. + * @param lockup The lockup for the app. + * @param isAnimated Whether the card background should be animated. + * @param pageInformation A MetricsPageInformation to apply the created template metrics information to. + * @param adData The data for the ad. + * @returns A `TodayCardMediumLockupWithScreenshots` with the correct media to be displayed. + */ +function createMediumLockupWithScreenshots(objectGraph, lockup, isAnimated, pageInformation, adData) { + var _a, _b, _c; + // Grab the first of the platform screenshots and trailers - the ones the ad will display. + const platformScreenshots = lockup.screenshots[0]; + const templateString = getTemplateTypeForMediumAdFromLockupWithScreenshots(platformScreenshots); + (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType(templateString); + if (objectGraph.props.enabled("advertSlotReporting")) { + (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType(templateString); + } + else { + (_c = lockup.searchAd) === null || _c === void 0 ? void 0 : _c.setTemplateType(templateString); + } + const iconData = iconFromData(objectGraph, adData, { + useCase: 0 /* ArtworkUseCase.Default */, + withJoeColorPlaceholder: true, + overrideTextColorKey: "textColor2", + }); + let backgroundColor = iconData.backgroundColor; + let secondaryTextColor = iconData.textColor; + const rgbWhite = { + type: "rgb", + red: 1, + green: 1, + blue: 1, + }; + // If `backgroundColor` is white, use `secondaryTextColor` as the "primary" color. + // Otherwise, if `secondaryTextColor` is white, remove it and use only `backgroundColor`. + // Native code has a fallback state where only `backgroundColor` is provided, so if + // either color is white, we prefer the single non-white color. + if (isColorEqualToColor(rgbWhite, backgroundColor)) { + backgroundColor = secondaryTextColor; + secondaryTextColor = undefined; + } + else if (isColorEqualToColor(rgbWhite, secondaryTextColor)) { + secondaryTextColor = undefined; + } + const mediumContainer = new MediumAdLockupWithScreenshotsBackground(lockup, [platformScreenshots], isAnimated, secondaryTextColor, backgroundColor, 8); + return new TodayCardMediaMediumLockupWithScreenshots(mediumContainer); +} +//# sourceMappingURL=today-ad-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js new file mode 100644 index 0000000..c030a5a --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js @@ -0,0 +1,112 @@ +import { isSome } from "@jet/environment"; +import * as validation from "@jet/environment/json/validation"; +import { isNothing } from "@jet/environment/types/optional"; +import { TodayCardLockupOverlay, TodayCardMediaAppEvent, } from "../../../api/models"; +import { asBooleanOrFalse } 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 { appEventOrPromotionStartDateFromData } from "../../app-promotions/app-event"; +import { appEventsAreEnabled } from "../../app-promotions/app-promotions-common"; +import { addNextPreferredContentRefreshDate } from "../../refresh/page-refresh-controller"; +import { todayCardArtworkDetails } from "../artwork/today-artwork-util"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary } from "../today-card-overlay-util"; +import { cardDisplayStyleFromData, cardStyleFromJoeColorsWithoutFallback, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, } from "../today-card-util"; +import { createTodayBaseCard } from "./today-base-card-builder"; +/** + * Create TodayCard displaying an app event + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using a app event media. + */ +export function createTodayAppEventCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayAppEventCard", () => { + var _a, _b, _c, _d, _e; + if (!appEventsAreEnabled(objectGraph)) { + return null; + } + const appEventData = appEventDataFromData(objectGraph, data); + if (isNothing(appEventData)) { + return null; + } + const appEventCard = createTodayBaseCard(objectGraph, data, cardConfig, context, (clickOptions) => { + // Add app event ID to click options + clickOptions.inAppEventId = appEventData.id; + const parentAppData = mediaRelationship.relationshipData(objectGraph, appEventData, "app"); + if (isSome(parentAppData)) { + clickOptions.relatedSubjectIds = [parentAppData.id]; + } + }); + if (isSome(appEventCard.editorialDisplayOptions)) { + appEventCard.editorialDisplayOptions.useMaterialBlur = true; + } + pushTodayCardLocation(objectGraph, data, cardConfig, context); + // Artwork + const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig); + if (isNothing(mediaDetails)) { + popTodayCardLocation(context); + return null; + } + // Tint and styling + const tintColor = (_b = (_a = mediaDetails.joeColors) === null || _a === void 0 ? void 0 : _a.textColor4) !== null && _b !== void 0 ? _b : color.black; + const artworkBackgroundColor = (_d = (_c = mediaDetails.joeColors) === null || _c === void 0 ? void 0 : _c.bgColor) !== null && _d !== void 0 ? _d : color.black; + const blurStyle = color.isDarkColor(artworkBackgroundColor) ? "dark" : "light"; + // App event + const metricsOptions = { + pageInformation: context.pageInformation, + locationTracker: context.locationTracker, + targetType: "eventModule", + }; + // Card style + appEventCard.style = + (_e = cardStyleFromJoeColorsWithoutFallback(mediaDetails.joeColors, "textColor4")) !== null && _e !== void 0 ? _e : (color.isDarkColor(tintColor) ? "dark" : "light"); + // Offer style + const offerStyle = offerStyleForTodayCard(objectGraph, appEventCard.style); + const editorialKind = mediaAttributes.attributeAsString(data, "label"); + const appEventOrDate = appEventOrPromotionStartDateFromData(objectGraph, appEventData, null, false, false, offerEnvironmentForTodayCard(appEventCard.style), offerStyle, true, metricsOptions, true, true, editorialKind, false, asBooleanOrFalse(cardConfig.allowUnpublishedAppEventPreviews)); + // Return early if we received a Date, as this means the App Event shouldn't be shown yet. + if (isNothing(appEventOrDate) || appEventOrDate instanceof Date) { + addNextPreferredContentRefreshDate(appEventOrDate, context.refreshController); + popTodayCardLocation(context); + return null; + } + const appEvent = appEventOrDate; + // Card + appEventCard.media = new TodayCardMediaAppEvent(appEvent.formattedDates, appEvent.startDate, tintColor, mediaDetails.artworks, mediaDetails.videos, mediaDetails.artworkLayoutsWithMetrics, blurStyle); + if (isSome(appEvent.lockup)) { + appEventCard.overlay = new TodayCardLockupOverlay(appEvent.lockup); + } + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, appEventCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return appEventCard; + }); +} +/** + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @returns The app event relationship data for this card + */ +function appEventDataFromData(objectGraph, data) { + let appEventData; + // Primary content + const primaryContent = mediaRelationship.relationshipCollection(data, "primary-content"); + if (primaryContent.length > 0) { + appEventData = primaryContent[0]; + } + else { + // Card contents + const cardContents = mediaRelationship.relationshipCollection(data, "card-contents"); + if (cardContents.length === 0) { + return null; + } + appEventData = cardContents[0]; + } + return appEventData; +} +//# sourceMappingURL=today-app-event-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js new file mode 100644 index 0000000..77bd0de --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js @@ -0,0 +1,244 @@ +import * as validation from "@jet/environment/json/validation"; +import { isNothing, isSome } from "@jet/environment/types/optional"; +import { ExternalUrlAction, TodayCard, TodayCardActionOverlay, TodayCardMediaArtwork, } from "../../../api/models"; +import { isNull } from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import { relationshipCollection } from "../../../foundation/media/relationships"; +import * as contentAttributes from "../../content/attributes"; +import { editorialNotesFromData, notesFromData } from "../../content/content"; +import { extractEditorialClientParams } from "../../editorial-pages/editorial-data-util"; +import { editorialItemActionFromData, subtitleFromData } from "../../lockups/lockups"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import { addMetricsEventsToVideo } from "../../metrics/helpers/media"; +import { todayCardArtworkDetails } from "../artwork/today-artwork-util"; +import { isCardDataOnboardingCard } from "../onboarding-cards"; +import { brandedTitleArtworkForCard, cardDisplayStyleFromData, cardStyleFromJoeColors, isCardOTDIntention, relatedCardContentsContentsFromData, todayCardArtworkTitleBackingGradientForKey, todayCardEditorialDisplayOptionsFromData, todayCardMetricsOptions, } from "../today-card-util"; +import { recoMetricsFromTodayItem } from "../today-parse-util"; +import { TodayCardDisplayStyle } from "../today-types"; +/** + * If the title is not supplied for app of the day we use this + */ +const appOfTheDayFallbackTitle = "FEATURED APP"; +/** + * If the title is not supplied for game of the day we use this + */ +const gameOfTheDayFallbackTitle = "FEATURED GAME"; +/** + * Creates a TodayCard used as the base for all other TodayCards + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param clickOptionsModifier A function that can modify the click options for the card + * @returns The newly created TodayCard, which can then be modified by other TodayCard builders + */ +export function createTodayBaseCard(objectGraph, data, cardConfig, context, clickOptionsModifier) { + return validation.context("createTodayBaseCard", () => { + const baseCard = new TodayCard(); + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.coercedCollectionTodayCardDisplayStyle); + // Heading + const heading = cardHeadingFromData(objectGraph, data, cardDisplayStyle, cardConfig); + baseCard.heading = heading; + // Title, title artwork + const title = cardTitleFromData(objectGraph, data, cardConfig); + baseCard.title = title; + const shortTitle = contentAttributes.contentAttributeAsString(objectGraph, data, [ + "shortEditorialNotes", + "name", + ]); + baseCard.shortTitle = shortTitle; + const cardTitleArtwork = brandedTitleArtworkForCard(objectGraph, data); + baseCard.titleArtwork = cardTitleArtwork; + // Description + const inlineDescription = cardDescriptionFromData(objectGraph, data); + baseCard.inlineDescription = inlineDescription; + const metricsOptions = todayCardMetricsOptions(objectGraph, data, cardConfig, context, title); + metricsOptions.adSlotOverride = context.parsedCardCount; + baseCard.clickAction = cardClickAction(objectGraph, data, cardDisplayStyle, cardConfig, context, metricsOptions, clickOptionsModifier); + // Configure impressions + metricsHelpersImpressions.addImpressionsFieldsToTodayCard(objectGraph, baseCard, metricsOptions, heading, cardDisplayStyle, isCardDataOnboardingCard(objectGraph, data)); + baseCard.editorialDisplayOptions = todayCardEditorialDisplayOptionsFromData(objectGraph, data, cardConfig); + return baseCard; + }); +} +/** + * @param objectGraph The dependency graph for the App Store + * @param cardDisplayStyle The display style of the card + * @param data The data to get the branded single app overlay heading from + * @param cardConfig The configuration for the card + * @returns The heading to use on the branded single app overlay + */ +export function cardHeadingFromData(objectGraph, data, cardDisplayStyle, cardConfig) { + var _a, _b; + let heading = null; + if (cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.useOTDTextStyle) { + if (cardConfig.isHorizontalShelfContext) { + // We don't support branded images or titles for a cards in a horizontal shelf + // so want to show the "APP OF THE DAY" / "GAME OF THE DAY" text as the heading instead. + heading = mediaAttributes.attributeAsString(data, "label"); + } + else { + heading = null; + } + } + else if (isCardOTDIntention(data, cardConfig)) { + heading = + (_a = mediaAttributes.attributeAsString(data, "alternateLabel")) !== null && _a !== void 0 ? _a : mediaAttributes.attributeAsString(data, "label"); + if (isNull(heading) && cardDisplayStyle === TodayCardDisplayStyle.AppOfTheDay) { + heading = appOfTheDayFallbackTitle; + } + else if (isNull(heading) && cardDisplayStyle === TodayCardDisplayStyle.GameOfTheDay) { + heading = gameOfTheDayFallbackTitle; + } + } + else { + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.AppEventCard: + const fallbackCardContents = fallbackCardContentFromData(objectGraph, data); + if (isSome(fallbackCardContents)) { + heading = + (_b = editorialNotesFromData(objectGraph, data, "badge", true)) !== null && _b !== void 0 ? _b : mediaAttributes.attributeAsString(fallbackCardContents, "kind"); + } + break; + default: + heading = mediaAttributes.attributeAsString(data, "label"); + break; + } + } + return heading; +} +/** + * @param objectGraph The dependency graph for the App Store + * @param data The media api data used to determine the title of this card + * @param cardConfig The configuration for the card + * @returns The title to use for this card + */ +function cardTitleFromData(objectGraph, data, cardConfig) { + var _a; + let title = null; + if (cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.useOTDTextStyle) { + title = + (_a = mediaAttributes.attributeAsString(data, "ofTheDayLabel")) !== null && _a !== void 0 ? _a : mediaAttributes.attributeAsString(data, "label"); + // We need to replace \n characters with
tags to ensure the + // newlines in APP OF THE DAY titles are maintained. The pattern must be a RegExp since string patterns + // only replace the first occurrence. + if (isSome(title)) { + title = title.replace(/\n/g, "
"); + } + } + if (isNothing(title)) { + title = notesFromData(objectGraph, data, "name", true); + } + if (isNothing(title)) { + // Lastly fallback to the first related content title + const fallbackCardContents = fallbackCardContentFromData(objectGraph, data); + title = isSome(fallbackCardContents) ? mediaAttributes.attributeAsString(fallbackCardContents, "name") : null; + } + return title; +} +/** + * @param objectGraph The dependency graph for the App Store + * @param data The media api data used to determine the description of this card + * @returns The title to use for this card + */ +function cardDescriptionFromData(objectGraph, data) { + var _a; + const editorialClientParams = extractEditorialClientParams(objectGraph, data); + const ignoreShortNotes = mediaAttributes.attributeAsBooleanOrFalse(data, "ignoreITunesShortNotes"); + if (ignoreShortNotes || editorialClientParams.suppressNoteShort) { + return null; + } + let description = notesFromData(objectGraph, data, "short", true); + if (isNothing(description) && !editorialClientParams.suppressNoteTagline) { + // Lastly fallback to the first related content title + const fallbackCardContents = fallbackCardContentFromData(objectGraph, data); + if (isSome(fallbackCardContents)) { + description = + (_a = notesFromData(objectGraph, fallbackCardContents, "tagline")) !== null && _a !== void 0 ? _a : subtitleFromData(objectGraph, fallbackCardContents); + } + } + return description; +} +/** + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data used to build the card + * @returns + */ +function fallbackCardContentFromData(objectGraph, data) { + const primaryContent = relationshipCollection(data, "primary-content"); + if ((primaryContent === null || primaryContent === void 0 ? void 0 : primaryContent.length) === 1) { + return primaryContent[0]; + } + // Card contents + const cardContents = relatedCardContentsContentsFromData(objectGraph, data); + if (cardContents.length === 1) { + return cardContents[0]; + } + return null; +} +/** + * @param objectGraph The dependency graph for the App Store + * @param data The media api data used to build the card this action is for + * @param cardDisplayStyle The display style of the card + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param baseMetricsOptions The base metrics options for the card + * @returns The click action used for tapping on the card + */ +function cardClickAction(objectGraph, data, cardDisplayStyle, cardConfig, context, baseMetricsOptions, clickOptionsModifier) { + var _a; + const clickOptions = baseMetricsOptions; + const franchise = mediaAttributes.attributeAsString(data, "label"); + // Metrics: JS: Add franchise/label to todayCard click events + const actionDetails = { + cardType: cardDisplayStyle, + franchise: franchise, + }; + const isOnboardingCard = isCardDataOnboardingCard(objectGraph, data); + if (isOnboardingCard) { + actionDetails["isOnboardingCard"] = isOnboardingCard; + } + clickOptions["actionDetails"] = actionDetails; + if (isSome(clickOptionsModifier)) { + clickOptionsModifier(clickOptions); + } + return editorialItemActionFromData(objectGraph, data, clickOptions, (_a = cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.clientIdentifierOverride) !== null && _a !== void 0 ? _a : objectGraph.host.clientIdentifier, recoMetricsFromTodayItem(context.currentTodayItem), cardConfig); +} +/** + * @param objectGraph The dependency graph for the App Store + * @param data The media api data used to build the base card + * @param baseCard The base card to add the artwork media to + * @param cropCode The crop code to use for the artwork + * @param context The context for the page were creating the card for + * @returns Whether or not we successfully added artwork media to the base card + */ +export function addArtworkMediaToBaseCard(objectGraph, data, baseCard, cardConfig, context) { + return validation.context("addArtworkMediaToBaseCard", () => { + const ignoreEditorialArt = mediaAttributes.attributeAsBooleanOrFalse(data, "ignoreEditorialArt"); + if (ignoreEditorialArt) { + return false; + } + const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig); + if (isNothing(mediaDetails)) { + return false; + } + if (isSome(context)) { + addMetricsEventsToVideo(objectGraph, mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.videos[0], { + pageInformation: context.pageInformation, + locationTracker: context.locationTracker, + id: data.id, + }); + } + baseCard.media = new TodayCardMediaArtwork(mediaDetails.artworks, mediaDetails.videos, mediaDetails.artworkLayoutsWithMetrics, todayCardArtworkTitleBackingGradientForKey(objectGraph, data, cardConfig)); + baseCard.style = cardStyleFromJoeColors(mediaDetails.joeColors, "bgColor"); + // External URL + if (baseCard.clickAction instanceof ExternalUrlAction && objectGraph.client.isiOS) { + baseCard.overlay = new TodayCardActionOverlay(baseCard.clickAction); + baseCard.style = "white"; + } + return true; + }); +} +//# sourceMappingURL=today-base-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js new file mode 100644 index 0000000..9088bfa --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js @@ -0,0 +1,58 @@ +import * as validation from "@jet/environment/json/validation"; +import { isSome } from "@jet/environment/types/optional"; +import { TodayCardMediaBrandedSingleApp } from "../../../api/models"; +import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data"; +import { deepLinkUrlFromData } from "../../linking/external-deep-link"; +import { todayCardArtworkDetails } from "../artwork/today-artwork-util"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util"; +import { cardDisplayStyleFromData, cardStyleFromJoeColors, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, todayCardArtworkTitleBackingGradientForKey, } from "../today-card-util"; +import { createTodayBaseCard } from "./today-base-card-builder"; +import { createTodaySingleAppCard } from "./today-single-app-card-builder"; +/** + * Create TodayCard displaying the App of the Day and Game of the Day editorial items + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using an artwork media to display the App of the Day and Game of the Day + */ +export function createTodayBrandedCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayBrandedCard", () => { + const brandedCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + pushTodayCardLocation(objectGraph, data, cardConfig, context); + // Configure the card style + const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig); + if (isSome(mediaDetails) && + isSome(mediaDetails.joeColors.bgColor) && + (mediaDetails.artworks.length > 0 || mediaDetails.videos.length > 0)) { + brandedCard.style = cardStyleFromJoeColors(mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.joeColors, "bgColor"); + } + else { + // If we don't have artwork, try building a single app card instead. + popTodayCardLocation(context); + return createTodaySingleAppCard(objectGraph, data, cardConfig, context); + } + const offerStyle = offerStyleForTodayCard(objectGraph, brandedCard.style); + const relatedContent = relatedCardContentsContentsFromData(objectGraph, data); + const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(brandedCard.style), offerStyle, deepLinkUrlFromData(objectGraph, data), true); + if (isNullOrEmpty(relatedContentLockups) || relatedContentLockups.length !== 1) { + popTodayCardLocation(context); + return null; + } + if (!cardConfig.isHorizontalShelfContext) { + brandedCard.overlay = relatedContentOverlayFromData(objectGraph, brandedCard, cardConfig, data, relatedContentLockups); + } + // Configure the card media + const brandedAppLockup = relatedContentLockups[0]; + brandedCard.media = new TodayCardMediaBrandedSingleApp(brandedAppLockup.icon, mediaDetails.artworks, mediaDetails.videos, mediaDetails.artworkLayoutsWithMetrics, todayCardArtworkTitleBackingGradientForKey(objectGraph, data, cardConfig)); + brandedCard.media.impressionMetrics = brandedAppLockup.impressionMetrics; + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, brandedCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return brandedCard; + }); +} +//# sourceMappingURL=today-branded-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js new file mode 100644 index 0000000..86cacec --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js @@ -0,0 +1,37 @@ +import * as validation from "@jet/environment/json/validation"; +import { deepLinkUrlFromData } from "../../linking/external-deep-link"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util"; +import { cardDisplayStyleFromData, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util"; +import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder"; +/** + * Create TodayCard displaying the full bleed image + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using the full bleed image media. + */ +export function createTodayFullBleedImageCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayFullBleedImageCard", () => { + const fullBleedImageCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + pushTodayCardLocation(objectGraph, data, cardConfig, context); + const didAddArtworkToCard = addArtworkMediaToBaseCard(objectGraph, data, fullBleedImageCard, cardConfig, context); + if (!didAddArtworkToCard) { + popTodayCardLocation(context); + return null; + } + const relatedContent = relatedCardContentsContentsFromData(objectGraph, data); + const offerStyle = offerStyleForTodayCard(objectGraph, fullBleedImageCard.style); + const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(fullBleedImageCard.style), offerStyle, deepLinkUrlFromData(objectGraph, data)); + const overlay = relatedContentOverlayFromData(objectGraph, fullBleedImageCard, cardConfig, data, relatedContentLockups); + fullBleedImageCard.overlay = overlay; + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, fullBleedImageCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return fullBleedImageCard; + }); +} +//# sourceMappingURL=today-full-bleed-image-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js new file mode 100644 index 0000000..d326c5c --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js @@ -0,0 +1,62 @@ +import * as validation from "@jet/environment/json/validation"; +import { TodayCardInAppPurchase } from "../../../api/models"; +import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data"; +import { inAppPurchaseLockupFromData } from "../../lockups/lockups"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary } from "../today-card-overlay-util"; +import { cardDisplayStyleFromData, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util"; +import { createTodayBaseCard } from "./today-base-card-builder"; +/** + * Create TodayCard displaying an in app purchase + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using the in app purchase media. + */ +export function createTodayInAppPurchaseCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayInAppPurchaseCard", () => { + const inAppPurchaseCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + pushTodayCardLocation(objectGraph, data, cardConfig, context); + const iAPData = inAppPurchaseDataFromRelatedContent(objectGraph, relatedCardContentsContentsFromData(objectGraph, data)); + if (isNullOrEmpty(iAPData)) { + popTodayCardLocation(context); + return null; + } + const lockup = inAppPurchaseLockupFromData(objectGraph, iAPData, { + offerStyle: "colored", + metricsOptions: { + pageInformation: context.pageInformation, + locationTracker: context.locationTracker, + }, + artworkUseCase: 1 /* ArtworkUseCase.LockupIconSmall */, + }); + if (isNullOrEmpty(lockup)) { + popTodayCardLocation(context); + return null; + } + lockup.theme = "infer"; + inAppPurchaseCard.media = new TodayCardInAppPurchase(lockup); + // we need to add impressions to the media here because + // there is no overlay on an IAP inAppPurchaseCard for some reason + inAppPurchaseCard.media.impressionMetrics = lockup.impressionMetrics; + inAppPurchaseCard.media.impressionMetrics.fields["parentId"] = inAppPurchaseCard.impressionMetrics.fields["id"]; + inAppPurchaseCard.style = "white"; + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, inAppPurchaseCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return inAppPurchaseCard; + }); +} +function inAppPurchaseDataFromRelatedContent(objectGraph, relatedContent) { + if (relatedContent.length === 1) { + const contentData = relatedContent[0]; + if (contentData.type === "in-apps") { + return contentData; + } + } + return null; +} +//# sourceMappingURL=today-in-app-purchase-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js new file mode 100644 index 0000000..b803c9f --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js @@ -0,0 +1,71 @@ +import * as validation from "@jet/environment/json/validation"; +import { TodayCardMediaList } from "../../../api/models"; +import * as videoDefaults from "../../constants/video-constants"; +import { todayCardArtworkDetails } from "../artwork/today-artwork-util"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary } from "../today-card-overlay-util"; +import { applyMultiAppFallbackToCollectionCard, cardDisplayStyleFromData, cardStyleFromJoeColorsWithoutFallback, listFallbackLimit, lockupsForRelatedContent, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util"; +import { TodayCardDisplayStyle, } from "../today-types"; +import { createTodayBaseCard } from "./today-base-card-builder"; +import { isEligibleForGamesApp } from "../../content/content"; +/** + * Create TodayCard displaying the list of lockups + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using a list or numbered list media. + */ +export function createTodayListCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayListCard", () => { + var _a, _b, _c; + const listCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + pushTodayCardLocation(objectGraph, data, cardConfig, context); + cardConfig.useJoeColorIconPlaceholder = true; + let relatedContents = relatedCardContentsContentsFromData(objectGraph, data); + if (preprocessor.GAMES_TARGET) { + // Filtering out non-eligible games as they can be used as content for stories collection. + // see `createFallbackListShelf` in article.ts. + relatedContents = relatedContents.filter((item) => isEligibleForGamesApp(item)); + } + const listLockups = lockupsForRelatedContent(objectGraph, relatedContents, cardConfig, context.pageInformation, context.locationTracker, undefined, undefined, undefined); + if (listLockups.length === 0) { + popTodayCardLocation(context); + return null; + } + cardConfig.canDisplayArcadeOfferButton = false; + // We require at least `listFallbackLimit` lockups for the list card + // If this is for use on top of an article its ok to have less than `listFallbackLimit` + if (listLockups.length < listFallbackLimit(objectGraph) && cardConfig.enableListCardToMultiAppFallback) { + applyMultiAppFallbackToCollectionCard(objectGraph, data, listLockups, listCard); + } + else { + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle); + if (cardDisplayStyle === TodayCardDisplayStyle.NumberedList) { + let index = 1; + for (const lockup of listLockups) { + lockup.ordinal = `${index}`; + index++; + } + } + const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig); + const cardStyle = cardStyleFromJoeColorsWithoutFallback(mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.joeColors, "bgColor"); + const isModernListCard = objectGraph.host.isiOS; + // Since list videos are very wide (21:9) and ~50% masked, we prevent them from going fullscreen + mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.videos.forEach((video) => { + video.canPlayFullScreen = false; + video.playbackControls = videoDefaults.noControls(objectGraph); + video.autoPlayPlaybackControls = videoDefaults.noControls(objectGraph); + }); + listCard.style = isModernListCard ? undefined : "white"; + listCard.media = new TodayCardMediaList(listLockups, (_a = mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.artworks) !== null && _a !== void 0 ? _a : [], (_b = mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.videos) !== null && _b !== void 0 ? _b : [], (_c = mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.artworkLayoutsWithMetrics) !== null && _c !== void 0 ? _c : [], undefined, isModernListCard ? cardStyle !== "white" : cardStyle === "dark"); + } + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, listCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return listCard; + }); +} +//# sourceMappingURL=today-list-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js new file mode 100644 index 0000000..d93fa7e --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js @@ -0,0 +1,62 @@ +import * as validation from "@jet/environment/json/validation"; +import { TodayCardMediaRiver } from "../../../api/models"; +import { isDarkColor, rgbWith } from "../../../foundation/util/color-util"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util"; +import { applyMultiAppFallbackToCollectionCard, cardDisplayStyleFromData, gridFallbackLimit, lockupsForCollectionCardFromData, popTodayCardLocation, pushTodayCardLocation, } from "../today-card-util"; +import { createTodayBaseCard } from "./today-base-card-builder"; +/** + * Create TodayCard displaying the river of lockups + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using a river media. + */ +export function createTodayRiverCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayRiverCard", () => { + const riverCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + cardConfig.useJoeColorIconPlaceholder = true; + pushTodayCardLocation(objectGraph, data, cardConfig, context); + const shouldIncludeLockupClickActions = objectGraph.client.isWeb; + const riverLockups = lockupsForCollectionCardFromData(objectGraph, data, cardConfig, context, shouldIncludeLockupClickActions); + if (riverLockups.length === 0) { + popTodayCardLocation(context); + return null; + } + cardConfig.canDisplayArcadeOfferButton = false; + if (riverLockups.length < gridFallbackLimit(objectGraph)) { + // P2: Some Today cards not showing on Cinar + // less that `gridFallbackLimit` lockups gets a fallback card on Emet only + applyMultiAppFallbackToCollectionCard(objectGraph, data, riverLockups, riverCard); + } + else { + riverCard.style = "dark"; + replaceHighLuminanceIconColorsInLockups(riverLockups); + riverCard.media = new TodayCardMediaRiver(riverLockups); + } + if (objectGraph.client.isWatch) { + riverCard.overlay = relatedContentOverlayFromData(objectGraph, riverCard, cardConfig, data, riverLockups); + } + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, riverCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return riverCard; + }); +} +/** + * Replace any icon background colors with luminance > 0.9 with grey color. + * This ensure that river background colors don't compete with the overlayed white text. + * + * @param riverLockups The river lockups. + */ +function replaceHighLuminanceIconColorsInLockups(riverLockups) { + for (const lockup of riverLockups) { + if (!isDarkColor(lockup.icon.backgroundColor, 90)) { + lockup.icon.backgroundColor = rgbWith(0.8, 0.8, 0.8); + } + } +} +//# sourceMappingURL=today-river-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js new file mode 100644 index 0000000..c50836a --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js @@ -0,0 +1,42 @@ +import * as validation from "@jet/environment/json/validation"; +import { deepLinkUrlFromData } from "../../linking/external-deep-link"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util"; +import { cardDisplayStyleFromData, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util"; +import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder"; +import { addSingleAppFallbackToCard } from "./today-single-app-card-builder"; +/** + * Create TodayCard displaying the full bleed image + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using the full bleed image media. + */ +export function createTodayShortImageCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayShortImageCard", () => { + const shortImageCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + pushTodayCardLocation(objectGraph, data, cardConfig, context); + const didAddArtworkToCard = addArtworkMediaToBaseCard(objectGraph, data, shortImageCard, cardConfig, context); + let didAddSingleAppFallback = false; + const relatedContent = relatedCardContentsContentsFromData(objectGraph, data); + const offerStyle = offerStyleForTodayCard(objectGraph, shortImageCard.style); + const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(shortImageCard.style), offerStyle, deepLinkUrlFromData(objectGraph, data)); + const overlay = relatedContentOverlayFromData(objectGraph, shortImageCard, cardConfig, data, relatedContentLockups); + shortImageCard.overlay = overlay; + if (!didAddArtworkToCard && relatedContent.length === 1) { + didAddSingleAppFallback = addSingleAppFallbackToCard(objectGraph, data, shortImageCard, relatedContent, cardConfig, context); + } + if (!didAddArtworkToCard && !didAddSingleAppFallback) { + popTodayCardLocation(context); + return null; + } + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, shortImageCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return shortImageCard; + }); +} +//# sourceMappingURL=today-short-image-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js new file mode 100644 index 0000000..18828c8 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js @@ -0,0 +1,81 @@ +import * as validation from "@jet/environment/json/validation"; +import { TodayCardMediaAppIcon, TodayCardMediaList, TodayCardParagraphOverlay } from "../../../api/models"; +import { isDefinedNonNull } from "../../../foundation/json-parsing/server-data"; +import { deepLinkUrlFromData } from "../../linking/external-deep-link"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util"; +import { cardDisplayStyleFromData, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util"; +import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder"; +/** + * Create TodayCard displaying the SingleApp card display style + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, either artwork or the single app icon. + */ +export function createTodaySingleAppCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodaySingleAppCard", () => { + const singleAppCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + pushTodayCardLocation(objectGraph, data, cardConfig, context); + // Next prefer artwork card, and then fallback to substyles + const didAddArtworkToCard = addArtworkMediaToBaseCard(objectGraph, data, singleAppCard, cardConfig, context); + const relatedContent = relatedCardContentsContentsFromData(objectGraph, data); + const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, "todayCard", null, deepLinkUrlFromData(objectGraph, data)); + const overlay = relatedContentOverlayFromData(objectGraph, singleAppCard, cardConfig, data, relatedContentLockups); + if (isDefinedNonNull(overlay) && overlay instanceof TodayCardParagraphOverlay) { + overlay.style = "white"; + } + singleAppCard.overlay = overlay; + if (!didAddArtworkToCard && relatedContent.length === 1) { + const didAddSingleAppFallback = addSingleAppFallbackToCard(objectGraph, data, singleAppCard, relatedContent, cardConfig, context); + if (!didAddSingleAppFallback) { + popTodayCardLocation(context); + return null; + } + } + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, singleAppCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return singleAppCard; + }); +} +/** + * Adds a single app fallback to the card. + * + * @param objectGraph - The object graph of the app store. + * @param data - The media data. + * @param card - The today card. + * @param relatedContent - The related content data. + * @param cardConfig - The configuration for the today card. + * @param context - The parse context for today card. + * @returns Returns true or false based on whether we could create a single app fallback. + */ +export function addSingleAppFallbackToCard(objectGraph, data, card, relatedContent, cardConfig, context) { + if (relatedContent.length !== 1) { + return false; + } + cardConfig.useJoeColorIconPlaceholder = true; + card.style = "dark"; + const offerStyle = offerStyleForTodayCard(objectGraph, card.style); + const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(card.style), offerStyle, deepLinkUrlFromData(objectGraph, data)); + if (relatedContentLockups.length !== 1) { + return false; + } + const lockup = relatedContentLockups[0]; + // tvOS doesn't have layout to properly render appIcon media. + // However, `media` can't be simply removed from TodayCard, as it is a required field in native code. + // This `TodayCardMediaList` works as an empty media placeholder, + // that allows to render text from TodayCard but not the image. + // From StarlightB AppIcon is disabled in native code and this check can be removed in the next major release. + if (objectGraph.client.isTV && !objectGraph.props.enabled("disableAppIconMediaTodayHeader")) { + card.media = new TodayCardMediaList([], [], [], [], undefined, undefined); + } + else { + card.media = new TodayCardMediaAppIcon(lockup.icon); + } + return true; +} +//# sourceMappingURL=today-single-app-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js new file mode 100644 index 0000000..77516f6 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js @@ -0,0 +1,33 @@ +import * as validation from "@jet/environment/json/validation"; +import { deepLinkUrlFromData } from "../../linking/external-deep-link"; +import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util"; +import { cardDisplayStyleFromData, lockupsForRelatedContent, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util"; +import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder"; +/** + * Create TodayCard displaying a video + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to build the card from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data` + * @returns The newly created TodayCard, using the video media. + */ +export function createTodayVideoCard(objectGraph, data, cardConfig, context, augmentingData) { + return validation.context("createTodayVideoCard", () => { + const videoCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + addArtworkMediaToBaseCard(objectGraph, data, videoCard, cardConfig, context); + pushTodayCardLocation(objectGraph, data, cardConfig, context); + const offerStyle = offerStyleForTodayCard(objectGraph, videoCard.style); + const relatedContent = relatedCardContentsContentsFromData(objectGraph, data); + const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, "todayCard", offerStyle, deepLinkUrlFromData(objectGraph, data)); + const overlay = relatedContentOverlayFromData(objectGraph, videoCard, cardConfig, data, relatedContentLockups); + videoCard.overlay = overlay; + // Special post-processing step for Acquisition story cards. + // This is needed to splice in data not included in initial response. + applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, videoCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context); + popTodayCardLocation(context); + return videoCard; + }); +} +//# sourceMappingURL=today-video-card-builder.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js b/node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js new file mode 100644 index 0000000..86af694 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js @@ -0,0 +1,23 @@ +/** + * Created by ls on 6/23/17. + */ +import * as serverData from "../../foundation/json-parsing/server-data"; +const isOnboardingCardKey = "isOnBoardingCard"; +/** + * Tag a data collection as onboarding cards for metrics. + * @param cardsData + */ +export function markDataCollectionAsOnboardingCards(objectGraph, cardsData) { + for (const card of cardsData) { + card[isOnboardingCardKey] = true; + } +} +/** + * Check whether given data was injected as onboarding card. + * @param cardData Media API data for single card. + * @returns A boolean returning `true` if `cardData` was from `makeOnBoardingCardToken`, `false` otherwise. + */ +export function isCardDataOnboardingCard(objectGraph, cardData) { + return serverData.asBooleanOrFalse(cardData, isOnboardingCardKey); +} +//# sourceMappingURL=onboarding-cards.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js new file mode 100644 index 0000000..5603a86 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js @@ -0,0 +1,32 @@ +import { makeRoutableArticlePageIntent, } from "../../api/intents/routable-article-page-intent"; +import { generateRoutes } from "../util/generate-routes"; +const { routes: routableArticlePageWithPlatformRoutes, makeCanonicalUrl: makeRoutableArticlePageWithPlatformUrl } = generateRoutes(makeRoutableArticlePageIntent, "/{platform}/story/{id}"); +const { routes: routableArticlePageWithoutPlatformRoutes, makeCanonicalUrl: makeRoutableArticlePageWithoutPlatformUrl, } = generateRoutes(makeRoutableArticlePageIntent, "/story/{id}"); +export { routableArticlePageWithPlatformRoutes, routableArticlePageWithoutPlatformRoutes }; +/** + * Generate the URL for an "Article Page" based on the {@linkcode intent} + * + * If the {@linkcode intent} has an explicitly-configured `platform` property, + * then the resulting URL will include the explicitly as the `{platform}` prefix + * segment. If the `platform` property is not provided, the resulting URL will + * not contain an explicit `{platform}` segment at all. + * + * @param objectGraph + * @param intent + * @returns the URL that, when parsed, produces the given `intent` + */ +export function makeRoutableArticlePageCanonicalUrl(objectGraph, intent) { + const intentWithExpectedID = { + ...intent, + // The `{id}` segment of the URL is expected to include the `id` prefix, which is removed + // when parsing the incoming URL into the `Intent` + id: `id${intent.id}`, + }; + if (intent.platform) { + return makeRoutableArticlePageWithPlatformUrl(objectGraph, intentWithExpectedID); + } + else { + return makeRoutableArticlePageWithoutPlatformUrl(objectGraph, intentWithExpectedID); + } +} +//# sourceMappingURL=routable-article-page-url-utils.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js new file mode 100644 index 0000000..ab0829f --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js @@ -0,0 +1,111 @@ +import { isSome } from "@jet/environment"; +import { isNothing } from "@jet/environment/types/optional"; +import { marketingItemContextFromString, TodayCardArcadeLockupOverlay, TodayCardLockupListOverlay, TodayCardLockupOverlay, TodayCardThreeLineOverlay, } from "../../api/models"; +import * as mediaAttributes from "../../foundation/media/attributes"; +import { upsellFromContentsOfUpsellResponse, upsellFromRelationshipOf, } from "../arcade/arcade-common"; +import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util"; +import { arcadeLockupFromData } from "../lockups/lockups"; +import { todayCardArtworkDetails } from "./artwork/today-artwork-util"; +import { cardStyleFromJoeColors, offerStyleForTodayCard } from "./today-card-util"; +// MARK: - Paragraph / Related Content Overlays +/** + * @param objectGraph The dependency graph for the App Store + * @param card The card being built that we'd like to add the overlay to + * @param cardConfig The configuration used to create the card + * @param data The media api data to build the card from + * @param relatedContentLockups The lockups created from the related content + * @returns A TodayCardLockupOverlay, TodayCardLockupListOverlay, TodayCardThreeLineOverlay, depending on whether + * the card should ignore the short notes, has a deep link, or has related content lockups. + */ +export function relatedContentOverlayFromData(objectGraph, card, cardConfig, data, relatedContentLockups) { + const editorialClientParams = extractEditorialClientParams(objectGraph, data); + if (isSome(editorialClientParams.suppressLockup) && editorialClientParams.suppressLockup) { + return null; + } + let overlay = null; + const hasSingleLockup = isSome(relatedContentLockups) && relatedContentLockups.length === 1; + const hasMultipleLockups = isSome(relatedContentLockups) && relatedContentLockups.length > 1; + const hasThreeLineCards = objectGraph.client.isMac || objectGraph.client.isWatch; + if (hasThreeLineCards) { + overlay = new TodayCardThreeLineOverlay(card.heading, card.title, card.inlineDescription); + } + else if (hasSingleLockup) { + overlay = new TodayCardLockupOverlay(relatedContentLockups[0]); + } + else if (hasMultipleLockups) { + overlay = new TodayCardLockupListOverlay(relatedContentLockups); + } + return overlay; +} +// MARK: - Arcade Acquisitions +/** + * Applies a set of overrides to an existing `card` built by the standard pipelie using the contents of augmenting data fetched externally. + * This function provides support for parsing editorial-items fetched for: + * - Today + * - Articles (with data augmentation) + * - Groupings (with data augmentation) + * + * @param card Card to override behavior of. + * @param data Original data `card` was created with + * @param augmentingData Data used to augment `card`. + * @param metricsContext Metrics context to use for overrides. + * @returns Overridden Card, or `null` if overrides failed. Note that `card` is modified *IN PLACE*. + */ +export function applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, card, cardConfig, cardDisplayStyle, data, augmentingData, context) { + const isAcquisitionStory = mediaAttributes.attributeAsBooleanOrFalse(data, "isAcquisition"); + if (!isAcquisitionStory) { + return; + } + // Try to build upsell data from upsell relationship, if that exists. + let upsellData = upsellFromRelationshipOf(objectGraph, data); + if (isNothing(upsellData) && isSome(augmentingData)) { + // Fallback: Try to build off upsell data off `augmentingData`. This relies on the caller being proactive about fetching upsell separately for endpoints that don't return upsell relationship. + upsellData = upsellFromContentsOfUpsellResponse(objectGraph, augmentingData.arcadeUpsellEditorialResponse); + } + // Override: Arcade Overlay + const arcadeLockupOverlay = arcadeOverlayFromData(objectGraph, data, card, cardConfig, cardDisplayStyle, upsellData, context); + if (arcadeLockupOverlay) { + card.overlay = arcadeLockupOverlay; + card.impressionMetrics.fields["displaysArcadeUpsell"] = true; + // If we're overriding the card style used with the overlay we should also update the style for the card itself. + const style = cardStyleForArcadeOverlay(objectGraph, data, card, cardConfig); + if (card.style !== style) { + card.style = style; + } + } +} +/** + * Whether or not current platform supports arcade overlay. + */ +function currentPlatformSupportsArcadeOverlay(objectGraph) { + // Check if on platform that will actually display data. + const platform = objectGraph.host.platform; + const platformSupportsArcadeLockupOverlay = platform === "iOS" || platform === "macOS"; + return platformSupportsArcadeLockupOverlay; +} +/** + * Creates an instance of `TodayCardArcadeLockupOverlay` to use for Acquisition Editorial Items. + * @param upsellData Upsell data containing editorial and iAP data. + * @param metricsContext Metrics context to use for generated lockup. + */ +function arcadeOverlayFromData(objectGraph, data, card, cardConfig, cardDisplayStyle, upsellData, context) { + if (!currentPlatformSupportsArcadeOverlay(objectGraph)) { + return null; + } + const style = cardStyleForArcadeOverlay(objectGraph, data, card, cardConfig); + const offerStyle = offerStyleForTodayCard(objectGraph, style); + const lockup = arcadeLockupFromData(objectGraph, upsellData, context, marketingItemContextFromString("editorialItem"), offerStyle, "todayCard"); + return new TodayCardArcadeLockupOverlay(lockup); +} +/** + * Returns the `TodayCardStyle` that should be used for the arcade overlay. + * Some cards use different styles when fetched as part of an article vs. today feed. Using the media artwork to determine the + * cardStyle in these cases avoids issues with today cards changing styles when transitionin from to today feed to articles. + */ +function cardStyleForArcadeOverlay(objectGraph, data, card, cardConfig) { + const useCardStyle = !cardConfig.enableListCardToMultiAppFallback; + const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig); + const styleFromMedia = cardStyleFromJoeColors(mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.joeColors); + return useCardStyle ? card.style : styleFromMedia; +} +//# sourceMappingURL=today-card-overlay-util.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js new file mode 100644 index 0000000..688f0fb --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js @@ -0,0 +1,785 @@ +import { isNothing, isSome } from "@jet/environment"; +import * as validation from "@jet/environment/json/validation"; +import { ArtworkContentMode, ExternalUrlAction, FlowAction, TodayCardActionOverlay, TodayCardMediaArtwork, TodayCardMediaHero, TodayCardMediaMultiApp, TodayCardMediaVideo, TodayCardMediaWithArtwork, } from "../../api/models"; +import { asBoolean, asDictionary, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, objectPathToString, } from "../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../foundation/media/attributes"; +import { relationshipCollection } from "../../foundation/media/relationships"; +import { Parameters } from "../../foundation/network/url-constants"; +import { URL } from "../../foundation/network/urls"; +import * as color from "../../foundation/util/color-util"; +import { artworkFromApiArtwork, editorialNotesFromData, hasMessagesExtensionFromData, isHiddenFromSpringboardFromData, joeColorSetFromData, notesFromData, screenSizeIPhone134, } from "../content/content"; +import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util"; +import * as lockupsEditorialContext from "../lockups/editorial-context"; +import { lockupsFromData } from "../lockups/lockups"; +import * as metricsHelpersLocation from "../metrics/helpers/location"; +import * as metricsHelpersImpressions from "./../metrics/helpers/impressions"; +import { createTodayAppEventCard } from "./cards/today-app-event-card-builder"; +import { createTodayBrandedCard } from "./cards/today-branded-card-builder"; +import { createTodayFullBleedImageCard } from "./cards/today-full-bleed-image-card-builder"; +import { createTodayGridCard } from "./cards/today-grid-card-builder"; +import { createTodayInAppPurchaseCard } from "./cards/today-in-app-purchase-card-builder"; +import { createTodayListCard } from "./cards/today-list-card-builder"; +import { createTodayRiverCard } from "./cards/today-river-card-builder"; +import { createTodayShortImageCard } from "./cards/today-short-image-card-builder"; +import { createTodaySingleAppCard } from "./cards/today-single-app-card-builder"; +import { createTodayVideoCard } from "./cards/today-video-card-builder"; +import { HeroMediaDisplayContext, OfTheDayIntention, TodayCardDisplayStyle, } from "./today-types"; +import { createTodayBaseCard } from "./cards/today-base-card-builder"; +// MARK: - Today Card Creation +/** + * Create a card with some appearance configuration within today page. + * @param {AppStoreObjectGraph} objectGraph The object graph for the app store. + * @param {Data} data to build card of. + * @param {TodayCardConfiguration} cardConfig flags on how to configure a given card. + * @param {TodayParseContext} context to store state updated throughout parsing. + * @param {TodayCardAugmentingData} augmentingData that stores some additional responses that may be used to enhance the contents of `data` + */ +export function todayCardFromData(objectGraph, data, cardConfig, context, augmentingData) { + return validation.catchingContext("todayCardFromData", () => { + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle); + const clientIdentifier = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, data); + // If clientIdentifier is empty, don't override the cardConfig as this may have been set + // at a higher level. + if (isSome(clientIdentifier)) { + if (shouldRespectClientIdentifierOverride(objectGraph, data, clientIdentifier, cardConfig)) { + cardConfig.clientIdentifierOverride = clientIdentifier; + } + else { + cardConfig.clientIdentifierOverride = null; + } + } + // Configure subtitle for cross link. + cardConfig.crossLinkSubtitle = crossLinkSubtitleFromData(objectGraph, data); + let card = null; + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.AppOfTheDay: + case TodayCardDisplayStyle.GameOfTheDay: + card = createTodayBrandedCard(objectGraph, data, cardConfig, context, augmentingData); + break; + case TodayCardDisplayStyle.Video: + card = createTodayVideoCard(objectGraph, data, cardConfig, context, augmentingData); + break; + case TodayCardDisplayStyle.FullBleedImage: + card = createTodayFullBleedImageCard(objectGraph, data, cardConfig, context, augmentingData); + break; + case TodayCardDisplayStyle.InAppPurchase: + card = createTodayInAppPurchaseCard(objectGraph, data, cardConfig, context, augmentingData); + break; + case TodayCardDisplayStyle.AppEventCard: + card = createTodayAppEventCard(objectGraph, data, cardConfig, context, augmentingData); + break; + case TodayCardDisplayStyle.List: + case TodayCardDisplayStyle.NumberedList: + card = createTodayListCard(objectGraph, data, cardConfig, context, augmentingData); + break; + case TodayCardDisplayStyle.River: + case TodayCardDisplayStyle.Grid: + if (objectGraph.client.isMac) { + card = createTodayGridCard(objectGraph, data, cardConfig, context, augmentingData); + } + else { + card = createTodayRiverCard(objectGraph, data, cardConfig, context, augmentingData); + } + break; + case TodayCardDisplayStyle.SingleApp: + card = createTodaySingleAppCard(objectGraph, data, cardConfig, context, augmentingData); + break; + case TodayCardDisplayStyle.ShortImage: + card = createTodayShortImageCard(objectGraph, data, cardConfig, context, augmentingData); + break; + default: + card = null; + break; + } + if (isNull(card)) { + objectGraph.console.log(`Unknown style: ${cardDisplayStyle}`); + return card; + } + // For certain platforms we add hero media to the card. + addHeroMediaToTodayCardIfNecessary(objectGraph, card, cardConfig, data); + addExternalLinkOverlayToTodayCardIfNecessary(card); + addOTDStyleToCardIfNecessary(card, cardConfig); + enableFlipAndBlurIfNecessary(objectGraph, card); + if (isNothing(card.media)) { + objectGraph.console.log(`Missing required media: ${cardDisplayStyle}`); + card = null; + } + return card; + }, (error) => { + objectGraph.console.log(error); + return null; + }); +} +/** + * On the watch we need the todayCard on the article to get the title and subtitle, + * so even if we dont successfully make a card for the article we create a basic one with an empty media so the title / subtitle can be used + * @param {AppStoreObjectGraph} objectGraph The object graph for the app store. + * @param {Data} data to build card of. + * @param {TodayCardConfiguration} cardConfig flags on how to configure a given card. + * @param {TodayParseContext} context to store state updated throughout parsing. + */ +export function fallbackWatchTodayCardFromData(objectGraph, data, cardConfig, context) { + if (!objectGraph.client.isWatch) { + return null; + } + const fallbackCard = createTodayBaseCard(objectGraph, data, cardConfig, context); + fallbackCard.media = new TodayCardMediaArtwork([], [], []); + return fallbackCard; +} +/** + * Create the {EditorialDisplayOptions for a card given the MAPI data + * @param objectGraph The dependency graph for the App Store + * @param data The MAPI data for this today card + * @param cardConfig The configuration for the card + * @returns The editorialDisplayOptions for this card + */ +export function todayCardEditorialDisplayOptionsFromData(objectGraph, data, cardConfig) { + var _a; + // here is where we make the display options + const editorialClientParams = extractEditorialClientParams(objectGraph, data); + const isExtraWideCard = objectGraph.client.isPad && (cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.isHeroCard); + const editorialDisplayOptions = { + suppressTagline: mediaAttributes.attributeAsBoolean(data, "ignoreITunesShortNotes"), + suppressLockup: asBoolean(editorialClientParams["suppressLockup"]), + showBadgeInSmallCards: (_a = cardConfig.alwaysShowBadgeInSmallCards) !== null && _a !== void 0 ? _a : asBoolean(editorialClientParams["showBadgeInSmallCards"]), + useMaterialBlur: cardConfig.alwaysUseMaterialBlur || isExtraWideCard || asBoolean(editorialClientParams["useMaterialBlur"]), + }; + return editorialDisplayOptions; +} +// MARK: - Today Configuration +/** + * Create the "default" card configuration that callers can generally base their configuration from. + */ +export function defaultTodayCardConfiguration(objectGraph) { + return { + useOTDTextStyle: false, + enableFullScreenVideo: true, + enableListCardToMultiAppFallback: true, + canDisplayArcadeOfferButton: true, + isHeroCard: false, + }; +} +// MARK: - Metrics / Location Tracking +/** + * Allows for specialized card builders to share the same location tracking push / pop code. + * + * @param objectGraph The dependency graph for the App Store + * @param data The media api data to get location information from + * @param cardConfig The configuration for the card + * @param context The parse context for the over all today page + */ +export function pushTodayCardLocation(objectGraph, data, cardConfig, context, titleOverride) { + const title = isDefinedNonNullNonEmpty(titleOverride) + ? titleOverride + : todayCardTitleFromData(objectGraph, data, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle)); + metricsHelpersLocation.pushContentLocation(objectGraph, todayCardMetricsOptions(objectGraph, data, cardConfig, context, title), title !== null && title !== void 0 ? title : ""); +} +export function todayCardMetricsOptions(objectGraph, data, cardConfig, context, titleOverride) { + var _a; + return metricsHelpersImpressions.impressionOptions(objectGraph, data, titleOverride !== null && titleOverride !== void 0 ? titleOverride : "", { + ...cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.baseMetricsOptions, + targetType: "todayCard", + pageInformation: context.pageInformation, + locationTracker: context.locationTracker, + isAdEligible: (_a = cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.isAdEligible) !== null && _a !== void 0 ? _a : false, + optimizationId: asString(data, "meta.personalizationData.optimizationId"), + optimizationEntityId: asString(data, "meta.personalizationData.optimizationEntityId"), + rowIndex: cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.currentRowIndex, + displayStyle: cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.metricsDisplayStyle, + }); +} +/** + * Pop the today card location that was pushed by the pushTodayCardLocation function. + * + * @param context The parse context for the over all today page + */ +export function popTodayCardLocation(context) { + metricsHelpersLocation.popLocation(context.locationTracker); +} +// MARK: - Card Title +/** + * @param objectGraph The dependency graph for the App Store + * @param data The media api data used to determine the title of the card + * @param cardDisplayStyle The display style of the card + * @returns The title of the card + */ +export function todayCardTitleFromData(objectGraph, data, cardDisplayStyle) { + let title = notesFromData(objectGraph, data, "name"); + if (isNullOrEmpty(title)) { + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.AppOfTheDay: + case TodayCardDisplayStyle.GameOfTheDay: + title = mediaAttributes.attributeAsString(data, "label"); + break; + default: + break; + } + } + return title; +} +// MARK: - Card Display Styles +/** + * These are today card types that can be coerced into an override type, there are times where we want to + * have all List cards for instance, render as grids, this set denotes the types that can be coerced. + */ +const coercibleTodayCardStyles = new Set([ + TodayCardDisplayStyle.Grid, + TodayCardDisplayStyle.List, + TodayCardDisplayStyle.NumberedList, + TodayCardDisplayStyle.River, +]); +/** + * Find the today card style for the given data, allowing coercion if necessary. + * @param data The media api data for an editorial item + * @param styleOverride The override style to use if the data has a style that can be coerced + * @returns The resolved todayCardDisplayStyle + */ +export function cardDisplayStyleFromData(data, styleOverride) { + const cardDisplayStyle = mediaAttributes.attributeAsString(data, "cardDisplayStyle"); + if (coercibleTodayCardStyles.has(cardDisplayStyle) && isSome(styleOverride)) { + return styleOverride; + } + return cardDisplayStyle; +} +// MARK: - Collection Cards +/** + * @param objectGraph Dependency graph for the App Store + * @param data MAPI data for the card these lockups are included in + * @param cardConfig The configuration for the card these lockups are included in + * @param context The parse context for the over all today page + * @param includeLockupClickActions Whether we should include the click actions for the lockups, this is only needed + * when a card allows clicks on a lockup + * @returns The list of lockups for the collection displayed on a TodayCard + */ +export function lockupsForCollectionCardFromData(objectGraph, data, cardConfig, context, includeLockupClickActions) { + const relatedContent = relatedCardContentsContentsFromData(objectGraph, data); + const filteredRelatedContent = relatedContent.filter((cardData) => { + const isHiddenFromSpringboard = isHiddenFromSpringboardFromData(objectGraph, cardData); + const hasMessagesExtension = hasMessagesExtensionFromData(objectGraph, cardData); + return !hasMessagesExtension || !isHiddenFromSpringboard; + }); + return lockupsForRelatedContent(objectGraph, filteredRelatedContent, cardConfig, context.pageInformation, context.locationTracker, undefined, undefined, undefined, includeLockupClickActions); +} +/** + * @param objectGraph Dependency graph for the App Store + * @param relatedContent The list of MAPI data objects to generate lockups for + * @param cardConfig The configuration for the card these lockups are included in + * @param pageInformation The pageInformation for the page these lockups are included in + * @param locationTracker The locationTracker used for impressions on these lockups + * @param offerEnvironment The offer environment to use for the lockups + * @param offerStyle The offerStyle to use for the lockups + * @param externalDeepLinkUrl The promotional deep link url to use on the lockup's offer. + * @param includeLockupClickActions Whether we should include the click actions for the lockups, this is only needed + * when a card allows clicks on a lockup + * @returns The list of lockups for the collection displayed on a TodayCard + */ +export function lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, pageInformation, locationTracker, offerEnvironment, offerStyle, externalDeepLinkUrl, includeLockupClickActions = true) { + if (isNullOrEmpty(relatedContent)) { + return []; + } + // IAPs are only suitable for the TodayCardInAppPurchase card. That card follows a separate path, so we should + // filter them out here. + const filteredRelatedContent = relatedContent.filter((cardData) => { + if (isDefinedNonNullNonEmpty(cardData.attributes)) { + return cardData.type !== "in-apps"; + } + return true; + }); + const options = { + lockupOptions: { + metricsOptions: { + pageInformation: pageInformation, + locationTracker: locationTracker, + }, + offerEnvironment: offerEnvironment, + offerStyle: offerStyle, + clientIdentifierOverride: cardConfig.clientIdentifierOverride, + externalDeepLinkUrl: externalDeepLinkUrl, + crossLinkSubtitle: cardConfig.crossLinkSubtitle, + artworkUseCase: 1 /* ArtworkUseCase.LockupIconSmall */, + canDisplayArcadeOfferButton: cardConfig.canDisplayArcadeOfferButton, + useJoeColorIconPlaceholder: cardConfig.useJoeColorIconPlaceholder, + skipDefaultClickAction: !includeLockupClickActions, + }, + filter: 76670 /* Filter.TodayCard */, + }; + return lockupsFromData(objectGraph, filteredRelatedContent, options); +} +// MARK: - Related Content +/** + * @param objectGraph Dependency graph for the App Store + * @param data MAPI data to get the relationship from + * @returns The card-contents relationship from the MAPI data + */ +export function relatedCardContentsContentsFromData(objectGraph, data) { + return relationshipCollection(data, "card-contents"); +} +// MARK: - Grid / List Collection Card Fallback +/** + * @param objectGraph The object graph for the app store + * @returns The min icon count that a Grid card should use before falling back to fallback media + */ +export function gridFallbackLimit(objectGraph) { + switch (objectGraph.client.deviceType) { + case "tv": + return 2; + default: + return 4; + } +} +/** + * @param objectGraph The object graph for the app store + * @returns The min icon count that a List card should use before falling back to fallback media + */ +export function listFallbackLimit(objectGraph) { + // Limits before using fallback media card for list-types and grid-types (including river). + switch (objectGraph.client.deviceType) { + case "tv": + return 3; + default: + return 4; + } +} +/** + * When we do not have enough content to render a list or grid card, we will use a multi + * app fallback media in its place. This also updates the click action to include a param + * indicating we're using fallback media. + * + * @param objectGraph The object graph for the app store + * @param data The MAPI data for the card + * @param fallbackItems The list of lockups to use as fallback media + * @param card The base card to apply the fallback media to + */ +export function applyMultiAppFallbackToCollectionCard(objectGraph, data, fallbackItems, card) { + const extraText = notesFromData(objectGraph, data, "short"); + card.media = new TodayCardMediaMultiApp(fallbackItems, extraText); + card.style = "dark"; + if (card.clickAction instanceof FlowAction) { + const updatedPageUrl = URL.from(card.clickAction.pageUrl); + updatedPageUrl.param(Parameters.showingFallbackMedia, "true"); + card.clickAction.pageUrl = updatedPageUrl.build(); + } +} +// MARK: - Editorial Artwork +/** + * @param objectGraph The object graph for the app store + * @param cardDisplayStyle The display style for the card we're getting the key path for + * @returns The keypath used to find the editorial artwork for the card + */ +export function editorialArtKeyPathForCardDisplayStyle(objectGraph, cardDisplayStyle) { + if (objectGraph.client.isWatch) { + return "editorialArtwork.subscriptionHero"; + } + else if (objectGraph.client.isVision) { + return "editorialArtwork.storyCenteredStatic16x9"; + } + else { + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.AppOfTheDay: + case TodayCardDisplayStyle.GameOfTheDay: + return "editorialArtwork.dayCard"; + case TodayCardDisplayStyle.AppEventCard: + return "editorialArtwork.eventCard"; + case TodayCardDisplayStyle.Video: + case TodayCardDisplayStyle.FullBleedImage: + return "editorialArtwork.mediaCard"; + default: + return "editorialArtwork.generalCard"; + } + } +} +/** + * @param objectGraph The object graph for the app store + * @param artworkData The artwork data to create the artwork from, from the cards media api data + * @param cropCode The crop code to use for the artwork, otherwise we use the correct crop for the platform + * @returns The artwork model for the card + */ +export function todayCardArtworkFromArtworkData(objectGraph, artworkData, cropCode) { + if (isNullOrEmpty(artworkData)) { + return null; + } + const artwork = artworkFromApiArtwork(objectGraph, artworkData, { + withJoeColorPlaceholder: true, + useCase: 15 /* ArtworkUseCase.TodayCardMedia */, + }); + if (cropCode) { + artwork.crop = cropCode; + } + else if (objectGraph.client.isMac || objectGraph.client.isTV) { + // Articles: Use new crossover crop code for macOS story card art + artwork.crop = "fn"; + } + else { + artwork.crop = "sr"; + } + return artwork; +} +/** + * Returns a branded title `Artwork` model object from the API `editorialArtwork.contentLogoTrimmed` response. + * This artwork can be used in place of a text title for a Today Card. + * @param objectGraph The dependency graph for the App Store + * @param data The media API data to fetch the Artwork from + * @returns The branded title `Artwork` object, or `undefined` if a branded title could not be found + */ +export function brandedTitleArtworkForCard(objectGraph, data) { + const brandedTitleData = mediaAttributes.attributeAsDictionary(data, "editorialArtwork.contentLogoTrimmed"); + return artworkFromApiArtwork(objectGraph, brandedTitleData, { + contentMode: ArtworkContentMode.scaleAspectFit, + allowingTransparency: true, + useCase: 17 /* ArtworkUseCase.TodayCardBrandedTitle */, + }); +} +/** + * @param objectGraph The object graph for the app store + * @param data The media api data for the card + * @param cardConfig The configuration for the card being built + * @returns The `bgGradientKind` field from the correct artwork dictionary + */ +export function todayCardArtworkTitleBackingGradientForKey(objectGraph, data, cardConfig) { + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle); + const artworkKey = editorialArtKeyPathForCardDisplayStyle(objectGraph, cardDisplayStyle); + const artworkData = mediaAttributes.attributeAsDictionary(data, artworkKey); + if (!isDefinedNonNull(artworkData)) { + return null; + } + return asString(artworkData, "bgGradientKind"); +} +/** + * @param objectGraph The object graph for the app store + * @param artworkData The artwork data from the media api data for a card, that allows us to determine the style + * @returns The light or dark style for this card, if we can determine it. + */ +export function todayCardStyleFromArtwork(objectGraph, artworkData) { + var _a; + if (isNullOrEmpty(artworkData)) { + return undefined; + } + const joeColors = joeColorSetFromData(artworkData); + const backgroundColor = joeColors.bgColor; + const hasGradient = ((_a = joeColors.textGradient) === null || _a === void 0 ? void 0 : _a.length) === 2; + if (!backgroundColor && !hasGradient) { + return undefined; + } + if (objectGraph.client.isiOS || objectGraph.client.isWeb) { + return cardStyleFromJoeColors(joeColors, "bgColor"); + } + else if (hasGradient) { + // Gradient colors are the text color, so if the gradient is a dark color, then + // the environment is a light environment + return color.isDarkColor(joeColors.textGradient[0]) ? "light" : "dark"; + } + else { + return color.isDarkColor(backgroundColor) ? "dark" : "light"; + } +} +export function cardStyleFromJoeColorsWithoutFallback(joeColors, preferredColorKeypath = "bgColor") { + if (isNothing(joeColors)) { + return undefined; + } + if (isSome(joeColors === null || joeColors === void 0 ? void 0 : joeColors.textGradient) && joeColors.textGradient.length === 2) { + // Rare, but if gradient colors are present, defer to them + return color.isDarkColor(joeColors.textGradient[0]) ? "white" : "dark"; + } + const preferredColor = joeColors[preferredColorKeypath]; + if (isNothing(preferredColor)) { + return undefined; + } + const luminance = color.luminanceFrom(preferredColor); + if (luminance <= 0.1) { + return "dark"; + } + else if (luminance >= 0.84) { + return "white"; + } + else { + return "light"; + } +} +export function cardStyleFromJoeColors(joeColors, preferredColorKeypath = "bgColor") { + var _a; + return (_a = cardStyleFromJoeColorsWithoutFallback(joeColors, preferredColorKeypath)) !== null && _a !== void 0 ? _a : "light"; +} +// MARK: - Editorial Text +/** + * Determines the heading for the card, were it to appear inline. + * If the source card heading is null, we default to the existing + * inline heading, if any. + * @param objectGraph The object graph for the app store + * @param card The today card we're collapsing the heading for + * @returns string The title to use for the card. + */ +export function collapsedHeadingForTodayCard(objectGraph, card) { + if (isDefinedNonNull(card.heading)) { + return card.heading.replace(/\n/g, " "); + } + return card.collapsedHeading; +} +// MARK: - Offers +/** + * Determines an appropriate OfferStyle for a TodayCard to ensure correct + * contrast with the card's contents. + * + * @param cardStyle The today style for the card + * @returns an appropriate offer style to use for the button + */ +export function offerStyleForTodayCard(objectGraph, cardStyle) { + return "transparent"; +} +/** + * Determines an appropriate OfferEnvironment for a TodayCard to ensure correct + * contrast with the card's contents. + * + * @param cardStyle The today style for the card + * @returns an appropriate offer environment to use for the button + */ +export function offerEnvironmentForTodayCard(cardStyle) { + if (cardStyle === "white") { + return "light"; + } + else { + return "todayCard"; + } +} +// MARK: - Hero Media +/** + * For mac and tv platforms, we add hero media to the today cards. + * + * @param objectGraph The dependency graph for the app store + * @param card The today card we're adding hero media to + * @param cardConfig The config used to create this card + * @param data The media api data used to create this card + */ +export function addHeroMediaToTodayCardIfNecessary(objectGraph, card, cardConfig, data) { + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle); + // MacOS and tvOS: Hero + const heroArt = todayCardHeroArtForData(objectGraph, data, cardConfig.heroDisplayContext, cardDisplayStyle, cardConfig.prevailingCropCodes); + let artworks = []; + let videos = []; + if (isDefinedNonNull(heroArt)) { + artworks = [heroArt]; + } + if (isDefinedNonNull(card.media) && + (card.media instanceof TodayCardMediaVideo || card.media instanceof TodayCardMediaArtwork)) { + videos = card.media.videos; + } + const hero = new TodayCardMediaHero(artworks, videos); + const supportsHeroMedia = objectGraph.client.isMac || + objectGraph.client.isTV || + objectGraph.client.isWeb || + (objectGraph.client.isVision && !cardConfig.isSearchContext) || + preprocessor.GAMES_TARGET; + if (supportsHeroMedia && hero.isValid()) { + card.heroMedia = hero; + // Ensure we update the card styling. We prefer hero art, and fallback to hero video. + const heroArtworkData = mediaAttributes.attributeAsDictionary(data, heroMediaArtworkKeyForContext(objectGraph, cardConfig.heroDisplayContext, cardDisplayStyle, Object.keys(mediaAttributes.attributeAsDictionary(data, "editorialArtwork")))); + let style = todayCardStyleFromArtwork(objectGraph, heroArtworkData); + if (!isDefinedNonNull(style)) { + const heroVideoPreviewData = asDictionary(todayCardHeroVideoFromData(objectGraph, data), "previewFrame"); + style = todayCardStyleFromArtwork(objectGraph, heroVideoPreviewData); + } + card.style = style; + } +} +/** + * For external links we will display the standard link overlay in place of whatever overlay we had previously + * + * @param card The today card we're adding hero media to + */ +export function addExternalLinkOverlayToTodayCardIfNecessary(card) { + if (card.clickAction instanceof ExternalUrlAction) { + card.overlay = new TodayCardActionOverlay(card.clickAction); + card.style = "white"; + } +} +/** + * @param objectGraph The dependency graph for the app store + * @param data The media api data to search for hero art in + * @param context The context in which this hero media will be displayed + * @param displayStyle The display style of the card + * @param prevailingCropCodes The prevailing crop code to use for inline hero media + * @returns The artwork to be used in the hero position for a today card + */ +export function todayCardHeroArtForData(objectGraph, data, context, displayStyle, prevailingCropCodes) { + const heroArtworkKey = heroMediaArtworkKeyForContext(objectGraph, context, displayStyle, Object.keys(mediaAttributes.attributeAsDictionary(data, "editorialArtwork"))); + const heroArtworkData = mediaAttributes.attributeAsDictionary(data, heroArtworkKey); + return todayCardArtworkFromArtworkData(objectGraph, heroArtworkData, heroMediaArtworkCropForContext(objectGraph, context, objectPathToString(heroArtworkKey), prevailingCropCodes)); +} +/** + * @param objectGraph The dependency graph for the app store + * @param context The context in which this hero media will be displayed + * @param displayStyle The display style of the card + * @returns The key to use to find the hero media artwork + */ +function heroMediaArtworkKeyForContext(objectGraph, context, displayStyle, availableArtworkKeys) { + if (context === "article" && (objectGraph.client.isMac || objectGraph.client.isiOS)) { + // Use "iOS" art + return editorialArtKeyPathForCardDisplayStyle(objectGraph, displayStyle); + } + else if (objectGraph.client.isVision) { + return "editorialArtwork.heroStatic16x9"; + } + else { + if (objectGraph.client.isTV && availableArtworkKeys.includes("categoryDetailStatic16x9")) { + return "editorialArtwork.categoryDetailStatic16x9"; + } + return "editorialArtwork.crossoverCard"; + } +} +/** + * @param objectGraph The dependency graph for the app store + * @param context The context in which this hero media will be displayed + * @param prevailingCropCodes The crop code to use if we're not overriding it + * @returns The crop code to use for hero artwork in a given context + */ +function heroMediaArtworkCropForContext(objectGraph, context, heroArtworkKey, prevailingCropCodes) { + if (context === HeroMediaDisplayContext.Article && objectGraph.client.isMac) { + // Use "iOS" art crop code + return "fn"; + } + else if (context === HeroMediaDisplayContext.Article && + objectGraph.client.isTV && + heroArtworkKey === "editorialArtwork.categoryDetailStatic16x9") { + return "sr"; + } + else { + return prevailingCropCodes === null || prevailingCropCodes === void 0 ? void 0 : prevailingCropCodes.defaultCrop; + } +} +/** + * @param objectGraph The dependency graph for the app store + * @param data The media api data to search for hero video in + * @returns The video to be used in the hero position for a today card + */ +function todayCardHeroVideoFromData(objectGraph, data) { + let videoData; + const videoData4x3 = mediaAttributes.attributeAsDictionary(data, "editorialVideo.storeFrontVideo4x3"); + const videoData16x9 = mediaAttributes.attributeAsDictionary(data, "editorialVideo.storeFrontVideo"); + // Today: Video Card: Incorrect key for 4x3 videos + // On pad prefer 4x3 fallback to 16x9 and on phone vice versa + if (objectGraph.client.isPad || objectGraph.client.screenSize.isEqualTo(screenSizeIPhone134)) { + videoData = videoData4x3 || videoData16x9; + } + else { + videoData = videoData16x9 || videoData4x3; + } + return videoData; +} +// MARK: - Crosslinks +/** + * Determines the cross link subtitle from card data. + */ +export function crossLinkSubtitleFromData(objectGraph, data) { + // Start with short notes + let subtitle = editorialNotesFromData(objectGraph, data, "short"); + // Fallback to name + if (!isDefinedNonNullNonEmpty(subtitle)) { + subtitle = notesFromData(objectGraph, data, "name"); + } + // Fallback to label + if (!isDefinedNonNullNonEmpty(subtitle)) { + const cardDisplayStyle = mediaAttributes.attributeAsString(data, "displayStyle"); + if (cardDisplayStyle === TodayCardDisplayStyle.AppOfTheDay || + cardDisplayStyle === TodayCardDisplayStyle.GameOfTheDay) { + subtitle = mediaAttributes.attributeAsString(data, "label"); + } + } + return subtitle; +} +// MARK: - Client Identifier Overrids +function shouldRespectClientIdentifierOverride(objectGraph, data, clientIdentifier, cardConfig) { + var _a; + if (clientIdentifier === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */ || clientIdentifier === "VisionAppStore" /* ClientIdentifier.VisionAppStore */) { + // If using the companion app, it's expected that we always respect the given identifier. + return true; + } + const relatedContent = relatedCardContentsContentsFromData(objectGraph, data); + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle); + // Do not apply the override if this is not a Content type card. + const cardTypesRequiringClientIdentifierOverride = new Set([ + TodayCardDisplayStyle.AppEventCard, + TodayCardDisplayStyle.Grid, + TodayCardDisplayStyle.InAppPurchase, + TodayCardDisplayStyle.List, + TodayCardDisplayStyle.NumberedList, + TodayCardDisplayStyle.River, + TodayCardDisplayStyle.ShortImage, + TodayCardDisplayStyle.SingleApp, + ]); + if (!cardTypesRequiringClientIdentifierOverride.has(cardDisplayStyle)) { + return false; + } + // Grids with >= 6 items are unsupported + if (relatedContent.length >= 6 && cardDisplayStyle === TodayCardDisplayStyle.Grid) { + return false; + } + // Content cards with Artwork are unsupported + const artworkKey = editorialArtKeyPathForCardDisplayStyle(objectGraph, cardDisplayStyle); + if (!mediaAttributes.attributeAsBooleanOrFalse(data, "ignoreEditorialArt") && + todayCardArtworkFromArtworkData(objectGraph, mediaAttributes.attributeAsDictionary(data, artworkKey), (_a = cardConfig.prevailingCropCodes) === null || _a === void 0 ? void 0 : _a.defaultCrop)) { + return false; + } + // Content cards with iAP are unsupported + if (inAppPurchaseDataFromRelatedContent(objectGraph, relatedContent)) { + return false; + } + return true; +} +function inAppPurchaseDataFromRelatedContent(objectGraph, relatedContent) { + if (relatedContent.length === 1) { + const contentData = relatedContent[0]; + if (contentData.type === "in-apps") { + return contentData; + } + } + return null; +} +/** + * If a card should be using the OTD style we need to make sure we set that on the card + * + * @param card The today card we're adding the style to + * @param cardConfig The config used to create this card + */ +function addOTDStyleToCardIfNecessary(card, cardConfig) { + if (isNothing(card.media)) { + return; + } + card.media.otdTextStyle = cardConfig.useOTDTextStyle; +} +/** + * Check if this card should have the flip and blur enabled, this is the case when a card has videos. + * @param objectGraph The dependency graph for the app store + * @param card The today card we're modifying + */ +function enableFlipAndBlurIfNecessary(objectGraph, card) { + const cardMedia = card.media; + const cardMediaHasArtwork = cardMedia instanceof TodayCardMediaWithArtwork; + if (!cardMediaHasArtwork) { + return; + } + const cardMdiaWithArtwork = cardMedia; + const hasVideos = isDefinedNonNullNonEmpty(cardMdiaWithArtwork.videos); + card.supportsMediaMirroring = hasVideos; +} +/** + * @param data The media API data used to determine the intention of the card + * @param cardConfig The configuration for the card + * @returns Whether this app is an App of the Day or Game of the Day + */ +export function isCardOTDIntention(data, cardConfig) { + let otdIntention = mediaAttributes.attributeAsString(data, "ofTheDayIntent"); + if (isNothing(otdIntention)) { + const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.coercedCollectionTodayCardDisplayStyle); + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.AppOfTheDay: + otdIntention = OfTheDayIntention.AppOfTheDay; + break; + case TodayCardDisplayStyle.GameOfTheDay: + otdIntention = OfTheDayIntention.GameOfTheDay; + break; + default: + break; + } + } + return otdIntention === OfTheDayIntention.AppOfTheDay || otdIntention === OfTheDayIntention.GameOfTheDay; +} +//# sourceMappingURL=today-card-util.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js new file mode 100644 index 0000000..98f9fbc --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js @@ -0,0 +1,534 @@ +import { isNothing, isSome } from "@jet/environment"; +import * as validation from "@jet/environment/json/validation"; +import { FetchTimingMetricsBuilder } from "@jet/environment/metrics"; +import * as models from "../../api/models"; +import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion"; +import { asArrayOrEmpty, asBoolean, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNullOrEmpty, } from "../../foundation/json-parsing/server-data"; +import { hasAttributes } from "../../foundation/media/attributes"; +import * as mediaDataFetching from "../../foundation/media/data-fetching"; +import * as mediaDataStructure from "../../foundation/media/data-structure"; +import { fetchData } from "../../foundation/media/network"; +import { Parameters, Path } from "../../foundation/network/url-constants"; +import { URL } from "../../foundation/network/urls"; +import { optional, required } from "../../foundation/util/promise-util"; +import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types"; +import { applySearchAdMissedOpportunityToShelvesIfNeeded, eligibleSlotPositionsForAdPlacement, iadInfoFromOnDeviceAdResponse, isAdPlacementEnabled, } from "../ads/ad-common"; +import * as adIncidents from "../ads/ad-incident-recorder"; +import { fetchAds, parallelOrganicRequestDidFinish } from "../ads/on-device-ad-fetch"; +import * as appPromotionsCommon from "../app-promotions/app-promotions-common"; +import { fetchPageWithAdditionalPageRequirements } from "../builders/additional-page-requirement-util"; +import { pageRouter } from "../builders/routing"; +import { shouldUsePrerenderedIconArtwork } from "../content/content"; +import { shelfForFooterButtons, shelfForTermsAndConditions, shelfForUnifiedMessage } from "../grouping/grouping-common"; +import { newLocationTracker } from "../metrics/helpers/location"; +import { iAdInformationFromMediaApiResponse } from "../metrics/helpers/models"; +import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../metrics/helpers/page"; +import { combinedRecoMetricsDataFromMetricsData } from "../metrics/helpers/util"; +import * as onDevicePersonalization from "../personalization/on-device-personalization"; +import { fetchTodayRecommendationsWithTimeout, isTodayTabArcadePersonalizationAvailable, } from "../personalization/on-device-recommendations-today"; +import { setPreviewPlatform } from "../preview-platform"; +import * as productPageVariants from "../product-page/product-page-variants"; +import { newPageRefreshControllerFromResponse } from "../refresh/page-refresh-controller"; +import { storeCohortIdForUserFromResponse } from "../search/landing/search-landing-cohort"; +import { FlattenedTodayItemType, createAdShelfForTodayPageContextIfNecessary, feedPreviewUrlFromFlattenedTodayItems, flattenTodayModules, nextFlattenedItemIsHydrated, todayShelfForEditorialItem, todayShelfForEditorialItemGroup, } from "./today-parse-util"; +import { TodayPageContext } from "./today-types"; +/** + * Keys used to manage additional data needed to render the today page. + * + * - onboardingCards: key used to fetch the onboarding cards for the feed. + * + * - ads: Key used for fetching the today placement ads + */ +export var TodayControllerAdditionalDataKey; +(function (TodayControllerAdditionalDataKey) { + TodayControllerAdditionalDataKey["OnboardingCards"] = "onboardingCards"; + TodayControllerAdditionalDataKey["Ads"] = "ads"; + TodayControllerAdditionalDataKey["ODP"] = "ODP"; + TodayControllerAdditionalDataKey["AMDData"] = "amdData"; +})(TodayControllerAdditionalDataKey || (TodayControllerAdditionalDataKey = {})); +export function defaultPlatforms(objectGraph) { + return mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph); +} +/// Resource attributes to include for `editorial-items` fetched for displaying in today page. +export function defaultAttributes(objectGraph) { + const attributes = [ + "editorialArtwork", + "editorialVideo", + "enrichedEditorialNotes", + "minimumOSVersion", + "headerName", + "headerBadge", + "headerTagline", + "editorialClientParams", + "requiredCapabilities", + "shortEditorialNotes", + ]; + if (objectGraph.bag.enableUpdatedAgeRatings) { + attributes.push("ageRating"); + } + if (shouldUsePrerenderedIconArtwork(objectGraph)) { + attributes.push("iconArtwork"); + } + return attributes; +} +export function prepareRequest(objectGraph, request, isPageRequest) { + var _a, _b; + request + .includingAgeRestrictions() + .includingAdditionalPlatforms(defaultPlatforms(objectGraph)) + .includingAttributes(defaultAttributes(objectGraph)) + .includingScopedAttributes("editorial-item-groups", ["editorialClientParams"]) + .includingScopedRelationships("editorial-items", ["primary-content", "header-contents"]) + .includingScopedRelationships("editorial-item-groups", ["header-contents"]) + .enablingFeature("editorialItemGroups") + .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)) + .includingRelationshipsForUpsell(true); + if (isPageRequest) { + request.includingAssociateKeys("editorial-item-groups", ["recommendations", "editorial-cards"]); + request.includingAssociateKeys("editorial-items", ["editorial-cards"]); + } + if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) { + request.enablingFeature("appEvents"); + request.addingQuery("meta", "personalizationData"); + request.includingScopedRelationships("app-events", ["app"]); + request.includingScopedAttributes("app-events", AppEventsAttributes); + } + if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) { + request.enablingFeature("offerItems"); + if (isPageRequest) { + request.includingKindsKeys("offer-items", ["winback"]); + } + } + if (impressionDemotion.isImpressionDemotionAvailable(objectGraph)) { + request.enablingFeature("eiGroupEISelectionOnDevice"); + } + if (isAdPlacementEnabled(objectGraph, "today")) { + request.enablingFeature("adSupport"); + } + request.enablingFeature("heroStyles"); + const displayDeviceDrivenContent = (_b = (_a = objectGraph.userDefaults) === null || _a === void 0 ? void 0 : _a.bool("displayDeviceDrivenContent")) !== null && _b !== void 0 ? _b : false; + if (objectGraph.bag.enableDeviceDrivenDiscoveryContent && + displayDeviceDrivenContent && + request.resourceType === "today") { + request.addingQuery("pairedDevices", "visionPro"); + } +} +// MARK: - Page Fetching +export function generateTodayPageRequest(objectGraph) { + // Note: The sparse limit is now 2 because story groups will include all of their content which means + // if we continue to use the original 6 count we'll end up with way too many EI's + // rdar://108635958 ([Network Launch | 283ms | 19%] App Store - Dawn - D33: 21A236 vs 20E245 network-warm-launch: + // extendedLaunchTime is 1731.0ms vs 1448.0ms) + const sparseCount = objectGraph.client.isWeb ? 40 : 1; + const sparseLimit = objectGraph.client.isWeb ? 40 : 2; + const mediaApiRequest = new mediaDataFetching.Request(objectGraph) + .forType("today") + .withSparseCount(sparseCount) + .withSparseLimit(sparseLimit); + return setPreviewPlatform(objectGraph, mediaApiRequest); +} +/** + * @param objectGraph The object graph for the AppStore. + * @param onboardingCardIds The onboardingCard ids to fetch. + * @returns The map of additional page requirements. + */ +function generateAdditionalPageRequirements(objectGraph, onboardingCardIds) { + const additionalRequirements = {}; + /** + * Currently, to avoid double filtering, the fix for: + * If an onboarding card is empty don't add it to the request + * depends on the `Request` object filtering IDs from under caller. The caller is expected to perform some validation to + * guarantee that this `Request` object is not malformed after it was processed here, and it can still build a valid URL. + * + * This mechanic is undesired, and once this filtering is removed from `Request`, the caller should filter empty onboarding + * IDs via an explicit method instead before instantiating an malformed `Request`. + */ + if (isDefinedNonNullNonEmpty(onboardingCardIds)) { + const onboardingCardsRequest = new mediaDataFetching.Request(objectGraph) + .withIdsOfType(onboardingCardIds, "editorial-items") + .includingAdditionalPlatforms(defaultPlatforms(objectGraph)) + .includingAttributes(defaultAttributes(objectGraph)); + additionalRequirements[TodayControllerAdditionalDataKey.OnboardingCards] = optional(fetchData(objectGraph, onboardingCardsRequest, {})); + } + if (isAdPlacementEnabled(objectGraph, "today")) { + additionalRequirements[TodayControllerAdditionalDataKey.Ads] = optional(fetchAds(objectGraph, "today")); + } + if (isTodayTabArcadePersonalizationAvailable(objectGraph)) { + additionalRequirements[TodayControllerAdditionalDataKey.ODP] = optional(fetchTodayRecommendationsWithTimeout(objectGraph, undefined)); + } + const amsEngagement = objectGraph.amsEngagement; + if (amsEngagement && impressionDemotion.isImpressionDemotionAvailable(objectGraph)) { + const request = { + timeout: 1000, + eventType: impressionDemotion.AMSEngagementAppStoreEventKey, + tab: "today", + }; + additionalRequirements[TodayControllerAdditionalDataKey.AMDData] = optional(amsEngagement.performRequest(request)); + } + return additionalRequirements; +} +export async function fetchTodayPage(objectGraph, options) { + const router = objectGraph.required(pageRouter); + const isTodayTestPage = options.url === "x-as3-internal:/today/test"; + const isTodayCardPreviewPage = options.url.indexOf(Path.todayCardPreview) !== -1; + if (isTodayTestPage) { + return await router.fetchPage(objectGraph, options.url, models.TodayPage); + } + else if (isTodayCardPreviewPage) { + const dataFetchUrl = URL.from(options.url); + dataFetchUrl.param(Parameters.fetchData, "true"); + return await router.fetchPage(objectGraph, dataFetchUrl.build(), models.TodayPage); + } + else { + const request = generateTodayPageRequest(objectGraph); + prepareRequest(objectGraph, request, true); + const requestHeaders = isSome(options.experimentIdHeader) + ? { "X-Apple-User-Experiment-Ids": options.experimentIdHeader } + : undefined; + return await buildTodayPageFromRequest(objectGraph, request, requestHeaders, options); + } +} +export async function buildTodayPageFromRequest(objectGraph, request, requestHeaders, options) { + var _a; + const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder(); + const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder); + const primaryDataFetchPromise = fetchData(modifiedObjectGraph, request, { + headers: requestHeaders !== null && requestHeaders !== void 0 ? requestHeaders : undefined, + }); + // Should this be needed on web, this should have a .catch() added to avoid + // an unhandled rejection. + if (!objectGraph.client.isWeb) { + void primaryDataFetchPromise.then((response) => { + if (isAdPlacementEnabled(objectGraph, "today")) { + parallelOrganicRequestDidFinish(objectGraph, "today"); + } + storeCohortIdForUserFromResponse(objectGraph, objectGraph.user.dsid, response); + }); + } + const primaryDataFetchInput = required(primaryDataFetchPromise); + const additionalRequirements = generateAdditionalPageRequirements(objectGraph, (_a = options === null || options === void 0 ? void 0 : options.onboardingCardIds) !== null && _a !== void 0 ? _a : []); + const pageDataRequirements = await fetchPageWithAdditionalPageRequirements(objectGraph, primaryDataFetchInput, additionalRequirements); + return fetchTimingMetricsBuilder.measureModelConstruction(() => { + var _a, _b; + const onboardingCardsResponse = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.OnboardingCards]; + const onboardingCards = mediaDataStructure.dataCollectionFromDataContainer(onboardingCardsResponse); + const adsResponse = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.Ads]; + const todayRecommendations = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.ODP]; + const amdData = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.AMDData]; + const flattenedTodayItems = flattenTodayModules(objectGraph, (_b = (_a = pageDataRequirements.primaryPageData.results) === null || _a === void 0 ? void 0 : _a.data) !== null && _b !== void 0 ? _b : [], todayRecommendations, onboardingCards, adsResponse); + return buildTodayPage(modifiedObjectGraph, flattenedTodayItems, pageDataRequirements.primaryPageData, adsResponse, amdData); + }); +} +// MARK: - Today Page Creation +/** + * @param objectGraph The dependency graph for the App Store + * @param flattenedTodayItems The flattened Today items used to render the page + * @param primaryPageData The primary page data for the Today page + * @param adsResponse The response from ad platforms + * @returns A TodayPage model + */ +export function buildTodayPage(objectGraph, flattenedTodayItems, primaryPageData, adsResponse, amdResponse) { + return validation.context("renderPage", () => { + if (isNullOrEmpty(flattenedTodayItems)) { + return null; + } + const pageContext = createTodayPageContext(objectGraph, flattenedTodayItems, primaryPageData, adsResponse, amdResponse); + const todayPage = todayPageFromPageContext(objectGraph, pageContext); + todayPage.shelves.splice(0, 0, shelfForUnifiedMessage(objectGraph, "todayPageHeader")); + return todayPage; + }); +} +/** + * From the provided today page context, create a new today page with the hydrated content + * @param objectGraph The dependency graph for the App Store + * @param pageContext The page context for the Today page + */ +export function todayPageFromPageContext(objectGraph, pageContext) { + var _a; + const shelves = []; + const feedPreviewUrl = feedPreviewUrlFromFlattenedTodayItems(objectGraph, pageContext.remainingContent); + let hasMoreHydratedContent = nextFlattenedItemIsHydrated(pageContext.remainingContent); + const isPageLoadNotPaginated = (_a = pageContext.remainingContent[0]) === null || _a === void 0 ? void 0 : _a.isFirstItemInModule; + while (hasMoreHydratedContent) { + const todayItem = pageContext.remainingContent.shift(); + if (isNothing(todayItem)) { + hasMoreHydratedContent = false; + break; + } + const preItemAdShelf = createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext); + if (isSome(preItemAdShelf)) { + shelves.push(preItemAdShelf); + } + pageContext.currentTodayItem = todayItem; + let todayItemShelf = null; + switch (todayItem.type) { + case FlattenedTodayItemType.EditorialItem: + todayItemShelf = todayShelfForEditorialItem(objectGraph, todayItem, pageContext); + break; + case FlattenedTodayItemType.EditorialItemGroup: + todayItemShelf = todayShelfForEditorialItemGroup(objectGraph, todayItem, pageContext); + break; + default: + break; + } + pageContext.currentTodayItem = undefined; + if (isSome(todayItemShelf)) { + shelves.push(todayItemShelf); + } + const postItemAdShelf = createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext); + if (isSome(postItemAdShelf)) { + shelves.push(postItemAdShelf); + } + hasMoreHydratedContent = nextFlattenedItemIsHydrated(pageContext.remainingContent); + } + if (isPageLoadNotPaginated) { + applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, shelves, "today", "today", pageContext.pageInformation); + } + const todayPage = new models.TodayPage(shelves); + const todayTab = objectGraph.bag.tabsStandard.find((tab) => tab.id === "today"); + const todayTabTitle = asString(todayTab, "title"); + const tabImageIdentifier = asString(todayTab, "image-identifier"); + const includeTitleDetail = isNothing(tabImageIdentifier) || tabImageIdentifier === "doc.text.image"; + const todayTitle = todayTabTitle !== null && todayTabTitle !== void 0 ? todayTabTitle : objectGraph.loc.string("PAGE_TITLE_TODAY"); + todayPage.title = todayTitle; + todayPage.tabTitle = todayTitle; + todayPage.titleDetail = includeTitleDetail ? todayPageTitleDetail(objectGraph) : undefined; + todayPage.shortTitleDetail = includeTitleDetail ? todayPageShortTitleDetail(objectGraph) : undefined; + todayPage.longTitle = includeTitleDetail ? todayPageLongTitle(objectGraph) : undefined; + todayPage.feedPreviewUrl = feedPreviewUrl; + if (isDefinedNonNullNonEmpty(pageContext.remainingContent)) { + pageContext.pageHasDisplayedContent = + pageContext.pageHasDisplayedContent || + shelves.some((shelf) => { + isDefinedNonNullNonEmpty(shelf.items); + }); + todayPage.nextPage = pageContext; + } + else if (!objectGraph.client.isWeb) { + footerShelvesForTodayPage(objectGraph, pageContext.pageInformation, pageContext.locationTracker).forEach((shelf) => { + todayPage.shelves.push(shelf); + }); + } + if (isAdPlacementEnabled(objectGraph, "today")) { + // Ad Incidents + todayPage.adIncidents = adIncidents.recordedIncidents(objectGraph, pageContext.adIncidentRecorder); + } + addMetricsEventsToPageWithInformation(objectGraph, todayPage, pageContext.pageInformation); + return todayPage; +} +/** + * Fetch the next batch of content for the Today page, and hydrate the page context + * @param objectGraph The dependency graph for the App Store + * @param pageContext The page context for the Today page + * @returns + */ +export async function hydrateNextBatchOfContent(objectGraph, pageContext) { + const batchLoadSize = pageContext.pageHasDisplayedContent ? 6 : 12; + let continueAddingContent = true; + let unhydratedItemIndex = 0; + let unhydratedContents = []; + while (continueAddingContent) { + const nextContent = pageContext.remainingContent[unhydratedItemIndex]; + switch (nextContent.type) { + case FlattenedTodayItemType.EditorialItem: + unhydratedContents.push(nextContent.data); + break; + case FlattenedTodayItemType.EditorialItemGroup: + unhydratedContents.push(nextContent.data); + const groupItems = asArrayOrEmpty(nextContent.data, "meta.associations.recommendations.data"); + unhydratedContents = [...unhydratedContents, ...groupItems]; + break; + default: + break; + } + unhydratedItemIndex++; + continueAddingContent = + unhydratedContents.length < batchLoadSize && unhydratedItemIndex < pageContext.remainingContent.length; + } + // MAPI request for module slice's contents + const requestedContentIds = new Set(unhydratedContents.map((content) => content.id)); + const contentsFetchRequest = new mediaDataFetching.Request(objectGraph, unhydratedContents, true, [ + "recommendations", + "editorial-cards", + ]); + prepareRequest(objectGraph, contentsFetchRequest, false); + const fetchedData = await fetchData(objectGraph, contentsFetchRequest, { allowEmptyDataResponse: true }); + const fetchedDataMap = {}; + for (const data of fetchedData.data) { + fetchedDataMap[data.id] = data; + } + // Hydrate the page context with the fetched data + for (const content of pageContext.remainingContent) { + hydrateFlattenedItemWithFetchedDataMap(content, fetchedDataMap); + } + /// Now take a pass and remove any items that are still unhydrated after performing the fetch + pageContext.remainingContent = pageContext.remainingContent.filter((content) => { + return content.isDataHydrated || !requestedContentIds.has(content.data.id); + }); + return pageContext; +} +/** + * For a given today item, take the fetched MAPI data and apply that to the item. + * + * @param flattenedItem The today item we're going to be hydrating + * @param fetchedDataMap The mapping from id to fetched media api data. + */ +function hydrateFlattenedItemWithFetchedDataMap(flattenedItem, fetchedDataMap) { + var _a, _b, _c; + switch (flattenedItem.type) { + case FlattenedTodayItemType.EditorialItem: + if (isDefinedNonNullNonEmpty(fetchedDataMap[flattenedItem.data.id])) { + flattenedItem.data = { + ...flattenedItem.data, + ...fetchedDataMap[flattenedItem.data.id], + }; + } + flattenedItem.isDataHydrated = hasAttributes(flattenedItem.data); + break; + case FlattenedTodayItemType.EditorialItemGroup: + if (isDefinedNonNullNonEmpty(fetchedDataMap[flattenedItem.data.id])) { + flattenedItem.data = { + ...flattenedItem.data, + attributes: (_a = fetchedDataMap[flattenedItem.data.id]) === null || _a === void 0 ? void 0 : _a.attributes, + relationships: (_b = fetchedDataMap[flattenedItem.data.id]) === null || _b === void 0 ? void 0 : _b.relationships, + meta: { + ...flattenedItem.data.meta, + ...(_c = fetchedDataMap[flattenedItem.data.id]) === null || _c === void 0 ? void 0 : _c.meta, + }, + }; + } + flattenedItem.isDataHydrated = hasAttributes(flattenedItem.data); + break; + default: + break; + } +} +/** + * @param objectGraph The dependency graph for the App Store + * @param todayItems The list of all the items displayed on the today page. + * @param todayResponse The response from the today endpoint + * @param adResponse The response from the ads service + * @returns The context to use when parsing today cards + */ +function createTodayPageContext(objectGraph, todayItems, todayResponse, adsResponse, amdResponse) { + var _a, _b, _c; + const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Today", "today", todayResponse, null, (_a = iadInfoFromOnDeviceAdResponse(objectGraph, "today", adsResponse, todayItems)) !== null && _a !== void 0 ? _a : iAdInformationFromMediaApiResponse(objectGraph, "today", todayResponse, (_b = adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.onDeviceAd) === null || _b === void 0 ? void 0 : _b.positionInfo, todayItems)); + const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph); + pageInformation.recoMetricsData = combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData); + const context = new TodayPageContext(todayItems, pageInformation, newLocationTracker(), newPageRefreshControllerFromResponse(todayResponse), impressionDemotion.impressionEventsFromData(objectGraph, amdResponse)); + if (isAdPlacementEnabled(objectGraph, "today")) { + const eligibleAdPositions = eligibleSlotPositionsForAdPlacement(objectGraph, "today"); + if (isSome(eligibleAdPositions)) { + // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers. + context.eligibleAdLocations = eligibleAdPositions.map((position) => position.slot); + } + const adIncidentRecorder = adIncidents.newRecorder(objectGraph, pageInformation.iAdInfo); + adIncidents.recordAdResponseEventsIfNeeded(objectGraph, adIncidentRecorder, adsResponse); + context.adIncidentRecorder = adIncidentRecorder; + context.adPlacementBehavior = getAdPlacementBehaviorForPage(todayItems); + if (isNothing(adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.failureReason)) { + context.adData = mediaDataStructure.dataFromDataContainer(objectGraph, adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.mediaResponse); + const rawPositionInfo = (_c = adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.onDeviceAd) === null || _c === void 0 ? void 0 : _c.positionInfo; + if (isDefinedNonNullNonEmpty(rawPositionInfo)) { + // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers. + const adjustedAdLocation = rawPositionInfo.slot - 1; + context.adLocation = adjustedAdLocation; + } + } + } + return context; +} +function getAdPlacementBehaviorForPage(todayItems) { + let adPlacementBehavior = models.AdPlacementBehavior.insertIntoShelf; + for (const todayItem of todayItems) { + let todayItemAdPlacementBehavior = adPlacementBehavior; + switch (todayItem.type) { + case FlattenedTodayItemType.EditorialItem: + todayItemAdPlacementBehavior = getAdPlacementBehaviorForData(todayItem.data); + break; + case FlattenedTodayItemType.EditorialItemGroup: + const groupItems = asArrayOrEmpty(todayItem.data, "meta.associations.recommendations.data"); + if (isDefinedNonNullNonEmpty(groupItems)) { + for (const groupItem of groupItems) { + todayItemAdPlacementBehavior = getAdPlacementBehaviorForData(groupItem); + if (todayItemAdPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) { + break; + } + } + } + break; + default: + break; + } + if (todayItemAdPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) { + adPlacementBehavior = todayItemAdPlacementBehavior; + break; + } + } + return adPlacementBehavior; +} +function getAdPlacementBehaviorForData(data) { + const replaceIfAdPresent = asBoolean(data, "meta.personalizationData.replaceIfAdPresent"); + if (isDefinedNonNull(replaceIfAdPresent) && replaceIfAdPresent) { + return models.AdPlacementBehavior.replaceOrganic; + } + else if (isDefinedNonNull(replaceIfAdPresent) && !replaceIfAdPresent) { + return models.AdPlacementBehavior.dropAd; + } + else { + return models.AdPlacementBehavior.insertIntoShelf; + } +} +/** + * Return the first shelf title for the today page. This is used to generate the tab title. + * @param objectGraph The dependency graph of the app store + */ +function todayPageTitleDetail(objectGraph) { + const now = new Date(); + const dateFormat = objectGraph.loc.string("Today.ShelfTitle.DateFormat"); + return objectGraph.loc.formatDate(dateFormat, now); +} +/** + * Return the first shelf title for the today page on iPad. This is used to generate the tab title. + * @param objectGraph The dependency graph of the app store + */ +function todayPageLongTitle(objectGraph) { + if (!objectGraph.client.isPad) { + return undefined; + } + const now = new Date(); + const dateFormat = objectGraph.loc.string("Today.ShelfTitle.DateFormat.IPad"); + return objectGraph.loc.formatDate(dateFormat, now); +} +/** + * Return a short version of the first shelf title for the today page. This is used to generate the tab title, + * and is used where less horizontal space is available. + * @param objectGraph The dependency graph of the app store + */ +export function todayPageShortTitleDetail(objectGraph) { + const now = new Date(); + return objectGraph.loc.formatDate("MMM d", now); +} +/** + * Generate the last shelves of the today page + * @param objectGraph The dependency graph for the app + * @param pageInformation The page information for the today page + * @param locationTracker The Location tracker for the current today page + * @returns The shelves that appear tha the bottom of the today page + */ +function footerShelvesForTodayPage(objectGraph, pageInformation, locationTracker) { + const shelves = []; + // Add Footer Buttons + const footerButtonShelf = shelfForFooterButtons(objectGraph, pageInformation, locationTracker); + if (isDefinedNonNull(footerButtonShelf)) { + shelves.push(footerButtonShelf); + } + // Add T&C + const url = objectGraph.bag.termsAndConditionsURL; + if (isDefinedNonNull(url)) { + const termsAndConditionsShelf = shelfForTermsAndConditions(objectGraph, url); + shelves.push(termsAndConditionsShelf); + } + return shelves; +} +//# sourceMappingURL=today-controller-util.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js new file mode 100644 index 0000000..0a2c0cb --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js @@ -0,0 +1,352 @@ +import * as validation from "@jet/environment/json/validation"; +import * as models from "../../api/models"; +import * as serverData from "../../foundation/json-parsing/server-data"; +import { attributeAsBooleanOrFalse } from "../../foundation/media/attributes"; +import { openTVArcadeAppAction } from "../arcade/arcade-common"; +import { editorialNotesFromData } from "../content/content"; +import * as metricsHelpersLocation from "../metrics/helpers/location"; +import { cardDisplayStyleFromData, collapsedHeadingForTodayCard, defaultTodayCardConfiguration, todayCardFromData, } from "./today-card-util"; +import { TodayCardDisplayStyle, TodayCardMetricsDisplayStyle, TodayParseContext, } from "./today-types"; +const supportedSmallHorizontalCardKinds = new Set([ + "artwork", + "appIcon", + "grid", + "multiApp", + "video", +]); +const supportedMediumHorizontalCardKinds = new Set([ + "brandedSingleApp", + "artwork", + "appIcon", + "grid", + "multiApp", + "video", +]); +const supportedLargeHorizontalCardKinds = new Set([ + "brandedSingleApp", + "artwork", + "appIcon", + "grid", + "multiApp", + "video", +]); +const iOSSupportedSmallHorizontalCardKinds = new Set([ + "brandedSingleApp", + "artwork", + "grid", + "video", +]); +const webSupportedSmallHorizontalCardKinds = new Set([ + "brandedSingleApp", + "artwork", + "river", + "appIcon", + "video", +]); +// MARK: - Horizontal Cards +/** + * The crop code to use for a card using the provided horizontal card shelf content type. + * @param {ShelfContentType} contentType The content type of the shelf's items. + * @returns {CropCode} The crop code to use for the artwork. + */ +function horizontalCardCropCodeForContentType(objectGraph, contentType, cardDisplayStyle) { + switch (contentType) { + case "smallStoryCard": + if (objectGraph.host.isTV) { + return cardDisplayStyle === TodayCardDisplayStyle.Video ? null : { defaultCrop: "fo" }; + } + else if (objectGraph.host.isiOS) { + return null; + } + else if (objectGraph.client.isWeb) { + return { defaultCrop: "sr" }; + } + else { + return { defaultCrop: "em" }; + } + case "mediumStoryCard": + return { defaultCrop: "el" }; + case "largeStoryCard": + return cardDisplayStyle === TodayCardDisplayStyle.Video ? { defaultCrop: "sr" } : { defaultCrop: "ek" }; + default: + return null; + } +} +export function isHorizontalCardSupportedForKind(objectGraph, kind, contentType) { + switch (contentType) { + case "smallStoryCard": + if (objectGraph.host.isiOS) { + return iOSSupportedSmallHorizontalCardKinds.has(kind); + } + else if (objectGraph.client.isWeb) { + return webSupportedSmallHorizontalCardKinds.has(kind); + } + else { + return supportedSmallHorizontalCardKinds.has(kind); + } + case "mediumStoryCard": + return supportedMediumHorizontalCardKinds.has(kind); + case "largeStoryCard": + return supportedLargeHorizontalCardKinds.has(kind); + case "todayCard": + return objectGraph.client.isWatch; + default: + return false; + } +} +export function horizontalCardItemsFromCards(objectGraph, cards, contentType, context, contentUnavailable, horizontalShelfCardConfig) { + const items = []; + for (const cardData of cards) { + const todayCard = horizontalCardItemFromCard(objectGraph, cardData, contentType, context, contentUnavailable, horizontalShelfCardConfig); + if (serverData.isNullOrEmpty(todayCard)) { + continue; + } + items.push(todayCard); + metricsHelpersLocation.nextPosition(context.metricsLocationTracker); + } + return items; +} +export function horizontalCardItemFromCard(objectGraph, cardData, contentType, context, contentUnavailable, horizontalShelfCardConfig) { + const parseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker); + if (horizontalShelfCardConfig === null || horizontalShelfCardConfig === undefined) { + horizontalShelfCardConfig = defaultTodayCardConfiguration(objectGraph); + horizontalShelfCardConfig.isHorizontalShelfContext = true; + } + if (objectGraph.client.deviceType !== "watch") { + // All today cards are deserialized through this code path on watchOS. + // We support all substyles, so we need to not specify this override. + horizontalShelfCardConfig.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid; + } + const cardDisplayStyle = cardDisplayStyleFromData(cardData, horizontalShelfCardConfig.coercedCollectionTodayCardDisplayStyle); + horizontalShelfCardConfig.prevailingCropCodes = horizontalCardCropCodeForContentType(objectGraph, contentType, cardDisplayStyle); + horizontalShelfCardConfig.horizontalCardContentType = contentType; + if (!serverData.isDefinedNonNull(cardData.attributes)) { + if (contentUnavailable) { + contentUnavailable(cardData); + } + return null; + } + const todayCard = todayCardFromData(objectGraph, cardData, horizontalShelfCardConfig, parseContext, context.augmentingData); + // Ignore the Date here, as we don't care about using it as a refresh signal when embedded in a page other than Today for now. + if (serverData.isNull(todayCard)) { + return null; + } + if (objectGraph.client.isiOS) { + todayCard.collapsedHeading = collapsedHeadingForTodayCard(objectGraph, todayCard); + todayCard.inlineDescription = featuredInDescriptionForCard(objectGraph, todayCard, cardData); + // In certain fallback cases, todayCardFromData() will make title and heading the same. + // If our generated heading and description are the same, we just show the heading. + if (todayCard.inlineDescription === todayCard.collapsedHeading) { + todayCard.inlineDescription = null; + } + // We want to avoid displaying the branded title over AotD/GotD cards, but only when they are not using + // the fallback art. + const brandedMedia = todayCard.media; + if (brandedMedia && brandedMedia.kind === "brandedSingleApp") { + // LOC: GLOBAL: App Store on iOS: Word wrapping in App/Game of the... + // Branded text on inline today cards is kind of a mess visually, so rely on the inline + // description. + todayCard.title = null; + } + } + if (contentType === "largeStoryCard") { + const heroMedia = todayCard.heroMedia; + if (!serverData.isDefinedNonNull(heroMedia) || + (heroMedia.artworks.length === 0 && heroMedia.videos.length === 0)) { + return null; + } + } + if (isHorizontalCardSupportedForKind(objectGraph, todayCard.media.kind, contentType)) { + if (serverData.isDefinedNonNull(context.filterOutMediaCardKinds) && + context.filterOutMediaCardKinds.has(todayCard.media.kind)) { + return null; + } + } + // Override `todayCard.clickAction` for some special cards. + overrideClickActionForTVAcquisitionCardIfNeeded(objectGraph, todayCard, cardData); + return todayCard; +} +export function shelfForHorizontalCardItems(objectGraph, cards, contentType, title, subtitle, context, contentUnavailable) { + if (!serverData.isDefinedNonNull(contentType)) { + return null; + } + const shelf = new models.Shelf(contentType); + if (title) { + shelf.title = title; + } + shelf.subtitle = subtitle; + shelf.isHorizontal = true; + switch (contentType) { + case "todayBrick": // As iOS doesn't support horizontalCardItemsFromCards, we map the small/medium/large story cards to todayBricks instead. + shelf.items = [ + featuredInTodayCardsFromData(objectGraph, cards, context.metricsPageInformation, context.metricsLocationTracker, () => true, contentUnavailable), + ]; + break; + default: + shelf.items = horizontalCardItemsFromCards(objectGraph, cards, contentType, context, contentUnavailable); + break; + } + return shelf; +} +/** + * Creates a shelf for mini today cards. + * + * @param objectGraph - The application store object graph. + * @param cards - An array of media data structures. + * @param contentType - The type of content for the shelf. + * @param title - The title of the shelf. + * @param subtitle - The subtitle of the shelf. + * @param context - The context for parsing today data. + * @param contentUnavailable - Optional handler for unavailable content. + * @returns A shelf containing today cards. + */ +export function shelfForMiniTodayCards(objectGraph, cards, title, subtitle, context, contentUnavailable) { + const shelf = new models.Shelf("miniTodayCard"); + if (title) { + shelf.title = title; + } + shelf.subtitle = subtitle; + shelf.isHorizontal = true; + const items = []; + const cardConfig = defaultTodayCardConfiguration(objectGraph); + cardConfig.metricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard; + cardConfig.alwaysShowBadgeInSmallCards = true; + cardConfig.alwaysUseMaterialBlur = true; + for (const cardData of cards) { + if (!serverData.isDefinedNonNull(cardData.attributes)) { + if (contentUnavailable) { + contentUnavailable(cardData); + } + continue; + } + const todayCard = todayCardFromData(objectGraph, cardData, cardConfig, context); + if (serverData.isNullOrEmpty(todayCard)) { + continue; + } + items.push(todayCard); + metricsHelpersLocation.nextPosition(context.locationTracker); + } + shelf.items = items; + return shelf; +} +// MARK: - Featured In +/** + * Determines the description for the card, were it to appear inline. + * @param {TodayCard} card The card in question (for legacy support) + * @param {Data} data the card data to get the description from + * @returns {string} The description text to use for the card. + */ +function featuredInDescriptionForCard(objectGraph, card, data) { + const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb; + if (isSmallStoryCardsSupported) { + let editorialName = editorialNotesFromData(objectGraph, data, "name"); + if (serverData.isDefinedNonNull(editorialName)) { + editorialName = editorialName.replace(/\n/g, " "); + } + switch (card.media.kind) { + case "brandedSingleApp": + if (card.overlay instanceof models.TodayCardMarketingLockupOverlay && + serverData.isDefinedNonNull(card.overlay.lockup)) { + return card.overlay.lockup.title; + } + else { + return editorialName; + } + default: + return editorialName; + } + } + switch (card.media.kind) { + case "brandedSingleApp": + if (card.overlay instanceof models.TodayCardThreeLineOverlay) { + return card.overlay.heading; + } + else if (serverData.isDefinedNonNull(card.title)) { + return card.title.replace(/\n/g, " "); + } + return null; + default: + const unformattedDescription = objectGraph.loc.string("TODAY_BRICK_STANDARD_DESCRIPTION"); + let heading; + let title; + if (card.overlay instanceof models.TodayCardThreeLineOverlay) { + heading = card.overlay.heading; + title = card.overlay.title; + } + else { + heading = card.heading; + title = card.title; + } + if (!unformattedDescription || !heading || !title) { + return null; + } + return unformattedDescription.replace("{heading}", heading).replace("{title}", title); + } +} +export function featuredInTodayCardsFromData(objectGraph, dataArray, metricsPageInformation, metricsLocationTracker, isIncluded, contentUnavailable) { + return validation.context("todayCardsFromPlatformData", () => { + if (!dataArray) { + return null; + } + const featuredInCardConfig = defaultTodayCardConfiguration(objectGraph); + featuredInCardConfig.enableFullScreenVideo = false; + featuredInCardConfig.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid; + featuredInCardConfig.prevailingCropCodes = { defaultCrop: "fo" }; + featuredInCardConfig.isHorizontalShelfContext = true; + const context = new TodayParseContext(metricsPageInformation, metricsLocationTracker); + const todayCards = []; + for (const data of dataArray) { + if (!serverData.isDefinedNonNull(data.attributes)) { + if (contentUnavailable) { + contentUnavailable(data); + } + continue; + } + const todayCard = todayCardFromData(objectGraph, data, featuredInCardConfig, context); + // Ignore the Date here, as we don't care about using it as a refresh signal when embedded in a page other than Today for now. + if (serverData.isNull(todayCard)) { + continue; + } + if (isIncluded(todayCard)) { + todayCard.collapsedHeading = collapsedHeadingForTodayCard(objectGraph, todayCard); + todayCard.inlineDescription = featuredInDescriptionForCard(objectGraph, todayCard, data); + // In certain fallback cases, todayCardFromData() will make title and heading the same. + // If our generated heading and description are the same, we just show the heading. + const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb; + if (isSmallStoryCardsSupported && todayCard.inlineDescription === todayCard.collapsedHeading) { + todayCard.inlineDescription = null; + } + // We want to avoid displaying the branded title over AotD/GotD cards, but only when they are not using + // the fallback art. + const brandedMedia = todayCard.media; + if (brandedMedia && brandedMedia.kind === "brandedSingleApp") { + // LOC: GLOBAL: App Store on iOS: Word wrapping in App/Game of the... + // Branded text on inline today cards is kind of a mess visually, so rely on the inline + // description. + todayCard.title = null; + } + todayCards.push(todayCard); + } + } + if (!todayCards.length) { + return null; + } + return new models.InlineTodayCards(todayCards); + }); +} +// MARK: - tvOS Acquisition EI Card +/** + * On tvOS, some special editorial items are supposed to link directly into the Apple Arcade app. + * This is to override the click action if needed. + * @param card Today card to potentially modify. + * @param data Data that card was built from. + */ +function overrideClickActionForTVAcquisitionCardIfNeeded(objectGraph, card, data) { + if (objectGraph.client.deviceType !== "tv" || !attributeAsBooleanOrFalse(data, "isAcquisition")) { + return; // Only override on acquisition stories on tvOS. + } + const openArcadeAppAction = openTVArcadeAppAction(objectGraph); + card.clickAction = openArcadeAppAction; +} +// endregion +//# sourceMappingURL=today-horizontal-card-util.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js new file mode 100644 index 0000000..a1e27b0 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js @@ -0,0 +1,1047 @@ +import { isNothing, isSome } from "@jet/environment"; +import * as validation from "@jet/environment/json/validation"; +import * as models from "../../api/models"; +import { asArrayOrEmpty, asBoolean, asDictionary, asInterface, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, } from "../../foundation/json-parsing/server-data"; +import { editorialCardFromData } from "../../foundation/media/associations"; +import * as mediaAttributes from "../../foundation/media/attributes"; +import { attributeAsString, hasAttributes } from "../../foundation/media/attributes"; +import * as mediaDataStructure from "../../foundation/media/data-structure"; +import { relationshipData } from "../../foundation/media/relationships"; +import { Parameters, Path, Protocol } from "../../foundation/network/url-constants"; +import { URL } from "../../foundation/network/urls"; +import { isAdPlacementEnabled } from "../ads/ad-common"; +import { categoryArtworkData } from "../categories"; +import { artworkFromApiArtwork, iconFromData } from "../content/content"; +import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util"; +import { addImpressionFields } from "../metrics/helpers/impressions"; +import { nextPosition, popLocation, pushContentLocation } from "../metrics/helpers/location"; +import { combinedRecoMetricsDataFromMetricsData } from "../metrics/helpers/util"; +import * as onDevicePersonalization from "../personalization/on-device-personalization"; +import * as color from "./../../foundation/util/color-util"; +import { asNumber } from "@apple-media-services/media-api"; +import { createTodayAdCard } from "./cards/today-ad-card-builder"; +import { defaultTodayCardConfiguration, todayCardFromData } from "./today-card-util"; +import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion"; +import { EditorialBackgroundType, TodayCardMetricsDisplayStyle, TodayHeaderArtworkBehavior, TodaySectionHeaderArtworkPlacement, } from "./today-types"; +/** + * We try to flatten the today response so we can treat it as a single list + * of items. These are the different item types in the flattened list. + */ +export var FlattenedTodayItemType; +(function (FlattenedTodayItemType) { + FlattenedTodayItemType["EditorialItem"] = "editorialItem"; + FlattenedTodayItemType["EditorialItemGroup"] = "editorialItemGroup"; +})(FlattenedTodayItemType || (FlattenedTodayItemType = {})); +/** + * Flatten the entire modules array from the TodayPage response into a single list of items. + * @param objectGraph The dependency graph for the App Store + * @param todayModules The today modules to flatten from the page response + * @param onboardingCardsData The media api data for the onboarding cards, inserted into the flattened list + * @param todayRecommendations The today recommendations result, inserted into the flattened list + * @param adsResponse The response from ad platforms + * @returns The flattened list of today items + */ +export function flattenTodayModules(objectGraph, todayModules, todayRecommendations, onboardingCardsData, adsResponse) { + const flattenedItems = []; + let insertedOnboardingCards = isNullOrEmpty(onboardingCardsData); + // Retrieve our personalization data + const personalizationDataContainer = personalizationDataContainerForTodayModules(objectGraph, todayModules); + let absoluteFeedIndex = 0; + for (const todayModule of todayModules) { + if (isNullOrEmpty(todayModule.contents)) { + continue; + } + const personalizedModuleDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", todayModule.contents, true, personalizationDataContainer); + todayModule.contents = personalizedModuleDataResult.personalizedData; + todayModule.onDevicePersonalizationProcessingType = personalizedModuleDataResult.processingType; + let isFirstItemInModule = true; + const moduleMetadata = { + label: todayModule.label, + title: todayModule.title, + meta: todayModule.meta, + date: todayModule.date, + onDevicePersonalizationProcessingType: todayModule.onDevicePersonalizationProcessingType, + isTodayForAppsModule: mediaDataStructure.isModuleTodayForApps(todayModule), + }; + if (!insertedOnboardingCards) { + for (const onboardingCardData of onboardingCardsData) { + flattenedItems.push({ + type: FlattenedTodayItemType.EditorialItem, + data: onboardingCardData, + isDataHydrated: hasAttributes(onboardingCardData), + isFirstItemInModule, + moduleMetadata: { ...moduleMetadata }, + containedAdSlots: [absoluteFeedIndex], + }); + } + isFirstItemInModule = false; + insertedOnboardingCards = true; + absoluteFeedIndex += 1; + } + for (let moduleItem of todayModule.contents) { + const onDeviceUseCase = asString(moduleItem, "meta.personalizationData.onDeviceUseCase"); + switch (moduleItem.type) { + case "editorial-items": + if (isSome(onDeviceUseCase)) { + // ODP personalization for Today Arcade stories. + const storyData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyData(onDeviceUseCase); + if (isSome(storyData)) { + moduleItem = storyData; + } + } + flattenedItems.push({ + type: FlattenedTodayItemType.EditorialItem, + data: moduleItem, + isDataHydrated: hasAttributes(moduleItem), + isFirstItemInModule, + moduleMetadata: { ...moduleMetadata }, + containedAdSlots: [absoluteFeedIndex], + }); + isFirstItemInModule = false; + absoluteFeedIndex += 1; + break; + case "editorial-item-groups": + const groupContents = asArrayOrEmpty(moduleItem, "meta.associations.recommendations.data"); + if (isNullOrEmpty(groupContents)) { + continue; + } + let storyGroupData; + if (isSome(onDeviceUseCase)) { + // ODP personalization for Today Arcade story groups. + storyGroupData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyGroupData(onDeviceUseCase); + } + if (isSome(storyGroupData)) { + moduleItem = storyGroupData; + } + else { + // ODP personalization for in-app event story groups. + const personalizedStoryGroupDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", groupContents, true, personalizationDataContainer); + moduleItem["meta"]["associations"]["recommendations"]["data"] = + personalizedStoryGroupDataResult.personalizedData; + todayModule.onDevicePersonalizationProcessingType = + personalizedStoryGroupDataResult.processingType; + } + flattenedItems.push({ + type: FlattenedTodayItemType.EditorialItemGroup, + data: moduleItem, + isDataHydrated: hasAttributes(moduleItem), + isFirstItemInModule, + moduleMetadata: { ...moduleMetadata }, + containedAdSlots: Array.from({ length: groupContents.length }, (key, value) => value + absoluteFeedIndex), + }); + isFirstItemInModule = false; + absoluteFeedIndex += groupContents.length; + break; + default: + break; + } + } + } + return flattenedItems; +} +/** + * Whether the next content item in the flattened list is hydrated. + * @param flattenedItems The flattened list of today items + * @returns True if the next content item is hydrated, false otherwise + */ +export function nextFlattenedItemIsHydrated(flattenedItems) { + for (const flattenedItem of flattenedItems) { + switch (flattenedItem.type) { + case FlattenedTodayItemType.EditorialItem: + case FlattenedTodayItemType.EditorialItemGroup: + return hasAttributes(flattenedItem.data); + default: + break; + } + } + return false; +} +/** + * Iterates through all the today modules, and creates a set of personalization + * data that is targetted only to the contents of these modules. + * + * @param dataArray The input array of today modules. + * @returns Any relevant OnDevicePersonalizaionData + */ +export function personalizationDataContainerForTodayModules(objectGraph, todayModules) { + if (!onDevicePersonalization.isPersonalizationAvailable(objectGraph)) { + return null; + } + // Here we iterate through all the modules, and look inside each content items meta for personalizationData. + const appIds = new Set(); + for (const todayModule of todayModules) { + if (isNull(todayModule.contents)) { + continue; + } + const appIdFromContent = (contentData) => { + return asString(contentData, "meta.personalizationData.appId"); + }; + for (const contentData of todayModule.contents) { + switch (contentData.type) { + case "editorial-item-groups": + const groupItems = asArrayOrEmpty(contentData.meta, "associations.recommendations.data"); + for (const groupItem of groupItems) { + const appId = appIdFromContent(groupItem); + if (isDefinedNonNullNonEmpty(appId)) { + appIds.add(appId.toString()); + } + } + break; + default: + const appId = appIdFromContent(contentData); + if (isDefinedNonNullNonEmpty(appId)) { + appIds.add(appId.toString()); + } + break; + } + } + } + return onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds); +} +// MARK: - Shelf Creation +/** + * @param objectGraph The dependency graph for the App Store + * @param todayItem The today item that this card is contained in, this could be a single item or a story group item + * @param todayCardData The MAPI data that will be used to create this today card + * @param isAdEligible Whether the current card is being placed in a slot that was eligible for an ad + * @param currentRowIndex The current row index this card is going to be in + * @param metricsDisplayStyle: The display style for impressions and location + * @param isHeroCard: Whether this is the first card in a hero story group + * @returns The configuration to use when parsing a today card + */ +function createTodayCardConfiguration(objectGraph, todayCardData, isAdEligible, currentRowIndex, metricsDisplayStyle, isHeroCard) { + var _a; + const cardConfig = defaultTodayCardConfiguration(objectGraph); + cardConfig.useOTDTextStyle = (_a = asBoolean(todayCardData, "meta.personalizationData.isOfTheDay")) !== null && _a !== void 0 ? _a : false; + cardConfig.replaceIfAdPresent = asBoolean(todayCardData, "meta.personalizationData.replaceIfAdPresent"); + cardConfig.isAdEligible = isAdEligible; + cardConfig.currentRowIndex = currentRowIndex; + cardConfig.metricsDisplayStyle = metricsDisplayStyle; + cardConfig.isHeroCard = isHeroCard; + if (objectGraph.client.isWeb) { + cardConfig.prevailingCropCodes = { + "defaultCrop": "sr", + "editorialArtwork.dayCard": "grav.west", + }; + } + return cardConfig; +} +/** + * @param objectGraph The dependency graph for the App Store + * @param pageContext The page context for the today page + * @returns The currentRowIndex from the pageContext, this is only used on iPhone, otherwise return + * null since we cant reliably know the row index + */ +function rowCountFromContext(objectGraph, pageContext) { + if (!objectGraph.client.isPhone) { + return undefined; + } + return pageContext.currentRowIndex; +} +/** + * Update the page context with the current row count, this uses the groupDisplayStyle to determine + * the display style for the cards in the current row + * @param pageContext The page context for the today page we're parsing + * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing + * @param currentItemIndex The index of the current item in the shelf, that we just created a card + * for, this method is called after creating a card + */ +function incrementRowIndexIfNecessary(pageContext, currentGroupDisplayStyle, currentItemIndex) { + switch (currentGroupDisplayStyle) { + case models.GroupDisplayStyle.Grid: + if (currentItemIndex % 2 === 1) { + pageContext.currentRowIndex++; + } + break; + case models.GroupDisplayStyle.Hero: + if (currentItemIndex === 0 || (currentItemIndex - 1) % 2 === 1) { + pageContext.currentRowIndex++; + } + break; + case models.GroupDisplayStyle.Standard: + pageContext.currentRowIndex++; + break; + default: + break; + } +} +/** + * Update the page context with the current metrics display style, this is based off the groupDisplayStyle + * @param objectGraph The dependency graph for the App Store + * @param pageContext The page context for the today page we're parsing + * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing + * @param currentItemIndex The index of the current item in the shelf, that we just created a card + * for, this method is called after creating a card + */ +function updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, currentGroupDisplayStyle, currentItemIndex) { + if (objectGraph.client.isPad) { + pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard; + return; + } + switch (currentGroupDisplayStyle) { + case models.GroupDisplayStyle.Grid: + pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard; + break; + case models.GroupDisplayStyle.Hero: + if (currentItemIndex === 0) { + pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard; + } + else { + pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard; + } + break; + case models.GroupDisplayStyle.Standard: + pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard; + break; + default: + break; + } +} +/** + * @param objectGraph The app store object graph + * @param data The data from MAPI for a single item in the today feed, group or single EI + * @returns The group display style for the given data + */ +function groupDisplayStyleFromData(objectGraph, data) { + var _a; + if (data.type === "editorial-items") { + return models.GroupDisplayStyle.Standard; + } + let groupDisplayStyle; + const editorialCard = editorialCardFromData(data); + if (hasAttributes(editorialCard)) { + groupDisplayStyle = mediaAttributes.attributeAsString(editorialCard, "editorialItemGroupDisplayStyle"); + } + if (isNothing(groupDisplayStyle)) { + groupDisplayStyle = + (_a = mediaAttributes.attributeAsString(data, "displayStyle")) !== null && _a !== void 0 ? _a : models.GroupDisplayStyle.Standard; + } + return isGroupDisplayStyleSupported(objectGraph, groupDisplayStyle) + ? groupDisplayStyle + : models.GroupDisplayStyle.Standard; +} +/** + * @param objectGraph The app dependency graph + * @param displayStyle The display style to check support for + * @returns Whether group display style is supported for this platform + */ +function isGroupDisplayStyleSupported(objectGraph, displayStyle) { + if (isNothing(displayStyle)) { + return false; + } + switch (displayStyle) { + case models.GroupDisplayStyle.Grid: + return objectGraph.client.isPhone; + default: + return true; + } +} +/** + * Determine if the current card is eligible for an ad, based on the parsed card count, and the + * bag defined values for slots that can have ads + * @param pageContext The page context for the today page + * @returns Whether the current card is eligible for an ad + */ +function isTodayCardConfigurationAdEligible(pageContext) { + if (pageContext.adLocation === pageContext.parsedCardCount) { + return true; + } + if (isNothing(pageContext.eligibleAdLocations)) { + return false; + } + return pageContext.eligibleAdLocations.includes(pageContext.parsedCardCount); +} +/** + * @param objectGraph The dependency graph for the App Store + * @param todayItem The flattened today item to create a shelf for + * @param pageContext The page context for the today page + * @returns The shelf for the given today item, or null if we cant create one + */ +export function todayShelfForEditorialItem(objectGraph, todayItem, pageContext) { + var _a; + const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => { + var _a, _b, _c; + const shelfItems = []; + (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0)); + const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data); + updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, 0); + const cardConfig = createTodayCardConfiguration(objectGraph, todayItem.data, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, false); + cardConfig.baseMetricsOptions = { + recoMetricsData: recoMetricsFromTodayItem(todayItem), + }; + const editorialItemTodayCard = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem.data); + if (isNothing(editorialItemTodayCard)) { + return shelfItems; + } + shelfItems.push(editorialItemTodayCard); + nextPosition(pageContext.locationTracker); + pageContext.parsedCardCount++; + incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, 0); + return shelfItems; + }); + shelf.contentsMetadata = { + type: "todaySection", + debugSectionTypeIndicatorColor: todayItem.type === FlattenedTodayItemType.EditorialItemGroup + ? color.named("systemGreen") + : color.named("systemBlue"), + groupDisplayStyle: models.GroupDisplayStyle.Standard, + }; + // If this is not the first item in the module, attempt to set the background + if (!todayItem.isFirstItemInModule) { + const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem); + if (isSome(editorialShelfBackgroundInfo)) { + shelf.background = editorialShelfBackgroundInfo.shelfBackground; + if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) { + shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor; + shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor; + shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor; + } + } + } + return shelf; +} +/** + + * @param objectGraph The dependency graph for the App Store + * @param todayItem The flattened today item, of type EditorialItemGroup to create a shelf for + * @param pageContext The page context for the today page + * @returns The shelf for the given today item, or null if we cant create one + */ +export function todayShelfForEditorialItemGroup(objectGraph, todayItem, pageContext) { + var _a; + let isValidHeroStoryGroup = true; + const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => { + var _a, _b, _c, _d, _e, _f, _g; + const shelfItems = []; + const displayLimit = impressionDemotion.isImpressionDemotionAvailable(objectGraph) + ? (_a = asNumber(todayItem.data, "meta.personalizationData.displayEICount")) !== null && _a !== void 0 ? _a : 100 + : 100; + let groupItems = asArrayOrEmpty(todayItem.data.meta, "associations.recommendations.data"); + if (isSome(pageContext.recoImpressionData)) { + groupItems = impressionDemotion.personalizeDataItems(groupItems, pageContext.recoImpressionData, (_b = pageContext.pageInformation.recoMetricsData) !== null && _b !== void 0 ? _b : {}); + } + const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data); + let parsedGroupItemCount = 0; + for (const [index, groupItem] of groupItems.entries()) { + (_c = pageContext.pageInformation.iAdInfo) === null || _c === void 0 ? void 0 : _c.updateContainerId((_d = pageContext.pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.containerIdForSlotIndex((_e = pageContext.parsedCardCount) !== null && _e !== void 0 ? _e : 0)); + updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, parsedGroupItemCount); + const cardConfig = createTodayCardConfiguration(objectGraph, groupItem, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, groupDisplayStyle === models.GroupDisplayStyle.Hero && index === 0); + const shelfItem = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, groupItem); + if (isSome(shelfItem)) { + shelfItems.push(shelfItem); + nextPosition(pageContext.locationTracker); + pageContext.parsedCardCount++; + incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, parsedGroupItemCount); + parsedGroupItemCount++; + } + if (cardConfig.isHeroCard && isNothing(shelfItem)) { + if (["debug", "internal"].includes(objectGraph.client.buildType)) { + validation.unexpectedType("defaultValue", `Hero story group ${(_f = todayItem.data) === null || _f === void 0 ? void 0 : _f.id} must contain a valid hero card at index ${index}. Unable to parse card ${groupItem.id}.`, null); + } + isValidHeroStoryGroup = false; + } + if (index < groupItems.length - 1 && + pageContext.adPlacementBehavior === models.AdPlacementBehavior.insertIntoShelf) { + // Attempt to insert the ad card after creating the next item in the group, but only + // if we're still **within** the story group, ads that fall at the beginning or end of + // the group are handled at the top level of the page parsing. + const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false)); + if (isSome(adCard)) { + pageContext.parsedCardCount++; + incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0); + nextPosition(pageContext.locationTracker); + shelfItems.push(adCard); + } + } + // if the count is equal to the story limit. stop here and break. + if (shelfItems.length === displayLimit) { + break; + } + } + if (isValidHeroStoryGroup) { + // The number of items required to be a valid hero story group, this includes the hero + // card, and two additional cards to form a row + const heroStoryGroupRequiredItemCount = 3; + if (shelfItems.length !== heroStoryGroupRequiredItemCount) { + if (["debug", "internal"].includes(objectGraph.client.buildType)) { + validation.unexpectedType("defaultValue", `Hero story group ${(_g = todayItem.data) === null || _g === void 0 ? void 0 : _g.id} must contain exactly ${heroStoryGroupRequiredItemCount} items but only found ${shelfItems.length} items.`, null); + } + isValidHeroStoryGroup = false; + } + } + return shelfItems; + }); + // Fallback to standard group display style if we had an issue creating a valid hero group + let groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data); + if (groupDisplayStyle === models.GroupDisplayStyle.Hero && !isValidHeroStoryGroup) { + groupDisplayStyle = models.GroupDisplayStyle.Standard; + } + shelf.contentsMetadata = { + type: "todaySection", + debugSectionTypeIndicatorColor: color.named("systemGreen"), + groupDisplayStyle: groupDisplayStyle, + }; + // If this is not the first item in the module, attempt to set the background + if (!todayItem.isFirstItemInModule) { + const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem); + if (isSome(editorialShelfBackgroundInfo)) { + shelf.background = editorialShelfBackgroundInfo.shelfBackground; + if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) { + shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor; + shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor; + shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor; + } + } + else if (groupDisplayStyle === models.GroupDisplayStyle.Hero && Array.isArray(shelf.items)) { + shelf.background = todaySectionBackgroundForHeroDisplayStyle(shelf.items); + } + } + return shelf; +} +/** + * @param objectGraph The dependency graph for the App Store + * @param todayItem The flattened today item, used to create the items in this shelf + * @param pageContext The page context for the today page + * @param itemProvider The function that will provide the items for this shelf + * @returns The shelf for the given today item + */ +function createTodayShelfWithItems(objectGraph, todayItem, pageContext, itemProvider) { + const shouldRecordShelfMetrics = todayItem.type === FlattenedTodayItemType.EditorialItemGroup; + const shelf = new models.Shelf("todayCard"); + shelf.id = todayItem.data.id; + shelf.isHorizontal = false; + shelf.header = createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext); + if (shouldRecordShelfMetrics) { + const shelfMetricsOptions = { + id: shelf.id, + kind: "editorialItemGroup", + softwareType: null, + targetType: "swoosh", + title: sectionTitleFromTodayItem(objectGraph, todayItem, true), + pageInformation: pageContext.pageInformation, + locationTracker: pageContext.locationTracker, + idType: "its_id", + recoMetricsData: recoMetricsFromTodayItem(todayItem), + }; + if (todayItem.type === FlattenedTodayItemType.EditorialItemGroup) { + shelfMetricsOptions["optimizationId"] = asString(todayItem.data, "meta.personalizationData.optimizationId"); + shelfMetricsOptions["optimizationEntityId"] = asString(todayItem.data, "meta.personalizationData.optimizationEntityId"); + } + addImpressionFields(objectGraph, shelf, shelfMetricsOptions); + pushContentLocation(objectGraph, shelfMetricsOptions, shelfMetricsOptions.title); + } + shelf.items = itemProvider(); + shelf.isHidden = isNullOrEmpty(shelf.items); + if (shouldRecordShelfMetrics) { + popLocation(pageContext.locationTracker); + nextPosition(pageContext.locationTracker); + } + return shelf; +} +function createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext) { + var _a; + const shouldSuppressHeader = (_a = asBoolean(todayItem.data, "meta.personalizationData.suppressHeader")) !== null && _a !== void 0 ? _a : false; + if (shouldSuppressHeader) { + return null; + } + const shelfHeaderConfiguration = { + eyebrowImageColor: null, + titleImageColor: null, + includeSeparator: false, + }; + const shelfHeader = { + eyebrow: sectionEyebrowFromTodayItem(objectGraph, todayItem), + eyebrowArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow), + eyebrowArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow), + title: sectionTitleFromTodayItem(objectGraph, todayItem), + titleArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title), + titleArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title), + subtitle: sectionSubtitleFromTodayItem(objectGraph, todayItem), + configuration: shelfHeaderConfiguration, + }; + if (isSome(shelfHeader.eyebrow) || isSome(shelfHeader.title) || isSome(shelfHeader.subtitle)) { + return shelfHeader; + } + else { + return null; + } +} +/** + * Create a today card for a given today item in a page context. This handles requirements for ad replacement if needed + * @param objectGraph The dependency graph of the app store + * @param pageContext The context of the page so we can tell whether its time to insert an ad + * @param cardConfig The configuration for the today item + * @param todayItem The flattened today item that should be placed in the shelf or replaced with an ad card + * @returns The today card to insert in a shelf + */ +function createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem) { + var _a, _b; + let editorialItemTodayCard; + if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.replaceOrganic && + isDefinedNonNull(cardConfig.replaceIfAdPresent) && + asBoolean(cardConfig.replaceIfAdPresent)) { + // the ad card should replace the organic + const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig); + if (isDefinedNonNullNonEmpty(adCard)) { + editorialItemTodayCard = adCard; + } + else { + editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext); + } + } + else if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.dropAd && + isDefinedNonNull(cardConfig.replaceIfAdPresent) && + !asBoolean(cardConfig.replaceIfAdPresent)) { + // the organic is not replaceable + editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext); + if (isDefinedNonNullNonEmpty(pageContext.adData)) { + const recorder = pageContext.adIncidentRecorder; + (_a = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _a === void 0 ? void 0 : _a.setMissedOpportunity(objectGraph, "EDITORIALTAKEOVER", (_b = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType); + } + } + else { + // we're using existing insertion logic that will insert an ad elsewhere. create a regular card for the flattened item + editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext); + } + return editorialItemTodayCard; +} +// MARK: - Ads +/** + * Try create a shelf to display an ad if we're currently at the correct spot in the feed. This is only used + * for single editorialItem shelves, and the end of an editorial item group, since these locations it does not + * make sense to include the ad within the shelf for that TodayItem. + * @param objectGraph The dependency graph of the app store + * @param pageContext The context of the page so we can tell whether its time to insert an ad + * @returns The card to insert at the ad slot if necessary + */ +export function createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext) { + let adShelf = null; + if (!isAdPlacementEnabled(objectGraph, "today") || + isNothing(pageContext.adData) || + pageContext.adPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) { + return adShelf; + } + if (pageContext.adLocation !== pageContext.parsedCardCount) { + return adShelf; + } + adShelf = new models.Shelf("todayCard"); + adShelf.id = pageContext.adData.id; + adShelf.isHorizontal = false; + adShelf.contentsMetadata = { + type: "todaySection", + debugSectionTypeIndicatorColor: color.named("systemBlue"), + groupDisplayStyle: models.GroupDisplayStyle.Standard, + }; + const shelfItems = []; + /// Attempt to create an ad card before creating the next item in the group + const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false)); + if (isSome(adCard)) { + pageContext.parsedCardCount++; + incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0); + nextPosition(pageContext.locationTracker); + shelfItems.push(adCard); + } + adShelf.items = shelfItems; + return isDefinedNonNullNonEmpty(adShelf.items) ? adShelf : null; +} +/** + * Try to create an ad card for the page context if the requirements are met + * @param objectGraph The dependency graph of the app store + * @param pageContext The context of the page so we can tell whether its time to insert an ad + * @param cardConfig The configuration of the card + * @returns The card to insert at the ad slot if necessary + */ +export function createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig) { + var _a, _b, _c; + if (!isAdPlacementEnabled(objectGraph, "today")) { + return null; + } + // The ad should respect its slot, unless we're using reco logic for organic replacement. If we need to replace + // the organic, we will arbitrarily respect the reco flag to avoid edge cases with mismatching data + if (pageContext.adLocation !== pageContext.parsedCardCount && + pageContext.adPlacementBehavior !== models.AdPlacementBehavior.replaceOrganic) { + return null; + } + (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0)); + const adCard = createTodayAdCard(objectGraph, pageContext.adData, pageContext.adIncidentRecorder, cardConfig, pageContext); + if (isSome(adCard)) { + return adCard; + } + else { + return null; + } +} +// MARK: - Shelf Header Content +/** + * @param objectGraph The dependency graph of the app store + * @param item The today item to look for the eyebrow in + * @returns The text displayed above the title on a section header + */ +export function sectionEyebrowFromTodayItem(objectGraph, item) { + const editorialClientParams = extractEditorialClientParams(objectGraph, item.data); + if (item.isFirstItemInModule || editorialClientParams.suppressHeaderBadge) { + return null; + } + let sectionBadge; + const editorialCard = editorialCardFromData(item.data); + if (hasAttributes(editorialCard)) { + sectionBadge = mediaAttributes.attributeAsString(editorialCard, "headerBadge"); + } + if (isSome(sectionBadge)) { + return sectionBadge; + } + switch (item.data.type) { + case "editorial-items": + sectionBadge = attributeAsString(item.data, "headerBadge"); + break; + case "editorial-item-groups": + sectionBadge = attributeAsString(item.data, ["editorialNotes", "badge"]); + break; + default: + break; + } + return sectionBadge; +} +/** + * @param objectGraph The dependency graph of the app store + * @param item The today item to look for the title in + * @param alwaysReturnTitle If true, the title will be returned even if it is the first item in a module + * @returns The title displayed above a section + */ +export function sectionTitleFromTodayItem(objectGraph, item, alwaysReturnTitle = false) { + const editorialClientParams = extractEditorialClientParams(objectGraph, item.data); + if ((item.isFirstItemInModule || editorialClientParams.suppressHeaderName) && !alwaysReturnTitle) { + return null; + } + let sectionTitle; + const editorialCard = editorialCardFromData(item.data); + if (hasAttributes(editorialCard)) { + sectionTitle = mediaAttributes.attributeAsString(editorialCard, "headerName"); + } + if (isSome(sectionTitle)) { + return sectionTitle; + } + switch (item.data.type) { + case "editorial-items": + sectionTitle = attributeAsString(item.data, "headerName"); + break; + case "editorial-item-groups": + sectionTitle = attributeAsString(item.data, ["editorialNotes", "name"]); + break; + default: + break; + } + return sectionTitle; +} +/** + * @param objectGraph The dependency graph of the app store + * @param item The today item to look for the subtitle in + * @returns The subtitle displayed below the title on a section header + */ +function sectionSubtitleFromTodayItem(objectGraph, item) { + const editorialClientParams = extractEditorialClientParams(objectGraph, item.data); + if (item.isFirstItemInModule || editorialClientParams.suppressHeaderTagline) { + return null; + } + let sectionSubtitle; + const editorialCard = editorialCardFromData(item.data); + if (hasAttributes(editorialCard)) { + sectionSubtitle = mediaAttributes.attributeAsString(editorialCard, "headerTagline"); + } + if (isSome(sectionSubtitle)) { + return sectionSubtitle; + } + switch (item.data.type) { + case "editorial-items": + sectionSubtitle = attributeAsString(item.data, "headerTagline"); + break; + case "editorial-item-groups": + sectionSubtitle = attributeAsString(item.data, ["editorialNotes", "tagline"]); + break; + default: + break; + } + return sectionSubtitle; +} +/** + * @param objectGraph The dependency graph of the app store + * @param item The today item to look for the subtitle in + * @returns The artwork displayed in the eyebrow of a today section. + */ +function sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, item, placement) { + var _a; + const editorialClientParams = extractEditorialClientParams(objectGraph, item.data); + const headerContents = relationshipData(objectGraph, item.data, "header-contents"); + const artworkBehavior = (_a = editorialClientParams.headerArtworkBehavior) !== null && _a !== void 0 ? _a : TodayHeaderArtworkBehavior.NoArtwork; + switch (placement) { + case TodaySectionHeaderArtworkPlacement.Eyebrow: + switch (artworkBehavior) { + case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge: + return categoryArtworkFromData(objectGraph, headerContents); + default: + return null; + } + case TodaySectionHeaderArtworkPlacement.Title: + switch (artworkBehavior) { + case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle: + return categoryArtworkFromData(objectGraph, headerContents); + case TodayHeaderArtworkBehavior.ContentArtworkWithTitle: + return iconFromData(objectGraph, headerContents, { + useCase: 1 /* ArtworkUseCase.LockupIconSmall */, + }); + default: + return null; + } + default: + return null; + } +} +/** + * @param objectGraph The dependency graph of the app store + * @param item The today item to look for the subtitle in + * @returns The artwork type displayed in the eyebrow of a today section. + */ +function sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, item, placement) { + const headerContents = relationshipData(objectGraph, item.data, "header-contents"); + const artworkBehavior = attributeAsString(item.data, [ + "editorialClientParams", + "headerArtworkBehavior", + ]); + switch (placement) { + case TodaySectionHeaderArtworkPlacement.Eyebrow: + switch (artworkBehavior) { + case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge: + const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents)); + return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null; + default: + return null; + } + case TodaySectionHeaderArtworkPlacement.Title: + switch (artworkBehavior) { + case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle: + const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents)); + return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null; + case TodayHeaderArtworkBehavior.ContentArtworkWithTitle: + const hasIconArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents)); + return hasIconArtwork ? models.ShelfHeaderArtworkType.Icon : null; + default: + return null; + } + default: + return null; + } +} +/** + * + * @param objectGraph The dependency graph of the app store + * @param data The MAPI data for the related content for a today item + * @returns The artwork to use for the related content + */ +function categoryArtworkFromData(objectGraph, data) { + const artworkData = categoryArtworkData(objectGraph, data, false, false, false); + if (isNothing(artworkData)) { + return null; + } + const artwork = artworkFromApiArtwork(objectGraph, artworkData, { + useCase: 20 /* ArtworkUseCase.CategoryIcon */, + allowingTransparency: true, + cropCode: "sr", + }); + return artwork; +} +/** + * @param objectGraph The dependency graph for the App Store + * @param todayItem The today item that contains the data for a editorial-item or editorial-item-group, this is used to look for + * editorialBackground, which can then be used to generate the section gradient background + * @returns The background to use for the section + */ +function todaySectionEditorialBackground(objectGraph, todayItem) { + const editorialBackground = mediaAttributes.attributeAsDictionary(todayItem.data, "editorialBackground", null); + const editorialBackgroundType = editorialBackground === null || editorialBackground === void 0 ? void 0 : editorialBackground["type"]; + if (isNothing(editorialBackgroundType)) { + return null; + } + let backgroundInfo = null; + switch (editorialBackgroundType) { + case EditorialBackgroundType.LinearGradient: + const linearGradientData = asInterface(editorialBackground); + const colors = linearGradientData.stops.map((stop) => color.fromHex(stop.color)); + const shelfBackground = { + type: "gradient", + colors: colors, + start: models.ShelfBackgroundGradientLocation.Top, + end: models.ShelfBackgroundGradientLocation.Bottom, + }; + const isDark = color.isDarkColor(colors[0]); + const secondaryLabelLightColor = { + type: "rgb", + red: 60 / 255, + green: 60 / 255, + blue: 67 / 255, + alpha: 0.6, + }; + const secondaryLabelDarkColor = { + type: "rgb", + red: 235.0 / 255, + green: 235.0 / 255, + blue: 245.0 / 255, + alpha: 0.6, + }; + backgroundInfo = { + shelfBackground: shelfBackground, + eyebrowColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor, + titleColor: isDark ? color.named("white") : color.named("black"), + subtitleColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor, + }; + break; + default: + backgroundInfo = null; + break; + } + return backgroundInfo; +} +/** + * @param todayCards The today cards that are contained in the section + * @returns The background to use for the section + */ +function todaySectionBackgroundForHeroDisplayStyle(todayCards) { + const backgroundColors = todayCards + .map((todayCard) => { + return todayCard.media.bestBackgroundColor(); + }) + .filter((backgroundColor) => isSome(backgroundColor)); + let shelfBackground = null; + if (backgroundColors.length > 0 && backgroundColors.length <= 4 && backgroundColors.length === todayCards.length) { + switch (backgroundColors.length) { + case 1: + shelfBackground = { + type: "materialGradient", + colors: { + colorCount: "oneColor", + color: backgroundColors[0], + }, + }; + break; + case 2: + shelfBackground = { + type: "materialGradient", + colors: { + colorCount: "twoColor", + top: backgroundColors[0], + bottom: backgroundColors[1], + }, + }; + break; + case 3: + shelfBackground = { + type: "materialGradient", + colors: { + colorCount: "threeColor", + top: backgroundColors[0], + bottomLeading: backgroundColors[1], + bottomTrailing: backgroundColors[2], + }, + }; + break; + case 4: + shelfBackground = { + type: "materialGradient", + colors: { + colorCount: "fourColor", + topLeading: backgroundColors[0], + topTrailing: backgroundColors[1], + bottomLeading: backgroundColors[2], + bottomTrailing: backgroundColors[3], + }, + }; + break; + default: + break; + } + } + else { + shelfBackground = { + type: "color", + color: color.named("secondarySystemBackground"), + }; + } + return shelfBackground; +} +// MARK: - Metrics +/** + * Retrieves the recommendation metrics data from a FlattenedTodayItem object. + * @param todayItem - The FlattenedTodayItem object containing the module metadata. + * @returns The recommendation metrics data as a JSONData object. + */ +export function recoMetricsFromTodayItem(todayItem) { + var _a, _b; + if (isNothing(todayItem)) { + return {}; + } + const recoMetricsData = (_a = asDictionary(todayItem.moduleMetadata, "meta.metrics")) !== null && _a !== void 0 ? _a : {}; + const combinedRecoMetricsData = (_b = combinedRecoMetricsDataFromMetricsData(recoMetricsData, todayItem.moduleMetadata.onDevicePersonalizationProcessingType, null)) !== null && _b !== void 0 ? _b : {}; + return combinedRecoMetricsData; +} +// MARK: - Debug Util +/** + * Generate a todayCardPreview url that contains the entire original today feed. + * @param objectGraph The dependency graph of the app store + * @param todayItems The flattened today feed items + * @returns The url to add to the today page for debug purposes + */ +export function feedPreviewUrlFromFlattenedTodayItems(objectGraph, todayItems) { + switch (objectGraph.client.buildType) { + case "debug": + case "internal": + const feedPreviewUrl = new URL(); + feedPreviewUrl.protocol = Protocol.https; + feedPreviewUrl.host = "apps.apple.com"; + feedPreviewUrl.pathname = `/${Path.todayCardPreview}`; + const idsParamValues = []; + for (const item of todayItems) { + switch (item.type) { + case FlattenedTodayItemType.EditorialItem: + idsParamValues.push(item.data.id); + break; + case FlattenedTodayItemType.EditorialItemGroup: + const groupItems = asArrayOrEmpty(item.data.meta, "associations.recommendations.data"); + idsParamValues.push(`${item.data.id}:[${groupItems.map((groupItem) => groupItem.id).join(",")}]`); + break; + default: + break; + } + } + feedPreviewUrl.param(Parameters.ids, idsParamValues.join(",")); + feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true"); + return decodeURIComponent(feedPreviewUrl.build()); + default: + return null; + } +} +/** + * Generate a todayCardPreview url that can display a single today card + * @param objectGraph The dependency graph of the app store + * @param todayCardId The id of the created today card + * @param cardConfig The card config for the created today card + * @returns The url to add to the today page for debug purposes + */ +export function todayCardPreviewUrlForTodayCard(objectGraph, todayCardId, cardConfig) { + if (isNothing(todayCardId) || !objectGraph.client.isiOS) { + return null; + } + switch (objectGraph.client.buildType) { + case "debug": + case "internal": + const feedPreviewUrl = new URL(); + feedPreviewUrl.protocol = Protocol.https; + feedPreviewUrl.host = "apps.apple.com"; + feedPreviewUrl.pathname = `/${Path.todayCardPreview}`; + feedPreviewUrl.param(Parameters.ids, `${todayCardId}`); + feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true"); + feedPreviewUrl.param(Parameters.isTodaySection, cardConfig.useOTDTextStyle ? "true" : "false"); + return decodeURIComponent(feedPreviewUrl.build()); + default: + return null; + } +} +//# sourceMappingURL=today-parse-util.js.map \ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js new file mode 100644 index 0000000..3c02e97 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js @@ -0,0 +1,118 @@ +import * as models from "../../api/models"; +import * as metricsHelpersLocation from "../metrics/helpers/location"; +/** + * Stateful container passed along pipeline from initial page building, mutating while components are being built. + * Properties on this *can* be snapshotted to re-initialize a context for shelf-reloading and pagination. + */ +export class TodayParseContext { + constructor(pageInformation, locationTracker, refreshController) { + // The location tracker for building metrics location information + this.locationTracker = metricsHelpersLocation.newLocationTracker(); + /// The number of cards that we've successfully parsed, This excludes failed cards + this.parsedCardCount = 0; + /// The number of rows of cards that we've successfully added to the page. This is only applicable on + /// iPhone where we know there is only one layout per page. This differs from parsedCardCount in that + /// rows can contain multiple cards, such is the case with hero / grid shelves. + this.currentRowIndex = 0; + this.pageInformation = pageInformation; + this.locationTracker = locationTracker !== null && locationTracker !== void 0 ? locationTracker : metricsHelpersLocation.newLocationTracker(); + this.refreshController = refreshController; + } +} +/** + * The `TodayPageContext` is used for building the initial page. It is used to maintain a record + * of the remaining items left in the today page, so it is also used for pagination + */ +export class TodayPageContext extends TodayParseContext { + constructor(remainingContent, pageInformation, locationTracker, refreshController, recoImpressionData) { + super(pageInformation, locationTracker, refreshController); + // Flag indicating whether we've ever successfully displayed any content on the page, if we're + // doing a secondary fetch and this is false, we try to fetch a bit more than normal pagination + // to have a higher chance of getting **some** content back. + this.pageHasDisplayedContent = false; + this.remainingContent = remainingContent; + this.adPlacementBehavior = models.AdPlacementBehavior.insertIntoShelf; + this.recoImpressionData = recoImpressionData; + } +} +/** + * A `TodayShelfToken` is used to maintain a record of each shelf's contents when subsequent content fetch is needed. It is encoded in the `url` property of `Shelf`. + */ +export class TodayShelfToken { + // endregion + constructor(todayModule, contentOffsetWithinModule, metricsPageInformation, metricsLocationTracker) { + this.todayModule = todayModule; + this.metricsPageInformation = metricsPageInformation; + this.contentOffsetWithinModule = contentOffsetWithinModule; + this.metricsLocationTracker = metricsLocationTracker; + } +} +/** + * These are the different ways we can display a today card + */ +export var TodayCardDisplayStyle; +(function (TodayCardDisplayStyle) { + TodayCardDisplayStyle["AppEventCard"] = "AppEventCard"; + TodayCardDisplayStyle["AppOfTheDay"] = "AppOfTheDay"; + TodayCardDisplayStyle["FullBleedImage"] = "FullBleedImage"; + TodayCardDisplayStyle["GameOfTheDay"] = "GameOfTheDay"; + TodayCardDisplayStyle["Grid"] = "Grid"; + TodayCardDisplayStyle["InAppPurchase"] = "InAppPurchase"; + TodayCardDisplayStyle["List"] = "List"; + TodayCardDisplayStyle["NumberedList"] = "NumberedList"; + TodayCardDisplayStyle["River"] = "River"; + TodayCardDisplayStyle["ShortImage"] = "ShortImage"; + TodayCardDisplayStyle["SingleApp"] = "SingleApp"; + TodayCardDisplayStyle["Video"] = "Video"; +})(TodayCardDisplayStyle || (TodayCardDisplayStyle = {})); +/** + * These are the different sizes of today card we can have, + * the reason this uses the term `DisplayStyle` is because the the metrics spec + * already uses the term `DisplayStyle` to describe the size of different components + * so we're reusing that term here. This is not to be confused with `TodayCardDisplayStyle` + */ +export var TodayCardMetricsDisplayStyle; +(function (TodayCardMetricsDisplayStyle) { + TodayCardMetricsDisplayStyle["SmallCard"] = "smallCard"; + TodayCardMetricsDisplayStyle["MediumCard"] = "mediumCard"; +})(TodayCardMetricsDisplayStyle || (TodayCardMetricsDisplayStyle = {})); +/** + * These are the different ways we can display artwork within a today section header + */ +export var TodayHeaderArtworkBehavior; +(function (TodayHeaderArtworkBehavior) { + TodayHeaderArtworkBehavior["NoArtwork"] = "no-artwork"; + TodayHeaderArtworkBehavior["ContentArtworkWithTitle"] = "content-artwork-with-title"; + TodayHeaderArtworkBehavior["CategoryArtworkWithTitle"] = "category-artwork-with-title"; + TodayHeaderArtworkBehavior["CategoryArtworkWithBadge"] = "category-artwork-with-badge"; +})(TodayHeaderArtworkBehavior || (TodayHeaderArtworkBehavior = {})); +/** + * The different types of today card OTD intentions + */ +export var OfTheDayIntention; +(function (OfTheDayIntention) { + OfTheDayIntention["AppOfTheDay"] = "app-of-the-day"; + OfTheDayIntention["GameOfTheDay"] = "game-of-the-day"; +})(OfTheDayIntention || (OfTheDayIntention = {})); +/** + * Indicates the different places we can have artwork in the section header + */ +export var TodaySectionHeaderArtworkPlacement; +(function (TodaySectionHeaderArtworkPlacement) { + TodaySectionHeaderArtworkPlacement["Eyebrow"] = "eyebrow"; + TodaySectionHeaderArtworkPlacement["Title"] = "title"; +})(TodaySectionHeaderArtworkPlacement || (TodaySectionHeaderArtworkPlacement = {})); +export var EditorialBackgroundType; +(function (EditorialBackgroundType) { + EditorialBackgroundType["LinearGradient"] = "linear-gradient"; +})(EditorialBackgroundType || (EditorialBackgroundType = {})); +/** + * Describes the context in which hero media appears. Used to + * configure cards differently on macOS depending on where it appears. + */ +export var HeroMediaDisplayContext; +(function (HeroMediaDisplayContext) { + HeroMediaDisplayContext["Inline"] = "inline"; + HeroMediaDisplayContext["Article"] = "article"; +})(HeroMediaDisplayContext || (HeroMediaDisplayContext = {})); +//# sourceMappingURL=today-types.js.map \ No newline at end of file -- cgit v1.2.3