import { 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 mediaRelationship from "../../foundation/media/relationships"; import * as artworkBuilder from "../content/artwork/artwork"; import * as metricsHelpersClicks from "../metrics/helpers/clicks"; import * as types from "./grouping-types"; import { GroupingAppEventShelfController } from "./shelf-controllers/grouping-app-event-shelf-controller"; import { GroupingArcadeFooterShelfController } from "./shelf-controllers/grouping-arcade-footer-shelf-controller"; import { GroupingBrickShelfController } from "./shelf-controllers/grouping-brick-shelf-controller"; import { GroupingRibbonBarShelfController } from "./shelf-controllers/grouping-ribbon-bar-shelf-controller"; import { GroupingCategoryShelfController } from "./shelf-controllers/grouping-category-shelf-controller"; import { GroupingEditorialCardShelfController } from "./shelf-controllers/grouping-editorial-card-shelf-controller"; import { GroupingEditorialStoryCardShelfController } from "./shelf-controllers/grouping-editorial-story-card-shelf-controller"; import { GroupingGameCenterActivityFeedController } from "./shelf-controllers/grouping-game-center-activity-feed-shelf-controller"; import { GroupingGameCenterContinuePlayingShelfController } from "./shelf-controllers/grouping-game-center-continue-playing-shelf-controller"; import { GroupingGameCenterPopularWithYourFriendsController } from "./shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller"; import { GroupingGameCenterReengagementShelfController } from "./shelf-controllers/grouping-game-center-reengagement-shelf-controller"; import { GroupingGameCenterSuggestedFriendsController } from "./shelf-controllers/grouping-game-center-suggested-friends-shelf-controller"; import { GroupingHeroCarouselShelfController } from "./shelf-controllers/grouping-hero-carousel-shelf-controller"; import { GroupingHorizontalCardShelfController } from "./shelf-controllers/grouping-horizontal-card-shelf-controller"; import { GroupingLargeBreakoutShelfController } from "./shelf-controllers/grouping-large-breakout-shelf-controller"; import { GroupingLinkShelfController } from "./shelf-controllers/grouping-link-shelf-controller"; import { GroupingLockupShelfController } from "./shelf-controllers/grouping-lockup-shelf-controller"; import { GroupingPersonalizedLockupShelfController } from "./shelf-controllers/grouping-personalized-lockup-shelf-controller"; import { createShelfAccessibilityMetadata, } from "./shelf-controllers/grouping-shelf-controller"; import * as groupingShelfControllerCommon from "./shelf-controllers/grouping-shelf-controller-common"; import { GroupingSmallBreakoutShelfController } from "./shelf-controllers/grouping-small-breakout-shelf-controller"; import { ArcadeDownloadPackShelfController } from "./shelf-controllers/arcade-download-pack-shelf-controller"; import { flowActionForAccountURL } from "../account/account-links-regex-parser"; import { GroupingMediaPageHeaderShelfController } from "./shelf-controllers/grouping-tags-header-shelf-controller"; import { applySearchAdMissedOpportunityToShelvesIfNeeded } from "../ads/ad-common"; import { GroupingTagBrickShelfController } from "./shelf-controllers/grouping-tag-brick-shelf-controller"; // region Constants /// The controllers that know how to render specific shelves const shelfControllers = [ new GroupingAppEventShelfController(), new GroupingArcadeFooterShelfController(), new GroupingBrickShelfController(), new GroupingRibbonBarShelfController(), new GroupingCategoryShelfController(), new GroupingEditorialCardShelfController(), new GroupingEditorialStoryCardShelfController(), new GroupingGameCenterContinuePlayingShelfController(), new GroupingGameCenterPopularWithYourFriendsController(), new GroupingGameCenterReengagementShelfController(), new GroupingGameCenterSuggestedFriendsController(), new GroupingGameCenterActivityFeedController(), new GroupingHeroCarouselShelfController(), new GroupingHorizontalCardShelfController(), new GroupingLargeBreakoutShelfController(), new GroupingLinkShelfController(), new GroupingLockupShelfController(), new GroupingPersonalizedLockupShelfController(), new GroupingSmallBreakoutShelfController(), new ArcadeDownloadPackShelfController(), new GroupingMediaPageHeaderShelfController(), new GroupingTagBrickShelfController(), ]; /** * For a given flattened grouping go through and use our controllers to render the associated shelves, this should be used * for the initial page load. The second fetches for these shelves are handled by the routing system and the routed * shelf controller. This method will add all the shelves to the current groupingParseContext. * * @param objectGraph The App Store dependency graph * @param flattenedGrouping The original grouping page response data from MAPI flattened into the list of featured content * @param groupingParseContext The current parse context for this rendering of the grouping. * @protected */ export function insertInitialShelvesIntoGroupingParseContext(objectGraph, flattenedGrouping, groupingParseContext) { validation.catchingContext(`parseGrouping`, () => { for (const mediaApiData of flattenedGrouping.data) { const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind"); if (types.isTopChart(featuredContentId)) { const editorialId = serverData.asString(mediaApiData, "id"); groupingParseContext.chartSet = lookupChartSetForEditorialId(editorialId, flattenedGrouping.editorialChartSets); } else { groupingParseContext.chartSet = null; } const baseRequirements = groupingShelfControllerCommon.createBaseShelfRequirements(objectGraph, mediaApiData, groupingParseContext); const token = baseRequirements.shelfToken; const baseMetricsOptions = baseRequirements.metricsOptions; let shelf; for (const shelfController of shelfControllers) { if (shelfController.supports(objectGraph, mediaApiData, featuredContentId)) { shelf = validation.catchingContext(`parseGroupingShelf`, () => { return shelfController.createShelf(objectGraph, mediaApiData, groupingParseContext, token, baseMetricsOptions); }); break; } } if (serverData.isDefinedNonNull(shelf)) { groupingParseContext.shelves.push(shelf); applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, groupingParseContext.shelves, "searchLanding", baseMetricsOptions.id, groupingParseContext.metricsPageInformation); } } }); } /** * Parse a single editorially programmed chart from media API data. * @returns The chart model or null. */ function makeEditorialChartFromMediaApiData(chartData) { const fcKind = mediaAttributes.attributeAsNumber(chartData, "editorialElementKind"); if (!types.isTopChart(fcKind)) { return null; } return { id: serverData.asString(chartData, "id"), href: mediaAttributes.attributeAsString(chartData, "chartHref"), name: mediaAttributes.attributeAsString(chartData, "name"), type: mediaAttributes.attributeAsString(chartData, "chart"), }; } /** * Parse a collection of charts, programmed by editorial, from media api data. * @returns The chart set model, or null. */ function makeEditorialChartSetFromMediaApiData(chartSetData) { const fcKind = mediaAttributes.attributeAsNumber(chartSetData, "editorialElementKind"); if (fcKind !== 424 /* types.FeaturedContentID.AppStore_ChartSet */) { return null; } return { id: serverData.asString(chartSetData, "id"), name: mediaAttributes.attributeAsString(chartSetData, "name"), editorialCharts: mediaRelationship .relationshipCollection(chartSetData, "children") .map((chartData) => makeEditorialChartFromMediaApiData(chartData)) .filter((chart) => isSome(chart)), }; } /** * Given a list of chart sets, find the chart set that has the provided editorial identifier. * * @param editorialId The chart or chart set identifier from editorial. * @param chartSets The list of all chart sets from the grouping. * @returns The charts associated with the editorial item from the grouping. */ function lookupChartSetForEditorialId(editorialId, editorialChartSets) { for (const chartSet of editorialChartSets) { if (chartSet.id === editorialId) { return chartSet; // editorialId is for a chart set itself } for (const chart of chartSet.editorialCharts) { if (chart.id === editorialId) { return chartSet; // / editorialId is for a specific chart from the chart set. } } } return null; } /** * Take the media API data for a single grouping, and flatten that tree structure into a single list of media api data objects. * * @param mediaApiGroupingData The original media API data for a single grouping */ export function flattenMediaApiGroupingData(objectGraph, mediaApiGroupingData) { const groupingData = []; const chartSets = []; function addMediaApiDataToGroupingData(mediaApiData) { const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind"); validation.catchingContext(`flattenGroupingTree.addMediaApiDataToGroupingData: ${featuredContentId}`, () => { const groupingTabData = mediaRelationship.relationshipData(objectGraph, mediaApiData, "tabs"); if (serverData.isDefinedNonNull(groupingTabData)) { addMediaApiDataToGroupingData(groupingTabData); } else if (types.shouldContinueToWalkChildren(featuredContentId)) { const featuredContentChildren = mediaRelationship.relationshipCollection(mediaApiData, "children"); for (const featuredContentChild of featuredContentChildren) { addMediaApiDataToGroupingData(featuredContentChild); } } else if (featuredContentId === 424 /* types.FeaturedContentID.AppStore_ChartSet */) { // Keep track of chartSets programmed by editorial, e.g. "Top Charts" includes "Top Free Apps" and "Top Paid Apps". const editorialChartSet = makeEditorialChartSetFromMediaApiData(mediaApiData); if (isSome(editorialChartSet)) { chartSets.push(editorialChartSet); } // Add charts from the chartSet, as defined by editorial, to grouping page data. const chartSetChildren = mediaRelationship.relationshipCollection(mediaApiData, "children"); for (const chartSetChild of chartSetChildren) { addMediaApiDataToGroupingData(chartSetChild); } } else { groupingData.push(mediaApiData); } }); } addMediaApiDataToGroupingData(mediaApiGroupingData); return { data: groupingData, editorialChartSets: chartSets, originalGroupingData: mediaApiGroupingData, }; } // endregion // region Shelves /** * Create a shelf for footer buttons, e.g "Send Gift" and "Redeem". * * @return Shelf configured to display `FooterButtons`s. */ export function shelfForFooterButtons(objectGraph, metricsPageInformation, metricsLocationTracker) { var _a, _b, _c, _d, _e, _f; if (objectGraph.user && objectGraph.user.isManagedAppleID) { return null; } // Watch App Store does not support titled button stacks, // and has different footer buttons. if (objectGraph.client.isWatch) { const shelf = new models.Shelf("action"); const items = []; const accountFlowAction = new models.FlowAction("account"); accountFlowAction.title = objectGraph.loc.string("ACCOUNT", "Account"); accountFlowAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://person.crop.circle"); const accountFlowClickOptions = { targetType: "button", id: "account", pageInformation: metricsPageInformation, locationTracker: metricsLocationTracker, }; metricsHelpersClicks.addClickEventToAction(objectGraph, accountFlowAction, accountFlowClickOptions); items.push(accountFlowAction); shelf.items = items; shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf); return shelf; } else { const items = []; const redeemTitle = objectGraph.loc.string("FOOTER_REDEEM", "Redeem"); const redeemActionUrl = objectGraph.client.isVision ? objectGraph.bag.redeemCodeLanding : objectGraph.bag.redeemUrl; const redeemFlowAction = flowActionForAccountURL(objectGraph, redeemActionUrl); const redeemButton = new models.TitledButton(redeemTitle, redeemFlowAction); redeemButton.id = "redeem"; items.push(redeemButton); if (objectGraph.bag.isMonetaryGiftingEnabled) { const giftTitle = objectGraph.loc.string("FOOTER_SEND_GIFT", "Send Gift"); const giftActionUrl = "gift"; const giftFlowAction = flowActionForAccountURL(objectGraph, giftActionUrl); const giftButton = new models.TitledButton(giftTitle, giftFlowAction); giftButton.id = "gift"; items.push(giftButton); } const topUpUrl = objectGraph.bag.accountTopUpURL; if (isSome(topUpUrl)) { const topUpTitle = (_a = objectGraph.bag.accountTopUpTitle) !== null && _a !== void 0 ? _a : objectGraph.loc.string("FOOTER_ADD_MONEY"); const topUpFlowAction = flowActionForAccountURL(objectGraph, topUpUrl); const topUpButton = new models.TitledButton(topUpTitle, topUpFlowAction); topUpButton.id = "topUp"; items.push(topUpButton); } let shelf; if (objectGraph.client.isVision) { shelf = new models.Shelf("titledButton"); shelf.items = items; } else { shelf = new models.Shelf("titledButtonStack"); const stack = new models.TitledButtonStack(items); // On compact width, we want to place a line break after each button. stack.compactLineBreaks = stack.buttons.map((value, index) => index); shelf.items = [stack]; const accessibilityLabel = objectGraph.loc.string("Shelves.Accessibility.Label"); const roleDescription = objectGraph.loc.string("Shelves.Accessibility.RoleDescription"); shelf.accessibilityMetadata = { label: (_d = (_b = shelf.title) !== null && _b !== void 0 ? _b : (_c = shelf.header) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : accessibilityLabel, roleDescription: roleDescription.replace("%d", `${(_f = (_e = stack.buttons) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0}`), }; } return shelf; } } /** * Create a T&C shelf that on click opens an external url. * * @param objectGraph * @param destinationUrl Url to open when terms and conditions is tapped. * @param hasSeparator Whether the footnote has a separator above it. * @return Shelf configured to display Terms and Conditions. */ export function shelfForTermsAndConditions(objectGraph, destinationUrl, hasSeparator = true) { const urlAction = new models.ExternalUrlAction(destinationUrl); const termsAndConditionTitle = objectGraph.loc.string("TermsAndConditions.Title"); const footnote = new models.Footnote(termsAndConditionTitle); footnote.clickAction = urlAction; footnote.presentationStyle = ["hasChevron", "textLightensOnHighlight"]; if (hasSeparator) { footnote.presentationStyle.push("hasSeparator"); } const shelf = new models.Shelf("footnote"); shelf.items = [footnote]; if (objectGraph.bag.emailSupportLinkURL) { const emailSupportAction = new models.ExternalUrlAction(objectGraph.bag.emailSupportLinkURL); const emailSupport = new models.Footnote("Email Support"); emailSupport.clickAction = emailSupportAction; emailSupport.presentationStyle = ["hasChevron", "textLightensOnHighlight"]; if (hasSeparator) { emailSupport.presentationStyle.push("hasSeparator"); } shelf.items.push(emailSupport); } return shelf; } export function shelfForUnifiedMessage(objectGraph, placement, context, deliveryMethod) { const shelf = new models.Shelf("unifiedMessage"); shelf.id = placement; shelf.items = [new models.UnifiedMessage(placement, context, deliveryMethod)]; shelf.isHidden = true; return shelf; } // endregion //# sourceMappingURL=grouping-common.js.map