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