diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js | |
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js')
| -rw-r--r-- | node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js new file mode 100644 index 0000000..53294d3 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js @@ -0,0 +1,319 @@ +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
\ No newline at end of file |
