diff options
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers')
25 files changed, 6917 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/arcade-download-pack-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/arcade-download-pack-shelf-controller.js new file mode 100644 index 0000000..0e89233 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/arcade-download-pack-shelf-controller.js @@ -0,0 +1,295 @@ +import * as models from "../../../api/models"; +import * as mediaDataFetching from "../../../foundation/media/data-fetching"; +import * as mediaNetwork from "../../../foundation/media/network"; +import * as urls from "../../../foundation/network/urls"; +import * as lockups from "../../lockups/lockups"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as validation from "@jet/environment/json/validation"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as content from "../../content/content"; +import { developerAttributes } from "../../../common/developer/developer-request"; +import { lockupShelfTokenFromBaseTokenAndMediaApiData } from "./grouping-lockup-shelf-controller-common"; +import { isNothing, isSome } from "@jet/environment/types/optional"; +import { arcadeOnboardingSubscriptionStatusFromString } from "../../../api/models"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +export class ArcadeDownloadPackShelfController extends GroupingShelfController { + constructor() { + super("ArcadeDownloadPackShelfController"); + // Stable identifier of the shelf to match in native. + // Used to make a shelf refresh request after user finishes onboarding. + this.shelfId = "arcadeDownloadPackShelf"; + this.supportedFeaturedContentIds = new Set([566 /* groupingTypes.FeaturedContentID.AppStore_ArcadeDownloadPackMarker */]); + } + shelfRoute(objectGraph) { + return [ + ...super.shelfRoute(objectGraph), + { + protocol: Protocol.internal, + path: `/${Path.grouping}/${Path.shelf}/{token}`, + query: [Parameters.isArcadeDownloadPackShelfPlaceholder], + }, + ]; + } + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext); + } + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { + shelfContents: [], + categoriesContents: [], + apps: [], + title: "", + }; + } + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + const downloadPackData = objectGraph.arcade.getDownloadPackGames(objectGraph.bag.arcadeDownloadPackShelfTTLInSeconds); + if (isNothing(downloadPackData) || downloadPackData.apps.length === 0) { + return { + shelfContents: [], + categoriesContents: [], + apps: [], + title: "", + }; + } + const downloadPackApps = downloadPackData.apps; + const shelfTitle = this.shelfTitle(objectGraph, arcadeOnboardingSubscriptionStatusFromString(downloadPackData.subscriptionStatus)); + // Check whether it was a call from native to render a placeholder. + if (isSome(shelfUrl.query[Parameters.isArcadeDownloadPackShelfPlaceholder])) { + return { + shelfContents: [], + categoriesContents: [], + apps: downloadPackApps, + title: shelfTitle, + }; + } + // Do hydration call otherwise. + const adamIDs = downloadPackApps.map((app) => app.adamId); + const categoryIDs = downloadPackApps.map((app) => app.categoryId); + const appsType = "apps"; + const categoriesType = "editorial-items"; + const batchRequest = new mediaDataFetching.Request(objectGraph) + .addingQuery(`ids[${appsType}]`, Array.from(adamIDs).join(",")) + .addingQuery(`ids[${categoriesType}]`, Array.from(categoryIDs).join(",")) + .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph)) + .includingAttributes(developerAttributes(objectGraph)); + return await mediaNetwork + .fetchData(objectGraph, batchRequest) + .then(async (dataContainer) => { + const shelfData = { + shelfContents: dataContainer.data.filter((data) => { + return data.type === appsType; + }), + categoriesContents: dataContainer.data.filter((data) => { + return data.type === categoriesType; + }), + apps: downloadPackApps, + title: shelfTitle, + responseTimingValues: dataContainer[ResponseMetadata.timingValues], + }; + return shelfData; + }); + } + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (shelfToken.isFirstRender) { + const downloadPackData = objectGraph.arcade.getDownloadPackGames(objectGraph.bag.arcadeDownloadPackShelfTTLInSeconds); + if (isNothing(downloadPackData) || downloadPackData.apps.length === 0) { + // No suggested games to display. Adding an empty hidden shelf with `refreshUrl` for future refresh from native when onboarding ends. + const shelf = this.emptyShelfWithRefreshUrl(objectGraph); + shelf.refreshUrl = this.shelfRefreshURL(shelfToken); + // Even though the shelf is hidden, need to increase position here, so when it becomes visible the index stays the same. + metricsHelpersLocation.nextPosition(groupingParseContext.metricsLocationTracker); + return shelf; + } + else { + const shelf = this.placeholderShelf(objectGraph, shelfToken, downloadPackData.apps, this.shelfTitle(objectGraph, arcadeOnboardingSubscriptionStatusFromString(downloadPackData.subscriptionStatus))); + metricsHelpersLocation.nextPosition(groupingParseContext.metricsLocationTracker); + return shelf; + } + } + else { + // If `shelfData` contains only `records` then it was a request for a placeholder shelf from native. + if (shelfData.apps.length > 0 && + (isNothing(shelfData.shelfContents) || shelfData.shelfContents.length === 0)) { + return this.placeholderShelf(objectGraph, shelfToken, shelfData.apps, shelfData.title); + } + else { + const shelfMetricsOptions = this.shelfMetrics(shelfData.title, shelfToken); + metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, shelfData.title); + const shelf = this.downloadPackShelf(objectGraph, shelfToken, shelfData); + // `refreshUrl` allows to update the shelf content if the onboarding experience was triggered again. + shelf.refreshUrl = this.shelfRefreshURL(shelfToken); + metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker); + metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions); + return shelf; + } + } + } + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + // Otherwise `baseMetricsOptions` will be used, however, at this point shelf doesn't have a title. + return null; + } + emptyShelfWithRefreshUrl(objectGraph) { + const shelf = new models.Shelf(this.useCustomDownloadPackCardShelf(objectGraph) ? "arcadeDownloadPackCard" : "smallLockup"); + shelf.id = this.shelfId; + shelf.isHidden = true; + return shelf; + } + placeholderShelf(objectGraph, shelfToken, downloadPackApps, title) { + const shelf = this.useCustomDownloadPackCardShelf(objectGraph) + ? this.downloadPackCardPlaceholderShelf(objectGraph, shelfToken, downloadPackApps.length) + : this.smallLockupsPlaceholderShelf(objectGraph, shelfToken, downloadPackApps); + shelf.url = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)).build(); + shelf.title = title; + return shelf; + } + downloadPackCardPlaceholderShelf(objectGraph, shelfToken, expectedItemCount) { + const shelf = new models.Shelf("arcadeDownloadPackCard"); + shelf.id = this.shelfId; + // For `arcadeDownloadPackCard` the same `ArcadeDownloadPackCard` model is used to render lockup placeholders. + const card = new models.ArcadeDownloadPackCard(); + card.numberOfPlaceholders = expectedItemCount; + shelf.items = [card]; + // See `shelfFetchShouldMergeWhenFetched` where `shelfToken.shelfStyle` is used to override `shelf.mergeWhenFetched` flag. + shelfToken.shelfStyle = shelf.contentType; + return shelf; + } + smallLockupsPlaceholderShelf(objectGraph, shelfToken, downloadPackApps) { + const shelf = new models.Shelf("smallLockup"); + shelf.id = this.shelfId; + shelf.items = []; + shelf.isHorizontal = true; + shelf.rowsPerColumn = 2; + // Use standard small lockup placeholders for iPad shelf. + shelf.items = Array(downloadPackApps.length).fill(new models.Placeholder()); + shelf.placeholderContentType = shelf.contentType; + shelf.contentType = "placeholder"; + shelfToken.showingPlaceholders = true; + shelfToken.remainingItems = downloadPackApps.map((value) => { + return { + id: value.adamId, + type: "apps", + }; + }); + return shelf; + } + downloadPackShelf(objectGraph, shelfToken, shelfData) { + const categories = this.categoriesMapFromResponse(objectGraph, shelfData.categoriesContents, shelfData.apps); + const useCustomShelf = this.useCustomDownloadPackCardShelf(objectGraph); + const contentType = useCustomShelf ? "arcadeDownloadPackCard" : "smallLockup"; + const apps = this.lockupsFromResponse(objectGraph, shelfToken, categories, useCustomShelf, // In the custom `arcadeDownloadPack` shelf app lockups the game category goes into lockup header, otherwise, in a standard shelf, it goes into subtitle. + useCustomShelf, // In the custom `arcadeDownloadPack` shelf app lockups have dark environment. + content.artworkUseCaseFromShelfStyle(objectGraph, contentType), isSome(shelfData.shelfContents) ? shelfData.shelfContents : [], shelfData.apps); + const shelf = new models.Shelf(contentType); + shelf.id = this.shelfId; + shelf.title = shelfData.title; + if (useCustomShelf) { + const card = new models.ArcadeDownloadPackCard(); + card.lockups = apps; + shelf.items = [card]; + } + else { + shelf.items = apps; + shelf.isHorizontal = true; + shelf.rowsPerColumn = 2; + } + shelf.isHidden = apps.length === 0; + return shelf; + } + // On iPhone there is a bespoke 'arcadeDownloadPackCard' games card shelf. + // On iPad, always use `smallLockup` instead of `shelfToken.shelfStyle` from server + // to make editorial configuration work easier. + useCustomDownloadPackCardShelf(objectGraph) { + return objectGraph.client.isPhone; + } + shelfRefreshURL(shelfToken) { + return urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)) + .param(Parameters.isArcadeDownloadPackShelfPlaceholder, "true") + .build(); + } + lockupsFromResponse(objectGraph, shelfToken, categoryTitlesForAdamIDs, showCategoryHeader, darkEnvironment, artworkUseCase, responseData, records) { + return validation.context("lockupsFromResponse", () => { + const lockupDataMap = new Map(); + for (const lockupData of responseData) { + lockupDataMap.set(lockupData.id, lockupData); + } + const locationTracker = shelfToken.metricsLocationTracker; + const pageInformation = shelfToken.metricsPageInformation; + // Order of the apps from the local storage (records) must be preserved, + // so user always sees the same list of the apps like it was on the onboarding game suggestions screen. + const items = []; + for (const app of records) { + const lockupData = lockupDataMap.get(app.adamId); + if (isNothing(lockupData)) { + continue; + } + const lockup = lockups.lockupFromData(objectGraph, lockupData, { + offerStyle: darkEnvironment ? "transparent" : undefined, + offerEnvironment: darkEnvironment ? "dark" : undefined, + metricsOptions: { + pageInformation: pageInformation, + locationTracker: locationTracker, + badges: { + categoryId: app.categoryId, + }, + }, + metricsClickOptions: { + id: lockupData.id, + pageInformation: pageInformation, + locationTracker: locationTracker, + badges: { + categoryId: app.categoryId, + }, + }, + artworkUseCase: artworkUseCase, + shouldHideArcadeHeader: true, + isSubtitleHidden: showCategoryHeader, + }); + if (isNothing(lockup)) { + continue; + } + lockups.cleanupArcadeDownloadPackLockupMetricsIfNeeded(lockup, objectGraph); + metricsHelpersLocation.nextPosition(locationTracker); + if (showCategoryHeader) { + lockup.heading = categoryTitlesForAdamIDs.get(lockup.adamId); + } + else { + lockup.subtitle = categoryTitlesForAdamIDs.get(lockup.adamId); + } + items.push(lockup); + } + return items; + }); + } + categoriesMapFromResponse(objectGraph, responseData, records) { + const result = new Map(); + for (const app of records) { + const category = responseData.find((data) => data.id === app.categoryId); + if (!category) { + continue; + } + const editorialNotesName = content.editorialNotesFromData(objectGraph, category, "name"); + result.set(app.adamId, editorialNotesName); + } + return result; + } + shelfTitle(objectGraph, subscriptionStatus) { + return objectGraph.loc.string(subscriptionStatus === "new" ? "Arcade.DownloadPack.ShelfTitle.NewUser" : "Arcade.DownloadPack.ShelfTitle"); + } + shelfMetrics(title, shelfToken) { + return { + id: shelfToken.id, + kind: null, + softwareType: "Arcade", + targetType: "swoosh", + title: title, + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + idType: "its_contentId", + fcKind: shelfToken.featuredContentId, + }; + } +} +//# sourceMappingURL=arcade-download-pack-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-app-event-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-app-event-shelf-controller.js new file mode 100644 index 0000000..6acbb2a --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-app-event-shelf-controller.js @@ -0,0 +1,260 @@ +import { isSome } from "@jet/environment"; +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaDataFetching from "../../../foundation/media/data-fetching"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import { fetchData } from "../../../foundation/media/network"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as appPromotionCommon from "../../app-promotions/app-promotions-common"; +import * as metricsHelpersUtil from "../../metrics/helpers/util"; +import * as onDevicePersonalization from "../../personalization/on-device-personalization"; +import * as placeholders from "../../placeholders/placeholders"; +import { addVariantParametersToRequestForItems } from "../../product-page/product-page-variants"; +import * as refresh from "../../refresh/page-refresh-controller"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { attributeAsBooleanOrFalse } from "../../../foundation/media/attributes"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import { shelfTitleAttributePathForFeaturedContentId } from "./grouping-shelf-controller-common"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +export class GroupingAppEventShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingAppEventShelfController"); + this.supportedFeaturedContentIds = new Set([ + 519 /* groupingTypes.FeaturedContentID.AppStore_AppEventsMarker */, + 518 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedAppEventsMarker */, + ]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + var _a; + if (((_a = shelfToken.recommendationsHref) === null || _a === void 0 ? void 0 : _a.length) > 0) { + try { + const mediaApiData = await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters); + const shelfResponseData = mediaDataStructure.dataFromDataContainer(objectGraph, mediaApiData); + const shelfContents = this.initialShelfDataFromGroupingMediaApiData(objectGraph, shelfResponseData); + shelfContents.responseTimingValues = mediaApiData[ResponseMetadata.timingValues]; + const title = mediaAttributes.attributeAsString(shelfResponseData, shelfTitleAttributePathForFeaturedContentId(objectGraph, shelfToken.featuredContentId)); + if (serverData.isDefinedNonNull(title) && (title === null || title === void 0 ? void 0 : title.length) > 0) { + shelfContents.shelfTitle = title; + } + return shelfContents; + } + catch { + return { shelfContents: [] }; + } + } + else { + const appEvents = []; + const contingentItems = []; + const offerItems = []; + for (const remainingItem of shelfToken.remainingItems) { + switch (remainingItem.type) { + case "contingent-items": + contingentItems.push(remainingItem); + break; + case "offer-items": + offerItems.push(remainingItem); + break; + case "app-events": + appEvents.push(remainingItem); + break; + default: + break; + } + } + const appEventsRequest = new mediaDataFetching.Request(objectGraph, appEvents); + addVariantParametersToRequestForItems(objectGraph, appEventsRequest, appEvents); + const contingentItemsRequest = new mediaDataFetching.Request(objectGraph, contingentItems); + addVariantParametersToRequestForItems(objectGraph, contingentItemsRequest, contingentItems); + const offerItemsRequest = new mediaDataFetching.Request(objectGraph, offerItems); + addVariantParametersToRequestForItems(objectGraph, offerItemsRequest, offerItems); + const hydrationResults = await Promise.all([ + this.fetchRemainingItems(objectGraph, appEventsRequest), + this.fetchRemainingItems(objectGraph, contingentItemsRequest), + this.fetchRemainingItems(objectGraph, offerItemsRequest), + ]); + const hydratedDataMap = { ...hydrationResults[0], ...hydrationResults[1], ...hydrationResults[2] }; + const shelfContents = []; + for (const remainingItem of shelfToken.remainingItems) { + const data = hydratedDataMap[remainingItem.id]; + if (isSome(data)) { + shelfContents.push(data); + } + } + groupingShelfControllerCommon.flushRequestedItemsFromShelfToken(shelfToken, new Set([...offerItemsRequest.ids, ...contingentItemsRequest.ids, ...appEventsRequest.ids])); + return { shelfContents: shelfContents }; + } + } + async fetchRemainingItems(objectGraph, remainingItemsRequest) { + const apiDataMap = {}; + const addDataToMap = (data) => { + for (const item of data.data) { + apiDataMap[item.id] = item; + } + }; + if (remainingItemsRequest.ids.size > 0) { + groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, remainingItemsRequest); + try { + await fetchData(objectGraph, remainingItemsRequest).then((mediaApiData) => { + addDataToMap(mediaApiData); + }); + } + catch (fetchError) { + objectGraph.console.error("Error fetching remaining items", remainingItemsRequest.ids); + } + } + return apiDataMap; + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + const shouldPersonalizeData = baseShelfToken.featuredContentId === 518 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedAppEventsMarker */; + let personalizedDataResult = null; + const shelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData); + if (shouldPersonalizeData && serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) { + personalizedDataResult = this.personalizedDataResultFromDataItems(objectGraph, shelfData.shelfContents); + } + const appEventShelfToken = { + ...baseShelfToken, + shouldPersonalizeData: shouldPersonalizeData, + personalizedDataResult: personalizedDataResult, + }; + const hasContents = serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents); + const shelfPersonalizationAvailable = !attributeAsBooleanOrFalse(mediaApiData, "noPersonalizationAvailable"); + if (!hasContents && shelfPersonalizationAvailable) { + appEventShelfToken.recommendationsHref = mediaApiData.href; + appEventShelfToken.isValidRecommendationsShelf = true; + } + else { + appEventShelfToken.isValidRecommendationsShelf = hasContents; + } + return appEventShelfToken; + } + // endregion + // region Metrics + /** + * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping + * page controller + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the + * grouping page controller + */ + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + const shelfMetricsOptions = { ...baseMetricsOptions }; + if (serverData.isDefinedNonNullNonEmpty(shelfToken.personalizedDataResult)) { + const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(baseMetricsOptions.recoMetricsData, shelfToken.personalizedDataResult.processingType, null); + shelfMetricsOptions.recoMetricsData = recoMetricsData; + } + return shelfMetricsOptions; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + var _a; + if (!appPromotionCommon.appEventsAreEnabled(objectGraph)) { + return null; + } + if (!shelfToken.isValidRecommendationsShelf) { + return null; + } + const metricsOptions = { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: shelfToken.metricsPageInformation.recoMetricsData, + }; + // First personalize the data, as this may cause things to be re-ordered + let sortedShelfContents = shelfData.shelfContents; + // Only try to use the personalized data the first render, second renders the shelfContents has already been personalized + if (serverData.isDefinedNonNullNonEmpty(shelfToken.personalizedDataResult) && shelfToken.isFirstRender) { + sortedShelfContents = shelfToken.personalizedDataResult.personalizedData; + } + // Now find which items are hydrated, and add the rest to the token + const hydratedShelfContents = []; + for (const data of sortedShelfContents) { + if (serverData.isNull(data.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(data); + continue; + } + hydratedShelfContents.push(data); + } + // Now create our shelf from the hydrated items + const displayableAppEvents = appPromotionCommon.appPromotionsFromData(objectGraph, hydratedShelfContents, null, false, false, metricsOptions, false, true, shelfToken.isArcadePage, false); + refresh.addNextPreferredContentRefreshDate(displayableAppEvents.nextAppEventPromotionStartDate, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.refreshController); + const appPromotions = displayableAppEvents.appPromotions; + const shelfType = "appPromotion"; + const shelf = new models.Shelf(shelfType); + shelf.isHorizontal = true; + shelf.title = (_a = shelfData.shelfTitle) !== null && _a !== void 0 ? _a : shelfToken.title; + shelf.items = appPromotions; + const willHydrateShelfLater = serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender; + if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) { + placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId); + } + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + if (serverData.isNullOrEmpty(shelf.items) && serverData.isNullOrEmpty(shelf.url)) { + return shelfToken.isFirstRender ? null : GroupingAppEventShelfController.makeHiddenShelf(shelfToken); + } + return shelf; + } + // region Helpers + /** + * Personalizes the input list of app event data items. + * @param objectGraph + * @param dataItems The array of app event data items + */ + personalizedDataResultFromDataItems(objectGraph, dataItems) { + // Collect the app IDs we are interested in + const appIds = new Set(); + for (const data of dataItems) { + const appId = serverData.asString(data, "meta.personalizationData.appId"); + if ((appId === null || appId === void 0 ? void 0 : appId.length) > 0) { + appIds.add(appId); + } + } + // Now personalize the data + const personalizationDataContainer = onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds); + return onDevicePersonalization.personalizeDataItems(objectGraph, "groupingAppEvent", dataItems, false, personalizationDataContainer, null, null, null, true); + } + static makeHiddenShelf(shelfToken) { + const hiddenShelf = new models.Shelf(shelfToken.shelfStyle); + hiddenShelf.isHidden = true; + return hiddenShelf; + } +} +//# sourceMappingURL=grouping-app-event-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller.js new file mode 100644 index 0000000..d9f324d --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller.js @@ -0,0 +1,160 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaNetwork from "../../../foundation/media/network"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import * as color from "../../../foundation/util/color-util"; +import * as arcadeCommon from "../../arcade/arcade-common"; +import * as content from "../../content/content"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingArcadeFooterShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingArcadeFooterShelfController"); + this.supportedFeaturedContentIds = new Set([-1 /* groupingTypes.FeaturedContentID.Native_GroupingShelf */]); + this.supportedNativeGroupingShelfIds = new Set([1 /* groupingTypes.NativeGroupingShelfID.Arcade_SeeAllGamesFooter */]); + } + // endregion + // region Metrics + shouldImpressShelf() { + return false; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { + shelfContents: mediaDataStructure.dataCollectionFromResultsListContainer(mediaApiData), + responseTimingValues: null, + }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + const footerRequest = arcadeCommon.arcadeAppsRequestForIcons(objectGraph, this.numberOfIconsForArcadeAppGrid(objectGraph.client.deviceType)); + return await mediaNetwork + .fetchData(objectGraph, footerRequest) + .then((mediaApiData) => { + const shelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData); + shelfData.responseTimingValues = mediaApiData[ResponseMetadata.timingValues]; + return shelfData; + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + const footerShelfToken = { + ...baseShelfToken, + shouldIncludeShelfUrl: baseShelfToken.isFirstRender, + }; + footerShelfToken.showingPlaceholders = baseShelfToken.isFirstRender; + return footerShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const footer = new models.ArcadeFooter(); + const shelf = new models.Shelf("arcadeFooter"); + shelf.items = [footer]; + const impressionOptions = { + targetType: "arcadeSeeAllGamesFooter", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + title: objectGraph.loc.string("Arcade.SeeAllGames.Button.Title"), + id: shelfToken.id, + kind: "footer", + softwareType: "Arcade", + }; + metricsHelpersImpressions.addImpressionFields(objectGraph, footer, impressionOptions); + metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, impressionOptions.title); + footer.buttonAction = arcadeCommon.seeAllArcadeGamesPageFlowAction(objectGraph, "releaseDate", shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker); + const buttonImpressionOptions = { + targetType: "button", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + title: footer.buttonAction.title, + id: "arcade-see-all-games-button", + kind: "button", + softwareType: "Arcade", + }; + metricsHelpersImpressions.addImpressionFields(objectGraph, footer.buttonAction, buttonImpressionOptions); + metricsHelpersLocation.popLocation(impressionOptions.locationTracker); + const termsAndConditionsUrl = objectGraph.bag.termsAndConditionsURL; + const shouldAddTermsAndConditions = !serverData.isNull(termsAndConditionsUrl) && objectGraph.client.deviceType !== "tv"; + if (objectGraph.client.isiOS) { + if (shouldAddTermsAndConditions) { + const termsAndConditionTitle = objectGraph.loc.string("TermsAndConditions.Title"); + const urlAction = new models.ExternalUrlAction(termsAndConditionsUrl); + const footnote = new models.Footnote(termsAndConditionTitle); + footnote.clickAction = urlAction; + footnote.presentationStyle = ["hasChevron", "textLightensOnHighlight", "hasSeparator"]; + footer.footnote = footnote; + } + shelf.background = { + type: "color", + color: color.named("placeholderBackground"), + }; + } + if (serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) { + const metricsOptions = { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }; + footer.icons = content.impressionableAppIconsFromDataCollection(objectGraph, shelfData.shelfContents, metricsOptions, { + useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */, + }); + } + else { + footer.icons = []; + } + if (shelfToken.shouldIncludeShelfUrl) { + shelf.url = groupingShelfControllerCommon.groupingShelfUrl(shelfToken); + } + return shelf; + } + // region Helpers + /** + * The maximum number of icons that can be displayed in a device's icon grid, i.e. Arcade Grid Footer. + * This is used to limit the number of icon data to fetch to what we'll actually use. + * Note that views are expected to handle situations where we have less than the max number of icons. + * + * @param deviceType The device type that arcade icon grid is being displayed in. + * @returns The maximum number of icons to fetch. + */ + numberOfIconsForArcadeAppGrid(deviceType) { + switch (deviceType) { + case "phone": + return 9; + default: // iPad, provided `footerShowsIconsForPlatform == true`. + return 20; + } + } +} +//# sourceMappingURL=grouping-arcade-footer-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-brick-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-brick-shelf-controller.js new file mode 100644 index 0000000..671aac3 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-brick-shelf-controller.js @@ -0,0 +1,313 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as content from "../../content/content"; +import * as flowPreview from "../../content/flow-preview"; +import * as lockupsEditorialContext from "../../lockups/editorial-context"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as placeholders from "../../placeholders/placeholders"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import * as metricsLocation from "../../../common/metrics/helpers/location"; +import { defaultLayoutSize } from "../../../foundation/media/data-fetching"; +import * as color from "../../../foundation/util/color-util"; +import { isSome } from "@jet/environment"; +export class GroupingBrickShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingBrickShelfController"); + this.supportedFeaturedContentIds = new Set([ + 421 /* groupingTypes.FeaturedContentID.AppStore_BrickRow */, + 422 /* groupingTypes.FeaturedContentID.AppStore_Brick */, + 423 /* groupingTypes.FeaturedContentID.AppStore_CustomBrick */, + 261 /* groupingTypes.FeaturedContentID.Sundance_BrickRow */, + ]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "children") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters).then((shelfData) => { + return { + shelfContents: groupingShelfControllerCommon.mergeContentDataIntoEditorialData(shelfData.shelfContents, mediaRelationship.relationshipCollection(shelfToken.featuredContentData, "children")), + }; + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + // If suppress text is not provided, default to hiding. + let suppressText = mediaAttributes.attributeAsBoolean(mediaApiData, "suppressText"); + if (serverData.isNull(suppressText)) { + suppressText = true; + } + const brickShelfToken = { + ...baseShelfToken, + showSupplementaryText: !suppressText, + }; + brickShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData); + return brickShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const items = []; + const remainingBricks = []; // Data where metadata was missing. + const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle"); + const isCategoryBrick = displayStyle === "small"; + let shelf; + if (isCategoryBrick) { + // i.e. category bricks shelf + shelf = new models.Shelf("categoryBrick"); + const layoutSize = serverData.asNumber(shelfToken.featuredContentData.attributes, "layoutStyle.layoutSize"); + shelf.rowsPerColumn = layoutSize !== null && layoutSize !== void 0 ? layoutSize : defaultLayoutSize(objectGraph); + metricsLocation.currentLocation(shelfToken.metricsLocationTracker).name = "Browse Categories"; + } + else { + // i.e. "medium", also the default bricks shelf + shelf = new models.Shelf("brick"); + } + shelf.isHorizontal = true; + for (const brickData of shelfData.shelfContents) { + const brickModel = GroupingBrickShelfController.createBrick(objectGraph, brickData, isCategoryBrick, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker, shelfToken, groupingParseContext); + if (!brickModel) { + remainingBricks.push(brickData); + continue; + } + items.push(brickModel); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + if (serverData.isDefinedNonNull(shelfToken.presentationHints)) { + shelf.presentationHints = shelfToken.presentationHints; + } + if (serverData.isDefinedNonNull(shelfToken.showSupplementaryText)) { + shelf.presentationHints = { + ...shelf.presentationHints, + showSupplementaryText: shelfToken.showSupplementaryText, + }; + } + // We don't need this in our incomplete shelf URL, so we'll preemptively remove it. + delete shelfToken.maxItemCount; + // Override `featuredContentData` to only have remaining bricks that must be fetched. + if (serverData.isDefinedNonNull(serverData.traverse(shelfToken.featuredContentData, "relationships.children.data"))) { + shelfToken.featuredContentData.relationships["children"].data = remainingBricks; + } + // Set metadata + shelf.title = shelfToken.title; + shelf.subtitle = shelfToken.subtitle; + if (isCategoryBrick) { + const displayCount = serverData.asNumber(shelfToken.featuredContentData.attributes, "displayCount"); + shelf.items = items.slice(0, displayCount !== null && displayCount !== void 0 ? displayCount : items.length); + } + else { + shelf.items = items; + } + // See all + const hasSeeAll = serverData.asBooleanOrFalse(shelfToken.featuredContentData.attributes, "hasSeeAll"); + if (isCategoryBrick && hasSeeAll && !objectGraph.client.isWeb) { + // Setup shelf + const seeAllShelf = new models.Shelf("categoryBrick"); + seeAllShelf.items = this.sortCategories(objectGraph, items); + seeAllShelf.presentationHints = { isSeeAllContext: true }; + // Setup Page + const seeAllPage = new models.GenericPage([seeAllShelf]); + seeAllPage.title = shelfToken.title; + // Setup action + const seeAllAction = new models.FlowAction("page"); + seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL"); + seeAllAction.pageData = seeAllPage; + // Connect action + shelf.seeAllAction = seeAllAction; + // Metrics + metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, null, { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }); + } + // If no items should we display placeholders for this shelf? + const willHydrateShelfLater = shelf && serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender; + if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) { + placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId); + } + if (!willHydrateShelfLater && + GroupingBrickShelfController.shouldDisplayChooseYourFavoritesBrick(objectGraph, shelfToken, isCategoryBrick, groupingParseContext)) { + const brickModel = GroupingBrickShelfController.createChooseYourFavoritesBrick(objectGraph, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker); + shelf.items.splice(0, 0, brickModel); + } + shelfToken.presentationHints = shelf.presentationHints; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + return shelf; + } + // region Static Helpers + // Whether to display Choose Your Favorites brick. + // It will return TRUE when: + // - This is an Arcade page + // - This is a Category brick + // - Feature flag is enabled + // - The user is subscribed or trial enrolled + static shouldDisplayChooseYourFavoritesBrick(objectGraph, shelfToken, isCategoryBrick, groupingParseContext) { + return (shelfToken.isArcadePage && + isCategoryBrick && + objectGraph.featureFlags.isEnabled("arcade_choose_your_favorites_brick_Future") && + isSome(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.additionalShelfParameters) && + ((groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.additionalShelfParameters.isSubscribed) === "true" || + (groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.additionalShelfParameters.isTrialEnrolled) === "true")); + } + static createBrick(objectGraph, brickData, searchCategoryBricks, metricsPageInformation, metricsLocationTracker, shelfToken, groupingParseContext) { + const metricsOptions = { + targetType: searchCategoryBricks ? "tile" : "brick", + pageInformation: metricsPageInformation, + locationTracker: metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(brickData), + }; + const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, brickData, shelfToken, false, null, metricsOptions, groupingParseContext); + if (!metadata) { + return null; + } + const brickModel = new models.Brick(); + // Setup Artwork + const artworkOptions = { + useCase: 18 /* content.ArtworkUseCase.GroupingBrick */, + }; + if (searchCategoryBricks) { + const artworks = content.searchChartOrCategoryArtworkFromData(objectGraph, metadata.content, content.SearchChartOrCategoryBrickUseCase.categoryBreakout, models.GenericSearchPageShelfDisplayStyleDensity.Density1); + brickModel.artworks = artworks; + } + else if (metadata.artwork && + (shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.featuredContentId) !== 261 /* groupingTypes.FeaturedContentID.Sundance_BrickRow */) { + let artworkDict = serverData.asDictionary(metadata.artwork, "subscriptionHero"); + if (!artworkDict) { + artworkDict = serverData.asDictionary(metadata.artwork, "originalFlowcaseBrick"); + } + const artwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, artworkDict, artworkOptions); + brickModel.artworks = [artwork]; + } + else { + const artwork = groupingShelfControllerCommon.artworkFromFC(objectGraph, brickData, 1060, 520, artworkOptions); + brickModel.artworks = [artwork]; + } + brickModel.accessibilityLabel = metadata.title; + // Set supplementary text. + brickModel.shortEditorialDescription = metadata.title; + // Set action + brickModel.clickAction = metadata.action; + // Set personalization + const brickFeaturedContentId = mediaAttributes.attributeAsNumber(brickData, "editorialElementKind"); + if (brickFeaturedContentId === 435 /* groupingTypes.FeaturedContentID.AppStore_MSOBrickMarker */) { + brickModel.personalizationStyle = "mso"; + } + // Set flow preview actions + const contentData = mediaRelationship.relationshipData(objectGraph, brickData, "contents"); + if (serverData.isDefinedNonNull(contentData)) { + const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, contentData, metricsOptions); + brickModel.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, brickData, true, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride, brickModel.clickAction, metricsOptions, metricsClickOptions); + } + // Configure impressions + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, brickData, metadata.title, metricsOptions); + metricsHelpersImpressions.addImpressionFields(objectGraph, brickModel, impressionOptions); + // Safe area + brickModel.artworkSafeArea = models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea; + brickModel.textSafeArea = models.ChartOrCategorySafeArea.defaultTileTextSafeArea; + if (!brickModel.isValid()) { + return null; + } + return brickModel; + } + /// Creates and returns Choose Your Favorites brick. + static createChooseYourFavoritesBrick(objectGraph, metricsPageInformation, metricsLocationTracker) { + const brickModel = new models.Brick(); + // Artwork + const artwork = new models.Artwork("", 1060, 520, [], color.fromHex("efac78")); + brickModel.artworks = [artwork]; + // Labels + const title = objectGraph.loc.string("ARCADE_CHOOSE_YOUR_FAVORITES_BRICK_TITLE"); + brickModel.accessibilityLabel = title; + brickModel.shortEditorialDescription = title; + // Action + const flowAction = new models.FlowAction("arcadeDownloadPackCategories"); + flowAction.presentationContext = "presentModalFormSheet"; + flowAction.pageData = "unknown"; + brickModel.clickAction = flowAction; + // Impressions + const metricsOptions = { + targetType: "brick", + pageInformation: metricsPageInformation, + locationTracker: metricsLocationTracker, + recoMetricsData: null, + }; + const impressionOptions = metricsHelpersImpressions.impressionOptionsForArcadeChooseYourFavoritesBrick(metricsOptions); + metricsHelpersImpressions.addImpressionFields(objectGraph, brickModel, impressionOptions); + // Safe area + brickModel.artworkSafeArea = models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea; + brickModel.textSafeArea = models.ChartOrCategorySafeArea.defaultTileTextSafeArea; + return brickModel; + } + // endregion + // region Metrics + /** + * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping + * page controller + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the + * grouping page controller + */ + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + const shelfMetricsOptions = { ...baseMetricsOptions }; + const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle"); + // If this is a Category Bricks shelf, configure metrics title accordingly. + if (displayStyle === "small") { + shelfMetricsOptions.title = "Browse Categories"; + } + return shelfMetricsOptions; + } + // endregion + // region Sort + sortCategories(objectGraph, categories) { + return categories.sort((category1, category2) => { + try { + return category1.shortEditorialDescription.localeCompare(category2.shortEditorialDescription, objectGraph.loc.safeIdentifier, { + usage: "sort", + }); + } + catch (e) { + return 0; + } + }); + } +} +//# sourceMappingURL=grouping-brick-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-category-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-category-shelf-controller.js new file mode 100644 index 0000000..38d8324 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-category-shelf-controller.js @@ -0,0 +1,207 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as content from "../../content/content"; +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 { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingCategoryShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingCategoryShelfController"); + this.supportedFeaturedContentIds = new Set([425 /* groupingTypes.FeaturedContentID.AppStore_GenreStack */]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + const childListBoxes = mediaRelationship.relationshipData(objectGraph, mediaApiData, "children"); + if (childListBoxes) { + return { shelfContents: mediaRelationship.relationshipCollection(childListBoxes, "children") }; + } + else { + return { shelfContents: [] }; + } + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters).then((shelfData) => { + const childListBoxes = mediaRelationship.relationshipData(objectGraph, shelfToken.featuredContentData, "children"); + if (childListBoxes) { + const hydratedListBoxSections = groupingShelfControllerCommon.mergeContentDataIntoEditorialData(shelfData.shelfContents, mediaRelationship.relationshipCollection(childListBoxes, "children")); + return { shelfContents: hydratedListBoxSections }; + } + else { + return { shelfContents: [] }; + } + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + var _a; + if (serverData.isNullOrEmpty(shelfData.shelfContents)) { + return null; + } + const items = []; + let itemsHaveRectangularArtwork = false; + for (const category of shelfData.shelfContents) { + const grouping = mediaRelationship.relationshipData(objectGraph, category, "grouping"); + if (serverData.isNull(grouping)) { + continue; + } + const adamId = groupingShelfControllerCommon.contentIdFromContentItem(objectGraph, grouping); + if (serverData.isNull(category.attributes) || + serverData.isNull(grouping.attributes) || + groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(grouping); + continue; + } + const metricsBase = { + targetType: "listItem", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(category), + }; + const action = lockups.actionFromData(objectGraph, grouping, { ...metricsBase, id: adamId }, shelfToken.clientIdentifierOverride); + metricsHelpersImpressions.addImpressionFields(objectGraph, action, { + ...metricsBase, + kind: "link", + softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null, + title: action.title, + id: adamId, + }); + const artwork = mediaAttributes.attributeAsDictionary(grouping, "artwork"); + if (artwork.width > artwork.height) { + itemsHaveRectangularArtwork = true; + } + if (serverData.isDefinedNonNullNonEmpty(artwork)) { + action.artwork = content.artworkFromApiArtwork(objectGraph, artwork, { + allowingTransparency: true, + useCase: 20 /* content.ArtworkUseCase.CategoryIcon */, + }); + } + items.push(action); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + // The top level shelf on mac should always be sorted + // <rdar://problem/40954563> LOC: Global: MAS: Order of categories appears in English alphabetical order instead of in each language's alphabetical order. + if (objectGraph.client.isMac) { + this.sortCategories(objectGraph, items); + } + const shelf = this.shelfForCategoryActions(objectGraph, items, shelfToken); + shelf.title = shelfToken.title; + shelf.subtitle = shelfToken.subtitle; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + if (itemsHaveRectangularArtwork) { + const existingPresentationHints = (_a = shelf.presentationHints) !== null && _a !== void 0 ? _a : {}; + shelf.presentationHints = { + ...existingPresentationHints, + itemsHaveRectangularArtwork: true, + }; + } + return shelf; + } + shelfForCategoryActions(objectGraph, categories, shelfToken) { + const shelf = new models.Shelf("action"); + // Limit for number of items (`null` when there's no limit). + let itemLimit; + // Configure shelf orientation and limit + switch (objectGraph.client.deviceType) { + case "tv": + shelf.isHorizontal = true; + itemLimit = 8; + break; + case "mac": + shelf.isHorizontal = false; + itemLimit = null; + break; + case "web": + shelf.isHorizontal = true; + shelf.rowsPerColumn = 3; + itemLimit = 24; + break; + default: + shelf.isHorizontal = false; + itemLimit = 6; + break; + } + // Apply limit (if any) + if (itemLimit !== null && categories.length > itemLimit) { + shelf.items = categories.slice(0, itemLimit); + const allCategoriesAction = new models.FlowAction("page"); + allCategoriesAction.title = objectGraph.loc.string("ACTION_SEE_ALL"); + metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, allCategoriesAction, null, { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }); + shelf.seeAllAction = allCategoriesAction; + const allCategoriesShelf = new models.Shelf("action"); + allCategoriesShelf.isHorizontal = false; + this.sortCategories(objectGraph, categories); + allCategoriesShelf.items = categories; + const allCategoriesPage = new models.GenericPage([allCategoriesShelf]); + allCategoriesPage.title = objectGraph.loc.string("PAGE_TITLE_CATEGORIES"); + allCategoriesAction.pageData = allCategoriesPage; + } + else { + shelf.items = categories; + } + return shelf; + } + // region Helpers + /** + * Sort the category actions alphabetically + * @param objectGraph + * @param categories The cateogries we're going to be displaying + */ + sortCategories(objectGraph, categories) { + categories.sort((category1, category2) => { + try { + return category1.title.localeCompare(category2.title, objectGraph.loc.safeIdentifier, { + usage: "sort", + }); + } + catch (e) { + return 0; + } + }); + } +} +//# sourceMappingURL=grouping-category-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-card-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-card-shelf-controller.js new file mode 100644 index 0000000..12dc94f --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-card-shelf-controller.js @@ -0,0 +1,229 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as color from "../../../foundation/util/color-util"; +import * as flowPreview from "../../content/flow-preview"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as metricsHelpersUtil from "../../metrics/helpers/util"; +import * as onDevicePersonalization from "../../personalization/on-device-personalization"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingEditorialCardShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingEditorialCardShelfController"); + this.supportedFeaturedContentIds = new Set([ + 415 /* groupingTypes.FeaturedContentID.AppStore_HeroList */, + 416 /* groupingTypes.FeaturedContentID.AppStore_Hero */, + 501 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedHeroMarker */, + 417 /* groupingTypes.FeaturedContentID.AppStore_CustomHero */, + 258 /* groupingTypes.FeaturedContentID.Sundance_Flowcase */, + ]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "children") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const shelf = new models.Shelf("editorialCard"); + shelf.isHorizontal = true; + const personalizationDataContainer = this.personalizationDataContainerForEditorialCardItemsDataArray(objectGraph, shelfData.shelfContents); + const items = []; + for (const data of shelfData.shelfContents) { + const card = GroupingEditorialCardShelfController.makeEditorialCard(objectGraph, data, personalizationDataContainer, groupingParseContext, shelfToken); + if (serverData.isNullOrEmpty(card) || !card.isValid()) { + continue; + } + items.push(card); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + // We don't need this in our incomplete shelf URL, so we'll preemptively remove it. + delete shelfToken.maxItemCount; + if (objectGraph.client.isVision) { + shelf.presentationHints = { ...shelf.presentationHints, showSupplementaryText: true }; + } + shelf.items = items; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + return shelf; + } + static makeEditorialCard(objectGraph, itemData, personalizationDataContainer, groupingParseContext, shelfToken) { + var _a, _b, _c; + const metricsOptions = { + targetType: "hero", + pageInformation: shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsPageInformation, + locationTracker: shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData), + id: itemData.id, + idType: "editorial_id", + }; + const featuredContentId = mediaAttributes.attributeAsNumber(itemData, "editorialElementKind"); + const shouldPersonalizeContent = featuredContentId === 501 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedHeroMarker */; + const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, itemData, shelfToken, shouldPersonalizeContent, personalizationDataContainer, metricsOptions, groupingParseContext, () => { + shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.remainingItems.push(itemData); + }); + if (!metadata) { + return null; + } + const hasContentId = ((_b = (_a = metadata.content) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.length) > 0; + if (hasContentId) { + metricsOptions.id = metadata.content.id; + metricsOptions.idType = "its_id"; + metricsOptions.adamId = metadata.content.id; + } + const card = new models.EditorialCard(); + // Set caption + let caption = mediaAttributes.attributeAsString(itemData, "designBadge"); + if (!caption) { + caption = metadata.caption; + } + card.caption = caption; + // Set title + let title = mediaAttributes.attributeAsString(itemData, "title"); + if (!title) { + title = metadata.title; + } + card.title = title; + // Set subtitle + let subtitle = groupingShelfControllerCommon.unescapeHtmlString(mediaAttributes.attributeAsString(itemData, "designTag")); + if (!subtitle) { + subtitle = metadata.subtitle; + } + card.subtitle = subtitle; + // Setup Artwork + const artworkOptions = { + useCase: 19 /* content.ArtworkUseCase.GroupingHero */, + withJoeColorPlaceholder: true, + }; + if (metadata.artwork && (shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.featuredContentId) !== 258 /* groupingTypes.FeaturedContentID.Sundance_Flowcase */) { + let artworkDict = serverData.asDictionary(metadata.artwork, "subscriptionHero"); + if (serverData.isNull(artworkDict) && serverData.isDefinedNonNull(metadata.appEvent)) { + artworkDict = serverData.asDictionary(metadata.artwork, "eventCard"); + } + card.artwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, artworkDict, artworkOptions); + } + else { + card.artwork = groupingShelfControllerCommon.artworkFromFC(objectGraph, itemData, 416, 204, artworkOptions); + } + // Set action + card.clickAction = metadata.action; + // App event formatted dates + if (serverData.isDefinedNonNull(metadata.appEvent)) { + card.appEventFormattedDates = metadata.appEvent.formattedDates; + } + // Lockup + card.lockup = metadata.lockup; + // Overlay style + if (serverData.isDefinedNonNull(card.artwork) && serverData.isDefinedNonNull(card.artwork.backgroundColor)) { + const isArtworkDark = color.isDarkColor(card.artwork.backgroundColor); + card.mediaOverlayStyle = isArtworkDark ? "dark" : "light"; + if (serverData.isDefinedNonNull(card.lockup) && + serverData.isDefinedNonNull(card.lockup.offerDisplayProperties) && + objectGraph.host.isiOS) { + const offerEnvironment = isArtworkDark ? "dark" : "light"; + card.lockup.offerDisplayProperties = + card.lockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "transparent", offerEnvironment); + } + } + // Set adamId + card.adamId = serverData.asString(metadata.content, "id"); + // Set flow preview actions + const contentData = mediaRelationship.relationshipData(objectGraph, itemData, "contents"); + if (serverData.isDefinedNonNull(contentData)) { + const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, contentData, metricsOptions); + metricsClickOptions.targetType = metricsOptions.targetType; + card.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, itemData, false, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride, card.clickAction, metricsOptions, metricsClickOptions); + } + // Configure impressions + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, (_c = metadata.content) !== null && _c !== void 0 ? _c : itemData, metadata.title, metricsOptions); + if (serverData.isDefinedNonNull(metadata.onDevicePersonalizationDataProcessingType)) { + const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(impressionOptions.recoMetricsData, metadata.onDevicePersonalizationDataProcessingType, null); + impressionOptions.recoMetricsData = recoMetricsData; + } + if (serverData.isDefinedNonNull(metadata.appEvent)) { + impressionOptions.inAppEventId = metadata.appEvent.appEventId; + if (serverData.isDefinedNonNull(metadata.appEvent.lockup)) { + impressionOptions.relatedSubjectIds = [metadata.appEvent.lockup.adamId]; + } + } + if (serverData.isDefinedNonNull(shelfToken)) { + metricsHelpersImpressions.addImpressionFields(objectGraph, card, impressionOptions); + } + return card; + } + // region Helpers + /** + * Iterates through all the editorial cards data, and creates a set of personalization data that is targetted only to these cards. + * + * @param objectGraph + * @param dataArray The input array of editorial card data items + * @returns Any relevant OnDevicePersonalizaionData + */ + personalizationDataContainerForEditorialCardItemsDataArray(objectGraph, dataArray) { + var _a, _b; + if (!onDevicePersonalization.isPersonalizationAvailable(objectGraph)) { + return null; + } + // First iterate through and extract any relevant App IDs from meta.personalizationData for each card's contents. + const appIds = new Set(); + for (const data of dataArray) { + const featuredContentId = mediaAttributes.attributeAsNumber(data, "editorialElementKind"); + const shouldPersonalizeContent = featuredContentId === 501 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedHeroMarker */; + const isLinkNode = ((_a = serverData.asString(data, "url")) === null || _a === void 0 ? void 0 : _a.length) > 0; + const containsLinkNode = ((_b = mediaAttributes.attributeAsString(data, "link.url")) === null || _b === void 0 ? void 0 : _b.length) > 0; + const isContentNode = mediaRelationship.hasRelationship(data, "contents", false); + if (shouldPersonalizeContent && !isLinkNode && !containsLinkNode && isContentNode) { + const contentDataItems = mediaRelationship.relationshipCollection(data, "contents"); + for (const contentData of contentDataItems) { + const appId = serverData.asString(contentData, "meta.personalizationData.appId"); + if ((appId === null || appId === void 0 ? void 0 : appId.length) > 0) { + appIds.add(appId); + } + } + } + } + return onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds); + } +} +//# sourceMappingURL=grouping-editorial-card-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-story-card-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-story-card-shelf-controller.js new file mode 100644 index 0000000..73a0725 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-story-card-shelf-controller.js @@ -0,0 +1,143 @@ +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 { ShelfParameters } from "../../../foundation/network/url-constants"; +import * as color from "../../../foundation/util/color-util"; +import * as content from "../../content/content"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { createTodayBaseCard } from "../../today/cards/today-base-card-builder"; +import { TodayParseContext } from "../../today/today-types"; +import { GroupingShelfController, routesForFeaturedContentIds } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingEditorialStoryCardShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingEditorialStoryCardShelfController"); + this.supportedFeaturedContentIds = new Set([475 /* groupingTypes.FeaturedContentID.AppStore_HorizontalCardSwoosh */]); + } + // endregion + // region Shelf Builder + shelfRoute(objectGraph) { + return routesForFeaturedContentIds(this.supportedFeaturedContentIds, [ + `${ShelfParameters.contentType}=editorialStoryCard`, + ]); + } + // endregion + // region GroupingShelfController + _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) { + return false; + } + const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + const contentType = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle); + return contentType === "editorialStoryCard"; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "children") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + const shelfToken = { ...baseShelfToken }; + const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + shelfToken.shelfStyle = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle); + return shelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const items = []; + for (const card of shelfData.shelfContents) { + if (!mediaAttributes.hasAttributes(card) || groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.remainingItems.push(card); + shelfToken.isDeferring = true; + continue; + } + const cardModel = GroupingEditorialStoryCardShelfController.makeStoryCard(objectGraph, card, shelfToken); + if (serverData.isNullOrEmpty(cardModel)) { + continue; + } + items.push(cardModel); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + const shelf = new models.Shelf(shelfToken.shelfStyle); + shelf.title = shelfToken.title; + shelf.items = items; + shelf.isHorizontal = true; + shelf.background = { + type: "interactive", + }; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + shelf.isHorizontal = true; + return shelf; + } + static makeStoryCard(objectGraph, itemData, shelfToken) { + // Prefer `subscriptionHero` if it's available for grouping pages, and fallback to `mediaCard` if not. + let artworkData = mediaAttributes.attributeAsDictionary(itemData, "editorialArtwork.subscriptionHero"); + if (serverData.isNullOrEmpty(artworkData)) { + artworkData = mediaAttributes.attributeAsDictionary(itemData, "editorialArtwork.mediaCard"); + } + const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, { + cropCode: "fn", + withJoeColorPlaceholder: true, + useCase: 16 /* content.ArtworkUseCase.TodaySmallStoryCard */, + }); + if (serverData.isNull(artwork)) { + return null; + } + const title = mediaAttributes.attributeAsString(itemData, "editorialNotes.name"); + const heading = mediaAttributes.attributeAsString(itemData, "label"); + const description = mediaAttributes.attributeAsString(itemData, "editorialNotes.short"); + const cardModel = new models.EditorialStoryCard(title, artwork, null, heading, { + type: "text", + title: heading, + }, description); + const basicCard = createTodayBaseCard(objectGraph, itemData, null, new TodayParseContext(shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsPageInformation, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsLocationTracker)); + if (serverData.isDefinedNonNull(basicCard)) { + cardModel.clickAction = basicCard.clickAction; + } + const backgroundStyle = color.isDarkColor(artwork.backgroundColor) + ? "dark" + : "light"; + cardModel.shelfBackground = { + type: "artwork", + artwork: artwork, + style: backgroundStyle, + }; + return cardModel; + } +} +//# sourceMappingURL=grouping-editorial-story-card-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-activity-feed-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-activity-feed-shelf-controller.js new file mode 100644 index 0000000..4fa27fe --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-activity-feed-shelf-controller.js @@ -0,0 +1,223 @@ +import * as models from "../../../api/models"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { ActionMetrics } from "../../../api/models"; +import { makeGameCenterHeader } from "../../arcade/arcade-common"; +export class GroupingGameCenterActivityFeedController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingGameCenterActivityFeedController"); + this.batchGroupKey = "gameCenter"; + this.supportedFeaturedContentIds = new Set([ + 548 /* groupingTypes.FeaturedContentID.AppStore_GameCenterActivityFeedMarker */, + ]); + } + // endregion + // region Shelf Builder + shelfRoute(objectGraph) { + return [ + ...super.shelfRoute(objectGraph), + { + protocol: Protocol.internal, + path: `/${Path.grouping}/${Path.shelf}/{token}`, + query: [Parameters.isGameCenterActivityFeedShelf], + }, + ]; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { + shelfContents: [], + activities: [], + }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + const filter = this.gameCategoryFilter(shelfToken.gamesFilter); + const activityLimit = 20; + return await objectGraph.gameCenter.fetchActivityFeedCards(filter, activityLimit).then((activities) => { + return { + shelfContents: [], + activities: activities, + }; + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + incompleteShelfFetchStrategy(objectGraph) { + return models.IncompleteShelfFetchStrategy.OnPageLoad; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (shelfToken.isFirstRender) { + return this.pendingActivityFeedShelfForGrouping(objectGraph, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage); + } + else { + return this.activityFeedShelfForGrouping(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage); + } + } + pendingActivityFeedShelfForGrouping(objectGraph, shelfToken, isArcadePage) { + if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") { + return null; + } + const shelf = this.activityFeedShelfForGrouping(objectGraph, { + shelfContents: [], + activities: [], + }, shelfToken, isArcadePage); + const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)); + shelf.url = groupingShelfUrl.param(Parameters.isGameCenterActivityFeedShelf, "true").build(); + shelf.isHidden = shelf.items.length === 0; + shelf.batchGroup = this.batchGroupKey; + return shelf; + } + activityFeedShelfForGrouping(objectGraph, shelfData, shelfToken, isArcadePage) { + if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") { + return null; + } + const shelf = this.activityFeedShelf(objectGraph, shelfData.activities, shelfToken, isArcadePage); + const title = objectGraph.loc.string("Arcade.ActivityFeed.RecentActivity"); + shelf.header = makeGameCenterHeader(objectGraph, title); + // Connect the shelf's seeAllAction + groupingShelfControllerCommon.replaceShelfSeeAllAction(objectGraph, shelf, shelf.seeAllAction); + shelf.batchGroup = this.batchGroupKey; + // Hide when empty. + shelf.isHidden = shelf.items.length === 0; + return shelf; + } + // region Helpers + activityFeedShelf(objectGraph, activities, token, isArcadePage = false) { + const shelf = new models.Shelf("gameCenterActivityFeedCard"); + shelf.isHorizontal = true; + shelf.mergeWhenFetched = true; + shelf.batchGroup = this.batchGroupKey; + shelf.items = activities; + shelf.isHidden = shelf.items.length === 0; + activities.forEach((item, index) => { + const metricsImpressionOptions = { + id: "friendActivity", + idType: "static", + targetType: "chiclet", + kind: null, + softwareType: isArcadePage ? "Arcade" : null, + title: "", + pageInformation: token.metricsPageInformation, + locationTracker: token.metricsLocationTracker, + }; + metricsHelpersImpressions.addImpressionFields(objectGraph, item, metricsImpressionOptions); + // Create action metrics for click events. + const profileActionMetrics = new ActionMetrics(); + const profileAvatarActionMetrics = new ActionMetrics(); + const leaderboardActionMetrics = new ActionMetrics(); + const achievementActionMetrics = new ActionMetrics(); + const appActionMetrics = new ActionMetrics(); + // Create targetId and metrics array. + const targetIdAndMetricsArray = [ + { + targetId: "playerName", + metrics: profileActionMetrics, + }, + { + targetId: "profileImage", + metrics: profileAvatarActionMetrics, + }, + { + targetId: "leaderboardAchievement", + metrics: leaderboardActionMetrics, + }, + { + targetId: "achievement", + metrics: achievementActionMetrics, + }, + { + targetId: item.adamID || "gameIcon", + metrics: appActionMetrics, + }, + ]; + // Loop through `targetIdAndMetricsArray` and call `addClickEventToActivityFeedMetrics` for each metrics. + targetIdAndMetricsArray.forEach((targetIdAndMetricsDictionary) => metricsHelpersClicks.addClickEventToActivityFeedMetrics(objectGraph, targetIdAndMetricsDictionary.metrics, token.title, targetIdAndMetricsDictionary.targetId, { + pageInformation: token.metricsPageInformation, + locationTracker: token.metricsLocationTracker, + })); + // Assign action metrics to `item`. + item.profileActionMetrics = profileActionMetrics; + item.profileAvatarActionMetrics = profileAvatarActionMetrics; + item.leaderboardActionMetrics = leaderboardActionMetrics; + item.achievementActionMetrics = achievementActionMetrics; + item.appActionMetrics = appActionMetrics; + // Proceed to next position. + metricsHelpersLocation.nextPosition(token.metricsLocationTracker); + }); + // Setup see all action + let seeAllAction; + if (!objectGraph.featureFlags.isGSEUIEnabled("de7bbd8e")) { + seeAllAction = new models.GameCenterDashboardAction(); + seeAllAction.title = objectGraph.loc.string("Arcade.ActivityFeed.AllActivity", objectGraph.loc.string("ACTION_SEE_ALL")); + metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, null, { + pageInformation: token.metricsPageInformation, + locationTracker: token.metricsLocationTracker, + }); + } + shelf.seeAllAction = seeAllAction; + return shelf; + } + // endregion + /** + * Convert GameCategoryFilter to GamesFilter. Ideally these would be the same, but seed 1 has already left the station. + * @param gamesFilter + */ + gameCategoryFilter(gamesFilter) { + if (gamesFilter === "nonArcade") { + return "nonarcade"; + } + return gamesFilter; + } + // endregion + // region Metrics + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + return { + ...baseMetricsOptions, + title: "Friend Activity", + badges: { + gameCenter: true, + }, + idType: "shelf_id", + }; + } +} +//# sourceMappingURL=grouping-game-center-activity-feed-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-continue-playing-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-continue-playing-shelf-controller.js new file mode 100644 index 0000000..6a85582 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-continue-playing-shelf-controller.js @@ -0,0 +1,332 @@ +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 mediaDataFetching from "../../../foundation/media/data-fetching"; +import * as mediaNetwork from "../../../foundation/media/network"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import * as contentAttributes from "../../content/attributes"; +import * as content from "../../content/content"; +import * as flowPreview from "../../content/flow-preview"; +import * as filtering from "../../filtering"; +import * as lockups from "../../lockups/lockups"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { makeGameCenterHeader } from "../../arcade/arcade-common"; +import { injectCrossfireFlowForGameCenter } from "./grouping-game-center-popular-with-your-friends-shelf-controller"; +export class GroupingGameCenterContinuePlayingShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingGameCenterContinuePlayingShelfController"); + this.batchGroupKey = "gameCenterContinuePlaying"; + this.supportedFeaturedContentIds = new Set([500 /* groupingTypes.FeaturedContentID.AppStore_ContinuePlayingMarker */]); + } + // endregion + // region GroupingShelfController + _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + return (super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) && + this.supportsVideoCardShelf(objectGraph, objectGraph.host.platform)); + } + // endregion + // region Shelf Builder + shelfRoute(objectGraph) { + return [ + ...super.shelfRoute(objectGraph), + { + protocol: Protocol.internal, + path: `/${Path.grouping}/${Path.shelf}/{token}`, + query: [Parameters.isGameCenterContinuePlayingShelf], + }, + ]; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + const startTime = Date.now(); + const maxNumberOfGames = this.maximumNumberOfRecentGamesToRequest(); + const recentlyPlayedGamesPromise = objectGraph.gameCenter.fetchRecentlyPlayedGamesWithinSeconds(this.gameCategoryFilter(shelfToken.gamesFilter), maxNumberOfGames, objectGraph.bag.recentlyPlayedGamesWindowInSeconds); + return await recentlyPlayedGamesPromise.then(async (recentlyPlayedIds) => { + const endTime = Date.now(); + objectGraph.console.log("grouping-gamecenter-builder: requestForContinuePlaying NATIVE took " + + (endTime - startTime).toString(10) + + " milliseconds."); + let shelfDataPromise; + if (recentlyPlayedIds.length === 0) { + // One of the few exceptions where TS minifiers cannot derive data container type + // without some help, in this case using explicit type for the value passed to promise. + const emptyShelfData = { shelfContents: [] }; + return await Promise.resolve(emptyShelfData); + } + else { + const request = new mediaDataFetching.Request(objectGraph) + .withIdsOfType(recentlyPlayedIds.slice(0, this.maximumNumberOfRecentGamesToShow()), "apps") + .includingAgeRestrictions(); + groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, request); + shelfDataPromise = mediaNetwork + .fetchData(objectGraph, request, {}) + .then((dataContainer) => { + const shelfData = { + shelfContents: dataContainer.data, + responseTimingValues: dataContainer[ResponseMetadata.timingValues], + }; + return shelfData; + }); + } + return await shelfDataPromise; + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + incompleteShelfFetchStrategy(objectGraph) { + return models.IncompleteShelfFetchStrategy.OnPageLoad; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (shelfToken.isFirstRender) { + return this.pendingContinuePlayingForGrouping(objectGraph, shelfToken); + } + else { + return this.continuePlayingShelfForGrouping(objectGraph, shelfData.shelfContents, shelfToken); + } + } + pendingContinuePlayingForGrouping(objectGraph, shelfToken) { + const shelf = this.continuePlayingShelfForGrouping(objectGraph, [], shelfToken); + if (!shelf) { + return null; + } + const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)); + shelf.url = groupingShelfUrl.param(Parameters.isGameCenterContinuePlayingShelf, "true").build(); + shelf.batchGroup = this.batchGroupKey; + return shelf; + } + continuePlayingShelfForGrouping(objectGraph, shelfContents, shelfToken) { + return validation.context("continuePlayingShelfForGrouping", () => { + const shelf = this.videoCardContinuePlayingShelf(objectGraph, shelfContents, shelfToken); + shelf.mergeWhenFetched = false; // Always replace + shelf.batchGroup = this.batchGroupKey; + shelf.isHidden = shelf.items.length === 0; + shelf.header = makeGameCenterHeader(objectGraph, objectGraph.loc.string("GameCenter.ContinuePlayingShelf.Title"), shelfToken.subtitle); + return shelf; + }); + } + // endregion + // region Video Card Shelf + supportsVideoCardShelf(objectGraph, platform) { + switch (platform) { + case "iOS": + case "tvOS": + case "macOS": + return true; + default: + return false; + } + } + videoCardContinuePlayingShelf(objectGraph, dataArray, shelfToken) { + return validation.context("videoCardContinuePlayingShelf", () => { + const shelf = new models.Shelf("videoCard"); + shelf.isHorizontal = true; + shelf.batchGroup = this.batchGroupKey; + const items = []; + for (const data of dataArray) { + // Filter out unwanted content + if (filtering.shouldFilter(objectGraph, data)) { + continue; + } + const item = this.editorialSplashVideoCardForContinuePlaying(objectGraph, data, shelfToken); + if (item) { + items.push(item); + } + } + shelf.items = items; + return shelf; + }); + } + /** + * Create a `VideoCard` configured for CP Shelf. + * Specifically, it uses: + * - TV Top Shelf Static Image Still + * - Arcade Product Page Uber Video + * + * @param objectGraph + * @param data + * @param shelfToken + */ + editorialSplashVideoCardForContinuePlaying(objectGraph, data, shelfToken) { + return validation.context("editorialSplashVideoCardForContinuePlaying", () => { + var _a; + const lockupMetricsOptions = { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + targetType: "lockup", + }; + const shouldHideArcadeHeader = objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && + serverData.asBooleanOrFalse(shelfToken.isArcadePage); + const isArcadeLockup = content.isArcadeSupported(objectGraph, data); + const lockupOptions = { + metricsOptions: lockupMetricsOptions, + artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */, + offerEnvironment: "dark", + offerStyle: "white", + canDisplayArcadeOfferButton: true, + shouldHideArcadeHeader: shouldHideArcadeHeader, + isSubtitleHidden: isArcadeLockup && !shouldHideArcadeHeader, + }; + const video = this.editorialSplashVideoWithTopShelfStill(objectGraph, data); + if (!video || !video.preview) { + return null; + } + const lockup = lockups.lockupFromData(objectGraph, data, lockupOptions); + if (!lockup) { + return null; + } + lockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, lockup.clickAction); + const clickAction = this.clickActionForVideoCard(objectGraph, data, objectGraph.host.platform, lockupMetricsOptions, shelfToken.clientIdentifierOverride); + if (!clickAction) { + return null; + } + const videoCard = new models.VideoCard(); + videoCard.video = video; + videoCard.lockup = lockup; + videoCard.overlayStyle = "dark"; + videoCard.clickAction = clickAction; + // Set flow preview actions + const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, lockupMetricsOptions); + videoCard.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, data, true, shelfToken.clientIdentifierOverride, videoCard.clickAction, lockupMetricsOptions, metricsClickOptions); + // Configure impressions borrowing lockup values for now. + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, lockup.title, lockupMetricsOptions); + metricsHelpersImpressions.addImpressionFields(objectGraph, videoCard, impressionOptions); + (_a = videoCard.impressionMetrics) === null || _a === void 0 ? true : delete _a.fields["impressionIndex"]; + return videoCard; + }); + } + /** + * Return a video to use for continue playing with: + * - Product Uber + * - TV Top Shelf Static Still + */ + editorialSplashVideoWithTopShelfStill(objectGraph, data) { + return validation.context("editorialSplashVideoWithTopShelfStill", () => { + // TV Top Shelf Still (If Any): + let previewOverride = null; + const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialArtwork.topShelf"); + if (serverData.isDefinedNonNull(artworkData)) { + previewOverride = content.artworkFromApiArtwork(objectGraph, artworkData, { + withJoeColorPlaceholder: true, + useCase: 23 /* content.ArtworkUseCase.VideoCardStill */, + cropCode: "sr", + }); + } + return content.editorialSplashVideoFromData(objectGraph, data, previewOverride); + }); + } + // endregion + // region Helpers + /** + * Returns the click action to use for given platform for video card + */ + clickActionForVideoCard(objectGraph, data, platform, metricsOptions, clientIdentifierOverride) { + const lockupClickMetricsOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, metricsOptions); + let productPageAction = lockups.actionFromData(objectGraph, data, lockupClickMetricsOptions, clientIdentifierOverride); + productPageAction = injectCrossfireFlowForGameCenter(objectGraph, productPageAction); + // Wrap in Open for tv only. + if (platform === "tvOS") { + const openAppAction = new models.OpenAppAction(data.id, "app"); + const openAppClickOptions = { + actionType: "open", + id: data.id, + contextualAdamId: data.id, + anonymizationOptions: metricsOptions.anonymizationOptions, + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + }; + metricsHelpersClicks.addClickEventToAction(objectGraph, openAppAction, openAppClickOptions); + const stateAction = new models.OfferStateAction(data.id, productPageAction); + stateAction.openAction = openAppAction; + // If the app is downloading and the user clicks this item, take them to the product page instead of cancelling the download. + stateAction.cancelAction = productPageAction; + return stateAction; + } + else { + return productPageAction; + } + } + /** + * The maximum number of games we should ask GameCenterServer for. Note that some of the games it fetches will + * not be Arcade nor platform-compatible games. For this reason, you should ask for more than you need. + * As this call is performant enough with a time limit, we just request the maximum amount. + * + * The maximum that GameCenterServer will accept is 200. + */ + maximumNumberOfRecentGamesToRequest() { + return 200; + } + /** + * The maximum number of games to display on the shelf. + * + * The maximum that Media API will accept is 100. + */ + maximumNumberOfRecentGamesToShow() { + return 10; + } + /** + * Convert GameCategoryFilter to GamesFilter. Ideally these would be the same, but seed 1 has already left the station. + * @param gamesFilter + */ + gameCategoryFilter(gamesFilter) { + if (gamesFilter === "nonArcade") { + return "nonarcade"; + } + return gamesFilter; + } + // endregion + // region Metrics + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + return { + ...baseMetricsOptions, + badges: { + gameCenter: true, + }, + idType: "its_contentId", + title: objectGraph.loc.string("GameCenter.ContinuePlayingShelf.Title"), + }; + } +} +//# sourceMappingURL=grouping-game-center-continue-playing-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller.js new file mode 100644 index 0000000..a3eb652 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller.js @@ -0,0 +1,314 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataFetching from "../../../foundation/media/data-fetching"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaNetwork from "../../../foundation/media/network"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import * as contentAttributes from "../../content/attributes"; +import * as content from "../../content/content"; +import * as lockups from "../../lockups/lockups"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common"; +export class GroupingGameCenterPopularWithYourFriendsController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingGameCenterPopularWithYourFriendsController"); + this.batchGroupKey = "gameCenter"; + this.supportedFeaturedContentIds = new Set([ + 495 /* groupingTypes.FeaturedContentID.AppStore_PopularWithYourFriendsMarker */, + ]); + } + // endregion + // region Shelf Builder + shelfRoute(objectGraph) { + return [ + ...super.shelfRoute(objectGraph), + { + protocol: Protocol.internal, + path: `/${Path.grouping}/${Path.shelf}/{token}`, + query: [Parameters.isGameCenterPopularWithYourFriendsShelf], + }, + ]; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { + shelfContents: [], + }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + const popularWithYourFriendsPromise = objectGraph.gameCenter.fetchGamesPopularWithFriends(this.gameCategoryFilter(shelfToken.gamesFilter), 30); + return await popularWithYourFriendsPromise.then(async (response) => { + const gameplayRecords = response + .map((item) => this.gameplayHistoryFromData(item)) + .sort((a, b) => b.records.length - a.records.length); + const adamIds = gameplayRecords + .filter((record) => this.isCompatibleGameCenterPlatform(objectGraph, record.platformId)) + .map((record) => record.adamId); + if (adamIds.length === 0) { + // One of the few exceptions where TS minifiers cannot derive data container type + // without some help, in this case using explicit type for the value passed to promise. + const emptyShelfData = { shelfContents: [] }; + return await Promise.resolve(emptyShelfData); + } + // <rdar://problem/62490641> Send payload to MAPI to allow reco to reorder the "Popular with friends" shelf. + const request = new mediaDataFetching.Request(objectGraph) + // MAPI will refuse to serve this request if there are more than 100 items. + .withIdsOfType(adamIds.slice(0, 100), "apps") + .includingAgeRestrictions(); + groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, request); + return await mediaNetwork + .fetchData(objectGraph, request, {}) + .then((dataContainer) => { + const shelfData = { + shelfContents: dataContainer.data, + responseTimingValues: dataContainer[ResponseMetadata.timingValues], + }; + return shelfData; + }); + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + incompleteShelfFetchStrategy(objectGraph) { + return models.IncompleteShelfFetchStrategy.OnPageLoad; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (shelfToken.isFirstRender) { + return this.pendingPopularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken); + } + else { + return this.popularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken); + } + } + pendingPopularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken) { + const shelf = this.popularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken); + const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)); + shelf.url = groupingShelfUrl.param(Parameters.isGameCenterPopularWithYourFriendsShelf, "true").build(); + return shelf; + } + popularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken) { + const shelf = this.popularWithFriendsShelf(objectGraph, shelfData.shelfContents, shelfToken); + shelf.mergeWhenFetched = true; + shelf.batchGroup = this.batchGroupKey; + // Hide when empty. + shelf.isHidden = shelf.items.length === 0; + // Configure header + shelf.header.title = shelfToken.title; + shelf.header.subtitle = shelfToken.subtitle; + return shelf; + } + popularWithFriendsShelf(objectGraph, shelfContents, shelfToken) { + const shelfStyle = shelfToken.shelfStyle || "mediumLockup"; + const shelf = new models.Shelf(shelfStyle); + shelf.isHorizontal = true; + const maxNumberOfPlayersBeforeSeeAll = objectGraph.client.isTV ? 20 : 12; + const items = []; + for (let index = 0; index < shelfContents.length; index++) { + const data = shelfContents[index]; + const lockupOptions = { + metricsOptions: { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data), + anonymizationOptions: { + anonymizationString: `"GAME"${index + 1}`, + }, + }, + artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfStyle), + canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfStyle), + shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && shelfToken.isArcadePage, + shouldShowFriendsPlayingShowcase: true, + }; + // GameCenter should *not* be sending us IDs for non-GameCenter apps, but in the odd case that they do, we + // we check against the app's metadata to make sure we are displaying GC apps only here. + const isGameCenterEnabled = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isGameCenterEnabled"); + // It's possible that a pre-order has become available to friends, but not you yet (due to CDN/timezone reasons). + const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder"); + if (isPreorder || !isGameCenterEnabled) { + continue; + } + const lockup = lockups.lockupFromData(objectGraph, data, lockupOptions); + lockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, lockup.clickAction); + if (serverData.isDefinedNonNull(lockup)) { + items.push(lockup); + metricsHelpersLocation.nextPosition(lockupOptions.metricsOptions.locationTracker); + } + } + let thresholdForPlatform; + switch (objectGraph.client.deviceType) { + case "phone": + thresholdForPlatform = 2; + break; + case "pad": + thresholdForPlatform = 6; + break; + case "mac": + thresholdForPlatform = 6; + break; + case "tv": + thresholdForPlatform = 6; + break; + default: + thresholdForPlatform = 0; + } + shelf.header = makeGameCenterHeader(objectGraph); + if (items.length < thresholdForPlatform) { + shelf.isHidden = true; + return shelf; + } + shelf.items = items.slice(0, maxNumberOfPlayersBeforeSeeAll); + shelf.isHidden = false; + shelf.batchGroup = "gameCenter"; + if (items.length > maxNumberOfPlayersBeforeSeeAll) { + // The shelf for the see all page + const shelfForSeeAllItems = new models.Shelf("mediumLockup"); + shelfForSeeAllItems.items = items; + shelfForSeeAllItems.rowsPerColumn = 1; + // See all page + const seeAllPage = new models.GenericPage([shelfForSeeAllItems]); + seeAllPage.title = shelfToken.title; + // The action that opens the see all page + const seeAllAction = new models.FlowAction("page"); + seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL"); + seeAllAction.pageData = seeAllPage; + // Metrics + metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, null, { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }); + // Connect the shelf's seeAllAction + groupingShelfControllerCommon.replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction); + } + shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp"); + shelf.footerAction = openGamesUIAction(objectGraph); + shelf.footerStyle = { + $kind: "games", + bundleID: "com.apple.games", + width: 16, + height: 16, + }; + return shelf; + } + // endregion + // region Helpers + /** + * Maps GKGamePlatform to client's `deviceType` + * @param objectGraph + * @param platformId The platform ID as defined by GameCenter's GKGamePlatform + */ + isCompatibleGameCenterPlatform(objectGraph, platformId) { + switch (platformId) { + case 1: + return objectGraph.client.isiOS; + case 2: + return objectGraph.client.isMac; + case 3: + return objectGraph.client.isTV; + case 4: + return objectGraph.client.isWatch; + default: + return false; + } + } + gameplayHistoryFromData(data) { + const adamId = serverData.asString(data, "adamId"); + const platformId = serverData.asNumber(data, "platformId"); + const isArcade = serverData.asBooleanOrFalse(data, "isArcade"); + const records = this.gameplayHistoryRecordFromData(serverData.asArrayOrEmpty(data, "records")); + return new models.GameCenterGameplayHistory(adamId, platformId, isArcade, records); + } + gameplayHistoryRecordFromData(data) { + return data.map((recordData) => { + const playerId = serverData.asString(recordData, "playerId"); + const timestamp = serverData.asNumber(recordData, "timestamp"); + return new models.GameCenterGameplayHistoryRecord(playerId, timestamp); + }); + } + /** + * Convert GameCategoryFilter to GamesFilter. Ideally these would be the same, but seed 1 has already left the station. + * @param gamesFilter + */ + gameCategoryFilter(gamesFilter) { + if (gamesFilter === "nonArcade") { + return "nonarcade"; + } + return gamesFilter; + } + // endregion + // region Metrics + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + return { + ...baseMetricsOptions, + badges: { + gameCenter: true, + }, + idType: "its_contentId", + }; + } +} +/** + * For evaluating how we attribute referral from Game Center shelves to the product page + * We going to inject a hardcoded refApp `com.apple.gamecenter.from.browse` through crossfire pipeline for tracking the page event and buy action + * + * - Modify the FlowAction to include the GameCenter ReferrerData. + * - Replace the click action with a compound action of CrossfireReferralAction + FlowAction + */ +export function injectCrossfireFlowForGameCenter(objectGraph, clickAction) { + if (["iOS", "macOS", "tvOS"].includes(objectGraph.host.platform) && clickAction instanceof models.FlowAction) { + const gameCenterCrossfireReferrerData = { + app: "com.apple.gamecenter.from.browse", + kind: { + name: "gameCenter", + }, + }; + clickAction.referrerData = gameCenterCrossfireReferrerData; + return new models.CompoundAction([ + new models.CrossfireReferralAction(gameCenterCrossfireReferrerData), + clickAction, + ]); + } + else { + return clickAction; + } +} +//# sourceMappingURL=grouping-game-center-popular-with-your-friends-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-reengagement-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-reengagement-shelf-controller.js new file mode 100644 index 0000000..f4b3d74 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-reengagement-shelf-controller.js @@ -0,0 +1,254 @@ +import * as validation from "@jet/environment/json/validation"; +import * as models from "../../../api/models"; +import * as actions from "../../../api/models/actions"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaDataFetching from "../../../foundation/media/data-fetching"; +import * as mediaNetwork from "../../../foundation/media/network"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import * as color from "../../../foundation/util/color-util"; +import * as content from "../../content/content"; +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 { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { injectCrossfireFlowForGameCenter } from "./grouping-game-center-popular-with-your-friends-shelf-controller"; +export class GroupingGameCenterReengagementShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingGameCenterReengagementShelfController"); + this.batchGroupKey = "gameCenter"; + this.supportedFeaturedContentIds = new Set([494 /* groupingTypes.FeaturedContentID.AppStore_GameCenterReengagement */]); + } + // endregion + // region Shelf Builder + shelfRoute(objectGraph) { + return [ + ...super.shelfRoute(objectGraph), + { + protocol: Protocol.internal, + path: `/${Path.grouping}/${Path.shelf}/{token}`, + query: [Parameters.isGameCenterReengagementShelf], + }, + ]; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { + shelfContents: [], + achievementData: null, + achievementSummaryData: null, + }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await objectGraph.gameCenter.fetchRengagementDataForLocalPlayer().then(async (reengagementData) => { + const adamID = serverData.asString(reengagementData, "adamId"); + const achievement = serverData.asJSONData(reengagementData["achievement"]); + const achievementSummary = serverData.asJSONData(reengagementData["achievementSummary"]); + if (serverData.isNullOrEmpty(adamID)) { + // One of the few exceptions where TS minifiers cannot derive data container type + // without some help, in this case using explicit type for the value passed to promise. + const emptyShelfData = { + shelfContents: [], + responseTimingValues: null, + achievementData: null, + achievementSummaryData: null, + }; + return await Promise.resolve(emptyShelfData); + } + const request = new mediaDataFetching.Request(objectGraph) + .withIdOfType(adamID, "apps") + .includingAgeRestrictions(); + groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, request); + return await mediaNetwork + .fetchData(objectGraph, request, {}) + .then((dataContainer) => { + return { + shelfContents: dataContainer.data, + responseTimingValues: dataContainer[ResponseMetadata.timingValues], + achievementData: achievement, + achievementSummaryData: achievementSummary, + }; + }); + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (shelfToken.isFirstRender) { + return this.pendingGameCenterReengagementShelf(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage); + } + else { + return this.gameCenterReengagementShelf(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage); + } + } + pendingGameCenterReengagementShelf(objectGraph, shelfData, shelfToken, isArcadePage) { + const shelf = this.gameCenterReengagementShelf(objectGraph, shelfData, shelfToken, isArcadePage); + if (!shelf) { + return null; + } + const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)); + shelf.url = groupingShelfUrl.param(Parameters.isGameCenterReengagementShelf, "true").build(); + shelf.batchGroup = this.batchGroupKey; + return shelf; + } + gameCenterReengagementShelf(objectGraph, shelfData, shelfToken, isArcadePage = false) { + return validation.context("gameCenterReengagementShelf", () => { + if (!serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) { + return null; + } + const shelf = new models.Shelf("gameCenterReengagement"); + shelf.isHorizontal = false; + shelf.mergeWhenFetched = false; // Always replace + shelf.batchGroup = this.batchGroupKey; + const heroMetricsOptions = { + id: shelfToken.id, + kind: null, + softwareType: isArcadePage ? "Arcade" : null, + targetType: "achievements", + title: "Achievements Hero", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + idType: "its_contentId", + fcKind: shelfToken.featuredContentId, + badges: { + gameCenter: true, + }, + }; + // Hero Background / Artwork + const artwork = content.productEditorialVideoFromData(objectGraph, shelfData.shelfContents[0], 21 /* content.ArtworkUseCase.Uber */); + let backgroundColor = color.named("componentBackground"); + let preview = null; + if (serverData.isDefinedNonNullNonEmpty(artwork)) { + preview = artwork.preview; + backgroundColor = preview.backgroundColor; + } + // Build lockup used by reegnagement shelf + const lockupListOptions = { + lockupOptions: { + metricsOptions: { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }, + offerStyle: "white", + artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "smallLockup"), + isSubtitleHidden: true, + }, + }; + metricsHelpersLocation.pushContentLocation(objectGraph, heroMetricsOptions, heroMetricsOptions.title); + const heroLockup = lockups.lockupsFromData(objectGraph, shelfData.shelfContents, lockupListOptions)[0]; + heroLockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, heroLockup.clickAction); + metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker); + // Click action used by hero view (achievement card) + let heroAction = null; + if (serverData.isDefinedNonNullNonEmpty(heroLockup)) { + heroAction = new actions.GameCenterAchievementsAction(heroLockup.bundleId); + heroAction.title = "Achievements Hero"; // `addClickEventToAction` uses `title` for `name` field currently. + metricsHelpersClicks.addClickEventToAction(objectGraph, heroAction, heroMetricsOptions); + } + const shelfBadge = objectGraph.loc.string("GameCenter.Reengagement.Badge.GameCenter"); + const achievement = this.achievementFromData(objectGraph, shelfData.achievementData); + const achievementCounts = this.achievementCountsFromData(objectGraph, shelfData.achievementSummaryData); + const shelfMetadata = this.shelfMetadataForAchievement(objectGraph, achievement, achievementCounts); + const heroItem = new models.GameCenterReengagement("gamecenter.fill", shelfBadge, shelfMetadata.title, shelfMetadata.subtitle, achievement, heroLockup, backgroundColor, preview, heroAction); + shelf.items = [heroItem]; + /** + * For reengagement, the shelf is a container fully fulled by `heroItem`. + * Match what breakouts do by impressing `heroItem` directly instead of the `shelf`. + */ + metricsHelpersImpressions.addImpressionFields(objectGraph, heroItem, heroMetricsOptions); + return shelf; + }); + } + // region Helpers + achievementStatusFromData(objectGraph, data) { + const statusType = serverData.asString(data, "type"); + const status = new models.GameCenterAchievementStatus(statusType); + status.percent = serverData.asNumber(data, "percent"); + status.date = serverData.asString(data, "date"); + status.artwork = new models.Artwork(serverData.asString(data, "artwork.template"), serverData.asNumber(data, "artwork.width"), serverData.asNumber(data, "artwork.height"), []); + return status; + } + achievementFromData(objectGraph, data) { + const id = serverData.asString(data, "id"); + const title = serverData.asString(data, "title"); + const subtitle = serverData.asString(data, "subtitle"); + const status = this.achievementStatusFromData(objectGraph, serverData.asDictionary(data, "status")); + return new models.GameCenterAchievement(id, title, subtitle, status); + } + achievementCountsFromData(objectGraph, data) { + const completedAchievements = serverData.asNumber(data, "completedAchievements"); + const totalAchievements = serverData.asNumber(data, "totalAchievements"); + return { completed: completedAchievements, total: totalAchievements }; + } + shelfMetadataForAchievement(objectGraph, achievement, achievementCounts) { + if (!serverData.isDefinedNonNull(achievement)) { + return { title: "", subtitle: null }; + } + if (achievementCounts.completed === 0) { + return { + title: objectGraph.loc.string("GameCenter.Reengagement.Achievement.First.Title"), + subtitle: objectGraph.loc.string("GameCenter.Reengagement.Achievement.First.Subtitle"), + }; + } + switch (achievement.status.type) { + case "locked": + case "hidden": + case "inprogress": + return { + title: objectGraph.loc.string("GameCenter.Reengagement.Achievement.KeepPlaying.Title"), + subtitle: objectGraph.loc.string("GameCenter.Reengagement.Achievement.KeepPlaying.Subtitle"), + }; + case "completed": + const achievementsTotalCount = objectGraph.loc.stringWithCount("GameCenter.AchievementSummary.TotalToCompleteCount", achievementCounts.total); + const achievementsCompletedCount = objectGraph.loc.stringWithCount("GameCenter.AchievementSummary.NumberCompletedCount", achievementCounts.completed); + const subtitle = objectGraph.loc + .string("GameCenter.AchievementSummary.CompletedCount.Subtitle") + .replace("@@completedCount@@", achievementsCompletedCount) + .replace("@@totalCount@@", achievementsTotalCount); + return { + title: objectGraph.loc.string("GameCenter.Reengagement.Achievement.CompletedCount.Title"), + subtitle: subtitle, + }; + default: + return { title: "", subtitle: null }; + } + } +} +//# sourceMappingURL=grouping-game-center-reengagement-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-suggested-friends-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-suggested-friends-shelf-controller.js new file mode 100644 index 0000000..e9eb3f4 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-suggested-friends-shelf-controller.js @@ -0,0 +1,279 @@ +import * as models from "../../../api/models"; +import * as actions from "../../../api/models/actions"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingGameCenterSuggestedFriendsController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingGameCenterSuggestedFriendsController"); + this.batchGroupKey = "gameCenter"; + this.supportedFeaturedContentIds = new Set([496 /* groupingTypes.FeaturedContentID.AppStore_SuggestedFriendsMarker */]); + } + // endregion + // region Shelf Builder + shelfRoute(objectGraph) { + return [ + ...super.shelfRoute(objectGraph), + { + protocol: Protocol.internal, + path: `/${Path.grouping}/${Path.shelf}/{token}`, + query: [Parameters.isGameCenterSuggestedFriendsShelf], + }, + ]; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { + $kind: "friendingViaPush", + shelfContents: [], + suggestedFriends: [], + }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await objectGraph.gameCenter.fetchSuggestedFriends(10).then((suggestions) => { + if (objectGraph.props.enabled("gameCenterFriendingViaPush")) { + return { + $kind: "friendingViaPush", + shelfContents: [], + suggestedFriends: suggestions, + }; + } + else { + return { + $kind: "legacy", + shelfContents: [], + suggestedFriends: suggestions, + }; + } + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + incompleteShelfFetchStrategy(objectGraph) { + return models.IncompleteShelfFetchStrategy.OnPageLoad; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (shelfToken.isFirstRender) { + return this.pendingSuggestedFriendsShelfForGrouping(objectGraph, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage); + } + else { + return this.suggestedFriendsShelfForGrouping(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage); + } + } + pendingSuggestedFriendsShelfForGrouping(objectGraph, shelfToken, isArcadePage) { + if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") { + return null; + } + const shelf = this.suggestedFriendsShelfForGrouping(objectGraph, { + $kind: objectGraph.props.enabled("gameCenterFriendingViaPush") ? "friendingViaPush" : "legacy", + shelfContents: [], + suggestedFriends: [], + }, shelfToken, isArcadePage); + const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)); + shelf.url = groupingShelfUrl.param(Parameters.isGameCenterSuggestedFriendsShelf, "true").build(); + shelf.isHidden = shelf.items.length === 0; + shelf.batchGroup = this.batchGroupKey; + return shelf; + } + suggestedFriendsShelfForGrouping(objectGraph, shelfData, shelfToken, isArcadePage) { + if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") { + return null; + } + let shelf; + if (shelfData.$kind === "friendingViaPush") { + shelf = this.suggestedFriendsShelf(objectGraph, shelfData.suggestedFriends, shelfToken, isArcadePage); + } + else { + shelf = this.legacySuggestedFriendsShelf(objectGraph, shelfData.suggestedFriends, shelfToken, isArcadePage); + } + shelf.header = makeGameCenterHeader(objectGraph, shelfToken.title, shelfToken.subtitle); + shelf.batchGroup = this.batchGroupKey; + // Hide when empty. + shelf.isHidden = shelf.items.length === 0; + shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp"); + shelf.footerAction = openGamesUIAction(objectGraph); + shelf.footerStyle = { + $kind: "games", + bundleID: "com.apple.games", + width: 16, + height: 16, + }; + return shelf; + } + // region Helpers + suggestedFriendsShelf(objectGraph, suggestions, token, isArcadePage = false) { + const suggestionPrefix = "FRIEND_SUGGESTION"; + if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") { + return null; + } + const shelf = new models.Shelf("smallContactCard"); + shelf.isHorizontal = true; + shelf.mergeWhenFetched = true; + shelf.batchGroup = "gameCenter"; + const enrichedSuggestions = []; + for (let index = 0; index < suggestions.length; index++) { + const suggestionId = `${suggestionPrefix}${index + 1}`; + const suggestedFriend = suggestions[index]; + const buttonText = objectGraph.loc.string("INVITE"); + const subtitle = objectGraph.loc.string("FROM_CONTACTS"); + const metricsClickOptions = { + pageInformation: token.metricsPageInformation, + locationTracker: token.metricsLocationTracker, + id: suggestionId, + anonymizationOptions: { + anonymizationString: suggestionId, + }, + }; + let invitationType; + let shouldShowMessagesBadge; + if (suggestedFriend.supportsFriendingViaPush && suggestedFriend.contactAssociationID) { + invitationType = { + contact: { + contactID: suggestedFriend.contactID, + contactAssociationID: suggestedFriend.contactAssociationID, + }, + }; + shouldShowMessagesBadge = false; + } + else { + invitationType = { + messages: { + contactID: suggestedFriend.contactID, + }, + }; + shouldShowMessagesBadge = true; + } + const buttonAction = new actions.GameCenterInvitePlayerAction(invitationType); + metricsHelpersClicks.addClickEventToAction(objectGraph, buttonAction, { + ...metricsClickOptions, + actionType: "inviteFriend", + }); + const removeButtonAction = new actions.GameCenterDenylistPlayerAction(suggestedFriend.contactID); + metricsHelpersClicks.addClickEventToAction(objectGraph, removeButtonAction, { + ...metricsClickOptions, + actionType: "removeFriendSuggestion", + }); + const metricsImpressionOptions = { + pageInformation: token.metricsPageInformation, + locationTracker: token.metricsLocationTracker, + title: suggestionId, + id: suggestionId, + kind: "friendSuggestion", + softwareType: isArcadePage ? "Arcade" : null, + anonymizationOptions: { + anonymizationString: suggestionId, + }, + }; + const enrichedSuggestion = new models.SmallContactCard(suggestedFriend.contactID, suggestedFriend.fullName, subtitle, buttonText, suggestedFriend.contactID, buttonAction, removeButtonAction, shouldShowMessagesBadge); + metricsHelpersImpressions.addImpressionFields(objectGraph, enrichedSuggestion, metricsImpressionOptions); + enrichedSuggestions.push(enrichedSuggestion); + metricsHelpersLocation.nextPosition(token.metricsLocationTracker); + } + shelf.items = enrichedSuggestions; + shelf.isHidden = shelf.items.length === 0; + return shelf; + } + legacySuggestedFriendsShelf(objectGraph, cards, token, isArcadePage = false) { + const suggestionPrefix = "FRIEND_SUGGESTION"; + if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") { + return null; + } + const shelf = new models.Shelf("smallContactCard"); + shelf.isHorizontal = true; + shelf.mergeWhenFetched = true; + shelf.batchGroup = "gameCenter"; + const enrichedSuggestions = []; + for (let index = 0; index < cards.length; index++) { + const suggestionId = `${suggestionPrefix}${index + 1}`; + const smallContactCard = cards[index]; + smallContactCard.buttonText = objectGraph.loc.string("INVITE"); + smallContactCard.subtitle = objectGraph.loc.string("FROM_CONTACTS"); + const metricsClickOptions = { + pageInformation: token.metricsPageInformation, + locationTracker: token.metricsLocationTracker, + id: suggestionId, + anonymizationOptions: { + anonymizationString: suggestionId, + }, + }; + smallContactCard.buttonAction = new actions.LegacyGameCenterInvitePlayerAction(smallContactCard.contactId); + metricsHelpersClicks.addClickEventToAction(objectGraph, smallContactCard.buttonAction, { + ...metricsClickOptions, + actionType: "inviteFriend", + }); + smallContactCard.removeButtonAction = new actions.GameCenterDenylistPlayerAction(smallContactCard.contactId); + metricsHelpersClicks.addClickEventToAction(objectGraph, smallContactCard.removeButtonAction, { + ...metricsClickOptions, + actionType: "removeFriendSuggestion", + }); + const metricsImpressionOptions = { + pageInformation: token.metricsPageInformation, + locationTracker: token.metricsLocationTracker, + title: suggestionId, + id: suggestionId, + kind: "friendSuggestion", + softwareType: isArcadePage ? "Arcade" : null, + anonymizationOptions: { + anonymizationString: suggestionId, + }, + }; + metricsHelpersImpressions.addImpressionFields(objectGraph, smallContactCard, metricsImpressionOptions); + enrichedSuggestions.push(smallContactCard); + metricsHelpersLocation.nextPosition(token.metricsLocationTracker); + } + shelf.items = enrichedSuggestions; + shelf.isHidden = shelf.items.length === 0; + return shelf; + } + // endregion + // region Metrics + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + return { + ...baseMetricsOptions, + badges: { + gameCenter: true, + }, + idType: "its_contentId", + }; + } +} +//# sourceMappingURL=grouping-game-center-suggested-friends-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-hero-carousel-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-hero-carousel-shelf-controller.js new file mode 100644 index 0000000..b7a406c --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-hero-carousel-shelf-controller.js @@ -0,0 +1,182 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as breakoutsCommon from "../../arcade/breakouts-common"; +import * as contentAttributes from "../../content/attributes"; +import * as content from "../../content/content"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as article from "../../today/article"; +import * as heroCarouselOverlayCommon from "../hero/hero-carousel-overlay-common"; +import * as heroCommon from "../hero/hero-common"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingHeroCarouselShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingHeroCarouselShelfController"); + this.supportedFeaturedContentIds = new Set([480 /* groupingTypes.FeaturedContentID.AppStore_Breakout */]); + } + // endregion + // region GroupingShelfController + _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) { + return false; + } + const breakoutStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + return breakoutStyle === "hero"; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Metrics + shouldImpressShelf() { + return false; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (groupingParseContext.shelves.length !== 0) { + return null; + } + const shelf = new models.Shelf("heroCarousel"); + const mediaApiData = shelfToken.featuredContentData; + const heroCarouselMetricsOptions = { + targetType: "swoosh", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(mediaApiData), + }; + const heroCarousel = new models.HeroCarousel(); + heroCarousel.autoScrollConfiguration = { + isAutoScrollEnabled: objectGraph.bag.heroCarouselAutoScrollDuration > 0.0, + autoScrollInterval: objectGraph.bag.heroCarouselAutoScrollDuration, + }; + const heroCarouselImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, mediaApiData, "heroCarousel", heroCarouselMetricsOptions); + heroCarouselImpressionOptions.autoAdvanceInterval = heroCarousel.autoScrollConfiguration.autoScrollInterval; + metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, heroCarouselImpressionOptions); + metricsHelpersLocation.pushContentLocation(objectGraph, heroCarouselImpressionOptions, "heroCarousel"); + for (const itemData of shelfData.shelfContents) { + if (serverData.isNull(itemData.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(itemData); + continue; + } + const primaryContent = content.primaryContentForData(objectGraph, itemData); + if (breakoutsCommon.requiresPrimaryContent(objectGraph, itemData) && + !mediaAttributes.hasAttributes(primaryContent)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(itemData); + shelfToken.relationshipToFetch = "primary-content"; + continue; + } + const heroCarouselItemMetricsOptions = { + targetType: "largeBreakout", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData), + }; + const heroVideoData = heroCommon.heroVideoFromData(objectGraph, itemData); + const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, itemData); + if (serverData.isNullOrEmpty(heroVideoData.video) && serverData.isNullOrEmpty(heroArtworkData.artwork)) { + continue; + } + const heroCarouselItem = new models.HeroCarouselItem(); + const productData = article.productDataFromArticle(objectGraph, itemData); + const metricsTitle = heroCarouselOverlayCommon.heroCarouselItemTitleFromData(objectGraph, itemData); + const heroCarouselItemImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, metricsTitle, heroCarouselItemMetricsOptions); + heroCarouselItemImpressionOptions.isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isPreorder"); + metricsHelpersImpressions.addImpressionFields(objectGraph, heroCarouselItem, heroCarouselItemImpressionOptions); + // Push the heroCarouselItem here so that the click action has the item in its location + // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button + // action + // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field + metricsHelpersLocation.pushContentLocation(objectGraph, heroCarouselItemImpressionOptions, metricsTitle); + const titleEffectArtwork = heroVideoData.artworkData || heroArtworkData.artworkData; + const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor; + const overlayRequirements = { + metricsPageInformation: shelfToken.metricsPageInformation, + metricsLocationTracker: shelfToken.metricsLocationTracker, + canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfToken.shelfStyle), + lockupArtworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfToken.shelfStyle), + isContainedInPreorderExclusiveShelf: shelfToken.featuredContentId === 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */, + }; + heroCarouselItem.overlay = heroCarouselOverlayCommon.overlayFromData(objectGraph, itemData, overlayRequirements); + heroCarouselItem.backgroundColor = backgroundColor; + if (!objectGraph.client.isMac || objectGraph.props.isNotEnabled("macOSArcadeHeaderUpdates")) { + heroCarouselItem.titleEffect = breakoutsCommon.titleEffectFromArtwork(objectGraph, titleEffectArtwork); + } + heroCarouselItem.artwork = heroArtworkData.artwork; + heroCarouselItem.video = heroVideoData.video; + if (objectGraph.client.isWeb) { + const portraitVideo = heroCommon.heroVideoFromData(objectGraph, itemData, false, true); + heroCarouselItem.portraitVideo = portraitVideo === null || portraitVideo === void 0 ? void 0 : portraitVideo.video; + } + const heroCarouselItemAction = breakoutsCommon.actionFromData(objectGraph, itemData); + const heroCarouselItemClickOptions = { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData), + targetType: "largeBreakout", + id: itemData.id, + }; + if (heroCarouselItemAction) { + metricsHelpersClicks.addClickEventToAction(objectGraph, heroCarouselItemAction, heroCarouselItemClickOptions); + heroCarouselItem.clickAction = heroCarouselItemAction; + } + heroCarousel.items.push(heroCarouselItem); + metricsHelpersLocation.popLocation(heroCarouselItemImpressionOptions.locationTracker); + metricsHelpersLocation.nextPosition(heroCarouselItemImpressionOptions.locationTracker); + } + if (serverData.isDefinedNonNullNonEmpty(heroCarousel.items)) { + shelf.items = [heroCarousel]; + groupingParseContext.pageTitleEffect = heroCarousel.items[0].titleEffect; + } + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + metricsHelpersLocation.popLocation(heroCarouselImpressionOptions.locationTracker); + metricsHelpersLocation.nextPosition(heroCarouselImpressionOptions.locationTracker); + return shelf; + } +} +//# sourceMappingURL=grouping-hero-carousel-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-horizontal-card-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-horizontal-card-shelf-controller.js new file mode 100644 index 0000000..d02a4b6 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-horizontal-card-shelf-controller.js @@ -0,0 +1,122 @@ +import * as models from "../../../api/models"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as todayHorizontalCardUtil from "../../today/today-horizontal-card-util"; +import { TodayParseContext } from "../../today/today-types"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingHorizontalCardShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingHorizontalCardShelfController"); + this.supportedFeaturedContentIds = new Set([475 /* groupingTypes.FeaturedContentID.AppStore_HorizontalCardSwoosh */]); + } + // endregion + // region GroupingShelfController + _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) { + return false; + } + const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + const contentType = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle); + return contentType !== "editorialStoryCard"; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + const shelfToken = { ...baseShelfToken }; + const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + shelfToken.shelfStyle = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle); + return shelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + var _a; + const cardUnavailable = function (cardData) { + shelfToken.remainingItems.push(cardData); + return false; + }; + let shelf; + if (objectGraph.client.isiOS && objectGraph.featureFlags.isEnabled("mini_today_cards_grouping")) { + const todayParseContext = new TodayParseContext(shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker); + shelf = todayHorizontalCardUtil.shelfForMiniTodayCards(objectGraph, (_a = shelfData.shelfContents) !== null && _a !== void 0 ? _a : [], shelfToken.title, shelfToken.subtitle, todayParseContext); + } + else { + const cardContext = { + metricsLocationTracker: shelfToken.metricsLocationTracker, + metricsPageInformation: shelfToken.metricsPageInformation, + }; + let resolvedContentType; + // First check if the client explicitly supports small story cards. + // If it does, don't change the content type. + const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb; + if (isSmallStoryCardsSupported && shelfToken.shelfStyle === "smallStoryCard") { + resolvedContentType = shelfToken.shelfStyle; + } + else { + switch (objectGraph.client.deviceType) { + case "mac": + case "tv": + case "web": + resolvedContentType = shelfToken.shelfStyle; + break; + case "watch": + resolvedContentType = "todayCard"; + break; + default: + resolvedContentType = "todayBrick"; + break; + } + } + shelf = todayHorizontalCardUtil.shelfForHorizontalCardItems(objectGraph, shelfData.shelfContents, resolvedContentType, shelfToken.title, shelfToken.subtitle, cardContext, cardUnavailable); + if (shelf.contentType === "smallStoryCard" && Array.isArray(shelf.items)) { + shelf.items = shelf.items.filter((item) => { + if (!(item instanceof models.TodayCard)) { + return true; + } + return todayHorizontalCardUtil.isHorizontalCardSupportedForKind(objectGraph, item.media.kind, "smallStoryCard"); + }); + } + } + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + shelf.isHorizontal = true; + return shelf; + } +} +//# sourceMappingURL=grouping-horizontal-card-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-large-breakout-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-large-breakout-shelf-controller.js new file mode 100644 index 0000000..7b14c97 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-large-breakout-shelf-controller.js @@ -0,0 +1,193 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as breakoutsCommon from "../../arcade/breakouts-common"; +import * as contentAttributes from "../../content/attributes"; +import * as content from "../../content/content"; +import * as flowPreview from "../../content/flow-preview"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as article from "../../today/article"; +import * as heroCommon from "../hero/hero-common"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingLargeBreakoutShelfController extends GroupingShelfController { + // region Constructors + constructor(builderClass = null) { + super(builderClass || "GroupingLargeBreakoutShelfController"); + this.supportedFeaturedContentIds = new Set([480 /* groupingTypes.FeaturedContentID.AppStore_Breakout */]); + } + // endregion + // region GroupingShelfController + _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) { + return false; + } + const breakoutStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + return breakoutStyle === "large"; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Metrics + shouldImpressShelf() { + return false; + } + // endregion + // region Shelf Creation + isInHeroPosition() { + return false; + } + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const isFirstShelf = serverData.isDefinedNonNullNonEmpty(groupingParseContext) && + serverData.isNullOrEmpty(groupingParseContext.pageTitleEffect) && + groupingParseContext.shelves.length === 0; + const trimmedShelfContents = serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents) + ? [shelfData.shelfContents[0]] + : []; + const items = []; + for (const data of trimmedShelfContents) { + if (serverData.isNull(data.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(data); + continue; + } + const metricsOptions = { + targetType: this.isInHeroPosition() ? "heroBreakout" : "largeBreakout", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data), + }; + const heroVideoData = heroCommon.heroVideoFromData(objectGraph, data); + const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, data); + const titleEffectArtwork = heroVideoData.artworkData || heroArtworkData.artworkData; + const largeHeroBreakout = GroupingLargeBreakoutShelfController.createLargeBreakout(objectGraph, data, shelfToken, this.isInHeroPosition(), isFirstShelf, metricsOptions); + if (serverData.isNullOrEmpty(largeHeroBreakout)) { + continue; + } + items.push(largeHeroBreakout); + if (isFirstShelf) { + groupingParseContext.pageTitleEffect = breakoutsCommon.titleEffectFromArtwork(objectGraph, titleEffectArtwork); + } + } + const shelf = new models.Shelf("largeHeroBreakout"); + shelf.isHorizontal = false; + shelf.items = items; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + if (groupingParseContext.shelves.length === 0) { + shelf.presentationHints = { isFirstShelf: true }; + } + return shelf; + } + static createLargeBreakout(objectGraph, data, shelfToken, isHeroPosition, isFirstShelf, metricsOptions) { + const primaryContent = content.primaryContentForData(objectGraph, data); + if (breakoutsCommon.requiresPrimaryContent(objectGraph, data) && + !mediaAttributes.hasAttributes(primaryContent)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(data); + shelfToken.relationshipToFetch = "primary-content"; + return null; + } + const heroVideoData = heroCommon.heroVideoFromData(objectGraph, data); + const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, data); + if (serverData.isNullOrEmpty(heroVideoData.video) && serverData.isNullOrEmpty(heroArtworkData.artwork)) { + return null; + } + const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor; + const heading = isHeroPosition ? null : mediaAttributes.attributeAsString(data, "breakoutBadge"); + // If this EI can and should show the expected release date of an app, override the badge. + let badgeTitle; + const fallbackLabel = mediaAttributes.attributeAsString(data, "label"); + if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "showExpectedReleaseDate")) { + badgeTitle = objectGraph.loc.uppercased(content.dynamicPreorderDateFromData(objectGraph, primaryContent, fallbackLabel)); + } + else { + badgeTitle = fallbackLabel; + } + let badge = { type: "none" }; + if ((badgeTitle === null || badgeTitle === void 0 ? void 0 : badgeTitle.length) > 0) { + badge = { + type: "text", + title: badgeTitle, + }; + } + const title = content.editorialNotesFromData(objectGraph, data, "name") || + contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "name"); + const description = content.editorialNotesFromData(objectGraph, data, "short") || + contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "tagline"); + const backgroundStyle = breakoutsCommon.detailBackgroundStyleFromData(objectGraph, data, true, isFirstShelf, false); + const detailPosition = breakoutsCommon.detailPositionFromData(objectGraph, data, isFirstShelf, false); + const details = new models.BreakoutDetails(title, description, badge, null, backgroundStyle, breakoutsCommon.detailTextAlignmentForDetailPosition(objectGraph, detailPosition, data)); + const largeHeroBreakout = new models.LargeHeroBreakout(details, { + position: detailPosition || "leading", + }, heading, heroArtworkData.artwork, heroVideoData.video, null, backgroundColor); + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, largeHeroBreakout.details.title, metricsOptions); + const productData = article.productDataFromArticle(objectGraph, data); + const isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isPreorder"); + impressionOptions.isPreorder = isPreorder; + metricsHelpersImpressions.addImpressionFields(objectGraph, largeHeroBreakout, impressionOptions); + // Push the breakout here so that the click action has the breakout in its location + // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button + // action + // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field + metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, largeHeroBreakout.details.title); + const breakoutAction = breakoutsCommon.actionFromData(objectGraph, data); + const breakoutClickOptions = { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data), + targetType: "button", + id: data.id, + }; + metricsHelpersClicks.addClickEventToAction(objectGraph, breakoutAction, breakoutClickOptions); + largeHeroBreakout.details.callToActionButtonAction = breakoutAction; + largeHeroBreakout.clickAction = breakoutAction; + metricsHelpersLocation.popLocation(metricsOptions.locationTracker); + // Set flow preview actions + largeHeroBreakout.flowPreviewActionsConfiguration = + flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, data, true, shelfToken.clientIdentifierOverride, breakoutAction, metricsOptions, breakoutClickOptions); + return largeHeroBreakout; + } +} +//# sourceMappingURL=grouping-large-breakout-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-link-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-link-shelf-controller.js new file mode 100644 index 0000000..09865f5 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-link-shelf-controller.js @@ -0,0 +1,169 @@ +import { isNothing } from "@jet/environment"; +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import { createArtworkForResource } from "../../content/artwork/artwork"; +import * as lockupsEditorialContext from "../../lockups/editorial-context"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingLinkShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingLinkShelfController"); + this.supportedFeaturedContentIds = new Set([ + 437 /* groupingTypes.FeaturedContentID.AppStore_LinkList */, + 265 /* groupingTypes.FeaturedContentID.Sundance_LinkList */, + ]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + let shelfContents = mediaRelationship.relationshipCollection(mediaApiData, "children"); + if (serverData.isNullOrEmpty(shelfContents)) { + shelfContents = mediaAttributes.attributeAsArrayOrEmpty(mediaApiData, "links"); + } + return { shelfContents: shelfContents }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + const linkShelfToken = { + ...baseShelfToken, + shouldHideShelf: mediaAttributes.attributeAsBooleanOrFalse(mediaApiData, "hide"), + areContentLinks: serverData.isDefinedNonNullNonEmpty(mediaRelationship.relationshipCollection(mediaApiData, "children")), + }; + linkShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData); + return linkShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + if (shelfToken.shouldHideShelf) { + return null; + } + const items = []; + for (let linkIndex = 0; linkIndex < shelfData.shelfContents.length; linkIndex++) { + const link = shelfData.shelfContents[linkIndex]; + const metricsBase = { + targetType: "link", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }; + if (shelfToken.areContentLinks) { + metricsBase.recoMetricsData = mediaDataStructure.metricsFromMediaApiObject(link); + } + let action = null; + if (shelfToken.isSearchLandingPage) { + const searchAdAction = this.trendingSearchLinkFromData(objectGraph, link, shelfToken.metricsLocationTracker); + if (serverData.isNull(searchAdAction) || serverData.isNull(searchAdAction.action)) { + continue; + } + metricsHelpersImpressions.addImpressionFields(objectGraph, searchAdAction === null || searchAdAction === void 0 ? void 0 : searchAdAction.action, { + ...metricsBase, + kind: "link", + softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null, + title: searchAdAction.action.title, + id: `${linkIndex}`, + idType: "sequential", + }); + action = searchAdAction; + } + else { + const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, link, shelfToken, false, null, metricsBase, null); + if (serverData.isNullOrEmpty(metadata) || serverData.isNullOrEmpty(metadata.action.title)) { + continue; + } + action = metadata.action; + const contentId = groupingShelfControllerCommon.contentIdFromContentItem(objectGraph, link); + if (contentId) { + metricsHelpersImpressions.addImpressionFields(objectGraph, action, { + ...metricsBase, + kind: "link", + softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null, + title: action.title, + id: contentId, + }); + } + } + if (serverData.isNullOrEmpty(action)) { + continue; + } + items.push(action); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + if (serverData.isNullOrEmpty(items)) { + return null; + } + const actionsShouldHaveArtwork = shelfToken.isSearchLandingPage && + (objectGraph.client.isPhone || objectGraph.client.isVision) && + items.length >= 6; + if (actionsShouldHaveArtwork) { + for (const item of items) { + if (item instanceof models.SearchAdAction) { + item.action.artwork = createArtworkForResource(objectGraph, "systemimage://magnifyingglass"); + } + } + } + const shelf = new models.Shelf("action"); + shelf.isHorizontal = false; + shelf.items = items; + shelf.title = shelfToken.title; + shelf.subtitle = shelfToken.subtitle; + shelf.presentationHints = { isWidthConstrained: true }; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + if (shelfToken.isSearchLandingPage && (objectGraph.client.isPhone || objectGraph.client.isPad)) { + shelf.contentsMetadata = { + type: "searchLandingTrendingSection", + numberOfColumns: items.length >= 6 ? 2 : 1, + }; + } + return shelf; + } + // region Helpers + trendingSearchLinkFromData(objectGraph, link, locationTracker) { + const term = serverData.asString(link, "label"); + if (isNothing(term) || term.length === 0) { + return null; + } + const searchAction = new models.SearchAction(term, term, null, "suggested"); + metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", locationTracker); + metricsHelpersLocation.nextPosition(locationTracker); + return new models.SearchAdAction(searchAction); + } +} +//# sourceMappingURL=grouping-link-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller-common.js new file mode 100644 index 0000000..5c9273f --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller-common.js @@ -0,0 +1,608 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as adIncidents from "../../ads/ad-incident-recorder"; +import * as adStitch from "../../ads/ad-stitcher"; +import { searchLandingPagePositionInfo } from "../../ads/on-device-ad-stitch"; +import * as mediaUrlMapping from "../../builders/url-mapping"; +import * as videoDefaults from "../../constants/video-constants"; +import * as content from "../../content/content"; +import { CollectionShelfDisplayStyle } from "../../editorial-pages/editorial-page-types"; +import * as filtering from "../../filtering"; +import * as adLockups from "../../lockups/ad-lockups"; +import * as lockupsEditorialContext from "../../lockups/editorial-context"; +import * as lockups from "../../lockups/lockups"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as placeholders from "../../placeholders/placeholders"; +import * as room from "../../room/room-common"; +import * as topChartsCommon from "../../top-charts-common"; +import * as pageCommon from "../../util/page-common"; +import * as groupingTypes from "../grouping-types"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import * as color from "../../../foundation/util/color-util"; +import { pageRouter } from "../../builders/routing"; +import { isNothing, isSome } from "@jet/environment/types/optional"; +import * as modelsBase from "../../../api/models/base"; +import * as metricsLocation from "../../../common/metrics/helpers/location"; +import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common"; +import { makeRoomPageIntent } from "../../../api/intents/room-page-intent"; +import { makeChartsPageIntent } from "../../../api/intents/charts-page-intent"; +import { makeChartsPageURL } from "../../../common/charts/charts-page-url"; +import { getLocale } from "../../locale"; +import { getPlatform } from "../../preview-platform"; +import { genreIdFromChartURL } from "../../../common/grouping/render-grouping-page"; +import { injectCrossfireFlowForGameCenter } from "./grouping-game-center-popular-with-your-friends-shelf-controller"; +/** + * Determine the shelfContentType to use for a shelf given the shelf token + * @param objectGraph + * @param mediaApiData + * @param shelfToken + * @param groupingParseContext + */ +export function shelfContentType(objectGraph, mediaApiData, shelfToken, groupingParseContext) { + const featuredContentId = shelfToken.featuredContentId; + if (featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */ || + featuredContentId === 418 /* groupingTypes.FeaturedContentID.AppStore_Shelf */ || + featuredContentId === 495 /* groupingTypes.FeaturedContentID.AppStore_PopularWithYourFriendsMarker */ || + groupingTypes.isRecommendationsLockupShelf(featuredContentId)) { + let displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + if (!displayStyle) { + if (featuredContentId === 311 /* groupingTypes.FeaturedContentID.Sundance_RecommendedAppsShelf */ || + featuredContentId === 312 /* groupingTypes.FeaturedContentID.Sundance_RecommendedGamesShelf */) { + displayStyle = "large"; + } + else if (featuredContentId === 495 /* groupingTypes.FeaturedContentID.AppStore_PopularWithYourFriendsMarker */ || + featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) { + displayStyle = "medium"; + } + else { + displayStyle = "small"; + } + } + return `${displayStyle}Lockup`; + } + else if (featuredContentId === 431 /* groupingTypes.FeaturedContentID.AppStore_iAPShelf */) { + return "inAppPurchaseTiledLockup"; + } + else if (featuredContentId === 429 /* groupingTypes.FeaturedContentID.AppStore_ScreenShotShelf */) { + return "screenshotsLockup"; + } + else if (featuredContentId === 304 /* groupingTypes.FeaturedContentID.Sundance_iPhoneAppTrailerShelf */ || + featuredContentId === 305 /* groupingTypes.FeaturedContentID.Sundance_iPadAppTrailerShelf */ || + featuredContentId === 430 /* groupingTypes.FeaturedContentID.AppStore_VideoShelf */ || + featuredContentId === 420 /* groupingTypes.FeaturedContentID.AppStore_TrailerShelf */) { + return "appTrailerLockup"; + } + else if (groupingTypes.isTopChart(featuredContentId)) { + return "smallLockup"; + } + else if (featuredContentId === 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */) { + return "posterLockup"; + } + else { + switch (groupingParseContext.shelves.length % 3) { + case 0: { + return "smallLockup"; + } + case 1: { + return "mediumLockup"; + } + default: { + return "largeLockup"; + } + } + } +} +// region Shelf Token +export function lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + const lockupShelfToken = { + ...baseShelfToken, + shouldFilter: !mediaAttributes.attributeAsBooleanOrFalse(mediaApiData, "doNotFilter"), + chartUrl: mediaAttributes.attributeAsString(mediaApiData, "chartHref"), + chartIdentifier: mediaAttributes.attributeAsString(mediaApiData, "chart"), + roomRelationshipData: mediaRelationship.relationshipData(objectGraph, mediaApiData, "room"), + }; + if (groupingTypes.isTopChart(lockupShelfToken.featuredContentId)) { + lockupShelfToken.seeAllUrl = topChartsCommon.makeSeeAllUrlFromMediaApiData(objectGraph, mediaApiData, // chart shelf data includes selected chart + groupingParseContext.chartSet); + lockupShelfToken.showOrdinals = true; + lockupShelfToken.shouldFilter = false; + } + else if (serverData.isDefinedNonNullNonEmpty(lockupShelfToken.roomRelationshipData)) { + lockupShelfToken.seeAllUrl = mediaUrlMapping.hrefToRoutableUrl(objectGraph, lockupShelfToken.roomRelationshipData.href); + } + lockupShelfToken.shelfStyle = shelfContentType(objectGraph, mediaApiData, lockupShelfToken, groupingParseContext); + lockupShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData); + return lockupShelfToken; +} +// endregion +// region Shelf Creation +/** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ +export function createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const filterType = groupingTypes.isMacTopChart(shelfToken.featuredContentId) + ? 84862 /* filtering.Filter.ChartsMac */ + : 80894 /* filtering.Filter.All */; + let items = []; + // Stitch First Position Ad (only if lockup array is nonempty) + if (serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) { + const adLockup = lockupFromAdStitcher(objectGraph, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.adStitcher, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.adIncidentRecorder, searchLandingPagePositionInfo, shelfToken); + if (adLockup && adLockup instanceof models.Lockup) { + items.push(adLockup); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + shelfToken.ordinalIndex++; + shelfData.shelfContents = shelfData.shelfContents.filter((data) => data.id !== adLockup.adamId); // Filter dupe + } + } + // Data to use for `seeAllContents` in Category Breakout shelf + let seeAllContents; + // Build Lockups + for (const lockupData of shelfData.shelfContents) { + // If we encounter a type of app-events, this means they have been incorrectly programmed, + // and we should throw the shelf away. + if (lockupData.type === "app-events") { + return null; + } + // If we encounter a type of `editorial-items` in + // Category Breakout shelf, it is to use for `seeAllContents`. + if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */ && + lockupData.type === "editorial-items") { + seeAllContents = lockupData; + continue; + } + if (serverData.isNull(lockupData.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(lockupData); + continue; + } + // Filter out unwanted content + if (filtering.shouldFilter(objectGraph, lockupData, filterType)) { + continue; + } + const lockup = lockupFromData(objectGraph, lockupData, shelfToken); + if (lockup) { + items.push(lockup); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + shelfToken.ordinalIndex++; + } + } + // Truncate non-Arcade top charts to multiples of 3 + if (groupingTypes.isTopChart(shelfToken.featuredContentId) && + !groupingTypes.isArcadeTopChart(shelfToken.featuredContentId) && + !groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId) && + !objectGraph.client.isMac) { + items = pageCommon.truncateItems(items, 3); + } + // We don't need this in our incomplete shelf URL, so we'll preemptively remove it. + delete shelfToken.maxItemCount; + // Create shelf header + let shelfHeader; + if (groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId)) { + // Show Game Center eyebrow for Game Center top chart shelves + shelfHeader = makeGameCenterHeader(objectGraph, shelfToken.title); + } + else { + shelfHeader = { + eyebrow: shelfToken.eyebrow, + eyebrowArtwork: shelfToken.eyebrowArtwork, + title: shelfToken.title, + titleArtwork: shelfToken.titleArtwork, + subtitle: shelfToken.subtitle, + configuration: shelfToken.shelfHeaderConfiguration, + }; + } + // Create shelf + const shelf = new models.Shelf(shelfToken.shelfStyle); + shelf.isHorizontal = true; + shelf.items = items; + shelf.shouldFilterApps = shelfToken.shouldFilter; + if (groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId)) { + shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp"); + shelf.footerAction = openGamesUIAction(objectGraph); + shelf.footerStyle = { + $kind: "games", + bundleID: "com.apple.games", + width: 16, + height: 16, + }; + shelf.items.forEach((lockup, index) => { + if (isSome(lockup) && lockup instanceof models.Lockup) { + lockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, lockup.clickAction); + } + }); + } + if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) { + // Configure Category Breakout shelf + configureCategoryBreakoutShelf(objectGraph, shelfToken, shelf, shelfHeader, items, seeAllContents, groupingParseContext); + } + else if (!objectGraph.props.enabled("redownloadButtonTintUsingOfferTheme")) { + shelf.items.forEach((lockup, index) => { + if (isSome(lockup) && lockup instanceof models.Lockup) { + lockup.redownloadButtonTint = color.named("systemBlue"); + } + }); + } + // If no items should we display placeholders for this shelf? + const willHydrateShelfLater = shelf && serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender; + if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) { + placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId); + } + // Create the action + let action; + if (groupingTypes.isTopChart(shelfToken.featuredContentId)) { + action = new models.FlowAction("topCharts"); + } + else { + action = new models.FlowAction("page"); + // The web client makes a separate request for See All pages, so we don't sidepack for web. + if (!placeholders.isPlaceholderShelf(shelf) && !objectGraph.client.isWeb) { + // Create the sidepacked room page + const preferredShelfStyle = preferredRoomSeeAllShelfStyle(objectGraph, shelfToken.shelfStyle); + action.pageData = room.seeAllPage(objectGraph, shelfToken.title, preferredShelfStyle); + } + } + if (objectGraph.client.isWeb) { + if (groupingTypes.isTopChart(shelfToken.featuredContentId)) { + const genre = (shelfToken.seeAllUrl && genreIdFromChartURL(shelfToken.seeAllUrl)) || shelfToken.pageGenreId || ""; + const destination = makeChartsPageIntent({ + ...getLocale(objectGraph), + ...getPlatform(objectGraph), + chart: shelfToken.chartIdentifier, + genreId: genre.toString(), + }); + action.destination = destination; + action.pageUrl = makeChartsPageURL(objectGraph, destination); + } + else { + const destination = makeRoomPageIntent({ + ...getLocale(objectGraph), + ...getPlatform(objectGraph), + id: shelfToken.id, + }); + action.destination = destination; + action.pageUrl = room.makeCanonicalRoomPageUrl(objectGraph, destination); + } + } + else { + action.pageUrl = shelfToken.seeAllUrl; + } + action.title = objectGraph.loc.string("ACTION_SEE_ALL"); + action.referrerUrl = shelfToken.metricsPageInformation.pageUrl; + metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, action, shelfToken.seeAllUrl, { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }); + const shouldIncludeSeeAllAction = shouldShowSeeAll(objectGraph, shelf.items, shelfToken); + const seeAllActionIsValid = serverData.isDefinedNonNull(action.pageUrl) || serverData.isDefinedNonNull(action.pageData); + if (shouldIncludeSeeAllAction && seeAllActionIsValid) { + groupingShelfControllerCommon.replaceShelfHeaderSeeAllAction(objectGraph, shelfHeader, action); + } + if (shelfToken.shelfStyle === "screenshotsLockup" || shelfToken.shelfStyle === "appTrailerLockup") { + // `suppressTagline` is passed from MediaAPI as a string (see rdar://89933411) + const shouldShowSupplementaryText = mediaAttributes.attributeAsString(shelfToken.featuredContentData, "suppressTagline") !== "true"; + if (serverData.isNull(shelf.presentationHints)) { + shelf.presentationHints = { showSupplementaryText: shouldShowSupplementaryText }; + } + else { + shelf.presentationHints = { + ...shelf.presentationHints, + showSupplementaryText: shouldShowSupplementaryText, + }; + } + } + // On macOS, posters should take up the full width unless the shelf has enough items. + if (shelfToken.shelfStyle === "posterLockup" && objectGraph.client.isMac && shelf.items.length < 3) { + if (serverData.isNull(shelf.presentationHints)) { + shelf.presentationHints = { isLowDensity: true }; + } + else { + shelf.presentationHints = { ...shelf.presentationHints, isLowDensity: true }; + } + } + shelf.header = shelfHeader; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + return shelf; +} +// endregion +// region Lockup Creation +/** + * Create a lockup for shelfContents to display within a grouping shelf + * @param objectGraph + * @param lockupData shelfContents to create lockup for. + * @param shelfToken Shelf shelfToken + */ +export function lockupFromData(objectGraph, lockupData, shelfToken) { + if (serverData.isNullOrEmpty(lockupData)) { + return null; + } + // Set the ordinal + let ordinalString; + if (shelfToken.showOrdinals) { + ordinalString = objectGraph.loc.decimal(shelfToken.ordinalIndex); + } + let offerStyle = null; + if (serverData.isDefinedNonNull(shelfToken.shelfBackground) && + (shelfToken.shelfBackground.type === "color" || shelfToken.shelfBackground.type === "interactive")) { + offerStyle = "white"; + } + let clientIdentifierOverride; + if (serverData.isDefinedNonNullNonEmpty(shelfToken)) { + clientIdentifierOverride = shelfToken.clientIdentifierOverride; + } + // Create the lockup + const lockupOptions = { + ordinal: ordinalString, + metricsOptions: { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(lockupData), + isAdvert: adLockups.isAdvert(objectGraph, lockupData), + }, + clientIdentifierOverride: clientIdentifierOverride, + artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfToken.shelfStyle), + offerStyle: offerStyle, + canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfToken.shelfStyle), + isContainedInPreorderExclusiveShelf: shelfToken.featuredContentId === 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */, + shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && shelfToken.isArcadePage, + }; + let lockup; + switch (shelfToken.shelfStyle) { + case "appTrailerLockup": + lockup = lockups.trailersLockupFromData(objectGraph, lockupData, lockupOptions, videoDefaults.defaultVideoConfiguration(objectGraph)); + break; + case "screenshotsLockup": + lockup = lockups.screenshotsLockupFromData(objectGraph, lockupData, lockupOptions); + break; + case "posterLockup": + lockup = lockups.posterLockupFromData(objectGraph, lockupData, lockupOptions); + break; + case "inAppPurchaseLockup": + case "inAppPurchaseTiledLockup": + lockup = lockups.inAppPurchaseLockupFromData(objectGraph, lockupData, lockupOptions); + break; + case "smallImageLockup": + case "mediumImageLockup": + case "largeImageLockup": + lockup = lockups.imageLockupFromData(objectGraph, lockupData, lockupOptions, CollectionShelfDisplayStyle.EditorialLockupLarge); + break; + default: + lockup = lockups.lockupFromData(objectGraph, lockupData, lockupOptions); + } + if (serverData.isNull(lockup) || !lockup.isValid()) { + return null; + } + return lockup; +} +// endregion +// region See All +/** + * Determine whether lockup shelves should show "See All" on given platforms. + * This covers both top charts shelves and lockup shelves. + * Special care should be taken to not make assumptions about the hydration state of the lockup shelves. + * + * @param objectGraph The App Store object graph. + * @param readyItems Items that will be vended as part of `shelf.items` in this shelf. This may be subset of items in shelf following subsequent fetch. + * @param shelfToken Grouping shelf shelfToken for shelf. This is used for determining whether shelf is part of initial page render, and to check whether there are more items to load. + * @return Whether or not a lockup shelf with given properties should have a "See All" action. + */ +export function shouldShowSeeAll(objectGraph, readyItems, shelfToken) { + // We do not want to attach "See All" if the grouping type is a Category Breakout Marker. + if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) { + return false; + } + // We do not want to attach "See All" if the grouping type is one of the Arcade Top Chart types + if (groupingTypes.isArcadeTopChart(shelfToken.featuredContentId)) { + return false; + } + // We do not want to attach "See All" if the grouping type is one of the Game Center Top Chart types + if (groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId)) { + return false; + } + // On tvOS, "See All" is exposed as a clickable cell in-line with lockup cells. We want to show "See All" cells for: + // 1. Shelf that have completed loading, i.e. don't have `shelfToken.remainingItems`. + // 2. Lockup shelves that have *a lot* of content, which we clip within grouping shelves and show in full only in in the "See All" page. + // + // *Note* that we are *NOT* showing "See All" for shelves that have a "See All" url configured on tvOS. + // It appears that every shelf has a "See All" url, and that url returns the exact same contents as contents of shelf's `contents.data` as part of initial page fetch causing this: + // <rdar://problem/50230334> RFW4: Grouping: Shelves shouldn't have see all unless there's more to show. + // + // It appears we don't have capability to know in advance whether "See All" url has more to show than what is part of `contents.data` in initial page fetch. + // In our experiments, it looks like they are always the same. If this changes, we'd also want: + // const hasSeeAllRoom = shelfToken.seeAllUrl?.length > 0; + // if (hasSeeAllRoom) { + // return true; + // } + // + // In it's current form, we don't do (2), since we don't limit the number of lockups in a shelf we display within groupings. That is tracked under: + // <rdar://problem/50671635> Grouping: Limit number of items in shelves + if (objectGraph.client.isTV) { + // Whether or not this shelf is a partially hydrated shelf. This means there should be a secondary fetch request which causes the shelf will grow. + // We'll opt to *NEVER* show "See All" if the shelf will grow, and tack it on in the secondary shelf fetch. + const willHaveSubsequentFetch = serverData.isDefinedNonNullNonEmpty(shelfToken.remainingItems); + if (willHaveSubsequentFetch) { + return false; + } + // Whether or not this shelf is a nonempty top charts. + const isNonemptyTopCharts = groupingTypes.isTopChart(shelfToken.featuredContentId) && serverData.isDefinedNonNullNonEmpty(readyItems); + if (isNonemptyTopCharts) { + return true; + } + // Otherwise, don't show "See All" on tvOS. + return false; + } + // On all other platforms, we always to attach "See All", except AppStore_ComingSoon, + // which should not because the see all page won't drop lockups whose pre-order has + // been released (which the AppStore_ComingSoon swoosh will). + return shelfToken.featuredContentId !== 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */; +} +/** + * Coerces the provided shelf content type into a type suitable for use in a 'See All' room. + * @param objectGraph + * @param originalShelfStyle The content type that defines the 'See All' parent. + * @returns {models.ShelfContentType} The content type to use for the 'See All' room. Returns `null` if there is no + * preference, at which point the room builder will provide its default. + */ +export function preferredRoomSeeAllShelfStyle(objectGraph, originalShelfStyle) { + if (!originalShelfStyle) { + return null; + } + switch (originalShelfStyle) { + case "inAppPurchaseLockup": + case "inAppPurchaseTiledLockup": + return originalShelfStyle; + default: + return null; + } +} +// endregion +// region Category Breakout +export function configureCategoryBreakoutShelf(objectGraph, shelfToken, shelf, shelfHeader, items, seeAllContents, groupingParseContext) { + // If `seeAllContents` is not hydrated, fetch it from the relationship data. + if (isNothing(seeAllContents)) { + seeAllContents = mediaRelationship.relationshipData(objectGraph, shelfToken.featuredContentData, "see-all-contents"); + } + // Trailing artwork + shelfHeader.configuration.includeTrailingArtwork = true; + // If `seeAllContents` is sparse, add it to the remaining items to hydrate. + if (!mediaAttributes.hasAttributes(seeAllContents)) { + const badge = mediaAttributes.attributeAsString(shelfToken.featuredContentData, "name"); + shelfHeader.eyebrow = badge; + shelfHeader.title = ""; + shelf.footerTitle = ""; + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(seeAllContents); + return; + } + // Badge + const badge = mediaAttributes.attributeAsString(shelfToken.featuredContentData, "name"); + shelfHeader.eyebrow = badge; + // Title + const editorialNotes = mediaAttributes.attributeAsDictionary(seeAllContents, "editorialNotes"); + const title = serverData.asString(editorialNotes, "name"); + shelfHeader.title = title; + // Configure location name to the updated title + metricsLocation.currentLocation(shelfToken.metricsLocationTracker).name = title; + // Configure impression name to the updated title if placeholder shelf used + if (serverData.isDefinedNonNullNonEmpty(shelfToken.originalPlaceholderShelfImpressionMetrics)) { + shelfToken.originalPlaceholderShelfImpressionMetrics.fields.name = title; + } + // Artwork + const editorialArtwork = mediaAttributes.attributeAsDictionary(seeAllContents, "editorialArtwork"); + // Drop the shelf if there is no editorial artwork. + if (isNothing(editorialArtwork)) { + shelf.isHidden = true; + return; + } + // Background artwork + const backgroundArtworkDict = serverData.asDictionary(editorialArtwork, "storyBackgroundStatic16x9"); + const backgroundArtworkOptions = { + useCase: 28 /* content.ArtworkUseCase.CategoryBreakoutShelf */, + withJoeColorPlaceholder: true, + }; + const backgroundArtwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, backgroundArtworkDict, backgroundArtworkOptions); + const backgroundStyle = color.isDarkColor(backgroundArtwork.backgroundColor) ? "dark" : "light"; + shelf.background = { + type: "artwork", + artwork: backgroundArtwork, + style: backgroundStyle, + }; + // Lockup offerDisplayProperties + items.forEach((lockup, index) => { + if (isSome(lockup) && lockup instanceof models.Lockup) { + lockup.offerDisplayProperties = lockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "transparent", backgroundStyle); + lockup.subtitleTextFilter = "plusLight"; + if (!objectGraph.props.enabled("redownloadButtonTintUsingOfferTheme")) { + lockup.redownloadButtonTint = color.named("white"); + } + } + }); + // Trailing artwork + const trailingArtworkDict = serverData.asDictionary(editorialArtwork, "contentGraphicTrimmed"); + // Drop the shelf if there is no trailing artwork. + if (isNothing(trailingArtworkDict)) { + shelf.isHidden = true; + return; + } + const trailingArtworkOptions = { + contentMode: modelsBase.ArtworkContentMode.scaleAspectFit, + useCase: 18 /* content.ArtworkUseCase.GroupingBrick */, + }; + const trailingArtwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, trailingArtworkDict, trailingArtworkOptions); + shelfHeader.trailingArtwork = trailingArtwork; + // Footer + const actionUrl = serverData.asString(seeAllContents.attributes, "url"); + const footerTitle = serverData.asString(seeAllContents.attributes, "breakoutCallToActionLabel"); + const clickOptions = { + id: seeAllContents.id, + idType: "its_id", + targetType: "button", + actionType: "navigate", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }; + if (isSome(actionUrl)) { + // Footer title + shelf.footerTitle = footerTitle; + // Footer action + const flowPage = objectGraph.required(pageRouter).fetchFlowPage(actionUrl); + const flowAction = new models.FlowAction(flowPage); + flowAction.title = footerTitle; + flowAction.pageUrl = actionUrl; + metricsHelpersClicks.addClickEventToAction(objectGraph, flowAction, clickOptions); + shelf.footerAction = flowAction; + } + else { + const metricsOptions = { + targetType: "button", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(seeAllContents), + }; + const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, seeAllContents, shelfToken, false, null, metricsOptions, groupingParseContext); + if (isSome(metadata.action)) { + // Clear `actionMetrics` before adding a click event with updated `title`. + metadata.action.actionMetrics.clearAll(); + metadata.action.title = footerTitle !== null && footerTitle !== void 0 ? footerTitle : serverData.asString(editorialNotes, "callToAction"); + metricsHelpersClicks.addClickEventToAction(objectGraph, metadata.action, clickOptions); + shelf.footerAction = metadata.action; + shelf.footerTitle = metadata.action.title; + } + } +} +// endregion +// region Ads +/** + * Performs `lockupFromData`, but additional with Ad stitch related side-effects. + * @param objectGraph The AppStore dependency graph + * @param adStitcher Stitcher to source shelfContents from. May be undefined + * @param adIncidentRecorder Incident recorder for adding ads. + * @param positionInfo Position this ad is being stitched into. + * @param shelfToken shelfToken for shelf this ad lockup will be in. + */ +export function lockupFromAdStitcher(objectGraph, adStitcher, adIncidentRecorder, positionInfo, shelfToken) { + const task = adStitch.consumeTask(adStitcher, positionInfo); + if (serverData.isNull(task)) { + return null; // no task for position + } + // Try to create lockup + const lockupData = task.data; + const lockup = lockupFromData(objectGraph, lockupData, shelfToken); + if (serverData.isDefinedNonNull(lockup)) { + shelfToken.includedAdAdamIds = [lockupData.id]; + } + else { + adIncidents.recordLockupFromDataFailed(objectGraph, adIncidentRecorder, lockupData); + } + return lockup; +} +// endregion +//# sourceMappingURL=grouping-lockup-shelf-controller-common.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller.js new file mode 100644 index 0000000..211db36 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller.js @@ -0,0 +1,104 @@ +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as groupingTypes from "../grouping-types"; +import { createLockupShelf, lockupShelfTokenFromBaseTokenAndMediaApiData, } from "./grouping-lockup-shelf-controller-common"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +export class GroupingLockupShelfController extends GroupingShelfController { + // region Constructors + constructor(builderClass = null) { + super(builderClass || "GroupingLockupShelfController"); + this.supportedFeaturedContentIds = new Set([ + ...groupingTypes.topChartFeaturedContentIds, + ...groupingTypes.lockupShelfFeaturedContentIds, + ]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + const shelfContents = mediaRelationship.relationship(mediaApiData, "contents"); + let shelfData = shelfContents ? shelfContents.data : null; + if (!shelfData || shelfData.length === 0) { + shelfData = mediaRelationship.relationshipCollection(mediaApiData, "children"); + } + return { shelfContents: shelfData }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext); + } + // endregion + // region Metrics + /** + * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping + * page controller + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the + * grouping page controller + */ + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + const shelfMetricsOptions = { ...baseMetricsOptions }; + shelfMetricsOptions.displayStyle = shelfToken.shelfStyle; + // Reconfigure Category Breakout metrics options + if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) { + const seeAllContents = mediaRelationship.relationshipData(objectGraph, shelfToken.featuredContentData, "see-all-contents"); + const editorialNotes = mediaAttributes.attributeAsDictionary(seeAllContents, "editorialNotes"); + const title = serverData.asString(editorialNotes, "name"); + shelfMetricsOptions.title = title; + shelfMetricsOptions.idType = "its_contentId"; + shelfMetricsOptions.badges = { forYou: true }; + shelfMetricsOptions.targetType = "swooshBreakout"; + } + return shelfMetricsOptions; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const isCategoryBreakoutShelf = shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */; + if (isCategoryBreakoutShelf) { + const hasNoContents = !serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents); + const isClientPad = objectGraph.client.isPad; + // Drop Category Breakout IF + // - It has no contents OR + // - Client is an iPad OR + if (hasNoContents || isClientPad) { + return null; + } + } + return createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext); + } +} +//# sourceMappingURL=grouping-lockup-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-personalized-lockup-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-personalized-lockup-shelf-controller.js new file mode 100644 index 0000000..7e28d30 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-personalized-lockup-shelf-controller.js @@ -0,0 +1,368 @@ +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 mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaNetwork from "../../../foundation/media/network"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as odpCommon from "../../personalization/on-device-recommendations-common"; +import { defaultRoomShelfContentType } from "../../room/room-page"; +import { platformPrefersLargeTitles } from "../../room/room-common"; +import * as groupingTypes from "../grouping-types"; +import { createLockupShelf, lockupShelfTokenFromBaseTokenAndMediaApiData, } from "./grouping-lockup-shelf-controller-common"; +import { GroupingShelfController, routesForFeaturedContentIds } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { isNothing, isSome } from "@jet/environment"; +export class GroupingPersonalizedLockupShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingPersonalizedLockupShelfController"); + this.supportedFeaturedContentIds = groupingTypes.recommendationsLockupShelfFeaturedContentIds; + } + // endregion + // region Shelf Builder + shelfRoute(objectGraph) { + return routesForFeaturedContentIds(this.supportedFeaturedContentIds, [ + `${Parameters.isOnDeviceRecommendationsShelf}?`, + `${Parameters.onDeviceRecommendationsUseCase}?`, + ]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + const shelfContents = mediaRelationship.relationship(mediaApiData, "contents"); + let shelfData = shelfContents ? shelfContents.data : null; + if (!shelfData || shelfData.length === 0) { + shelfData = mediaRelationship.relationshipCollection(mediaApiData, "children"); + } + const recoMetrics = mediaDataStructure.metricsFromMediaApiObject(shelfContents); + return { + shelfContents: shelfData || [], + containsODPShelfContents: false, + recoMetrics: recoMetrics, + candidates: null, + isHiddenShelf: objectGraph.client.isWeb, + responseTimingValues: null, + }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + var _a; + const onDeviceRecommendationsUseCase = parameters[Parameters.onDeviceRecommendationsUseCase]; + if ((onDeviceRecommendationsUseCase === null || onDeviceRecommendationsUseCase === void 0 ? void 0 : onDeviceRecommendationsUseCase.length) > 0) { + return await odpCommon + .recommendedAppsForUseCase(objectGraph, onDeviceRecommendationsUseCase, "shelf") + .then((response) => { + return { + shelfContents: mediaDataStructure.dataCollectionFromDataContainer(response.dataContainer), + containsODPShelfContents: true, + recoMetrics: response.recoMetrics, + candidates: response.candidates, + isHiddenShelf: false, + }; + }) + .catch(async (error) => { + if (error instanceof odpCommon.NoODPCandidatesError) { + return await GroupingPersonalizedLockupShelfController.fetchODPFallbackContent(objectGraph, shelfUrl, shelfToken, parameters).catch((fallbackError) => { + return GroupingPersonalizedLockupShelfController.makeHiddenShelfData(shelfToken); + }); + } + else { + return GroupingPersonalizedLockupShelfController.makeHiddenShelfData(shelfToken); + } + }); + } + else if (((_a = shelfToken.recommendationsHref) === null || _a === void 0 ? void 0 : _a.length) > 0) { + return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((mediaApiData) => { + const personalizedFeaturedContentData = mediaDataStructure.dataFromDataContainer(objectGraph, mediaApiData); + const personalizedLockupShelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, personalizedFeaturedContentData); + personalizedLockupShelfData.responseTimingValues = mediaApiData[ResponseMetadata.timingValues]; + personalizedLockupShelfData.shelfTitle = mediaAttributes.attributeAsString(personalizedFeaturedContentData, "name"); + return personalizedLockupShelfData; + }); + } + else { + return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((mediaApiData) => { + const personalizedFeaturedContentData = mediaDataStructure.dataCollectionFromDataContainer(mediaApiData); + const personalizedLockupShelfData = { + shelfContents: personalizedFeaturedContentData || [], + containsODPShelfContents: false, + recoMetrics: null, + candidates: null, + isHiddenShelf: false, + responseTimingValues: mediaApiData[ResponseMetadata.timingValues], + }; + personalizedLockupShelfData.shelfTitle = shelfToken.title; + return personalizedLockupShelfData; + }); + } + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + const personalizedLockupShelfToken = lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext); + this.addPersonalizationValuesToShelfToken(objectGraph, personalizedLockupShelfToken, mediaApiData, groupingParseContext); + return personalizedLockupShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + var _a, _b; + if (!shelfToken.isValidRecommendationsShelf) { + return null; + } + let shelf = null; + if (isNothing(shelfToken.title)) { + // If we've fetched the title in a secondary lookup, update the title here + // in the token, since other places may rely on it. + shelfToken.title = shelfData.shelfTitle; + } + if (shelfData.containsODPShelfContents) { + shelf = this.personalizedShelf(objectGraph, shelfData, shelfToken, groupingParseContext); + const seeAllUrl = new urls.URL() + .set("protocol", Protocol.internal) + .path(`${Path.onDeviceRecommendations}`) + .param(Parameters.onDeviceRecommendationsUseCase, shelfToken.onDeviceRecommendationsUseCase) + .param(Parameters.token, JSON.stringify(shelfToken)) + .build(); + const seeAllAction = new models.FlowAction("page", seeAllUrl); + seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL"); + seeAllAction.pageData = this.odpSeeAllPage(objectGraph, shelfData.shelfTitle, defaultRoomShelfContentType); + groupingShelfControllerCommon.replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction); + shelf.mergeWhenFetched = false; + } + else if (shelfData.isHiddenShelf) { + shelf = GroupingPersonalizedLockupShelfController.makeHiddenShelf(shelfToken); + } + else if (serverData.isDefinedNonNull(shelfToken.recommendationsHref)) { + shelf = this.personalizedShelf(objectGraph, shelfData, shelfToken, groupingParseContext); + shelf.url = this.addOnDeviceQueryParamsIfNecessary(objectGraph, shelf.url, shelfToken); + } + else { + shelf = createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext); + shelf.url = this.addOnDeviceQueryParamsIfNecessary(objectGraph, shelf.url, shelfToken); + } + const isUsingHeader = isSome(shelf.header); + const isMissingHeaderTitle = isUsingHeader && serverData.isNullOrEmpty((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.title); + const isMissingLegacyShelfTitle = !isUsingHeader && serverData.isNullOrEmpty(shelf.title); + const hasShelfDataTitle = ((_b = shelfData.shelfTitle) === null || _b === void 0 ? void 0 : _b.length) > 0; + if (isMissingHeaderTitle && hasShelfDataTitle) { + shelf.header.title = shelfData.shelfTitle; + } + else if (isMissingLegacyShelfTitle && hasShelfDataTitle) { + shelf.title = shelfData.shelfTitle; + } + return shelf; + } + // endregion + // region Helpers + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfData The media api shelfContents array for this shelf + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * @private + */ + personalizedShelf(objectGraph, shelfData, shelfToken, groupingParseContext) { + /** + * Update shelf impressions for personalized shelves. + * <rdar://problem/69308786> reco_algo_id doesn't get passed to Figaro for out of band personalization marker + * + * By happenstance, the location tracker encoded in the shelf token usually contains the swoosh location, given the happy path fetches *more* items. + * For Personalized shelves, we need to pop the "empty" shelf location and replace it with the fully populated one. + */ + const location = metricsHelpersLocation.currentLocation(shelfToken.metricsLocationTracker); + if (location && location.fcKind === shelfToken.featuredContentId) { + metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker); + } + const shelfMetricsOptions = { + id: shelfToken.id, + kind: null, + softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null, + targetType: "swoosh", + title: shelfToken.title, + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + idType: "its_contentId", + fcKind: shelfToken.featuredContentId, + recoMetricsData: shelfData.recoMetrics, + displayStyle: shelfToken.shelfStyle, + }; + metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, shelfToken.title); + const shelf = createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext); + metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker); + metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions); + return shelf; + } + /** + * Check in the media api data for this shelf and add any of the necessary personalization fields to the shelfToken. + * + * @param objectGrpah + * @param shelfToken The shelf token in its current state + * @param mediaApiData The media api data for this section of the grouping page + * @param groupingParseContext The parse context for the grouping page so far + * @private + */ + addPersonalizationValuesToShelfToken(objectGrpah, shelfToken, mediaApiData, groupingParseContext) { + const isPersonalizedShelfMarker = shelfToken.featuredContentId === 476 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedShelfMarker */; + // If we don't have a logged in user don't show personalized shelves + // For search grouping we allow these shelves even if there is no logged in user. + // For the personalized shelf markers we are going to allow reco to send back a fallback list of ids so that + // even logged out users can see these shelves. + // rdar://64005675 (Client to not drop personalized marker response for non-signed users (dsId - 0)) + if (!groupingParseContext.hasAuthenticatedUser && + !shelfToken.isSearchLandingPage && + !isPersonalizedShelfMarker) { + objectGrpah.console.log(`Skipping recommendations shelf with fcID ${shelfToken.featuredContentId}: No user logged-in`); + return; + } + const onDevicePersonalizationUseCase = mediaAttributes.attributeAsString(mediaApiData, "onDevicePersonalizationUseCase"); + const usingOnDevicePersonalization = (onDevicePersonalizationUseCase === null || onDevicePersonalizationUseCase === void 0 ? void 0 : onDevicePersonalizationUseCase.length) > 0; + if (usingOnDevicePersonalization) { + shelfToken.onDeviceRecommendationsUseCase = onDevicePersonalizationUseCase; + shelfToken.recommendationsHref = mediaApiData.href; + shelfToken.isValidRecommendationsShelf = true; + return; + } + const shelfContents = mediaRelationship.relationshipCollection(mediaApiData, "contents"); + const hasContents = serverData.isDefinedNonNullNonEmpty(shelfContents); + const shelfPersonalizationAvailable = !mediaAttributes.attributeAsBooleanOrFalse(mediaApiData, "noPersonalizationAvailable"); + if (!hasContents && shelfPersonalizationAvailable) { + shelfToken.recommendationsHref = mediaApiData.href; + shelfToken.isValidRecommendationsShelf = true; + // tslint:disable-next-line:no-redundant-jump + } + else { + shelfToken.isValidRecommendationsShelf = hasContents; + } + } + /** + * For a given shelf url, add the on device personalization query param if its needed + * + * @param objectGraph + * @param urlString The original url string for this shelf + * @param shelfToken The shelf shelfToken for this current shelf creation request + */ + addOnDeviceQueryParamsIfNecessary(objectGraph, urlString, shelfToken) { + var _a; + if (serverData.isNullOrEmpty(urlString)) { + return null; + } + const isOnDeviceRecommendationsGamesForYouEnabled = objectGraph.host.isiOS; + if (!isOnDeviceRecommendationsGamesForYouEnabled) { + return urlString; + } + if (((_a = shelfToken.onDeviceRecommendationsUseCase) === null || _a === void 0 ? void 0 : _a.length) > 0) { + const url = new urls.URL(urlString); + url.param(Parameters.isOnDeviceRecommendationsShelf, "true"); + url.param(Parameters.onDeviceRecommendationsUseCase, shelfToken.onDeviceRecommendationsUseCase); + return url.build(); + } + else { + return urlString; + } + } + /** + * Creates a page that can be used for side-packing see all pages into a room. + * + * @param objectGraph + * @param {string} title The title of the destination page + * @param {ShelfContentType} preferredShelfContentType The content type to use for the page + * @returns {GenericPage} A GenericPage which will use the parentShelfItems from the see all to render the initial room + */ + odpSeeAllPage(objectGraph, title, preferredShelfContentType) { + const shelf = new models.Shelf(preferredShelfContentType || defaultRoomShelfContentType); + shelf.isHorizontal = false; + shelf.items = "parentShelfItems"; + const page = new models.GenericPage([shelf]); + page.isIncomplete = true; + page.title = title; + if (platformPrefersLargeTitles(objectGraph)) { + page.presentationOptions = ["prefersLargeTitle"]; + } + return page; + } + // endregion + // region ODP Fallback + static async fetchODPFallbackContent(objectGraph, url, shelfToken, parameters) { + return await new Promise((resolve, reject) => { + const fallbackRequest = groupingShelfControllerCommon.generateShelfRequest(objectGraph, shelfToken, parameters); + if (!fallbackRequest) { + const errorMessage = `OnDeviceRecommendationsShelfController: Could not construct media API request for: ${url}`; + validation.unexpectedType("defaultValue", errorMessage, null); + reject(new Error(errorMessage)); + return; + } + groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, fallbackRequest); + fallbackRequest.attributingTo(url.build()); + mediaNetwork + .fetchData(objectGraph, fallbackRequest) + .then((dataContainer) => { + const shelfData = mediaDataStructure.dataFromDataContainer(objectGraph, dataContainer); + const shelfContents = mediaRelationship.relationship(shelfData, "contents"); + const recoMetrics = mediaDataStructure.metricsFromMediaApiObject(shelfContents); + const fallbackShelfData = { + shelfContents: mediaDataStructure.dataCollectionFromDataContainer(shelfContents), + containsODPShelfContents: false, + recoMetrics: recoMetrics, + candidates: null, + responseTimingValues: dataContainer[ResponseMetadata.timingValues], + }; + resolve(fallbackShelfData); + }) + .catch((error) => { + const errorMessage = `OnDeviceRecommendationsShelfController: Failed to fetch fallback shelf contents: ${url}`; + validation.unexpectedType("defaultValue", errorMessage, null); + reject(error); + }); + }); + } + static makeHiddenShelfData(shelfToken) { + const hiddenShelfData = { + shelfContents: [], + containsODPShelfContents: false, + recoMetrics: null, + candidates: null, + isHiddenShelf: true, + }; + return hiddenShelfData; + } + static makeHiddenShelf(shelfToken) { + const hiddenShelf = new models.Shelf(shelfToken.shelfStyle); + hiddenShelf.isHidden = true; + return hiddenShelf; + } +} +//# sourceMappingURL=grouping-personalized-lockup-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-ribbon-bar-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-ribbon-bar-shelf-controller.js new file mode 100644 index 0000000..bc0211c --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-ribbon-bar-shelf-controller.js @@ -0,0 +1,269 @@ +import { attributeAsString } from "@apple-media-services/media-api"; +import { isNothing, isSome } from "@jet/environment"; +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import { seeAllArcadeGamesPageFlowAction } from "../../arcade/arcade-common"; +import { pageRouter } from "../../builders/routing"; +import { hrefToRoutableUrl } from "../../builders/url-mapping"; +import { categoryArtworkData } from "../../categories"; +import * as artworkBuilder from "../../content/artwork/artwork"; +import * as content from "../../content/content"; +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 { areAppTagsEnabled } from "../../util/app-tags-util"; +import * as lottery from "../../util/lottery"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingRibbonBarShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingRibbonBarShelfController"); + this.supportedFeaturedContentIds = new Set([556 /* groupingTypes.FeaturedContentID.AppStore_RibbonBarMarker */]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + var _a; + const items = []; + const shelf = new models.Shelf("ribbonBar"); + shelf.isHorizontal = true; + const isSAGUpliftEnabledForCurrentUser = lottery.isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.arcadeCategoryBarSAGUpliftDisplayRate); + // Display See All Games facet when: + // - This is an Arcade page AND + // - This is the first render of the shelf AND + // - Bag is enabled for current user + if (shelfToken.isArcadePage && shelfToken.isFirstRender && isSAGUpliftEnabledForCurrentUser) { + const seeAllGamesRibbonItem = GroupingRibbonBarShelfController.createSeeAllGamesRibbonItem(objectGraph, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker); + items.push(seeAllGamesRibbonItem); + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + // If we have any hydrated items stored in the token, it means we found an unhydrated priorized item. In this + // case, we combine the initial hydrated items with the secondary hydrated items, and build the entire set + // of ribbon items from this list. As we are manipulating the order, we also need to replace the original + // shelf rather than merge. + let combinedShelfContents = (_a = shelfData.shelfContents) !== null && _a !== void 0 ? _a : []; + if (isSome(shelfToken.initialHydratedItems) && shelfToken.initialHydratedItems.length > 0) { + combinedShelfContents = shelfToken.initialHydratedItems.concat(combinedShelfContents); + } + // Tracks the initial set of hydrated items from the initial shelf load. This will be stored in the shelf + // token only if we find a prioritized item that is deferred. + const initialHydratedItems = []; + let isPrioritizedItemDeferred = false; + for (const ribbonData of combinedShelfContents) { + if (serverData.isNull(ribbonData.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(ribbonData); + if (GroupingRibbonBarShelfController.shouldPriorizeItemWithData(objectGraph, ribbonData)) { + isPrioritizedItemDeferred = true; + } + continue; + } + let isTextOnly = false; + if (isSome(shelfToken.featuredContentData)) { + const displayStyle = attributeAsString(shelfToken.featuredContentData, "displayStyle"); + isTextOnly = displayStyle === "textOnly"; + } + const ribbonModel = GroupingRibbonBarShelfController.createRibbonItem(objectGraph, ribbonData, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker, isTextOnly, shelfToken, groupingParseContext); + if (isSome(ribbonModel)) { + if (GroupingRibbonBarShelfController.shouldPriorizeItemWithData(objectGraph, ribbonData)) { + items.unshift(ribbonModel); + } + else { + items.push(ribbonModel); + } + initialHydratedItems.push(ribbonData); + } + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + if (objectGraph.client.isiOS || + objectGraph.featureFlags.isEnabled("shelves_2_0_arcade") || + objectGraph.featureFlags.isEnabled("shelves_2_0_generic")) { + shelf.items = items; + if (isPrioritizedItemDeferred && initialHydratedItems.length > 0) { + shelfToken.initialHydratedItems = initialHydratedItems; + } + } + else { + // Only set `shelf.items` if there are any `items` present + // so that hydrated items reload when they are fetched. + if (items.length > 0) { + const ribbonBar = new models.RibbonBar(items); + shelf.items = [ribbonBar]; + } + } + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + return shelf; + } + // region Static Helpers + static createRibbonItem(objectGraph, itemData, metricsPageInformation, metricsLocationTracker, isTextOnly, shelfToken, groupingParseContext) { + var _a, _b, _c; + const metricsOptions = { + targetType: "facet", + pageInformation: metricsPageInformation, + locationTracker: metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData), + }; + let metadata; + if (itemData.type === "tags") { + if (!areAppTagsEnabled(objectGraph, "grouping")) { + return null; + } + metadata = groupingShelfControllerCommon.metadataForTag(objectGraph, itemData, shelfToken, metricsOptions); + } + else { + metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, itemData, shelfToken, false, null, metricsOptions, groupingParseContext); + } + const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, itemData, metricsOptions); + metricsClickOptions.targetType = metricsOptions.targetType; + const actionFromData = lockups.actionFromData(objectGraph, itemData, metricsClickOptions, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride); + const action = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.action) !== null && _a !== void 0 ? _a : actionFromData; + const editorialNotesName = (_b = content.editorialNotesFromData(objectGraph, itemData, "name")) !== null && _b !== void 0 ? _b : attributeAsString(itemData, "name"); + const title = (_c = metadata === null || metadata === void 0 ? void 0 : metadata.title) !== null && _c !== void 0 ? _c : editorialNotesName; + const ribbonModel = new models.RibbonBarItem(title, action); + // Setup Artwork + const artworkDict = categoryArtworkData(objectGraph, itemData); + let artwork; + if (isTextOnly) { + artwork = null; + } + else { + if (isSome(artworkDict)) { + artwork = content.artworkFromApiArtwork(objectGraph, artworkDict, { + useCase: 29 /* content.ArtworkUseCase.RibbonBarFacet */, + }); + } + else { + let resource; + if (shelfToken.isArcadePage) { + resource = "resource://arcade-ribbon-bar-fallback-icon"; + } + else { + resource = "resource://appstore-ribbon-bar-fallback-icon"; + } + artwork = artworkBuilder.createArtworkForResource(objectGraph, resource, 36, 36); + } + } + ribbonModel.artwork = artwork; + ribbonModel.accessibilityLabel = title; + // Configure impressions + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, title, metricsOptions); + metricsHelpersImpressions.addImpressionFields(objectGraph, ribbonModel, impressionOptions); + if (!ribbonModel.isValid()) { + return null; + } + return ribbonModel; + } + /// Creates and returns Arcade See All Games ribbon item. + static createSeeAllGamesRibbonItem(objectGraph, metricsPageInformation, metricsLocationTracker) { + const title = objectGraph.loc.string("Arcade.CategoryBar.AllGames.Title"); + const action = seeAllArcadeGamesPageFlowAction(objectGraph, "releaseDate", metricsPageInformation, metricsLocationTracker, title, "AllGames", "none", "facet"); + const ribbonModel = new models.RibbonBarItem(title, action); + // Setup Artwork + // To update this artwork, run `cat artwork.png | base64` and prefix with `data:image/png;base64,`. + const resource = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAACACAYAAADHy7H2AAABdWlDQ1BrQ0dDb2xvclNwYWNlRGlzcGxheVAzAAAokXWQvUvDUBTFT6tS0DqIDh0cMolD1NIKdnFoKxRFMFQFq1OafgltfCQpUnETVyn4H1jBWXCwiFRwcXAQRAcR3Zw6KbhoeN6XVNoi3sfl/Ticc7lcwBtQGSv2AijplpFMxKS11Lrke4OHnlOqZrKooiwK/v276/PR9d5PiFlNu3YQ2U9cl84ul3aeAlN//V3Vn8maGv3f1EGNGRbgkYmVbYsJ3iUeMWgp4qrgvMvHgtMunzuelWSc+JZY0gpqhrhJLKc79HwHl4plrbWD2N6f1VeXxRzqUcxhEyYYilBRgQQF4X/8044/ji1yV2BQLo8CLMpESRETssTz0KFhEjJxCEHqkLhz634PrfvJbW3vFZhtcM4v2tpCAzidoZPV29p4BBgaAG7qTDVUR+qh9uZywPsJMJgChu8os2HmwiF3e38M6Hvh/GMM8B0CdpXzryPO7RqFn4Er/QcXKWq8UwZBywAAAARjSUNQDA0AAW4D4+8AAAB4ZVhJZk1NACoAAAAIAAUBEgADAAAAAQABAAABGgAFAAAAAQAAAEoBGwAFAAAAAQAAAFIBKAADAAAAAQACAACHaQAEAAAAAQAAAFoAAAAAAAAASAAAAAEAAABIAAAAAQACoAIABAAAAAEAAACCoAMABAAAAAEAAACAAAAAACBAcQEAAAAJcEhZcwAACxMAAAsTAQCanBgAAAIGaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4yMTk2PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjIxNjA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KIUPY5gAAGmtJREFUeAHtXXuMJMV5r+qZ2Qe7h299bA7O3B0GGwOXBwEsbMXCC1H+QJjHgpbHEcyBEiLl4cgJxLKElIkUS4DtQCCSpZOSEAx3hjW+YwmclAfa4MQScfBL2Tg5ED5uyfFYw0Lu9nZvHl35/b7qmu3Z65numZ3dndnpupvt7np+31e/+qq6uuorpVKXSiCVQCqBVAKpBFIJpBJIJZBKIJVAKoF6EtD1ApcbZpRa0fxJHwpAMe3hVprfleS1ZRVlRkayqr8/ozbPa7Wh36gTW3118KBRw8NG7djR2sqamrJ0D73mqdmztRqY8QQKc8M+yirpfN5fSWgYA/zdOJarKnfoNQNabLkrwe/MjFbnnqvV9LRXkfGm+bLOT5ZaweuygWCuvLKXhOgDB060gqDl5mHyylNTY6SpoMfHy8vNL5ze5PPIe2pF8g6X08i9GRvrUacf0/qR5cm/aSCIUA5N9uhHJxcc4ebma7aoTHabMmYLNPZpaDcbAJE+hGfg13RZLn939Y0xnucVlPGPI/9ZlPUO7t9Qpb7XUfnHXDyza6QvTJ/zb+YqgD9woAAmRLuhAvpVdmG70t6ZyujTkecQaBnwld/jad0yXgNayyjjBCB+FGX8HH5vqkJxWo8/e9jxIoCYmfH1ZHMaoimCTR7dgJr0dV755o6xYVUo3YiKvgJEfgJyolAAANWjPKuxhdimSnJsLrm6jgY6GuVSHc/jbhbiP4xq+gGENqH37vtHpjK7dgEMj1bAuiSnRI8EgdN45pbRzyDX6wDyS5D4LFw/jDIHlNYefja/leCVOVt+i7gj2N/B7xXw+qLKZZ/Uj40LKAgINIYCwhpyDZNs7rorp3fvJjHK3Dx6p9LmbuXp86XSSajvs5oYTrXMSqIvZcRLSxwydCIH0lABymThw58UpMrl4xDQ8ypT/rJ+fOLV5WgGJ1gzduWwyvU+AAauA68bAQBwB5aMKYOeEoq2/LaQz4qwOCYBZ/ixZWVQak5D7Qjrvoj4Z/B/WD+x/yFcVRi4fE7imHlixwGhUz1m57V/CUK+IML3/XmQeYI1LjVkQDDIRNVL/ga1whsE0xvRws8n+yMWo8KFuxP6uWcXbiPhr0iDCRApi7JOUdlMRpXK00iyC9rhhWbA4Pg1t157AYh/Gt3eeQAZKxxdklQ8LsIjKygkS0un47eaboYt5WWRLysbKyOXOa+ShhLlP2SIH3lmmTkIfQCNERSZx1Qx+9vUCA7ACE/kyEAiZzgI4xsAnNl53UMqm/0CxF6EBkAfrUogrQe13YP7HpCHrgPjAotgkEgcoOO0fad9to8MQqDghF2rPCM9gYSfe+YVz4xHvyDfIF4GVwhDyu1FIHk6BhB8gJa7FTL/trn1+k9xrMBuAmGJHMdABL0Zu3obyj2gvAxB8AESUy2TPvLJH8smDSw3+JFGchPQKtfwPdPjORwuz6xOGw+PEkF4DvyQP8uB5pMye3GlvAENM6vK/hy6iM/72eJfw08JGKQL51O8o1ATOSCsH5nPm1tHb0eCR1E4+6HjATEWo4lyWoVI5Mq2nKKocd9Mqb7yp/XfTBwNd21JKDE7R19QWe9yAOs9xOcbAytbGkSS9Csex/JKFcOBbAYNdFCVS3ejm/g6/LTK53WS12kyFesCNTNvdl6FkbH5kiTQAQgokvYRi5DG/gEioIiyxi/PoZ3tUAven0hg4SA1VV1n7ro4xwgCeguC9/FIbWLFzsB2cVb27FuoHU5Aa6HT0F80O2/4OPyMOnIkll+ykggIiGcz0z03Ievz8eMoPFEBLGS1HWsrACdVbRndFwcPNxLI0kXw3bue2/2ynaQx5nehCRiTKjnIsl7CNQ0jJPqkm8hlPoLX6RtIDQf28qofQ1osEKANMuwSmA9e1H5d3g6UKUCwmBuIyX2tgwUR0oqL6HC3o9FcJiQtLHAME+lkgAhWzW2jv4gIv4IfB4bCq80uMlm7eLI+MYhExWhzBTW5JWwytp5jIyAjafnm86ObIMxz2bTg7LDfltK+fy1Q2UkUMd5C/24uFmLPss08kvDhYSuTkvokQM8xgaiEyLjt6ekFWuw8lfPPFBKPHI3FcDwQBgZsnEIZo2ezOQBCe4ogmiq+dPky8DbqYxJl06Xx6tIzO6Kza2NfO4eBhmvYdaOu/K1CrfsGUof0eCDMzVk0efoM5DOIH/vPWITVKXO1g0gr1CWclqlgJaPoyRrqkh+P6Hy9XUDfSZxaylGnmm9LPZiXxlQ/3FPjnPqqy0k8EAYGbPZKnYYsOXKmqqybKcLbz0mXZjaaO6/h9LfCnEg071uutl2BNsMSL5gUk/vO+MO6KXNGIqPVJpKMLt2+TNehP1oY4QRvv22BoD0KkGqns0RTBVnTr+Yx60h37FhVCL3YakLv3IP4tkXvTnPkCxqQF3Nqhfg/y5/EbyUMN/FAGBwMpGH6pJ/l9GhnQYH8BjzoHIa5diRtRwthWSiVt5qOb0pIgVlKbbuU6ljt/WSrO6gh+fKbiN54ICxmY+cN7IBk0bez7sCvtnxE0p23vjuwCESmjIl59x0gMkGbe9bjtZr0RoBQnbJLnoCIjuwfpHoaWAOSAiEW0J2sEWKZq0RIgVARRfRN56qDaH5q+aZAqCEZ+25Ud6BdI2VneqdAqFtv3aIPkrw+1hVUGrheJJBqhPVSk8vkIwXCMgW4XpKnQFgvNblMPlIgLFOA6yV5CoT1UpPL5CMFwjIFuF6Sp0BYLzW5TD5SICxTgOsleQqE9VKTy+Sju4DQPZ8OGoZFdwGhgU8Hss4P4uwW7HQXEBpuJ0zQwQtTGuA3BUIDwlrPUZMDwV8XLQOWG7z4DmKSVe747eAVSl7yxbfxQKgs+4YtBDqsjxVJxotTorfJH9fVl1WxYDe4vhpBWT5vuZqcxN4QE2zkcYCIiN++XpZf2q9I6OKB4JZ9e3oOwuFSf2ydIBwSlrDW0cKAhdE/1Z+z9pQqy/SrCUR0jBMF7tz42ylcLjJh+Q3Wmpu5xYD6d/FAOLrZCsMo2ggoQEpMExZv/RLaKVTDKtnMAi2ewAba0Ek8CADGxgKZwFobIdF5jnx5sl1PLM5JZdFgxkn8hlmLB4Lb+6j020jIVsJ9AXUzDRfQJvdYlS6V+q6zjqZmZ2ttXrH7HgxM2DFNJ+3jIIta9nlmxWCGljrDxp08OalbZ/FAGJgJMtBv4IatJD5Nm9R+QAbpB8286NfpJxZRxsejgRCMiXxP2VFEwD3TdYTjzm+Y+AHZ7wMM/ys019rwG2IovlJ7zrWbQgc3TQNVh4OWFYOvUAlrfSstBDt+YKUT28T/S8iZPZuW16KrOBg7QDA/oqUVOETtEGdHbtgA62UxxH0dw/ppofzcVthH2L27tGiASv8gkF8WBUS3qDaTmd3Harjf8V2A+HtC3uIO75OphS1n8SzkXkI/+xbQwrTRoDk59Zr5hAiELRvAWOvv673P0kor9HhgI7oOdbEaQVrO7CwrHq68Hy3r/3DD7fEwPkG/tnacBOD4gHz+u96z/0cQmFZnnVXTMil3Q7PrgLmg95DuOZ3xepCmRF5Dwm47pjkIgCOd/apYQgdRfpYeYgoogU3qWCAwM1g8PyF2B/dMvIiCngPiOKBKsOteUq/lH2qtHrRsvE+bB4WQsbG+0Nb3aNoqLShzP/rZBcgYecB0RnTs9vC13cIJmNfjtv/n1HkX/b0QNpKMvERAEMG99JKYnINhx7vxO4TZBNpLoBptx4ZCmggCDxZYqdof0Xue+SdnJhDPdR20QVmMiO15+hXg/R4YsuxHAlgdsR1N3cSrHCjCtwiF9Vt9GrTBDOrmXtFstMuc0Ix/IiCQN752iRnbb00cwSBqDGJBgd6HQIgTOgXPe6GNaVbRuXJJQ0AHbDNnswOqWN6LLuGPA1rswDcBYQQDoyHtX6lS8avI61T0MZQX8hdArDG/jgZcjRgLg3FwQwOot+vH9/04eDNKPLPYsLZzBp/NTaPnm4x5XGcyF0F9oiJgRl7DAB/JWn0wOD5YUbCJCGMYtNquzdf1E8+IoU1p4Qn6SqSvODKFjMmPglX2P4KQv4IfbRlyjEGgIIwVsurT0MAk/uG0AvQIOdRBBlr6FYwM7szt3fevYj0/P1l2tJP+OOcEGBevKjxQsbS1qNXO0buN9u+EMM6B9WDbfTC2AJaCopDwv6WOZBtmbMdIwUjJ1gnen43C24F/v+aYhjGtrcjE2iBMKsvgM4UK49y/hLt7UfTleMR5FEHBlldGY3T88L+lzvKLLDnwreSM+X5W9mF471XHyvfpCZgYXi0z/Y6KxVdKsE3DjtnSZyGDSxF+Pn4fAXE018uBC8HBltpqh/GJwakx+igynsH1Z3j+iSrr7+on9/2UhQld4+NFCGtZNYPEWo2N8U1C3jbMbVd/VJUzn0URF+J3NkJ/AddTUQrMC0Ej2YEbvFrmSD/VPFU/p/rfRBn/A2h+X/WbSdqYZknNgoBpF+HFpwadPcXlEE5xOflgDDnxZMg7RRVP6VWZEx5U17LKqiItA42Yy5RUrjyvtl00t/QtgK9MPF+qMp1clbj5BwEWvlG48yrCOSGMpgf7wWuupbyykIH+sppTBTUwcDxS1jipRs0NF924JkxX0vuWVA6EkAGROcXvEkNDpShBJSWomXgCyHfxVvPWIDVPWbVAC9SjQwDBsQin35dZAfXKqRUmA0HMjkr4zEzRnaFRK34S/5YAYWlBUjGc3+apZLOzK1KGfD3cglPkpnCGBL4boJBlqf+lPCR9DroNT9EA15EV5pen5o2M+PySuFb8JpVLGi+VQCqBVAKpBFIJpBJYHxJYkYFcOlhcAXBwaV0wWFz6utyK0loChPT1sbVHD8dVbNu9Pq7phFJ/L05lObGgtk1gQkk+NFXkl04oVUSR+KZpjVA1xcyDwjf2XYYp5k+h5PPwSzLFzLL57i9Xvo/jxj3DuzIv4OLRT1wQKXKKuYAp5t6VnmLeecPZmLe6DMQ0MsXs+JAvVHwIXBXPITm4cF4Zp/2mmDmzxnl30QgHf3gPiNyFejwHVdnAR6eKXAI+cRHn/Gs9O3/5umM/wbgPMfbjz/uQ2ve0b+7T33rmu4zNrqvZ6VdWDPPAH2NuueaX8SX6Xjxejt+m6I9OFQAzWcg5vsJXBrOOnV8oetWthPNP1UcnUMT1INOQxB5V6LkPPB5zdVOVPMEDM2/IVT5D3zZ6Po6gfVy182doo76m9+7/EhlsBgwEAQTEmsJn6Ou4puErWPTRiwoIfYaWcInDeKvkKp+hQWIOdYDP0OVXcLzEHfqJiX9b8c/Q7nxlc/M1l+CI3OchlGHj++GjUByw3HWV5FJphaEK4cKUTB9W7OwFGHaSkGbAIOluvfYBlcndo0ol7uvA52xZCsmyHJ/uyuir4Ryf0IE8cE9jraL+EACKownNGFZjPS8DSpxfCcJc3Lp0Jf48LJqA5yv/5vVnYGXSU/iwPIyVSh+gIAqB+fDHe/5W27lyQ3RAbZZKOC85cwuO9f2LgCCutUzkCBpGxGmwv6eyAgIu2uWaBpQhfVGb8Esc4FO/MVxsewq+gP8dzu6+UO9+uchP5+QhiSMzsU7GAoOD7I+w3sP/GhavfhRL1fgNnF/A1qLiSUk9R5rIm48zEKnGfx8rjH5DxjU447peQoY5zWFuuu4cCPirqljkfknml0hezGO1nAjftvl+0PpznctywcyfBzxgHIdP8glcMsampno52IIwP4M8P4fv7XaZFrHY3o78FaTFaPNFIXV8fEGAXY/uodesXDycg53JADhchqcS7Kevl+kKh9mteb3Qgsexcukq1Vu+SkqcTFZuLBAANlljINlpM4pxAU8Osy0kUe+TjJAVikWgYo8T1i8q9UmMbX4VHkYdOsSVzZGOIKFalQPRtQboRaNkk/W0kVmuimewWi6L67zOZqG4/WtYMNcquG6uHiGxQFB3XZyVg6a56kepi4OegN1EfNp6Ja9WGLUWV/na171PS7GVjb0RRLjT1U0Pl93x0FN2LaL52ln9OdpwxSY/n+rrEnPL1TyrEzu/Aw0nD9F/4iuzsMEOsLZs2oYstqIfsjm5kqPzbR9f2nPQ2H/Bv0rbY36HXsOCX1u5JxE6Pe1kciEGxQxuf73nmLDdA/Z5+iW812zHC8VWCTq4IZYHx7TL6uTr3HBQ5cUzEci181SzneRIP2gmDhTBrIIRdTTvFQMa5uOdxGSFVmu/4gS43YjxDWZ44bCqqSbwg4TRwggC5VLZMJrZjMy4KpkDxQAc4YhtfY/XbTYKc5osqiWpQ0O1eCd/5PCMQPt1Dq/ConTZJYAA7OJQd7oYIxmMUksYDLNugzsSWG2ERPBeqjsRCJYXowfVcB9XG2OH8MlrKaXVVOwmmKEACDZt5/wlcLG5hxfwAIc7Q2MZvK/l4oHwapDUNwPInHmybdneqFau7eQvUggI0qpXzRe5kzvybGh6C4PCn+Z8A9tYZzlb3cHOCj2QlPh4IFT6zOCDEiAgZdkCk5azpvFYuQEBGZXrsRMsH4sgybWakRFum2M8gN6yGxG7nb0sv+GPgDHUxgPBZeB1pEAc9e7KV6t4CI8wusO7fDV26Tvr6pvE9Zs4YmdJIKW2UQmkQIiV2LrQhLFcdhcQ4juFisAwOJDYbnBRCVinN90FhG6p1SbA2l1AaEJA3ZIkBUK31HQMnykQYgTULcEpELqlpmP4TIEQI6BuCU6B0C01HcNnCoQYAXVLcAqEbqnpGD5TIMQIqFuCUyB0S03H8JkCIUZA3RKcAqFGTWMtVld9mUiBUAMIzruBD5YuSUdeUyDEVlsHr1CK5W0xQgqERVlE3nXomkXLi04O4uRA8GU/A5eBdrK2xEYXY/ctRFZ73vrSrK8cm8f9csmFGZnlmnrW47WasHgguLOhPb1g1/lj6VbnDaQC8OJsJy3WTlRwqmO1NPJ2gChmdjSOD1bJF39WZ7SGT8H6ZcuJsccfJyAnHgib3ZHAPu0hYPNrh42nq8b+eh6G9GFVBK6yTF+e5A/QYipb5nl8sGwSWQzvkDtyjHrlRdO4h3V/mq+ShPN213ggLO4c5hmC3BnMTbF1M3WZt9VVKlW/7w65UDMz0Xs4jzxrN/362p6Z2Hnaj3XDA09xsTzI+suYDj0eCG7vo2/eRM7OSkonAYG0Cp8wlPAW7pW0epq7j3KzZ1uRaWNPve0kTi3l4AubkXy/gObKOlPqxjGP2i6KXednd/24p6jr3JwVWE/msCqZd6AuN9uxQlTktvTj8Naenq6DkQEO+dCPHMAYoI7TeqpOaHsG2YE8BsOG2/pgds+bFkKtfYQ6g+Qkm2Dt7melH9v3LiB1MOg3NTdAtr2zLYRvADQ2hfOE9MtC86GsVf9RDLguwy//ByzGua4wKmZb+qFafFiTI20/VUXvDSFySwvsI4jtpMAAFeT6z7TGAcT14L7c9i+SFqz8S+tih1SP+RcRTF8fLb5EOncsjt478RPw+GNEsqYA8NABjvv50A2SWPMCjYdZmmt0gyGG4scINrJVK8Xckyjkv6FyqHrqqppQGat+K/VvK46nt2YCyydP6Uf3vy+2IisCiiaN5oUlxJhv4OBP3pbr97DR+ayyLzlegMYegkGtN2A752mWL6aScd51HC2JgEBkmV27+nB9Txn/gSDTUyAcWiyzAIwraRXD5QWBHQJed7WXGQDN/6mKOUt3z9FYALvDyfTeZ/4WBjsnoWo3gkm+k7NHtBBbRX7qFmWp4V+2fpyoh27B6Af14xOvCq1btsTyy/wTAYERFU5YF9t9FI5RD0M4tEwGIWPixQTawYHCXSVh6I8l2nq4OLX8wuHuninj4kMM+EfjWbCb5G1EPz+L1vFbAPEx0QY0RJnAVeYTcrnbYatxGjaYPsw8UTwFbodlji53XZqvo9VdGe7uw2ncvQtbGs/lG4onlcxnmQEVK3dZlcsNQht8U+/dZw2M5kcySc92CBftiqt5lSPoYa6NEWDd8yGooT+UwaPv44BqAQRbDF9VZG42EBdjyzytna519+TBysVeOf6kOVk6N60b3oDqwl2YROQfJpeBS3DPkdIAgJqRCjR6FwTzgjMfjLDEzvGLE2AvAE3fhtlh2J8uc2x0HKW6lkaS2aBCsnQ8ktZqHuxz2H/pPcljGscvnwOeOafBf4s8s8wc6mBApF72vwnNR9Cz0YrhdKZO4kLEJ4kOCtB/OtVpbrn2DhBxN34XSD9MGPgyk8FWR0HZCmrxpAyKCQAjAyNUgsnChz/IDL9ymbOHBzCv8mWNE9/NH1zZG/u6WIN9J1AxVedl7kc1wNakNyTVTlZxLC/oKaFYxy8hwcpqnVvUPwRcBuXhPGiUyDPLOXg35hAKfVjv2fcgC3WG03mf1DUMBGZszbqO+FQ7ENSw6indCGKuADGfQCgNONEoZ08wSGMSCqd1zomZwBPVaOaROWwR493ZmB8CDBP6if3/wAJlbBNxUm0jxISBZG69/tcg/FGUdQnyOAtXWJqDBtKoFYKQbiV4Zb7CL76XwPAPfu/g9wqUx4so9ym95zuv45nmgxvSBExD1zTJ0ocemsRxwJOVDxuwbLpFZbLbQPAWkASbwGoDiuAbBs5LDqtIKbv5P2LGycNA1UfL17Mo6x0MCN9Qpb7XORZwGTfTHbi0S69ije3SA0WdFy1Hgfer7MJ2tMozURmnIz4MV0FFa8M3DrbcVjq8tcAMsIeZXaPeRcUfUYXitB5/9rArhADgtLl7/XX+Sa9NA8EV4MzVtfocZpd/o1c0Go0pVYKvAFC4frzRbCLjC/hhl3ol8o4sMIGnAOD0Y7rZrs8VsWwguIzcOUpq8zz0QD/eJbb6cirZML7t0+3Y4RS6S9L8dWrK0s2pU34bGJixLXBu2Ec5paQj5WYJELD9zl1ZVThoZyhZ7tBrBrTY9/VW8koiye8MjhzmEcu0DOtkvGm+rPN28N4sLy5dy4DgMgxfUfMrmj/LQgGtA1iY+CbuV5rfduK1CfGkSVIJpBJIJZBKIJVAKoFUAqkEUgmkEuhoCfw/vJfHMO4YqncAAAAASUVORK5CYII="; + const artwork = artworkBuilder.createArtworkForResource(objectGraph, resource, 36, 36); + ribbonModel.artwork = artwork; + ribbonModel.accessibilityLabel = title; + // Configure impressions + const metricsOptions = { + targetType: "facet", + pageInformation: metricsPageInformation, + locationTracker: metricsLocationTracker, + recoMetricsData: null, + }; + const impressionOptions = metricsHelpersImpressions.impressionOptionsForArcadeSeeAllGamesRibbonItem(metricsOptions); + metricsHelpersImpressions.addImpressionFields(objectGraph, ribbonModel, impressionOptions); + return ribbonModel; + } + static createTagsRibbonShelf(objectGraph, data) { + const tagsData = mediaRelationship.relationshipViewsCollection(data, "categorizations"); + const ribbonBarShelf = new models.Shelf("ribbonFlow"); + const ribbonItems = []; + for (const tagData of tagsData) { + const name = attributeAsString(tagData, "name"); + let pageUrl = null; + switch (tagData.type) { + case "genres": + pageUrl = attributeAsString(tagData, "url"); + break; + case "tags": + const href = serverData.asString(tagData, "href"); + pageUrl = hrefToRoutableUrl(objectGraph, href); + break; + default: + break; + } + if (isNothing(pageUrl)) { + continue; + } + const flowPage = objectGraph.required(pageRouter).fetchFlowPage(pageUrl); + const flowAction = new models.FlowAction(flowPage); + flowAction.pageUrl = pageUrl; + if (isSome(name)) { + const ribbonItem = new models.RibbonBarItem(name, flowAction); + ribbonItems.push(ribbonItem); + } + } + ribbonBarShelf.items = ribbonItems; + ribbonBarShelf.isHorizontal = true; + return ribbonBarShelf; + } + /** + * Determines if an item should be given priority and moved to the front of the list. + * @param objectGraph Current object graph + * @param data The data for the item + * @returns True if the item should be moved to the front of the list. + */ + static shouldPriorizeItemWithData(objectGraph, data) { + var _a, _b; + const displayDeviceDrivenContent = (_b = (_a = objectGraph.userDefaults) === null || _a === void 0 ? void 0 : _a.bool("displayDeviceDrivenContent")) !== null && _b !== void 0 ? _b : false; + return (displayDeviceDrivenContent && + objectGraph.bag.ribbonBarVisionEditorialItemIds.includes(data.id) && + objectGraph.bag.enableDeviceDrivenDiscoveryContent); + } +} +//# sourceMappingURL=grouping-ribbon-bar-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller-common.js new file mode 100644 index 0000000..a1b05cd --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller-common.js @@ -0,0 +1,992 @@ +import { isNothing, isSome } from "@jet/environment"; +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataFetching from "../../../foundation/media/data-fetching"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import * as appEvents from "../../app-promotions/app-event"; +import * as appPromotionsCommon from "../../app-promotions/app-promotions-common"; +import { pageRouter } from "../../builders/routing"; +import * as legacyArtwork from "../../content/artwork/legacy-artwork"; +import * as contentAttributes from "../../content/attributes"; +import * as content from "../../content/content"; +import * as externalDeepLink from "../../linking/external-deep-link"; +import * as lockups from "../../lockups/lockups"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as onDevicePersonalization from "../../personalization/on-device-personalization"; +import * as productPageVariants from "../../product-page/product-page-variants"; +import * as refresh from "../../refresh/page-refresh-controller"; +import * as mediaRequestUtils from "../../builders/url-mapping-utils"; +import { defaultTodayCardConfiguration, lockupsForRelatedContent } from "../../today/today-card-util"; +import { isLockupShelf, } from "../grouping-types"; +import { categoryArtworkData } from "../../categories"; +import { hrefToRoutableUrl } from "../../builders/url-mapping"; +import { clientIdentifierForEditorialContextInData } from "../../lockups/editorial-context"; +import { areAppTagsEnabled } from "../../util/app-tags-util"; +import { AppEventsAttributes } from "../../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types"; +/** + * Create the base 2 requirements to start parsing a grouping page shelf. These include the base shelf token and the + * base metrics options + * + * @param objectGraph The App Store dependency graph for making native calls and viewing properties + * @param mediaApiData The media api data for this specific shelf + * @param groupingParseContext The grouping page parsing context. + */ +export function createBaseShelfRequirements(objectGraph, mediaApiData, groupingParseContext) { + const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind"); + // Populate the gamesFilter, if it is provided. + let gamesFilter = null; + const rawGamesFilter = mediaAttributes.attributeAsString(mediaApiData, "gamesFilter"); + switch (rawGamesFilter) { + case "arcade": + case "nonArcade": + case "all": + gamesFilter = rawGamesFilter; + break; + default: + if (featuredContentId === 495 /* FeaturedContentID.AppStore_PopularWithYourFriendsMarker */ || + featuredContentId === 500 /* FeaturedContentID.AppStore_ContinuePlayingMarker */) { + gamesFilter = "arcade"; // Defaulting to Arcade because this param won't be passed until 20I. + } + break; + } + // Build Shelf eyebrow, title, and subtitle + let eyebrow = null; + let title = mediaAttributes.attributeAsString(mediaApiData, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId)); + let titleArtwork = null; + let badges = null; + let subtitle = mediaAttributes.attributeAsString(mediaApiData, "tagline"); + const shelfHeaderConfiguration = {}; + // 'Similar To' Personalised Shelf + // Check if personalised shelf has a badge-content, if it's got valid content, and the clients support eyebrows and title artwork: + // - Reco shelf title becomes the eyebrow + // - Featured App in badge-content becomes the shelf title, and we add the icon as title artwork + // - No subtitle should be shown + // - Flag to use the eyebrown name for metrics + let wantsEyebrowNameForMetrics = false; + const badgeContent = mediaRelationship.relationshipCollection(mediaApiData, "badge-content")[0]; + if (featuredContentId === 476 /* FeaturedContentID.AppStore_PersonalizedShelfMarker */ && + serverData.isDefinedNonNullNonEmpty(badgeContent)) { + eyebrow = objectGraph.loc.uppercased(mediaAttributes.attributeAsString(mediaApiData, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId))); + subtitle = null; + const dataType = badgeContent.type; + if (dataType === "collections") { + title = content.notesFromData(objectGraph, badgeContent, "name"); + const artworkData = categoryArtworkData(objectGraph, badgeContent, false); + // Reconfigure eyebrow text color only if there is eyebrow artwork. + if (isSome(artworkData)) { + titleArtwork = content.artworkFromApiArtwork(objectGraph, artworkData, { + useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */, + style: "unadorned", + }); + const eyebrowColor = { type: "named", name: "secondaryText" }; + shelfHeaderConfiguration.eyebrowColor = eyebrowColor; + } + badges = { forYou: true }; + } + else { + wantsEyebrowNameForMetrics = true; + title = mediaAttributes.attributeAsString(badgeContent, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId)); + titleArtwork = content.iconFromData(objectGraph, badgeContent, { + useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */, + }); + } + } + const shelfToken = { + featuredContentId, + id: serverData.asString(mediaApiData, "id"), + presentationHints: {}, + metricsPageInformation: groupingParseContext.metricsPageInformation, + metricsLocationTracker: groupingParseContext.metricsLocationTracker, + pageGenreId: groupingParseContext.pageGenreId, + featuredContentData: mediaApiData, + title: title, + subtitle: subtitle, + eyebrow: eyebrow, + titleArtwork: titleArtwork, + shelfHeaderConfiguration: shelfHeaderConfiguration, + shouldFilter: false, + gamesFilter: gamesFilter, + remainingItems: [], + isFirstRender: true, + isDeferring: false, + showOrdinals: false, + hasExistingContent: false, + showingPlaceholders: false, + ordinalIndex: 1, + isSearchLandingPage: groupingParseContext.isSearchLandingPage, + isArcadePage: groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage, + }; + const shelfMetricsOptions = { + id: shelfToken.id, + kind: null, + softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null, + targetType: "swoosh", + title: wantsEyebrowNameForMetrics ? shelfToken.eyebrow : shelfToken.title, + badges: badges, + pageInformation: groupingParseContext.metricsPageInformation, + locationTracker: groupingParseContext.metricsLocationTracker, + idType: "its_contentId", + fcKind: featuredContentId, + recoMetricsData: recoMetricsDataForFCData(objectGraph, mediaApiData), + }; + return { + shelfToken: shelfToken, + metricsOptions: shelfMetricsOptions, + }; +} +// endregion +// region Shelf Tokens +/** + * Whether we should defer building the rest of a shelf given a shelf token + * @param token + */ +export function shouldDefer(token) { + return token && token.isDeferring && token.isFirstRender; +} +/** + * Due to upstream oddities, we get the shelf title in the form of `title` for these markers. These are + * unique markers in that they are not filled by reco. + * + * For personalization markers, they are programmed in DJ with the name field hidden, and it is filled in by + * Reco. Without wanting to expose loc strings to that name field, we unfortunately have to use this other + * attribute key. + * + * This will all go away once these markers are replaced with proper Reco equivalents (i.e. when popular- + * with-your-friends and suggested-friends move to Reco). + */ +export function shelfTitleAttributePathForFeaturedContentId(objectGraph, id) { + switch (id) { + case 548 /* FeaturedContentID.AppStore_GameCenterActivityFeedMarker */: + case 495 /* FeaturedContentID.AppStore_PopularWithYourFriendsMarker */: + case 496 /* FeaturedContentID.AppStore_SuggestedFriendsMarker */: + return "title"; + default: + return "name"; + } +} +/** + * Returns the URL schema for grouping shelves that may need to fetch additional content. + * Grouping-Related builders can extend on this scheme if needed, e.g. query param on Continue Playing shelves. + * @param token Token to encode in URL for subsequent fetch. + */ +export function groupingShelfUrl(token) { + let shelfUrl = new urls.URL() + .set("protocol", Protocol.internal) + .append("pathname", Path.grouping) + .append("pathname", Path.shelf) + .append("pathname", encodeURIComponent(JSON.stringify(token))) + .param(Parameters.groupingFeaturedContentId, `${token.featuredContentId}`); + if (isSome(token.nativeGroupingShelfId)) { + shelfUrl = shelfUrl.param(Parameters.nativeGroupingShelfId, `${token.nativeGroupingShelfId}`); + } + return shelfUrl.build(); +} +/** + * Configure `url` on a standard grouping shelf if it needs to fetch more content. + * @param shelf Shelf to add url to. + * @param token Token tode encode in URL for subsequent fetch. + */ +export function createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, token) { + if (serverData.isNullOrEmpty(token)) { + return null; + } + // Web does not curently support pagination via "fetch more", so shelf token URLs are not needed. + // Note: removing these URLs for web reduces view model size by ~45%. + if (objectGraph.client.isWeb) { + return null; + } + // Ensure the token has a `shelfStyle` so we can construct an empty shelf in the case of a hydration error, + // or the shelf being empty upon hydration. + if (serverData.isNull(token.shelfStyle)) { + token.shelfStyle = shelf.contentType; + } + const hasNonPlaceholderItems = shelf.contentType !== "placeholder" && serverData.isDefinedNonNullNonEmpty(shelf.items); + token.hasExistingContent = token.hasExistingContent || (hasNonPlaceholderItems && token.isFirstRender); + const shouldAddFirstRenderUrl = token.remainingItems.length || token.recommendationsHref || token.onDeviceRecommendationsUseCase; + if (shouldAddFirstRenderUrl && token.isFirstRender) { + return groupingShelfUrl(token); + } + else { + return null; + } +} +/** + * Updates a shelf URL based on the provided token. + * @param shelf Shelf to add url to. + * @param token Token to encode in the url. + */ +export function updateShelfUrlWithNewToken(objectGraph, shelf, token) { + const originalShelfUrl = urls.URL.from(shelf.url); + const updatedUrl = urls.URL.from(createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, token)); + // Add missing query params to the updated URL from the original. + for (const key of Object.keys(originalShelfUrl.query)) { + if (serverData.isNull(updatedUrl.query[key])) { + updatedUrl.query[key] = originalShelfUrl.query[key]; + } + } + // Finally, update the shelf's URL. + shelf.url = updatedUrl.build(); +} +/** + * From a grouping shelf token determine the list of unhydrated MAPI data to fetch + * this will also handle the case were whe need to fetch additional relationship data + * + * This method will also hoist up ids of content needed to be fetched from an unhydrated relationship if needed. For <rdar://problem/42797176>. + * + * This is necessary to accommodate some MAPI objects which have a nested relationship that isn't hydrated and cannot + * be hydrated by re-fetching the content at parent ID. + * + * @param token The shelfToken for the shelf we're fetching data for + */ +export function unhydratedRemainingItemsFromShelfToken(objectGraph, token) { + var _a; + const shouldFetchRelationshipItems = ((_a = token.relationshipToFetch) === null || _a === void 0 ? void 0 : _a.length) > 0; + let remainingItems = token.remainingItems; + if (shouldFetchRelationshipItems) { + remainingItems = token.remainingItems.map((remainingItem) => { + return mediaRelationship.relationshipData(objectGraph, remainingItem, token.relationshipToFetch); + }); + } + return remainingItems; +} +/** + * Given a MAPI response for a list of unhydrated shelf items, and the original grouping shelf token determine + * the list of hydrated MAPI data. This also handles the case where we need to hoist up relationship data + * + * If ids to be fetched were hoisted from nested relationships, replace the original unhydrated nested + * relationship on parent with hydrated data. + * + * @param token + * @param mediaApiData + */ +export function hydratedRemainingItemsForShelfTokenFromMediaApiData(objectGraph, token, mediaApiData) { + var _a; + const didFetchRelationshipItems = ((_a = token.relationshipToFetch) === null || _a === void 0 ? void 0 : _a.length) > 0; + let hyrdatedItems = mediaDataStructure.dataCollectionFromDataContainer(mediaApiData); + if (didFetchRelationshipItems) { + const dataMapping = {}; + for (const dataItem of mediaApiData.data) { + dataMapping[dataItem.id] = dataItem; + } + hyrdatedItems = []; + for (const remainingItem of token.remainingItems) { + const unhydratedRelationshipData = mediaRelationship.relationshipData(objectGraph, remainingItem, token.relationshipToFetch); + if (serverData.isDefinedNonNullNonEmpty(unhydratedRelationshipData)) { + remainingItem.relationships[token.relationshipToFetch].data = [ + dataMapping[unhydratedRelationshipData.id], + ]; + } + hyrdatedItems.push(remainingItem); + } + } + return hyrdatedItems; +} +/** + * Deletes all remainingItems that have been requested for hydration from the shelfToken. + * + * @param shelfToken + * @param requestedItems + */ +export function flushRequestedItemsFromShelfToken(shelfToken, requestedItemIds) { + shelfToken.remainingItems = shelfToken.remainingItems.filter((remainingItem) => { + return !requestedItemIds.has(remainingItem.id); + }); +} +// endregion +// region Shelf Requests +/** + * Generates a media API request for fetching a shelf's data + * @param objectGraph + * @param {URL} url The URL of the page being requested + * @param {Parameters} parameters The parameters that were extracted from the URL + * @returns {Request} A media API request + */ +export function generateShelfRequest(objectGraph, token, parameters) { + var _a; + // Whether or not token is for fetching additional items that were unhydated. + // These tokens are used when a server returned explicit IDs for the contents of shelves, and some (or all) of those IDs were fully unhydrated. + const isFetchingAdditionalContent = serverData.isDefinedNonNullNonEmpty(token.remainingItems); + // Whether or not token is for fetching personalized recommendations. + // Recommendation shelves can sometimes return with no IDs provided within its shelf. We are expected to use the `recommendationsHref` to fetch in this case. + // Note that server can sometimes choose to prepopulate IDs for personalized shelves, even if `recommendationsHref` is present. + const isPersonalizedRefetch = !isFetchingAdditionalContent && ((_a = token.recommendationsHref) === null || _a === void 0 ? void 0 : _a.length) > 0; + if (isFetchingAdditionalContent) { + // Remaining items to fetch, since they were unhydrated in original page response. + const remainingItemsToFetch = unhydratedRemainingItemsFromShelfToken(objectGraph, token); + // MAPI Request + const mediaApiRequest = new mediaDataFetching.Request(objectGraph, remainingItemsToFetch, true); + productPageVariants.addVariantParametersToRequestForItems(objectGraph, mediaApiRequest, remainingItemsToFetch); + prepareGroupingShelfRequest(objectGraph, mediaApiRequest); + return mediaApiRequest; + } + else if (isPersonalizedRefetch) { + const mediaApiRequest = new mediaDataFetching.Request(objectGraph, token.recommendationsHref).includingAgeRestrictions(); + prepareGroupingShelfRequest(objectGraph, mediaApiRequest); + if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) { + mediaApiRequest.enablingFeature("appEvents"); + mediaApiRequest.includingMetaKeys("editorial-elements:contents", ["personalizationData", "cppData"]); + mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes); + mediaApiRequest.includingScopedRelationships("app-events", ["app"]); + } + if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { + mediaApiRequest.enablingFeature("contingentItems"); + mediaRequestUtils.configureContingentItemsForGroupingRequest(mediaApiRequest); + } + if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) { + mediaApiRequest.enablingFeature("offerItems"); + mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest); + } + if (areAppTagsEnabled(objectGraph, "grouping")) { + mediaRequestUtils.configureTagsForMediaRequest(mediaApiRequest); + } + return mediaApiRequest; + } + return null; +} +/** + * Modify request for a items fetched for grouping shelf items. (i.e. individual IDs). + * This should be ideally be in grouping builder's `prepareRequest`, but that would change how url-driven requests are configured. + * @param objectGraph + * @param request Request to modify. + */ +export function prepareGroupingShelfRequest(objectGraph, request) { + request + .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph)) + .includingRelationshipsForUpsell(true) + .includingMacOSCompatibleIOSAppsWhenSupported(true) + .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)); + let attributes = ["editorialArtwork", "editorialVideo", "minimumOSVersion"]; + if (request.includesResourceType("app-events") && appPromotionsCommon.appEventsAreEnabled(objectGraph)) { + request.enablingFeature("appEvents"); + request.includingMetaKeys("editorial-elements:contents", ["personalizationData", "cppData"]); + request.includingScopedAttributes("app-events", AppEventsAttributes); + request.includingScopedRelationships("app-events", ["app"]); + } + if (request.includesResourceType("contingent-items") && + appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { + request.enablingFeature("contingentItems"); + mediaRequestUtils.configureContingentItemsForGroupingRequest(request); + attributes = []; + } + if (request.includesResourceType("offer-items") && appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) { + request.enablingFeature("offerItems"); + mediaRequestUtils.configureOfferItemsForMediaRequest(request); + attributes = []; + } + if (request.includesResourceType("apps") || request.includesResourceType("app-events")) { + attributes = attributes.concat("screenshotsByType", "videoPreviewsByType", "expectedReleaseDateDisplayFormat"); + } + if (areAppTagsEnabled(objectGraph, "grouping")) { + mediaRequestUtils.configureTagsForMediaRequest(request); + } + request.includingAttributes(attributes); +} +// endregion +// region Metrics +export function recoMetricsDataForFCData(objectGraph, mediaApiData) { + const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind"); + switch (featuredContentId) { + // All of these kinds just require walking their children + case 425 /* FeaturedContentID.AppStore_GenreStack */: + case 415 /* FeaturedContentID.AppStore_HeroList */: + case 416 /* FeaturedContentID.AppStore_Hero */: + case 417 /* FeaturedContentID.AppStore_CustomHero */: + case 501 /* FeaturedContentID.AppStore_PersonalizedHeroMarker */: + case 258 /* FeaturedContentID.Sundance_Flowcase */: + case 421 /* FeaturedContentID.AppStore_BrickRow */: + case 422 /* FeaturedContentID.AppStore_Brick */: + case 423 /* FeaturedContentID.AppStore_CustomBrick */: + case 261 /* FeaturedContentID.Sundance_BrickRow */: + case 584 /* FeaturedContentID.AppStore_TagsBrick */: + case 587 /* FeaturedContentID.AppStore_PersonalizedTagsBrick */: { + const childrenRelationship = mediaRelationship.relationship(mediaApiData, "children"); + return mediaDataStructure.metricsFromMediaApiObject(childrenRelationship); + } + case 437 /* FeaturedContentID.AppStore_LinkList */: + case 265 /* FeaturedContentID.Sundance_LinkList */: { + const contentRelationship = mediaRelationship.relationship(mediaApiData, "children"); + const textLinks = mediaAttributes.attributeAsArrayOrEmpty(mediaApiData, "links"); + if (serverData.isDefinedNonNullNonEmpty(contentRelationship)) { + return mediaDataStructure.metricsFromMediaApiObject(contentRelationship); + } + else if (serverData.isDefinedNonNullNonEmpty(textLinks)) { + return mediaDataStructure.metricsFromMediaApiObject(mediaApiData); + } + return null; + } + case 414 /* FeaturedContentID.AppStore_TabRoot */: + case 424 /* FeaturedContentID.AppStore_ChartSet */: + case 566 /* FeaturedContentID.AppStore_ArcadeDownloadPackMarker */: { + return null; // unapplicable fckinds + } + default: { + if (isLockupShelf(featuredContentId)) { + let childrenRelationship = mediaRelationship.relationship(mediaApiData, "contents"); + if (serverData.isNull(childrenRelationship)) { + return null; + } + const contentItems = childrenRelationship.data; + if (!contentItems || contentItems.length === 0) { + childrenRelationship = mediaRelationship.relationship(mediaApiData, "children"); + } + return mediaDataStructure.metricsFromMediaApiObject(childrenRelationship); + } + else { + objectGraph.console.warn("Unknown featured content ID:", featuredContentId); + return null; + } + } + } +} +// endregion +// region Artwork +/** + * Creating an artwork model with the crop required for a grouping page piece of art + */ +export function groupingArtworkFromApiArtwork(objectGraph, artworkData, options) { + const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, options); + if (artwork) { + artwork.crop = "sr"; + } + return artwork; +} +export function artworkFromFC(objectGraph, node, width, height, options) { + const artworkData = mediaAttributes.attributeAsDictionary(node, "artwork"); + if (artworkData instanceof Array) { + const artwork = legacyArtwork.closestArtworkMatchingSize(objectGraph, artworkData, width, height); + artwork.crop = "bb"; + return artwork; + } + else if (artworkData != null) { + return groupingArtworkFromApiArtwork(objectGraph, artworkData, options); + } + return null; +} +export function artworkForTags(objectGraph, node, width, height, options, metricsOptions) { + const lockupData = serverData.asArrayOrEmpty(node.meta, "associations.apps.data"); + const artworks = []; + if (isSome(lockupData)) { + for (const lockup of lockupData) { + const lockupOptions = { + artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */, + metricsOptions: metricsOptions, + useJoeColorIconPlaceholder: true, + joeColorPlaceholderSelectionLogic: content.bestJoeColorPlaceholderSelectionLogic, + }; + const lockupFromData = lockups.lockupFromData(objectGraph, lockup, lockupOptions); + const lockupIcon = lockupFromData === null || lockupFromData === void 0 ? void 0 : lockupFromData.icon; + if (isSome(lockupIcon)) { + artworks.push(lockupIcon); + } + } + } + return artworks; +} +// endregion +// region FC Metadata +export function metadataForFCData(objectGraph, fcData, token, shouldPersonalizeContent, personalizationDataContainer, metricsOptions, context, unavailableCallback) { + var _a, _b, _c; + const callUnavailable = function (contentData) { + if (unavailableCallback) { + unavailableCallback(); + } + else { + token === null || token === void 0 ? void 0 : token.remainingItems.push(contentData); + } + }; + const isLinkNode = ((_a = serverData.asString(fcData, "url")) === null || _a === void 0 ? void 0 : _a.length) > 0; + const containsLinkNode = ((_b = mediaAttributes.attributeAsString(fcData, "link.url")) === null || _b === void 0 ? void 0 : _b.length) > 0; + const isContentNodeUsingPrimaryContent = mediaRelationship.hasRelationship(fcData, "primary-content", false); + const isContentNode = mediaRelationship.hasRelationship(fcData, "contents", false) || isContentNodeUsingPrimaryContent; + // Define whether data is a Category Grouping kind. + let isCategoryGroupingKind = mediaAttributes.attributeAsString(fcData, "kind") === "CategoryGrouping"; + if (containsLinkNode || isLinkNode) { + return metadataForLink(objectGraph, fcData, token, metricsOptions, unavailableCallback); + } + else if (isContentNode) { + let contentData; + // Define category grouping content to set when content is a Category Grouping kind. + let categoryGroupingContent; + let personalizedDataResult; + if (shouldPersonalizeContent && !isContentNodeUsingPrimaryContent) { + const contentDataItems = mediaRelationship.relationshipCollection(fcData, "contents"); + personalizedDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "groupingCommon", contentDataItems, true, personalizationDataContainer, false, 1); + const personalizedContentDataItems = personalizedDataResult.personalizedData; + if (personalizedContentDataItems.length === 0) { + return null; + } + contentData = personalizedContentDataItems[0]; + } + else { + contentData = isContentNodeUsingPrimaryContent + ? mediaRelationship.relationshipData(objectGraph, fcData, "primary-content") + : mediaRelationship.relationshipData(objectGraph, fcData, "contents"); + } + // Check whether content is a Category Grouping kind. + if (mediaAttributes.attributeAsString(contentData, "kind") === "CategoryGrouping") { + categoryGroupingContent = contentData; + // Update content to its primary content. + contentData = mediaRelationship.relationshipData(objectGraph, contentData, "primary-content"); + isCategoryGroupingKind = true; + } + // If the content data is null don't even bother with the call unavailable since we have no way of knowing how to fetch it + if (serverData.isNull(contentData)) { + return null; + } + else if (serverData.isNull(contentData.attributes) || shouldDefer(token)) { + if (serverData.isDefinedNonNullNonEmpty(token)) { + token.isDeferring = true; + } + callUnavailable(contentData); + return null; + } + // Generate the subtitle + let subtitle = content.notesFromData(objectGraph, contentData, "tagline") || + lockups.subtitleFromData(objectGraph, contentData); + // Generate the click action + // Using the fcData here because the ids need to match the id used for the impressions, otherwise the reporting + // heat maps dont work. + // rdar://61527868 (Metrics: Arcade Data Mismatch (Location and Impressions have different name fields)) + const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, fcData, metricsOptions); + metricsClickOptions.targetType = metricsOptions.targetType; + let action = lockups.actionFromData(objectGraph, contentData, metricsClickOptions, token === null || token === void 0 ? void 0 : token.clientIdentifierOverride); + // Understand if we're dealing with an article here, so when we go to generate app events, we know if we + // should use the action we've created above or the app event action. + const isArticle = mediaAttributes.attributeAsBooleanOrFalse(contentData, "isCanvasAvailable"); + // Find the artwork and title depending on the content type + let artwork = null; + let caption = null; + // Find lockup (if any) + let lockup = null; + // Find app event (if any) + let appEvent; + const shortEditorialDescription = mediaAttributes.attributeAsString(contentData, "itunesNotes.short"); + const hasContentId = ((_c = contentData.id) === null || _c === void 0 ? void 0 : _c.length) > 0; + const contentMetricsOptions = { + ...metricsOptions, + id: hasContentId ? contentData.id : fcData.id, + idType: hasContentId ? "its_id" : "editorial_id", + }; + switch (contentData.type) { + case "groupings": { + artwork = mediaAttributes.attributeAsDictionary(contentData, "artwork"); + // If data is a Category Grouping kind, reconfigure content. + if (isCategoryGroupingKind) { + contentData = categoryGroupingContent !== null && categoryGroupingContent !== void 0 ? categoryGroupingContent : fcData; + } + break; + } + case "editorial-items": { + // Check for an app-event relationship + const relatedCardContents = mediaRelationship.relationshipData(objectGraph, contentData, "card-contents"); + if (serverData.isDefinedNonNullNonEmpty(relatedCardContents)) { + const clickOptions = { + ...contentMetricsOptions, + inAppEventId: relatedCardContents.id, + }; + const parentAppData = mediaRelationship.relationshipData(objectGraph, relatedCardContents, "app"); + if (serverData.isDefinedNonNull(parentAppData)) { + clickOptions.relatedSubjectIds = [parentAppData.id]; + } + const appEventOrDate = appEvents.appEventOrPromotionStartDateFromData(objectGraph, relatedCardContents, null, false, true, "dark", "white", false, clickOptions, false, true, null, token.isArcadePage, false); + const cardDisplayStyle = mediaAttributes.attributeAsString(contentData, "cardDisplayStyle"); + if (cardDisplayStyle === "AppEventCard") { + if (appEventOrDate instanceof Date) { + // If we get a date back, we have a valid app event, but it starts in the future. + // We don't want the object containing this event to render yet, so return early. + refresh.addNextPreferredContentRefreshDate(appEventOrDate, context.refreshController); + return null; + } + else if (isNothing(appEventOrDate)) { + return null; + } + else { + appEvent = appEventOrDate; + if (!isArticle) { + action = appEvent.clickAction; + } + if (serverData.isNullOrEmpty(subtitle)) { + subtitle = content.notesFromData(objectGraph, relatedCardContents, "short"); + } + } + } + } + caption = mediaAttributes.attributeAsString(contentData, "label"); + if (caption) { + // This is a hack for AOTD/GOTD + caption = caption.replace(/\n/g, " "); + } + const relatedContent = mediaRelationship.relationshipData(objectGraph, contentData, "contents"); + const tagline = serverData.asString(contentData, "editorialNotes.tagline"); + if (serverData.isNullOrEmpty(subtitle)) { + if (tagline) { + subtitle = tagline; + } + else if (relatedContent) { + subtitle = content.notesFromData(objectGraph, relatedContent, "short"); + } + } + if (serverData.isNullOrEmpty(subtitle) && serverData.isDefinedNonNull(appEvent)) { + subtitle = appEvent.subtitle; + } + let crossLinkSubtitle = mediaAttributes.attributeAsString(contentData, "editorialNotes.short"); + if (isNothing(crossLinkSubtitle) || crossLinkSubtitle.length === 0) { + crossLinkSubtitle = subtitle; + } + const cardConfig = defaultTodayCardConfiguration(objectGraph); + if (serverData.isNull(appEvent) && + externalDeepLink.deepLinkUrlFromData(objectGraph, contentData) && + !objectGraph.client.isiOS) { + cardConfig.crossLinkSubtitle = crossLinkSubtitle; + } + cardConfig.clientIdentifierOverride = clientIdentifierForEditorialContextInData(objectGraph, contentData); + if (serverData.isDefinedNonNull(appEvent)) { + lockup = appEvent.lockup; + } + else { + // On iOS and Web, we always attempt to create a lockup. On other platforms, only do this is there is a cross link. + // iOS offer style will be determined later based on artwork. + const offerEnvironment = objectGraph.client.isiOS ? null : "dark"; + const offerStyle = objectGraph.client.isiOS ? null : "white"; + if (objectGraph.client.isiOS || + objectGraph.client.isWeb || + externalDeepLink.deepLinkUrlFromData(objectGraph, contentData)) { + metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, token === null || token === void 0 ? void 0 : token.title); + const relatedLockups = lockupsForRelatedContent(objectGraph, mediaRelationship.relationshipCollection(contentData, "card-contents"), cardConfig, metricsOptions.pageInformation, metricsOptions.locationTracker, offerEnvironment, offerStyle, externalDeepLink.deepLinkUrlFromData(objectGraph, contentData)); + if (relatedLockups.length === 1) { + lockup = relatedLockups[0]; + } + metricsHelpersLocation.popLocation(contentMetricsOptions.locationTracker); + } + } + } + // falls through + default: { + // Create a lockup if possible on iOS + const validLockupContentTypes = [ + "apps", + "arcade-apps", + "app-bundles", + "in-apps", + ]; + if (serverData.isNull(lockup) && + validLockupContentTypes.indexOf(contentData.type) > -1 && + objectGraph.host.isiOS) { + metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, token === null || token === void 0 ? void 0 : token.title); + const lockupOptions = { + metricsOptions: { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(contentData), + }, + clientIdentifierOverride: token === null || token === void 0 ? void 0 : token.clientIdentifierOverride, + artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, token === null || token === void 0 ? void 0 : token.shelfStyle), + canDisplayArcadeOfferButton: true, + shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && + token.isArcadePage, + }; + lockup = lockups.lockupFromData(objectGraph, contentData, lockupOptions); + metricsHelpersLocation.popLocation(contentMetricsOptions.locationTracker); + } + artwork = + contentAttributes.contentAttributeAsDictionary(objectGraph, contentData, "editorialArtwork") || + mediaAttributes.attributeAsDictionary(contentData, "editorialArtwork"); + if (serverData.isNullOrEmpty(subtitle) && serverData.isDefinedNonNull(lockup)) { + subtitle = lockup.subtitle; + } + break; + } + } + if (serverData.isDefinedNonNull(action)) { + action.presentationStyle = ["textFollowsTintColor"]; + // If data is NOT a Category Grouping kind, reconfigure action title. + if (!isCategoryGroupingKind) { + // Prefer design tag or editorial name for title when available + const designTag = unescapeHtmlString(mediaAttributes.attributeAsString(fcData, "designTag")); + const editorialTitle = content.notesFromData(objectGraph, contentData, "name"); + action.title = designTag || editorialTitle || action.title || subtitle || caption; + } + } + return { + action: action, + caption: caption, + title: action === null || action === void 0 ? void 0 : action.title, + subtitle: subtitle, + artwork: artwork, + shortEditorialDescription: shortEditorialDescription, + content: contentData, + lockup: lockup, + appEvent: appEvent, + onDevicePersonalizationDataProcessingType: personalizedDataResult === null || personalizedDataResult === void 0 ? void 0 : personalizedDataResult.processingType, + }; + } + return null; +} +/// Gets the action and title for a brick that is backed by tags. +export function metadataForTag(objectGraph, fcData, token, metricsOptions) { + const shortEditorialDescription = mediaAttributes.attributeAsString(fcData, "name"); + const href = serverData.asString(fcData, "href"); + const url = hrefToRoutableUrl(objectGraph, href); + const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url); + const flowAction = new models.FlowAction(flowPage); + flowAction.pageUrl = url; + return { + action: flowAction, + caption: "null", + title: shortEditorialDescription, + subtitle: "null", + artwork: null, + shortEditorialDescription: shortEditorialDescription, + }; +} +function metadataForLink(objectGraph, fcData, token, metricsOptions, unavailableCallback) { + const isFCLinkData = serverData.isDefinedNonNull(serverData.asString(fcData, "url")); + const linkData = isFCLinkData ? fcData : mediaAttributes.attributeAsDictionary(fcData, "link"); + const callUnavailable = function (contentData) { + if (unavailableCallback) { + unavailableCallback(); + } + else { + token === null || token === void 0 ? void 0 : token.remainingItems.push(contentData); + } + }; + if (serverData.isNull(linkData) || shouldDefer(token)) { + callUnavailable(fcData); + return null; + } + const target = serverData.asString(linkData, "target"); + const url = serverData.asString(linkData, "url"); + // Prefer design tag for title when available + const label = serverData.asString(linkData, "label"); + const designTag = unescapeHtmlString(mediaAttributes.attributeAsString(fcData, "designTag")); + const title = designTag || label; + let action = null; + if (target === "external") { + action = new models.ExternalUrlAction(url); + action.title = title; + } + else { + const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url); + const flowAction = new models.FlowAction(flowPage); + flowAction.pageUrl = url; + flowAction.title = title; + action = flowAction; + } + action.presentationStyle = ["textFollowsTintColor"]; + // Configure metrics + const clickOptions = { + ...metricsOptions, + id: "", + }; + metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickOptions); + return { + action: action, + caption: null, + title: title, + subtitle: null, + artwork: null, + shortEditorialDescription: null, + }; +} +// endregion +// region Editorial Data Merging +export function mergeContentDataIntoEditorialData(contentDataArray, editorialItemsDataArray) { + const contentDataMap = {}; + for (const contentData of contentDataArray) { + contentDataMap[contentData.id] = contentData; + } + // The relationships we're interested in merging fetched content for + const relationShipsToMerge = ["contents", "grouping"]; + const mergedEditorialData = []; + // Steps for merging + // 1. Loop through the editorial items array + // 2. For each editorial item loop through the relationships we're interested in + // 3. If this editorial item has any of these relationships loop through that relationsip looking in our content map + // for the hydrated version of each piece of data in the relationship + // 4. If all the content has been fetched for each relationship we can add it to our resulting array of hydrated editorial items. + for (const editorialItemData of editorialItemsDataArray) { + let hasHydratedAllRelationships = true; + for (const relationshipType of relationShipsToMerge) { + const unhydratedRelationshipCollection = mediaRelationship.relationshipCollection(editorialItemData, relationshipType); + if (serverData.isDefinedNonNull(unhydratedRelationshipCollection)) { + const hydratedRelationship = []; + for (const unhydratedData of unhydratedRelationshipCollection) { + const hydratedData = contentDataMap[unhydratedData.id]; + if (serverData.isDefinedNonNullNonEmpty(hydratedData)) { + hydratedRelationship.push(hydratedData); + } + } + if (hydratedRelationship.length === unhydratedRelationshipCollection.length) { + editorialItemData.relationships[relationshipType] = { data: hydratedRelationship }; + } + else { + hasHydratedAllRelationships = false; + } + } + } + if (hasHydratedAllRelationships) { + mergedEditorialData.push(editorialItemData); + } + } + return mergedEditorialData; +} +// endregion +// region Shelf Info +/** + * For incomplete shelf fetches via a secondary lookup, whether a given shelf w/ token should merge or replace. + * @param objectGraph + * @param token The token corresponding to the shelf being built + */ +export function shelfFetchShouldMergeWhenFetched(objectGraph, token) { + /** + * <rdar://problem/60069585> [POLISH] Arcade Coming Soon: If too few items, the coming soon swoosh should show larger items. + * Always reload posterLockup shelves on macOS to adapt if the presentationHint "isLowDensity" changed. + */ + if (token.shelfStyle === "posterLockup" && objectGraph.client.isMac) { + return false; + } + else if (token.showingPlaceholders) { + return false; + } + // Always reload Arcade download pack shelf + // as there is always only a single cell (card) that contains lockups or their placeholders. + if (token.shelfStyle === "arcadeDownloadPackCard") { + return false; + } + if (token.shelfStyle === "ribbonBar" && + isSome(token.initialHydratedItems) && + token.initialHydratedItems.length > 0) { + return false; + } + // Generally true to support partially hydrated shelves. + return true; +} +// endregion +// region Search Landing Shelves +/** + * Modify `shelf` in place for global customization for all SLP shelves. + * @param objectGraph + * @param shelf + * @param token + */ +export function modifyShelfForSearchLandingGrouping(objectGraph, shelf, token) { + shelf.seeAllAction = null; + shelf.isHorizontal = false; + if (shelf.shouldFilterApps) { + // <rdar://problem/64772261> App Store: SLP: Installed app 'Open' button show up inconsistently (Filtering is sometimes not applied) + shelf.filteredItemsMinimumCount = 0; + shelf.filteringExcludedItems = token.includedAdAdamIds; + } +} +// endregion +// region Card Shelves +/** + * The shelf content type to use for the inline card display style. + * @param objectGraph + * @param {HorizontalCardDisplayStyle} style The display style for the inline card. + * @returns {ShelfContentType} The shelf content type to use. + */ +export function contentTypeForHorizontalCardDisplayStyle(objectGraph, style) { + switch (style) { + case "small": + return "smallStoryCard"; + case "medium": + return "mediumStoryCard"; + case "large": + return "largeStoryCard"; + case "card": + if (objectGraph.client.isiOS) { + return "editorialStoryCard"; + } + else { + return null; + } + default: + return null; + } +} +// endregion +/** + * Determine the best content id to use given a media api data object + * @param objectGraph + * @param contentItem The media api data model to find the id from + */ +export function contentIdFromContentItem(objectGraph, contentItem) { + let contentId = mediaAttributes.attributeAsString(contentItem, "adamId"); + if (!contentId) { + contentId = mediaAttributes.attributeAsString(contentItem, "contentId"); + } + if (!contentId) { + contentId = mediaAttributes.attributeAsString(contentItem, "id"); + } + return contentId; +} +export function unescapeHtmlString(str) { + if (serverData.isNull(str)) { + return null; + } + const escapedString = str + .replace(/&/g, "&") + .replace(/>/g, ">") + .replace(/</g, "<") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/`/g, "`") + .replace(/\r\n/g, " ") + .replace(/ /g, " ") + .replace(/<span>/g, "") + .replace(/<\/span>/g, "") + .replace(/<br>/g, " ") + .replace(/\u23ce/g, "") + .replace(/<i>/g, "") + .replace(/<\/i>/g, "") + .replace(/<b>/g, "") + .replace(/<\/b>/g, ""); + if (escapedString.match(/^\s*$/)) { + return null; + } + return escapedString; +} +/** + * Update the shelf header to use the provided seeAll action. + * @param objectGraph The App Store object graph used to check feature flags + * @param shelfHeader The shelf header to update + * @param seeAllAction The "See All" action to apply + */ +export function replaceShelfHeaderSeeAllAction(objectGraph, shelfHeader, seeAllAction) { + if (objectGraph.featureFlags.isEnabled("shelf_header")) { + // Modern headers make title tappable with a chevron (>). + shelfHeader.titleAction = seeAllAction; + } + else { + // Legacy headers show "See All" textual button on trailing edge. + shelfHeader.accessoryAction = seeAllAction; + } +} +/** + * Update the shelf header to use the provided seeAll action. + * @param objectGraph The App Store object graph used to check feature flags + * @param shelf The shelf whose header needs updating + * @param seeAllAction The see all action to apply + */ +export function replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction) { + if (objectGraph.featureFlags.isEnabled("shelf_header")) { + if (isSome(shelf.header)) { + replaceShelfHeaderSeeAllAction(objectGraph, shelf.header, seeAllAction); + } + else { + shelf.header = { + titleAction: seeAllAction, + }; + } + } + else { + shelf.seeAllAction = seeAllAction; + } +} +//# sourceMappingURL=grouping-shelf-controller-common.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller.js new file mode 100644 index 0000000..fabf85c --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller.js @@ -0,0 +1,395 @@ +import { isSome } from "@jet/environment"; +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaDataFetching from "../../../foundation/media/data-fetching"; +import * as mediaNetwork from "../../../foundation/media/network"; +import { ResponseMetadata } from "../../../foundation/network/network"; +import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants"; +import * as urls from "../../../foundation/network/urls"; +import * as mediaUrlMapping from "../../builders/url-mapping"; +import { shouldUsePrerenderedIconArtwork } from "../../content/content"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as impressionDemotion from "../../personalization/on-device-impression-demotion"; +import * as productVariants from "../../product-page/product-page-variants"; +import * as refresh from "../../refresh/page-refresh-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +/** + * A GroupingShelfController is responsible for all the logic around parsing and rending + * a single grouping page shelf. + * + * The `ShelfMetadata` is a type the is specific for a given shelf and has some additional data needed to render + * that shelf. + */ +export class GroupingShelfController { + // endregion + // region AnyGroupingShelfController + /** + * Indicates whether this grouping shelf controller can create a shelf for the given mediaApiData. + * @param objectGraph The App Store dependency graph + * @param mediaApiData The outer data object containing the FC properties and data + * @param featuredContentId The featured content id for this shelf data + * @param nativeGroupingShelfId The id of the custom shelf type, one not defined on the server + */ + supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + return this._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId); + } + /** + * Indicates whether this grouping shelf controller can create a shelf for the given mediaApiData. + * @param objectGraph The App Store dependency graph + * @param mediaApiData The outer data object containing the FC properties and data + * @param featuredContentId The featured content id for this shelf data + * @param nativeGroupingShelfId The id of the custom shelf type, one not defined on the server + */ + _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + const isFeaturedContentIdSupported = this.supportedFeaturedContentIds.has(featuredContentId); + let isNativeGroupingShelfIdSupported; + if (serverData.isDefinedNonNull(nativeGroupingShelfId)) { + isNativeGroupingShelfIdSupported = this.supportedNativeGroupingShelfIds.has(nativeGroupingShelfId); + } + else { + isNativeGroupingShelfIdSupported = true; + } + return isFeaturedContentIdSupported && isNativeGroupingShelfIdSupported; + } + /** + * This method will return a grouping page shelf regardless of the type of controller + * @param objectGraph The App Store dependency graph + * @param groupingParseContext The parse context for the grouping page so far + * @param mediaApiData The outer data object containing the FC properties and data + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the + * grouping page controller + */ + createShelf(objectGraph, mediaApiData, groupingParseContext, baseShelfToken, baseMetricsOptions) { + var _a, _b, _c; + const typedMediaApiData = mediaApiData; + const shelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, typedMediaApiData); + const shelfToken = this.shelfTokenFromBaseTokenAndMediaApiData(objectGraph, typedMediaApiData, baseShelfToken, groupingParseContext); + const shelfMetricsOptions = this.shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions); + const hasShelfMetricsOptions = serverData.isDefinedNonNullNonEmpty(shelfMetricsOptions); + if (hasShelfMetricsOptions && this.shouldImpressShelf()) { + metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, shelfToken.title); + } + /// Reorder the shelf contents based on the impression data if available + if (serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) { + shelfData.shelfContents = impressionDemotion.personalizeDataItems(shelfData.shelfContents, (_a = groupingParseContext.recoImpressionData) !== null && _a !== void 0 ? _a : {}, (_b = baseMetricsOptions === null || baseMetricsOptions === void 0 ? void 0 : baseMetricsOptions.recoMetricsData) !== null && _b !== void 0 ? _b : {}); + } + const shelf = this._createShelf(objectGraph, shelfToken, shelfData, groupingParseContext); + if (hasShelfMetricsOptions && this.shouldImpressShelf()) { + metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker); + if (serverData.isDefinedNonNull(shelf)) { + metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions); + // rdar://84952935 (Placeholder shelves are not being impressed + // For placeholder shelves we end up replacing the entire shelf, so we need to make sure the original + // impression metrics are included in the token, so they can be added when the real content is fetched + // We're doing this here because this is where we decide whether the original shelf should be impressed + if (((_c = shelf.url) === null || _c === void 0 ? void 0 : _c.length) > 0 && + serverData.isDefinedNonNullNonEmpty(shelf.impressionMetrics) && + shelfToken.showingPlaceholders) { + const originalShelfUrlString = shelf.url; + try { + // Extract the token from the URL. + // Note: Although we have access to the shelfToken here, we do not know that + // the url was constructed from token in its current state. To be safe, + // if not efficient, we reverse engineer the URL to get the token. + const originalShelfUrl = urls.URL.from(originalShelfUrlString); + const encodedToken = originalShelfUrl.pathComponents().pop(); + const shelfTokenFromUrl = JSON.parse(decodeURIComponent(encodedToken)); + // Modify the token to include the impressions metrics. + shelfTokenFromUrl.originalPlaceholderShelfImpressionMetrics = shelf.impressionMetrics; + groupingShelfControllerCommon.updateShelfUrlWithNewToken(objectGraph, shelf, shelfTokenFromUrl); + } + catch { + shelf.url = originalShelfUrlString; + } + } + } + } + this.finalizeInitialShelfForDisplay(objectGraph, shelf, shelfToken, shelfData, groupingParseContext); + if (hasShelfMetricsOptions && this.shouldPrepareLocationTrackerForNextPosition()) { + metricsHelpersLocation.nextPosition(groupingParseContext.metricsLocationTracker); + } + return shelf; + } + /** + * Initialize a builder with globally unique name. + * + * @param {string} builderClass Globally unique name. + */ + constructor(builderClass) { + // region Supported Types + this.supportedFeaturedContentIds = new Set([]); + this.supportedNativeGroupingShelfIds = new Set([]); + this.builderClass = builderClass; + } + /** + * Determines the strategy for fetching incomplete shelves based on feature flags and shelf type + * + * @param objectGraph - The application store object graph. + * @returns The strategy for fetching incomplete shelves, either on shelf appearance or on page load. + */ + incompleteShelfFetchStrategy(objectGraph) { + if (objectGraph.client.isiOS) { + return models.IncompleteShelfFetchStrategy.OnShelfWillAppear; + } + else { + return models.IncompleteShelfFetchStrategy.OnPageLoad; + } + } + // endregion + // region Metrics + /** + * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping + * page controller + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the + * grouping page controller + */ + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + return baseMetricsOptions; + } + /** + * Whether the shelf itself should be impressed, there are some cases where the shelf itself + * does not get impressed, just the contents. + */ + shouldImpressShelf() { + return true; + } + /** + * Whether we should move the location tracker to the next position after creating our shelf + */ + shouldPrepareLocationTrackerForNextPosition() { + return true; + } + // endregion + // Shelf Finalization + /** + * This method will set any required fields on our shelf once it has created as part of the initial page rendering. + * This includes things like timing metrics, hiding empty shelves etc. + * + * @param objectGraph The App Store dependency graph + * @param shelf The created shelf + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context for the grouping page so far + * @private + */ + finalizeInitialShelfForDisplay(objectGraph, shelf, shelfToken, shelfData, groupingParseContext) { + var _a, _b; + if (serverData.isNullOrEmpty(shelf)) { + return; + } + // Should not show see all links on search groupings + if (shelfToken.isSearchLandingPage) { + groupingShelfControllerCommon.modifyShelfForSearchLandingGrouping(objectGraph, shelf, shelfToken); + } + if (((_a = shelf.url) === null || _a === void 0 ? void 0 : _a.length) > 0 && + serverData.isDefinedNonNullNonEmpty(groupingParseContext.additionalShelfParameters)) { + shelf.url = urls.URL.from(shelf.url) + .append("query", groupingParseContext.additionalShelfParameters) + .build(); + } + // If we're on iOS and no prior fetch strategy has been defined, set the fetchStrategy to OnShelfWillAppear. + shelf.fetchStrategy = this.incompleteShelfFetchStrategy(objectGraph); + // Shelf will fetch content after sending follow-up fetch request. + const willFetchShelfContent = isSome(shelf) && ((_b = shelf.url) === null || _b === void 0 ? void 0 : _b.length) > 0; + if (serverData.isNullOrEmpty(shelf.items) && !willFetchShelfContent) { + shelf.isHidden = true; + } + shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf); + } + /** + * This method will set any required fields on our shelf once it has been fetched as a result of a secondary fetch. + * This includes things like timing metrics, hiding empty shelves etc. + * + * @param objectGraph The AppStore dependency graph + * @param shelf The created shelf + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @private + */ + finalizeSecondaryShelfForDisplay(objectGraph, shelf, shelfToken, shelfData) { + if (serverData.isNullOrEmpty(shelf)) { + return; + } + if (shelfToken.remainingItems.length) { + const remainingIds = shelfToken.remainingItems.map((data) => { + return data.id; + }); + objectGraph.console.warn("Could not load items for: " + remainingIds.join(",")); + } + if (shelf) { + shelf.mergeWhenFetched = groupingShelfControllerCommon.shelfFetchShouldMergeWhenFetched(objectGraph, shelfToken); + shelf.networkTimingMetrics = shelfData.responseTimingValues; + shelf.nextPreferredContentRefreshDate = refresh.nextPreferredContentRefreshDateForController(refresh.newPageRefreshController()); + } + // Merge the original impression metrics with the newly created impression metrics. + if (serverData.isDefinedNonNullNonEmpty(shelfToken.originalPlaceholderShelfImpressionMetrics)) { + // If the `shelf.impressionMetrics` is null, we just defer to the original metrics. + if (serverData.isNull(shelf.impressionMetrics)) { + shelf.impressionMetrics = shelfToken.originalPlaceholderShelfImpressionMetrics; + } + else { + for (const key in shelfToken.originalPlaceholderShelfImpressionMetrics.fields) { + if (Object.prototype.hasOwnProperty.call(shelfToken.originalPlaceholderShelfImpressionMetrics.fields, key)) { + shelf.impressionMetrics.fields[key] = + shelfToken.originalPlaceholderShelfImpressionMetrics.fields[key]; + } + } + } + } + if (!shelfToken.hasExistingContent && serverData.isNullOrEmpty(shelf.items)) { + shelf.isHidden = true; + } + // Should not show see all links on search groupings + if (shelfToken.isSearchLandingPage) { + groupingShelfControllerCommon.modifyShelfForSearchLandingGrouping(objectGraph, shelf, shelfToken); + } + shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf); + } + // endregion + // region ShelfBuilder + async handleShelf(objectGraph, url, parameters, matchedRuleIdentifier) { + const tokenJson = parameters["token"]; + const shelfToken = JSON.parse(tokenJson); + shelfToken.isFirstRender = false; + try { + const shelfData = await this.secondaryShelfDataForShelfUrl(objectGraph, url, shelfToken, parameters); + const shelf = this._createShelf(objectGraph, shelfToken, shelfData, null); + this.finalizeSecondaryShelfForDisplay(objectGraph, shelf, shelfToken, shelfData); + return shelf; + } + catch (error) { + if (shelfToken && !shelfToken.hasExistingContent) { + const hiddenShelf = new models.Shelf(shelfToken.shelfStyle); + hiddenShelf.isHidden = true; + return hiddenShelf; + } + else { + throw error; + } + } + } + shelfRoute(objectGraph) { + if (serverData.isDefinedNonNullNonEmpty(this.supportedNativeGroupingShelfIds)) { + return routesForNativeGroupingShelfIds(this.supportedNativeGroupingShelfIds); + } + else { + return routesForFeaturedContentIds(this.supportedFeaturedContentIds); + } + } + // endregion + // region Static Base Helpers + /** + * This is a standard default implementation for the secondary shelf data fetch. This can be used for all the + * grouping shelf controls that dont implement a custom ShelfDataType + * @param objectGraph + * @param shelfUrl + * @param parameters + */ + static async secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((mediaApiData) => { + const hydratedItems = groupingShelfControllerCommon.hydratedRemainingItemsForShelfTokenFromMediaApiData(objectGraph, shelfToken, mediaApiData); + return { + shelfContents: hydratedItems, + responseTimingValues: mediaApiData[ResponseMetadata.timingValues], + }; + }); + } + /** + * This is a standard default implementation for the media api request, for an incomplete grouping shelf. + * + * @param objectGraph + * @param shelfUrl + * @param parameters + */ + static async secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters) { + const urlString = shelfUrl.build(); + let request; + if (mediaUrlMapping.isMediaUrl(objectGraph, shelfUrl)) { + request = new mediaDataFetching.Request(objectGraph, urlString); + } + else { + request = groupingShelfControllerCommon.generateShelfRequest(objectGraph, shelfToken, parameters); + } + if (!request) { + return await Promise.reject(new Error(`Could not construct media API request for: ${shelfUrl}`)); + } + request.includingAdditionalPlatforms(defaultMediaApiPlatforms(objectGraph)); + request.includingAttributes(defaultMediaApiAttributes(objectGraph)); + request.usingCustomAttributes(productVariants.shouldFetchCustomAttributes(objectGraph)); + request.attributingTo(shelfUrl.build()); + return await mediaNetwork.fetchData(objectGraph, request).then((mediaApiData) => { + groupingShelfControllerCommon.flushRequestedItemsFromShelfToken(shelfToken, request.ids); + return mediaApiData; + }); + } +} +export function createShelfAccessibilityMetadata(objectGraph, shelf) { + var _a; + let accessibilityLabel = objectGraph.loc.string("Shelves.Accessibility.Label"); + if (isSome(shelf.title)) { + accessibilityLabel = `${shelf.title}, ${accessibilityLabel}`; + } + else if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.title)) { + accessibilityLabel = `${shelf.header.title}, ${accessibilityLabel}`; + } + const accessibilityRoleDescription = objectGraph.loc.string("Shelves.Accessibility.RoleDescription"); + return { + label: accessibilityLabel, + roleDescription: accessibilityRoleDescription, + }; +} +function routeForFeaturedContentId(featuredContentId, nativeGroupingShelfId, additionalQueryParams) { + const query = serverData.isDefinedNonNullNonEmpty(additionalQueryParams) + ? [...additionalQueryParams] + : []; + query.push(`${Parameters.groupingFeaturedContentId}=${featuredContentId}`); + if (serverData.isDefinedNonNullNonEmpty(nativeGroupingShelfId)) { + query.push(`${Parameters.nativeGroupingShelfId}=${nativeGroupingShelfId}`); + } + return { + protocol: Protocol.internal, + path: `/${Path.grouping}/${Path.shelf}/{token}`, + query: query, + }; +} +export function routesForFeaturedContentIds(featuredContentIds, additonalQueryParams) { + const routes = []; + for (const featuredContentId of featuredContentIds) { + routes.push(routeForFeaturedContentId(featuredContentId, null, additonalQueryParams)); + } + return routes; +} +export function routesForNativeGroupingShelfIds(nativeGroupingShelfIds, additonalQueryParams) { + const routes = []; + for (const nativeGroupingShelfId of nativeGroupingShelfIds) { + routes.push(routeForFeaturedContentId(-1 /* FeaturedContentID.Native_GroupingShelf */, nativeGroupingShelfId, additonalQueryParams)); + } + return routes; +} +// region Media Api Attributes +function defaultMediaApiPlatforms(objectGraph) { + return mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph); +} +function defaultMediaApiAttributes(objectGraph) { + const attributes = ["editorialArtwork", "isAppleWatchSupported", "requiredCapabilities", "badge-content"]; + if (objectGraph.appleSilicon.isSupportEnabled) { + attributes.push("macRequiredCapabilities"); + } + if (objectGraph.client.isMac) { + attributes.push("hasMacIPAPackage"); + } + if (objectGraph.bag.enableUpdatedAgeRatings) { + attributes.push("ageRating"); + } + if (shouldUsePrerenderedIconArtwork(objectGraph)) { + attributes.push("iconArtwork"); + } + return attributes; +} +// endregion +//# sourceMappingURL=grouping-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-small-breakout-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-small-breakout-shelf-controller.js new file mode 100644 index 0000000..d7eac20 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-small-breakout-shelf-controller.js @@ -0,0 +1,168 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaPlatformAttributes from "../../../foundation/media/platform-attributes"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as color from "../../../foundation/util/color-util"; +import * as breakoutsCommon from "../../arcade/breakouts-common"; +import * as contentAttributes from "../../content/attributes"; +import * as content from "../../content/content"; +import * as flowPreview from "../../content/flow-preview"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as article from "../../today/article"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +export class GroupingSmallBreakoutShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingSmallBreakoutShelfController"); + this.supportedFeaturedContentIds = new Set([480 /* groupingTypes.FeaturedContentID.AppStore_Breakout */]); + } + // endregion + // region GroupingShelfController + _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) { + if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) { + return false; + } + const breakoutStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle"); + return breakoutStyle === "small"; + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Metrics + shouldImpressShelf() { + return false; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const items = []; + for (const data of shelfData.shelfContents) { + if (serverData.isNull(data.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(data); + continue; + } + const primaryContent = content.primaryContentForData(objectGraph, data); + if (breakoutsCommon.requiresPrimaryContent(objectGraph, data) && + !mediaAttributes.hasAttributes(primaryContent)) { + shelfToken.isDeferring = true; + shelfToken.remainingItems.push(data); + shelfToken.relationshipToFetch = "primary-content"; + continue; + } + const metricsOptions = { + targetType: "smallBreakout", + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data), + }; + // If this EI can and should show the expected release date of an arcade app, override the badge. + let badgeTitle; + const fallbackLabel = mediaAttributes.attributeAsString(data, "label"); + if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "showExpectedReleaseDate")) { + badgeTitle = objectGraph.loc.uppercased(content.dynamicPreorderDateFromData(objectGraph, primaryContent, fallbackLabel)); + } + else { + badgeTitle = fallbackLabel; + } + let badge = { type: "none" }; + if ((badgeTitle === null || badgeTitle === void 0 ? void 0 : badgeTitle.length) > 0) { + badge = { + type: "text", + title: badgeTitle, + }; + } + const title = content.editorialNotesFromData(objectGraph, data, "name") || + contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "name"); + const description = content.editorialNotesFromData(objectGraph, data, "short") || + contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "tagline"); + const artwork = content.iconFromData(objectGraph, primaryContent, { + useCase: 5 /* content.ArtworkUseCase.ArcadeSmallBreakout */, + withJoeColorPlaceholder: true, + }); + const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data); + const artworkData = mediaPlatformAttributes.platformAttributeAsDictionary(primaryContent, attributePlatform, "artwork"); + const backgroundColor = color.fromHex(serverData.asString(artworkData, "textColor4")) || (artwork === null || artwork === void 0 ? void 0 : artwork.backgroundColor); + const details = new models.BreakoutDetails(title, description, badge, null, breakoutsCommon.detailBackgroundStyleFromColor(objectGraph, backgroundColor), null); + const smallBreakout = new models.SmallBreakout(details, artwork, backgroundColor); + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, smallBreakout.details.title, metricsOptions); + const productData = article.productDataFromArticle(objectGraph, data); + const isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isPreorder"); + impressionOptions.isPreorder = isPreorder; + metricsHelpersImpressions.addImpressionFields(objectGraph, smallBreakout, impressionOptions); + // Push the breakout here so that the click action has the breakout in its location + // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button + // action + // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field + metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, smallBreakout.details.title); + const breakoutAction = breakoutsCommon.actionFromData(objectGraph, data); + const breakoutClickOptions = { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data), + targetType: "button", + id: data.id, + }; + metricsHelpersClicks.addClickEventToAction(objectGraph, breakoutAction, breakoutClickOptions); + smallBreakout.details.callToActionButtonAction = breakoutAction; + smallBreakout.clickAction = breakoutAction; + metricsHelpersLocation.popLocation(metricsOptions.locationTracker); + // Set flow preview actions + smallBreakout.flowPreviewActionsConfiguration = + flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, data, true, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride, breakoutAction, metricsOptions, breakoutClickOptions); + items.push(smallBreakout); + } + const shelf = new models.Shelf("smallBreakout"); + shelf.isHorizontal = false; + shelf.items = items; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + if (groupingParseContext.shelves.length === 0) { + shelf.presentationHints = { isFirstShelf: true }; + } + return shelf; + } +} +//# sourceMappingURL=grouping-small-breakout-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tag-brick-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tag-brick-shelf-controller.js new file mode 100644 index 0000000..66baba8 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tag-brick-shelf-controller.js @@ -0,0 +1,246 @@ +import * as models from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaDataStructure from "../../../foundation/media/data-structure"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as content from "../../content/content"; +import * as flowPreview from "../../content/flow-preview"; +import * as lockupsEditorialContext from "../../lockups/editorial-context"; +import * as metricsHelpersClicks from "../../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import * as placeholders from "../../placeholders/placeholders"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common"; +import { defaultLayoutSize } from "../../../foundation/media/data-fetching"; +import { isSome } from "@jet/environment"; +export class GroupingTagBrickShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingTagBrickShelfController"); + this.supportedFeaturedContentIds = new Set([ + 584 /* groupingTypes.FeaturedContentID.AppStore_TagsBrick */, + 587 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedTagsBrick */, + ]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + var _a; + if (isSome(mediaApiData)) { + const shelfContents = (_a = mediaRelationship.relationshipCollection(mediaApiData, "contents")) !== null && _a !== void 0 ? _a : null; + return { shelfContents: shelfContents }; + } + return { shelfContents: null }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((shelfData) => { + return { + shelfContents: shelfData.data, + }; + }); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + // If suppress text is not provided, default to hiding. + let suppressText = mediaAttributes.attributeAsBoolean(mediaApiData, "suppressText"); + if (serverData.isNull(suppressText)) { + suppressText = true; + } + const brickShelfToken = { + ...baseShelfToken, + showSupplementaryText: !suppressText, + }; + brickShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData); + return brickShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + const items = []; + const remainingBricks = []; // Data where metadata was missing. + const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle"); + const isCategoryBrick = displayStyle === "small"; + const shelf = new models.Shelf("tagBrick"); + const layoutSize = serverData.asNumber(shelfToken.featuredContentData.attributes, "layoutStyle.layoutSize"); + shelf.rowsPerColumn = layoutSize !== null && layoutSize !== void 0 ? layoutSize : defaultLayoutSize(objectGraph); + shelf.isHorizontal = true; + if (isSome(shelfData.shelfContents)) { + for (const tagData of shelfData.shelfContents) { + const brickModel = GroupingTagBrickShelfController.createBrick(objectGraph, tagData, isCategoryBrick, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker, shelfToken, groupingParseContext); + if (!isSome(brickModel === null || brickModel === void 0 ? void 0 : brickModel.shortEditorialDescription)) { + shelfToken.remainingItems.push(tagData); + remainingBricks.push(tagData); + continue; + } + if (isSome(brickModel)) { + items.push(brickModel); + } + metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker); + } + } + if (serverData.isDefinedNonNull(shelfToken.presentationHints)) { + shelf.presentationHints = shelfToken.presentationHints; + } + if (serverData.isDefinedNonNull(shelfToken.showSupplementaryText)) { + shelf.presentationHints = { + ...shelf.presentationHints, + showSupplementaryText: shelfToken.showSupplementaryText, + }; + } + // We don't need this in our incomplete shelf URL, so we'll preemptively remove it. + delete shelfToken.maxItemCount; + // Override `featuredContentData` to only have remaining bricks that must be fetched. + if (serverData.isDefinedNonNull(serverData.traverse(shelfToken.featuredContentData, "relationships.contents.data")) && + isSome(shelfToken.featuredContentData.relationships)) { + shelfToken.featuredContentData.relationships["contents"].data = remainingBricks; + } + // Set metadata + shelf.title = shelfToken.title; + shelf.subtitle = shelfToken.subtitle; + if (isCategoryBrick) { + const displayCount = serverData.asNumber(shelfToken.featuredContentData.attributes, "displayCount"); + shelf.items = items.slice(0, displayCount !== null && displayCount !== void 0 ? displayCount : items.length); + } + else { + shelf.items = items; + } + // See all + const hasSeeAll = serverData.asBooleanOrFalse(shelfToken.featuredContentData.attributes, "hasSeeAll"); + if (isCategoryBrick && hasSeeAll) { + // Setup shelf + const seeAllShelf = new models.Shelf("categoryBrick"); + seeAllShelf.items = items; + seeAllShelf.presentationHints = { isSeeAllContext: true }; + // Setup Page + const seeAllPage = new models.GenericPage([seeAllShelf]); + seeAllPage.title = shelfToken.title; + // Setup action + const seeAllAction = new models.FlowAction("page"); + seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL"); + seeAllAction.pageData = seeAllPage; + // Connect action + shelf.seeAllAction = seeAllAction; + // Metrics + metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, "", { + pageInformation: shelfToken.metricsPageInformation, + locationTracker: shelfToken.metricsLocationTracker, + }); + } + // If no items should we display placeholders for this shelf? + const willHydrateShelfLater = shelf && serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender; + if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) { + placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId); + } + shelfToken.presentationHints = shelf.presentationHints; + shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken); + return shelf; + } + // region Static Helpers + static createBrick(objectGraph, brickData, searchCategoryBricks, metricsPageInformation, metricsLocationTracker, shelfToken, groupingParseContext) { + var _a, _b, _c; + const metricsBrickData = (_a = mediaDataStructure.metricsFromMediaApiObject(brickData)) !== null && _a !== void 0 ? _a : undefined; + const metricsOptions = { + targetType: searchCategoryBricks ? "tile" : "brick", + pageInformation: metricsPageInformation, + locationTracker: metricsLocationTracker, + recoMetricsData: metricsBrickData, + }; + const metadata = groupingShelfControllerCommon.metadataForTag(objectGraph, brickData, shelfToken !== null && shelfToken !== void 0 ? shelfToken : null, metricsOptions); + if (!metadata) { + const brickModel = new models.Brick(); + brickModel.shortEditorialDescription = mediaAttributes.attributeAsString(brickData, "name"); + return brickModel; + } + const brickModel = new models.Brick(); + // Setup Artwork + const artworkOptions = { + useCase: 18 /* content.ArtworkUseCase.GroupingBrick */, + }; + const collectionIcons = groupingShelfControllerCommon.artworkForTags(objectGraph, brickData, 1060, 520, artworkOptions, metricsOptions); + if (collectionIcons.length > 0) { + const collectionIconBackgroundColor = collectionIcons[0].backgroundColor; + brickModel.collectionIcons = collectionIcons; + if (isSome(collectionIconBackgroundColor) && (collectionIconBackgroundColor === null || collectionIconBackgroundColor === void 0 ? void 0 : collectionIconBackgroundColor.type) === "rgb") { + brickModel.backgroundColor = + (_b = content.closestTagBackgroundColorForIcon(collectionIconBackgroundColor)) !== null && _b !== void 0 ? _b : undefined; + } + } + brickModel.accessibilityLabel = metadata.title; + // Set supplementary text. + brickModel.shortEditorialDescription = metadata.shortEditorialDescription; + // Set action + brickModel.clickAction = metadata.action; + // Set personalization + const brickFeaturedContentId = mediaAttributes.attributeAsNumber(brickData, "editorialElementKind"); + if (brickFeaturedContentId === 435 /* groupingTypes.FeaturedContentID.AppStore_MSOBrickMarker */) { + brickModel.personalizationStyle = "mso"; + } + // Set flow preview actions + const contentData = mediaRelationship.relationshipData(objectGraph, brickData, "contents"); + if (serverData.isDefinedNonNull(contentData)) { + const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, contentData, metricsOptions); + const clientIdentifierOverride = (_c = shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride) !== null && _c !== void 0 ? _c : null; + brickModel.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, brickData, true, clientIdentifierOverride, brickModel.clickAction, metricsOptions, metricsClickOptions); + } + // Configure impressions + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, brickData, metadata.title, metricsOptions); + metricsHelpersImpressions.addImpressionFields(objectGraph, brickModel, impressionOptions); + // Safe area + brickModel.artworkSafeArea = models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea; + brickModel.textSafeArea = models.ChartOrCategorySafeArea.defaultTileTextSafeArea; + if (!brickModel.isValid()) { + return null; + } + return brickModel; + } + // endregion + // region Metrics + /** + * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping + * page controller + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the + * grouping page controller + */ + shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) { + const shelfMetricsOptions = { ...baseMetricsOptions }; + const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle"); + // If this is a Category Bricks shelf, configure metrics title accordingly. + if (displayStyle === "small") { + shelfMetricsOptions.title = "Browse Categories"; + } + return shelfMetricsOptions; + } +} +//# sourceMappingURL=grouping-tag-brick-shelf-controller.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tags-header-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tags-header-shelf-controller.js new file mode 100644 index 0000000..bb6a950 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tags-header-shelf-controller.js @@ -0,0 +1,92 @@ +import * as models from "../../../api/models"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import { areAppTagsEnabled } from "../../util/app-tags-util"; +import { GroupingShelfController } from "./grouping-shelf-controller"; +import { attributeAsDictionary } from "@apple-media-services/media-api"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import { isSome } from "@jet/environment"; +import * as content from "../../content/content"; +import { artworkDictionaryFromData } from "../../arcade/breakouts-common"; +import * as color from "../../../foundation/util/color-util"; +/** + * This groupings controller adds the logic to display a header shelf on the groupings page. Th primary expereince + * is artwork and the secondary experience includes fallback icons. + */ +export class GroupingMediaPageHeaderShelfController extends GroupingShelfController { + // region Constructors + constructor() { + super("GroupingMediaPageHeaderShelfController"); + this.supportedFeaturedContentIds = new Set([585 /* groupingTypes.FeaturedContentID.AppStore_MediaPageHeader */]); + } + // endregion + // region Shelf Creation Prerequisites + /** + * For a given mediaApiData extract the actual shelfContents array needed to render this shelf + * + * @param mediaApiData The outer shelfContents object containing the shelf contents + */ + initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) { + return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") }; + } + /** + * For a given url that this controller handles, we should return a promise that will result in the `ShelfData` + * needed to render this shelf + * + * @param objectGraph The App Store dependency graph + * @param shelfUrl The url that this controller handled on a secondary fetch + * @param parameters The extracted parameters from the shelf url + */ + async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) { + return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters); + } + /** + * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type + * + * @param objectGraph The App Store dependency graph + * @param baseShelfToken The base grouping shelf token created by the grouping-controller + * @param mediaApiData The outer data object containing the FC properties and data + * @param groupingParseContext The parse context for the grouping page so far + */ + shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) { + return baseShelfToken; + } + // endregion + // region Shelf Creation + /** + * + * @param objectGraph The App Store dependency graph + * @param shelfToken The shelf shelfToken for this current shelf creation request + * @param shelfData The media api shelfContents array for this shelf + * @param groupingParseContext The parse context used to generate the grouping page on the initial page load, + * this will be missing when this controller renders a secondary or incomplete shelf fetch. + */ + _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) { + var _a; + if (!areAppTagsEnabled(objectGraph, "grouping")) { + return null; + } + const headerData = shelfData.shelfContents[0]; + let title; + if (isSome(headerData)) { + const editorialNotes = attributeAsDictionary(headerData, "editorialNotes"); + title = (_a = serverData.asString(editorialNotes, "name")) !== null && _a !== void 0 ? _a : null; + const artworkDictionary = artworkDictionaryFromData(objectGraph, headerData); + const artworkData = serverData.asDictionary(artworkDictionary, "categoryDetailStatic16x9"); + const artworkFromData = content.artworkFromApiArtwork(objectGraph, artworkData, { + withJoeColorPlaceholder: true, + useCase: 0 /* content.ArtworkUseCase.Default */, + cropCode: "CDS.ApTCHM01", + }); + if (isSome(title) && isSome(artworkFromData)) { + const isArtworkDark = color.isDarkColor(artworkFromData.backgroundColor); + const pageHeader = new models.MediaPageHeader(null, title, null, artworkFromData, null, null, false, null, null, isArtworkDark ? "dark" : "light"); + const shelf = new models.Shelf("mediaPageHeader"); + shelf.items = [pageHeader]; + return shelf; + } + return null; + } + return null; + } +} +//# sourceMappingURL=grouping-tags-header-shelf-controller.js.map
\ No newline at end of file |
