summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/search/landing
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search/landing')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js84
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js836
2 files changed, 920 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js
new file mode 100644
index 0000000..6baf947
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js
@@ -0,0 +1,84 @@
+/**
+ * Manages storing Cohort IDs for Search Landing Page (SLP)
+ *
+ * # What is Cohort ID?
+ * Cohort IDs (a.k.a. cluster IDs) are used to bucket users into different categories, e.g. a gamer.
+ * This ID can be used to specify a SLP that is suited to that cateogry of users, e.g. a page featuring more games.
+ *
+ * # SLP Endpoint Constraints
+ * Today, SLPs are not personalized, and rely heavily on CDN caching.
+ * We cannot fire a single request to SLP endpoint to have it adapt to a user's cohort based on cookies, etc.
+ *
+ * As a workaround we:
+ * 1. Store the cohort ID for user if we ever load a personalized endpoint, e.g. Today.
+ * 2. Send stored cohort ID as a query param on the SLP endpoint, if we have any.
+ */
+"use strict";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+// region exports
+/**
+ * Should be called whenever we recieve a MAPI response from a personalized endpoint.
+ * Persists the cohort id that may be present in given MAPI respose for specified user.
+ * @param dsid The user's dsid.
+ * @param metaDataProviding MAPI response that may contain cohort id for current user.
+ */
+export function storeCohortIdForUserFromResponse(objectGraph, dsid, metaDataProviding) {
+ if (serverData.isNullOrEmpty(dsid)) {
+ return;
+ }
+ const cohortId = cohortIdFromResponse(metaDataProviding);
+ if (serverData.isNullOrEmpty(cohortId)) {
+ return;
+ }
+ setCohortIdForDSID(objectGraph, dsid, cohortId);
+}
+/**
+ * Return the stored cohort id for given dsid.
+ * @param dsid The DSID of user to fetch cohort id for.
+ */
+export function cohortIdForUser(objectGraph, dsid) {
+ if (serverData.isNullOrEmpty(dsid)) {
+ return null;
+ }
+ return getCohortIdForDSID(objectGraph, dsid);
+}
+/**
+ * Deletes cohort id for given user. For testing.
+ */
+export function deleteCohortIdForUser(objectGraph, dsid) {
+ setCohortIdForDSID(objectGraph, dsid, undefined);
+}
+// endregion
+// region Internals
+/**
+ * Given a top-level MAPI response `metaDataProviding`, returns the cohort ID, if any.
+ * @param metaDataProviding A MAPI Response that may contain a `meta.metrics` blob with `clusterId`
+ */
+function cohortIdFromResponse(metaDataProviding) {
+ return serverData.asString(metaDataProviding, "meta.metrics.clusterId");
+}
+/**
+ * Converts a DSID into a dictionary key for storing cohort ID.
+ * @param dsid DSID to generate storage key for.
+ */
+function cohortIDStorageKeyForDSID(dsid) {
+ return dsid + "-cohort-id";
+}
+/**
+ * Set the stored cohort id for given user (by dsid).
+ * @param dsid DSID to associate cohortId with
+ * @param cohortId The cohort id for user.
+ */
+function setCohortIdForDSID(objectGraph, dsid, cohortId) {
+ const cohortForDSIDKey = cohortIDStorageKeyForDSID(dsid);
+ objectGraph.storage.storeString(cohortForDSIDKey, cohortId);
+}
+/**
+ * Gets the stored cohort id for given user by dsid.
+ * @param dsid DSID to get cohort id for.
+ */
+function getCohortIdForDSID(objectGraph, dsid) {
+ const cohortForDSIDKey = cohortIDStorageKeyForDSID(dsid);
+ return objectGraph.storage.retrieveString(cohortForDSIDKey);
+}
+//# sourceMappingURL=search-landing-cohort.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js
new file mode 100644
index 0000000..808384b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js
@@ -0,0 +1,836 @@
+import { isNothing, isSome, unwrapOptional } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as filtering from "../../filtering";
+import * as adLockups from "../../lockups/ad-lockups";
+import * as lockups from "../../lockups/lockups";
+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 mediaRelationships from "../../../foundation/media/relationships";
+import { searchLandingPageAdShelfIdentifier, searchLandingPagePositionInfo } from "../../ads/on-device-ad-stitch";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import * as content from "../../content/content";
+import * as metricsHelpersPage from "../../metrics/helpers/page";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { createChartsCategoryShelf } from "../content/search-categories";
+import { MediumAdLockupWithScreenshotsBackground, } from "../../../api/models";
+import * as adStitch from "../../ads/ad-stitcher";
+import * as adIncidents from "../../ads/ad-incident-recorder";
+import * as searchShelves from "../content/search-shelves";
+import { CollectionShelfDisplayStyle } from "../../editorial-pages/editorial-page-types";
+import { buildBrick } from "../../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-brick-collection-shelf-builder";
+import { iconFromData } from "../../content/content";
+import * as searchHistoryShelf from "../shelves/search-history-shelf";
+import { adLogger } from "../search-ads";
+import { asString } from "../../../foundation/json-parsing/server-data";
+import { addImpressionsFieldsToAd } from "../../metrics/helpers/impressions";
+import { makeSearchResultsPageIntent } from "../../../api/intents/search-results-page-intent";
+import { getLocale } from "../../locale";
+import { actionFor } from "../../../foundation/runtime/action-provider";
+import * as impressionDemotion from "../../personalization/on-device-impression-demotion";
+import { applySearchAdMissedOpportunityToShelvesIfNeeded } from "../../ads/ad-common";
+// #region Shelf Creation
+export function firstShelfMarkerMatchingUseCase(dataContainer, searchLandingPageContext, onDevicePersonalizationUseCase) {
+ const shelfData = dataContainer.data;
+ if (serverData.isNullOrEmpty(shelfData)) {
+ return null;
+ }
+ for (const dataItem of shelfData) {
+ if (serverData.isNullOrEmpty(dataItem)) {
+ continue;
+ }
+ /// Skip shelf if not valid for context page type.
+ const shelfMetadata = serverData.asDictionary(dataItem, "meta");
+ const shelfPageType = pageTypeFromShelfMetaCategory(shelfMetadata === null || shelfMetadata === void 0 ? void 0 : shelfMetadata.category);
+ if (shelfPageType !== searchLandingPageContext.pageType) {
+ continue;
+ }
+ if (onDevicePersonalizationUseCase ===
+ mediaAttributes.attributeAsString(dataItem, "onDevicePersonalizationUseCase")) {
+ return dataItem;
+ }
+ }
+ return null;
+}
+/**
+ * Inserts the shelf made from the data container into the grouping parse context
+ * @param objectGraph The App Store dependency graph
+ * @param dataContainer The response data
+ * @param searchLandingPageContext The context for the search page, e.g. landing or focus
+ */
+export function insertShelvesIntoSearchPageContext(objectGraph, dataContainer, searchLandingPageContext) {
+ var _a;
+ const shelfData = dataContainer.data;
+ if (serverData.isNullOrEmpty(shelfData)) {
+ return;
+ }
+ // index to compare adPositionInfo
+ let builtShelves = 0;
+ const supportsFocus = objectGraph.bag.mediaAPISearchFocusEnabled && isSome(searchLandingPageContext.pageType);
+ for (const dataItem of shelfData) {
+ if (serverData.isNullOrEmpty(dataItem)) {
+ continue;
+ }
+ /// Skip shelf if not valid for context page type.
+ if (supportsFocus) {
+ const shelfMetadata = serverData.asDictionary(dataItem, "meta");
+ const shelfPageType = pageTypeFromShelfMetaCategory(shelfMetadata === null || shelfMetadata === void 0 ? void 0 : shelfMetadata.category);
+ if (isSome(shelfPageType) && shelfPageType !== searchLandingPageContext.pageType) {
+ continue;
+ }
+ }
+ const adMeta = dataContainer.meta || null;
+ const adUnitShelf = shelfFromAdStitcher(objectGraph, searchLandingPageContext, builtShelves, adMeta);
+ if (isSome(adUnitShelf)) {
+ searchLandingPageContext.shelves.push(adUnitShelf);
+ metricsHelpersLocation.nextPosition(searchLandingPageContext.metricsLocationTracker);
+ }
+ /// Get the necessary metadata for the shelf
+ const shelfKind = mediaAttributes.attributeAsString(dataItem, "contentKind");
+ const shelfAttributes = searchShelves.shelfAttributesFromData(objectGraph, dataItem, shelfKind);
+ const shelfContext = searchLandingPageShelfContext(objectGraph, dataItem, shelfAttributes, searchLandingPageContext, shelfKind);
+ /// Push the shelf content location so each shelf has the correct parent and starting index
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfContext.metricsOptions, (_a = shelfAttributes.title) !== null && _a !== void 0 ? _a : "");
+ /// Create the shelf
+ const shelf = createShelf(objectGraph, dataItem, searchLandingPageContext, shelfContext, shelfAttributes, shelfKind);
+ /// Pop the shelf location
+ metricsHelpersLocation.popLocation(searchLandingPageContext.metricsLocationTracker);
+ /// If the shelf is empty, skip it
+ if (serverData.isNullOrEmpty(shelf)) {
+ continue;
+ }
+ /// Add impressions for the shelf
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfContext.metricsOptions);
+ applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, [shelf], "searchLanding", shelfContext.metricsOptions.id, searchLandingPageContext.metricsPageInformation);
+ /// Add the shelf to the page context for processing later
+ searchLandingPageContext.shelves.push(shelf);
+ builtShelves += 1;
+ /// Make sure each shelf is represented by its own impressions index position
+ metricsHelpersLocation.nextPosition(searchLandingPageContext.metricsLocationTracker);
+ }
+}
+function pageTypeFromShelfMetaCategory(category) {
+ switch (category) {
+ case "search-landing":
+ return searchShelves.SearchPageType.Landing;
+ case "search-focus":
+ return searchShelves.SearchPageType.Focus;
+ default:
+ return undefined;
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfContext The context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @param shelfKind The content kind for the shelf
+ * @returns A shelf if supported, null otherwise
+ */
+function createShelf(objectGraph, data, pageContext, shelfContext, shelfAttributes, shelfKind) {
+ switch (shelfKind) {
+ case models.SearchLandingPageContentKind.Suggestion:
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return createSuggestedLinksShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else if (pageContext.pageType === searchShelves.SearchPageType.Focus) {
+ return createSuggestedSearchesShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else {
+ return createSuggestedLinkActionsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ case models.SearchLandingPageContentKind.CategoriesAndCharts:
+ return createChartsCategoryShelf(objectGraph, data, false, shelfAttributes, pageContext, shelfContext);
+ case models.SearchLandingPageContentKind.Apps:
+ return createLockupsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ case models.SearchLandingPageContentKind.EditorialCollection:
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return buildBrickShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else {
+ return null;
+ }
+ default:
+ return createShelfFromMarker(objectGraph, data, pageContext, shelfContext, shelfAttributes);
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfContext The context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @returns A shelf if supported, null otherwise
+ */
+function createShelfFromMarker(objectGraph, data, pageContext, shelfContext, shelfAttributes) {
+ if (data.type !== "search-recommendations-marker") {
+ return null;
+ }
+ switch (mediaAttributes.attributeAsString(data, "onDevicePersonalizationUseCase")) {
+ case "recentSearches":
+ return searchHistoryShelf.createShelfWithContext(objectGraph, pageContext, shelfAttributes);
+ default:
+ return null;
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ */
+function createMediumAdLockupWithScreenshotsBackgroundShelf(objectGraph, data, pageContext) {
+ var _a, _b, _c;
+ const shelf = new models.Shelf("mediumAdLockupWithScreenshotsBackground");
+ shelf.isHorizontal = false;
+ const offerEnvironment = "dark";
+ const offerStyle = "white";
+ const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, asString(data.attributes.name), {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ targetType: "card",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ });
+ metricsOptions.kind = "adItem";
+ // Set up iAdInfo
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, data);
+ const lockupOptions = {
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ disableFastImpressionsForAds: true,
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "mediumAdLockupWithScreenshotsBackground"),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mediumAdLockupWithScreenshotsBackground"),
+ };
+ const videoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ };
+ let lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ const platformScreenshots = lockup.screenshots[0];
+ const templateString = adLockups.getTemplateTypeForMediumAdFromLockupWithScreenshots(platformScreenshots);
+ pageContext.metricsPageInformation.iAdInfo.setTemplateType(templateString);
+ const iconData = iconFromData(objectGraph, data, {
+ useCase: 0 /* ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ overrideTextColorKey: "textColor2",
+ });
+ // Update the lockup value after setting the template type so that the value gets added to the lockup.
+ lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.setTemplateType(templateString);
+ }
+ else {
+ (_b = lockup.searchAd) === null || _b === void 0 ? void 0 : _b.setTemplateType(templateString);
+ }
+ const backgroundColor = iconData.backgroundColor;
+ const secondaryTextColor = iconData.textColor;
+ const mediumAd = new MediumAdLockupWithScreenshotsBackground(lockup, [platformScreenshots], true, secondaryTextColor, backgroundColor, (_c = objectGraph.bag.todayAdMediumLockupScreenshotsRiverSpeed) !== null && _c !== void 0 ? _c : 8);
+ addImpressionsFieldsToAd(objectGraph, mediumAd, metricsOptions, metricsOptions.pageInformation.iAdInfo);
+ mediumAd.clickAction = lockups.actionFromData(objectGraph, data, metricsOptions, null);
+ shelf.items = [mediumAd];
+ return shelf;
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ */
+function createCondensedAdLockupWithIconBackgroundShelf(objectGraph, data, pageContext) {
+ var _a, _b, _c;
+ const shelf = new models.Shelf("condensedAdLockupWithIconBackground");
+ shelf.isHorizontal = false;
+ const offerEnvironment = "dark";
+ const offerStyle = "white";
+ const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, asString(data.attributes.name), {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ targetType: "card",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ });
+ metricsOptions.kind = "adItem";
+ // Set up iAdInfo
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, data);
+ const lockupOptions = {
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ disableFastImpressionsForAds: true,
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "condensedAdLockupWithIconBackground"),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "condensedAdLockupWithIconBackground"),
+ };
+ (_a = pageContext.metricsPageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType("APPLOCKUP");
+ const videoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ };
+ const lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType("APPLOCKUP");
+ }
+ else {
+ (_c = lockup.searchAd) === null || _c === void 0 ? void 0 : _c.setTemplateType("APPLOCKUP");
+ }
+ const condensedAd = new models.CondensedAdLockupWithIconBackground(lockup, lockup.icon);
+ addImpressionsFieldsToAd(objectGraph, condensedAd, metricsOptions, metricsOptions.pageInformation.iAdInfo);
+ shelf.items = [condensedAd];
+ return shelf;
+}
+// #endregion
+// #region Suggested Links/Actions Shelves
+/**
+ * Creates the suggested links shelf
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedLinkActionsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ const items = [];
+ const shelf = new models.Shelf("action");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ for (const [linkIndex, link] of linkData.entries()) {
+ const metricsBase = {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ };
+ const searchAdAction = trendingSearchLinkActionFromData(objectGraph, link, pageContext, shelfAttributes);
+ if (serverData.isNull(searchAdAction) || serverData.isNull(searchAdAction.action)) {
+ continue;
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAdAction.action, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: null,
+ title: searchAdAction.action.title,
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ items.push(searchAdAction);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ else if (objectGraph.client.isPhone || objectGraph.client.isPad) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: items.length >= 6 ? 2 : 1,
+ };
+ }
+ return shelf;
+}
+/**
+ * Creates the suggested searches shelf for the focus page.
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedSearchesShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a, _b, _c;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ if (isNothing(linkData)) {
+ return null;
+ }
+ const items = [];
+ const shelf = new models.Shelf("singleColumnList");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ for (const [linkIndex, link] of linkData.entries()) {
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ continue; // search term is required
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm;
+ const searchAction = createFocusPageSearchAction(objectGraph, displayTerm !== null && displayTerm !== void 0 ? displayTerm : "", searchTerm !== null && searchTerm !== void 0 ? searchTerm : "", undefined, pageContext.metricsLocationTracker, "suggested", undefined, /// MAINTAINER'S NOTE: In the future, we could use this to attribute the suggestion source to the search result fetch.
+ pageContext.metricsPageInformation, (_b = shelfAttributes.searchLandingItemDisplayStyle) !== null && _b !== void 0 ? _b : undefined);
+ if (isNothing(searchAction) || serverData.isNullOrEmpty(searchAction)) {
+ continue;
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAction, {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ kind: "link",
+ softwareType: null,
+ title: (_c = searchAction.title) !== null && _c !== void 0 ? _c : "",
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ items.push(searchAction);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ return shelf;
+}
+/**
+ * Creates the suggested links shelf
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedLinksShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ const links = [];
+ const shelf = new models.Shelf("searchLink");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfContext.metricsOptions);
+ for (const [linkIndex, link] of linkData.entries()) {
+ const metricsBase = {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ };
+ const searchLink = trendingSearchLinkFromData(objectGraph, link, pageContext, shelfAttributes);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchLink, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: null,
+ title: searchLink.clickAction.title,
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ if (serverData.isNullOrEmpty(searchLink)) {
+ continue;
+ }
+ links.push(searchLink);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(links)) {
+ return null;
+ }
+ shelf.items = links;
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ else if (objectGraph.client.isPhone) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: links.length >= 6 ? 2 : 1,
+ };
+ }
+ return shelf;
+}
+/**
+ * Creates a suggestion link action for the link data
+ * NOTE: This is legacy and the newer SLP protocol uses `trendingSearchLinkFromData`
+ * @param objectGraph The App Store dependency graph
+ * @param link The data for an individual suggestion link
+ * @param pageContext The page context for the search link
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns An action representing the suggestion link
+ */
+function trendingSearchLinkActionFromData(objectGraph, link, pageContext, shelfAttributes) {
+ var _a, _b;
+ /// We should always have search term, but not necessarily displayTerm.
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ return null;
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm;
+ /// MAINTAINER'S NOTE: In the future, we could use this to attribute the suggestion source to the search result fetch.
+ const searchAction = new models.SearchAction(displayTerm, searchTerm, null, "suggested", undefined, undefined);
+ searchAction.artwork = createArtworkForSearchAction((_b = shelfAttributes.searchLandingItemDisplayStyle) !== null && _b !== void 0 ? _b : undefined, objectGraph);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", pageContext.metricsLocationTracker);
+ return isSome(searchAction) ? new models.SearchAdAction(searchAction) : null;
+}
+/**
+ * Creates a search action
+ * @param objectGraph The App Store dependency graph
+ * @param searchTerm The term to search for
+ * @param entity The entity to scope the search to, e.g. apps, stories, arcade
+ * @param metricsLocationTracker The metrics location tracker
+ * @param origin The source the search was fired from
+ * @param isFocusPage Whether the origin is in context of the focus page
+ * @param displayStyle The style to display the action item
+ * @returns An action representing a search
+ */
+export function createFocusPageSearchAction(objectGraph, title, searchTerm, entity, metricsLocationTracker, origin, source, metricsPageInformation = undefined, displayStyle) {
+ if (serverData.isNullOrEmpty(searchTerm)) {
+ return null;
+ }
+ // For Search Focus Page, text uses primary color and icon uses secondary color (instead of tintColor).
+ const searchAction = new models.SearchAction(title, searchTerm, null, origin, entity !== null && entity !== void 0 ? entity : undefined, source, []);
+ searchAction.artwork = createArtworkForSearchAction(displayStyle, objectGraph);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", metricsLocationTracker, metricsPageInformation);
+ return searchAction;
+}
+function createArtworkForSearchAction(displayStyle, objectGraph) {
+ var _a;
+ if ((displayStyle === null || displayStyle === void 0 ? void 0 : displayStyle.iconKind) === models.SearchLandingPageShelfItemIconKind.Symbol && ((_a = displayStyle === null || displayStyle === void 0 ? void 0 : displayStyle.iconKind) === null || _a === void 0 ? void 0 : _a.length)) {
+ return createArtworkForResource(objectGraph, `systemimage://${displayStyle.iconSymbol}`);
+ }
+ else if (objectGraph.client.isPhone || objectGraph.client.isVision) {
+ return createArtworkForResource(objectGraph, "systemimage://magnifyingglass");
+ }
+ return undefined;
+}
+/**
+ * Creates a suggestion link action for the link data
+ * @param objectGraph The App Store dependency graph
+ * @param link The data for an individual suggestion link
+ * @param pageContext The page context for the search link
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns An action representing the suggestion link
+ */
+function trendingSearchLinkFromData(objectGraph, link, pageContext, shelfAttributes) {
+ var _a, _b, _c;
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ return null;
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm; /// we should always have search term, but not necessarily displayTerm
+ let searchAction;
+ if (objectGraph.client.isWeb) {
+ const intent = makeSearchResultsPageIntent({
+ ...getLocale(objectGraph),
+ origin: "suggested",
+ term: displayTerm,
+ platform: (_b = objectGraph.activeIntent) === null || _b === void 0 ? void 0 : _b.previewPlatform,
+ });
+ searchAction = unwrapOptional(actionFor(intent, objectGraph));
+ }
+ else {
+ searchAction = new models.SearchAction(displayTerm, displayTerm, null, "suggested");
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", pageContext.metricsLocationTracker);
+ }
+ const artwork = createArtworkForSearchAction((_c = shelfAttributes.searchLandingItemDisplayStyle) !== null && _c !== void 0 ? _c : undefined, objectGraph);
+ return new models.SearchLink(displayTerm, searchAction, artwork, null);
+}
+// #endregion
+// #region Discover Lockups Shelves
+/**
+ * Creates a shelf composed of lockups
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this lockups shelf
+ * @param pageContext The page context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @param shelfContext The context for the shelf
+ * @returns A lockups shelf if any lockups could be made
+ */
+function createLockupsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a, _b, _c, _d, _e;
+ const filterType = 80894 /* filtering.Filter.All */;
+ const items = [];
+ let hasAdLockup = false;
+ let shelfData = initialShelfContentsFromData(data);
+ // Set metadata
+ const shelf = new models.Shelf(shelfContext.shelfStyle);
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ if (objectGraph.client.isVision) {
+ shelf.shouldFilterApps = !mediaAttributes.attributeAsBooleanOrFalse(data, "doNotFilter");
+ }
+ else {
+ shelf.shouldFilterApps = false;
+ }
+ shelf.filteringExcludedItems = shelfContext.filteringExcludedItems;
+ // Stitch First Position Ad (only if lockup array is nonempty)
+ if (serverData.isDefinedNonNullNonEmpty(shelfData)) {
+ const adLockup = lockupFromAdStitcher(objectGraph, pageContext, shelfContext);
+ if (adLockup && adLockup instanceof models.Lockup) {
+ hasAdLockup = true;
+ items.push(adLockup);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ shelfData = shelfData.filter((shelfItem) => shelfItem.id !== adLockup.adamId); // Filter dupe
+ }
+ }
+ // Determines whether all apps are displayed on SLP suggested apps shelf.
+ const hasDisplayCount = isSome(shelfAttributes.displayCount);
+ // If on device personalization is available, we need to personalize the data items. This reorders the shelf.
+ if (serverData.isDefinedNonNullNonEmpty(shelfData)) {
+ shelfData = impressionDemotion.personalizeDataItems(shelfData, (_a = pageContext.recoImpressionData) !== null && _a !== void 0 ? _a : {}, (_c = (_b = shelfContext.metricsOptions) === null || _b === void 0 ? void 0 : _b.recoMetricsData) !== null && _c !== void 0 ? _c : {});
+ }
+ // Build Lockups
+ for (const lockupData of shelfData) {
+ // 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 (serverData.isNull(lockupData.attributes)) {
+ continue;
+ }
+ // Filter out unwanted content
+ if (filtering.shouldFilter(objectGraph, lockupData, filterType)) {
+ continue;
+ }
+ const lockup = lockupFromData(objectGraph, lockupData, pageContext, shelfContext);
+ if (lockup) {
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ }
+ if (hasDisplayCount) {
+ // number of apps displayed on SLP suggested shelf
+ const displayCount = shelfAttributes.displayCount;
+ shelf.items = items.slice(0, displayCount);
+ }
+ else {
+ shelf.items = items;
+ }
+ if (hasDisplayCount) {
+ const seeAllShelf = new models.Shelf(shelfContext.shelfStyle);
+ if (hasAdLockup) {
+ // Ad is the first item in array so we are dropping it here.
+ seeAllShelf.items = items.splice(1, items.length - 1);
+ }
+ else {
+ seeAllShelf.items = items;
+ }
+ // Setup Page
+ const seeAllPage = new models.GenericPage([seeAllShelf]);
+ seeAllPage.title = shelf.title;
+ // Setup action
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.pageUrl = shelfAttributes.seeAllLink;
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = seeAllPage;
+ // Connect action
+ shelf.seeAllAction = seeAllAction;
+ // Metrics
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, seeAllAction.pageUrl, {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ });
+ const seeAllPageInformation = metricsHelpersPage.pageInformationForRoom(objectGraph, data.id);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, seeAllPage, seeAllPageInformation);
+ }
+ // Honor A/B treatment for using horizontal shelf for suggested apps, which may have an optional rowsPerColumn defined.
+ // NOTE: Client will choose an appropriate `rowsPerColumn` at display time, if not provided by server.
+ if (((_d = shelfAttributes.displayStyle) === null || _d === void 0 ? void 0 : _d.layout) === "horizontal" /* models.GenericSearchPageShelfDisplayStyleLayout.Horizontal */) {
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = (_e = shelfAttributes.displayStyle) === null || _e === void 0 ? void 0 : _e.layoutSize;
+ }
+ return shelf;
+}
+/**
+ * Create a lockup for shelfContents to display within a grouping shelf
+ * @param objectGraph
+ * @param lockupData shelfContents to create lockup for.
+ * @param pageContext The page context for the lockup
+ * @param shelfContext The shelf context for the lockup
+ */
+function lockupFromData(objectGraph, lockupData, pageContext, shelfContext) {
+ if (serverData.isNullOrEmpty(lockupData)) {
+ return null;
+ }
+ if (shelfContext.shelfStyle !== "smallLockup") {
+ return null;
+ }
+ let offerStyle = null;
+ if (serverData.isDefinedNonNull(shelfContext.shelfBackground) &&
+ (shelfContext.shelfBackground.type === "color" || shelfContext.shelfBackground.type === "interactive")) {
+ offerStyle = "white";
+ }
+ // Create the lockup
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(lockupData),
+ isAdvert: adLockups.isAdvert(objectGraph, lockupData),
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfContext.shelfStyle),
+ offerStyle: offerStyle,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfContext.shelfStyle),
+ isContainedInPreorderExclusiveShelf: false,
+ shouldHideArcadeHeader: false,
+ };
+ const lockup = lockups.lockupFromData(objectGraph, lockupData, lockupOptions);
+ if (serverData.isNull(lockup) || !lockup.isValid()) {
+ return null;
+ }
+ return lockup;
+}
+/**
+ * Performs `lockupFromData`, but additional with Ad stitch related side-effects.
+ * @param objectGraph The AppStore dependency graph
+ * @param pageContext The page context for the ad lockup
+ * @param shelfContext The shelf context for the ad lockup
+ */
+function lockupFromAdStitcher(objectGraph, pageContext, shelfContext) {
+ const task = adStitch.consumeTask(pageContext.adStitcher, shelfContext.adPositionInfo);
+ if (serverData.isNull(task)) {
+ return null; // no task for position
+ }
+ // Try to create lockup
+ const lockupData = task.data;
+ try {
+ const lockup = lockupFromData(objectGraph, lockupData, pageContext, shelfContext);
+ if (serverData.isDefinedNonNull(lockup)) {
+ shelfContext.filteringExcludedItems = [lockupData.id];
+ }
+ else {
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, lockupData);
+ }
+ return lockup;
+ }
+ catch (error) {
+ adLogger(objectGraph, `Failed to create SLP ad lockup: ${error}`);
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, lockupData);
+ return null;
+ }
+}
+// #endregion
+// #region Brick Shelves
+function buildBrickShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ const items = [];
+ const shelf = new models.Shelf("brick");
+ shelf.isHorizontal = mediaAttributes.attributeAsString(data, "layoutDirection") === "Horizontal";
+ const shelfContents = mediaRelationships.relationshipCollection(data, "contents");
+ for (const itemData of shelfContents) {
+ const metricsOptions = {
+ ...shelfContext.metricsOptions,
+ targetType: "brickMedium",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ // Using the location tracker from the context will cause positions to be unintentionally incremented
+ // by the lockup builder. Since we're only extracting icons we won't use the lockup metrics, so we can
+ // pass in a new, unused, location tracker instead.
+ const lockupMetricsOptions = {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ const brick = buildBrick(objectGraph, itemData, CollectionShelfDisplayStyle.BrickMedium, metricsOptions, lockupMetricsOptions);
+ brick.clickAction = createPrimaryActionForComponentFromData(objectGraph, itemData, shelfContext);
+ if (!brick.isValid()) {
+ continue;
+ }
+ items.push(brick);
+ metricsHelpersLocation.nextPosition(shelfContext.metricsOptions.locationTracker);
+ }
+ shelf.title = shelfAttributes.title;
+ shelf.items = items;
+ return shelf;
+}
+export function createPrimaryActionForComponentFromData(objectGraph, data, shelfContext) {
+ const clickOptions = createBrickClickOptionsFromData(objectGraph, data, shelfContext);
+ const primaryAction = lockups.actionFromData(objectGraph, data, clickOptions, null);
+ return primaryAction;
+}
+function createBrickClickOptionsFromData(objectGraph, data, shelfContext) {
+ const clickOptions = {
+ ...shelfContext.metricsOptions,
+ id: data.id,
+ targetType: "brickMedium",
+ };
+ return clickOptions;
+}
+// #endregion
+// #region Shelf Data Extraction
+/**
+ * Gets the initial raw shelf contents from the MAPI data object
+ * @param mediaApiData The raw MAPI data
+ * @returns A collection of data objects representing a shelf's contents
+ */
+function initialShelfContentsFromData(mediaApiData) {
+ const shelfContents = mediaRelationships.relationship(mediaApiData, "contents");
+ return shelfContents === null || shelfContents === void 0 ? void 0 : shelfContents.data;
+}
+// #endregion
+// #region Context Generators
+/**
+ * Performs either `createMediumAdLockupWithScreenshotsBackgroundShelf` or `createCondensedAdLockupWithIconBackgroundShelf
+ * depending on the format defined in adDisplayStyle, but with Ad stitch related side-effects.
+ * @param objectGraph The AppStore dependency graph
+ * @param pageContext The page context for the ad lockup
+ * @param builtShelves The index of the current shelf
+ * @param adMeta The SearchLandingPageAdMeta which includes adDisplayStyle
+ */
+function shelfFromAdStitcher(objectGraph, pageContext, builtShelves, adMeta) {
+ var _a;
+ const task = adStitch.consumeTask(pageContext.adStitcher, {
+ shelfIdentifier: searchLandingPageAdShelfIdentifier,
+ slot: builtShelves,
+ });
+ if (serverData.isNull(task)) {
+ return null; // no task for position
+ }
+ const adData = task.data;
+ try {
+ switch ((_a = adMeta === null || adMeta === void 0 ? void 0 : adMeta.adDisplayStyle) === null || _a === void 0 ? void 0 : _a.format) {
+ case "medium":
+ return createMediumAdLockupWithScreenshotsBackgroundShelf(objectGraph, adData, pageContext);
+ case "condensed":
+ return createCondensedAdLockupWithIconBackgroundShelf(objectGraph, adData, pageContext);
+ default:
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, adData);
+ return null;
+ }
+ }
+ catch (error) {
+ adLogger(objectGraph, `Failed to create SLP ad shelf: ${error}`);
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, adData);
+ return null;
+ }
+}
+/**
+ * Generates a shelf context for a search landing page shelf
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf's data object
+ * @param shelfAttributes The shelf's attribtues
+ * @param pageContext The context for the page containing the shelf
+ * @param shelfContentKind The type of content the shelf contains
+ * @returns A shelf context for a search landing page shelf
+ */
+function searchLandingPageShelfContext(objectGraph, data, shelfAttributes, pageContext, shelfContentKind = null) {
+ const baseShelfContext = searchShelves.baseShelfContext(objectGraph, data, shelfAttributes, pageContext);
+ switch (shelfContentKind) {
+ case models.SearchLandingPageContentKind.Apps:
+ return {
+ ...baseShelfContext,
+ shelfStyle: "smallLockup",
+ adPositionInfo: searchLandingPagePositionInfo,
+ };
+ default:
+ return baseShelfContext;
+ }
+}
+// #endregion
+//# sourceMappingURL=search-landing-shelf-controller.js.map \ No newline at end of file