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/today-controller-util.js | 534 +++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js (limited to 'node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js') 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 -- cgit v1.2.3