diff options
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search/content')
5 files changed, 1312 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js new file mode 100644 index 0000000..927de7c --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js @@ -0,0 +1,334 @@ +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 mediaRelationships from "../../../foundation/media/relationships"; +import { pageRouter } from "../../builders/routing"; +import { hrefToRoutableUrl } from "../../builders/url-mapping"; +import * as content from "../../content/content"; +import { addClickEventToAction, addClickEventToSeeAllAction } from "../../metrics/helpers/clicks"; +import { addImpressionFields } from "../../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../../metrics/helpers/location"; +import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../../metrics/helpers/page"; +import { createClickMetricsOptionsForChartOrCategory, createMetricsOptionsForChartOrCategory, } from "../../metrics/helpers/search/search-shelves"; +import { combinedRecoMetricsDataFromMetricsData } from "../../metrics/helpers/util"; +import * as onDevicePersonalization from "../../personalization/on-device-personalization"; +import * as searchShelves from "./search-shelves"; +import * as groupingShelfControllerCommon from "../../grouping/shelf-controllers/grouping-shelf-controller-common"; +/** + * Takes the raw page data and creates a charts and categories page + * @param objectGraph The App Store Object Graph + * @param resultData The data representing the charts and categories page + * @returns A charts and categories page + */ +export function searchChartsAndCategoriesPageFromData(objectGraph, resultData) { + var _a, _b; + /// Get the page's metadata + const pageTitle = mediaAttributes.attributeAsString(resultData, "title"); + const selectedTabId = serverData.asString(resultData, "meta.autoSelectedTabId"); + const sourceShelfCanonicalId = serverData.asString(resultData, "meta.sourceShelfCanonicalId"); + const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "SearchLanding", sourceShelfCanonicalId, resultData); + const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph); + pageInformation.recoMetricsData = combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData); + /// Generate a new SearchPageContext (mainly for making shelf contexts) + const searchPageContext = { + shelves: [], + metricsLocationTracker: metricsHelpersLocation.newLocationTracker(), + metricsPageInformation: pageInformation, + adStitcher: null, + adIncidentRecorder: null, + pageType: searchShelves.SearchPageType.ChartsAndCategories, + }; + /// Prep the shelves data, mappings, orderings, and page tabs list + const shelvesData = serverData.asArrayOrEmpty(resultData, "data"); + const shelvesMapping = {}; + const shelfOrdering = []; + const pageTabsCollector = []; + const pageTabsLocationTracker = metricsHelpersLocation.newLocationTracker(); + for (const shelfData of shelvesData) { + /// Generate the shelf attributes for the shelf + const shelfAttributes = searchShelves.shelfAttributesFromData(objectGraph, shelfData, models.SearchLandingPageContentKind.CategoriesAndCharts, models.SearchPageKind.CategoriesAndCharts); + /// Since the shelves act as their own page, we need to set a new location tracker for each shelf + const shelfPageContext = { + ...searchPageContext, + metricsLocationTracker: metricsHelpersLocation.newLocationTracker(), + }; + /// Generate the shelf context + const shelfContext = searchShelves.baseShelfContext(objectGraph, shelfData, shelfAttributes, shelfPageContext); + /// Push the shelf content location so each shelf has the correct parent and starting index + metricsHelpersLocation.pushContentLocation(objectGraph, shelfContext.metricsOptions, shelfAttributes.title); + /// Make the shelf + const shelf = createChartsCategoryShelf(objectGraph, shelfData, true, shelfAttributes, shelfPageContext, shelfContext); + metricsHelpersLocation.popLocation(shelfContext.metricsOptions.locationTracker); + if (isNothing(shelf)) { + continue; + } + /// Add the shelf to the shelves mapping for the shelf id + shelvesMapping[shelf.id] = shelf; + /// Add the shelf id to the shelf ordering (to maintain ordered shelves) + shelfOrdering.push(shelf.id); + /// Make a pageTab metadata model from the shelf and action + const pageTab = new models.PageTab(); + const action = new models.PageTabChangeAction(shelfData.id, shelfAttributes.title); + /// Generate the click actions + const pageTabClickActionOptions = { + id: shelfAttributes.title, + canonicalId: (_a = serverData.asString(shelfData.meta, "canonicalId")) !== null && _a !== void 0 ? _a : undefined, + actionType: "navigate", + targetType: "button", + pageInformation: searchPageContext.metricsPageInformation, + locationTracker: pageTabsLocationTracker, + }; + addClickEventToAction(objectGraph, action, pageTabClickActionOptions); + pageTab.action = action; + pageTab.id = shelf.id; + pageTab.title = `${(_b = shelf.title) !== null && _b !== void 0 ? _b : ""}`; /// We need a deep copy of the string as we will remove the reference possibly later. + pageTabsCollector.push(pageTab); + metricsHelpersLocation.nextPosition(pageTabsLocationTracker); + } + /// Return an empty page if there are no real shelves to show + if (!serverData.isDefinedNonNullNonEmpty(shelfOrdering)) { + return new models.SearchChartsAndCategoriesPage(); + } + /// Create the page tabs + const pageTabs = new models.PageTabs(); + /// The id needs to be static but it isn't tied to the payload + pageTabs.id = objectGraph.random.nextUUID(); + /// Add the pageTabs as its own shelf so it can be independent of the chart and category shelves + /// as well as to control the segmented control for swapping between category shelf orderings (e.g. category containers) + const pageTabsShelf = new models.Shelf("pageTabs"); + pageTabsShelf.items = [pageTabs]; + shelvesMapping[pageTabs.id] = pageTabsShelf; + /// Make the page and add the shelf mappings (which includes our pageTabs shelf) + const page = new models.SearchChartsAndCategoriesPage(); + page.shelfMapping = shelvesMapping; + const shelfOrderings = {}; + /** Normally, we would have the shelf orderings be based on some identifier in the payload on a container which contains + an array of shelves. E.g. + { + Container { + containerId: string + shelves: [ + { + shelfId1: string, + ... + }, + ... + ] + } + } + shelf orderings = { containerId: [shelfId1, ...]} + + However, the current protocol has no container, just a loose array of shelves. + { + shelves: [ + { + shelfId1: string, + ... + }, + ... + ] + } + + Therefore, we have to fake our ordering by having the ordering be the shelves' own identifiers. + + shelf orderings = { shelfId1: [shelfId1], shelfId2: [shelfId2], ...} + + We also will append the pageTabs shelf if we have multiple shelves representing multiple tabs on the page. + */ + for (const shelfOrderingElement of shelfOrdering) { + if (pageTabsCollector.length > 1) { + shelfOrderings[shelfOrderingElement] = [pageTabs.id, shelfOrderingElement]; + } + else { + shelfOrderings[shelfOrderingElement] = [shelfOrderingElement]; + } + } + for (const shelf of Object.values(shelvesMapping)) { + shelf.title = undefined; + } + /// Set the data on the page + page.title = pageTitle; + page.pageTabs = pageTabs; + page.columnCount = 2; + page.shelfOrderings = shelfOrderings; + /// If we don't have a reco selected default tab, just default to the first one + page.defaultShelfOrdering = shelfOrdering.includes(selectedTabId) ? selectedTabId : shelfOrdering[0]; + /// Set the data on the page tabs + pageTabs.tabs = pageTabsCollector; + pageTabs.selectedTabId = page.defaultShelfOrdering; + /// Add the page metrics + addMetricsEventsToPageWithInformation(objectGraph, page, searchPageContext.metricsPageInformation); + return page; +} +/** + * Creates a charts and categories shelf from the shelf data + * @param objectGraph The App Store Object Graph + * @param data The shelf data object + * @param isForSeeAllPage Whether or not this shelf will be displayed on the see-all page + * @param shelfAttributes The shelf's attributes + * @param searchPageContext The context for the page this shelf belongs to + * @param searchShelfContext The shelf's context + * @returns A charts and categories shelf + */ +export function createChartsCategoryShelf(objectGraph, data, isForSeeAllPage, shelfAttributes, searchPageContext, searchShelfContext) { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; + const items = []; + const chartsAndCategoriesData = mediaRelationships.relationshipCollection(data, "contents"); + const shelf = new models.Shelf("searchChartsAndCategories"); + shelf.isHorizontal = false; + shelf.id = data.id; + shelf.title = shelfAttributes.title; + shelf.presentationHints = { isWidthConstrained: true }; + if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) { + shelf.contentsMetadata = { + type: "searchLandingChartsAndCategoriesSection", + numberOfColumns: shelfAttributes.displayStyle.layoutSize, + }; + } + if (shelfAttributes.hasSeeAll) { + const action = new models.FlowAction("searchChartsAndCategories"); + action.pageUrl = shelfAttributes.seeAllLink; + action.title = objectGraph.loc.string("ACTION_SEE_ALL"); + const seeAllMetricsOptions = { + ...searchShelfContext.metricsOptions, + targetType: "button", + }; + addClickEventToSeeAllAction(objectGraph, action, action.pageUrl, seeAllMetricsOptions); + shelf.seeAllAction = action; + } + addImpressionFields(objectGraph, shelf, searchShelfContext.metricsOptions); + const brickUseCase = isForSeeAllPage + ? content.SearchChartOrCategoryBrickUseCase.seeAllPage + : content.SearchChartOrCategoryBrickUseCase.other; + for (const chartOrCategory of chartsAndCategoriesData) { + let name = null; + let badge = null; + if (chartOrCategory.type === "tags") { + const brickName = (_b = mediaAttributes.attributeAsString(chartOrCategory, "name")) !== null && _b !== void 0 ? _b : "tagbrick"; + name = brickName; + } + else { + const editorialNotes = mediaAttributes.attributeAsDictionary(chartOrCategory, "editorialNotes"); + if (serverData.isDefinedNonNullNonEmpty(editorialNotes)) { + name = serverData.asString(editorialNotes, "name"); + badge = serverData.asString(editorialNotes, "badge"); + } + } + const artwork = content.searchChartOrCategoryArtworkFromData(objectGraph, chartOrCategory, brickUseCase, (_c = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _c === void 0 ? void 0 : _c.layoutDensity); + const kind = mediaAttributes.attributeAsString(chartOrCategory, "kind"); + const linkData = mediaAttributes.attributeAsDictionary(chartOrCategory, "link"); + const linkUrl = serverData.asString(linkData, "url"); + const dataCollection = mediaRelationships.relationshipCollection(chartOrCategory, "primary-content"); + let isTitleRequired = true; + let action = null; + if (chartOrCategory.type === "tags") { + const href = serverData.asString(chartOrCategory, "href"); + const url = hrefToRoutableUrl(objectGraph, href); + const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url); + const flowAction = new models.FlowAction(flowPage); + flowAction.pageUrl = url; + flowAction.title = name; + action = flowAction; + } + else if (isSome(linkUrl)) { + switch (kind) { + case "CategoryChart": + const topChartAction = new models.FlowAction("topCharts"); + topChartAction.pageUrl = linkUrl; + topChartAction.title = name; + action = topChartAction; + break; + case "External": + // For external links, we don't require a title we can just show an image + isTitleRequired = false; + const target = serverData.asString(linkData, "target"); + if (target === "external") { + action = new models.ExternalUrlAction(linkUrl); + action.title = name !== null && name !== void 0 ? name : ""; + } + else { + const flowPage = objectGraph.required(pageRouter).fetchFlowPage(linkUrl); + const linkAction = new models.FlowAction(flowPage); + linkAction.pageUrl = linkUrl; + linkAction.title = name !== null && name !== void 0 ? name : ""; + action = linkAction; + } + break; + default: + break; + } + } + else if (serverData.isDefinedNonNullNonEmpty(dataCollection)) { + const chartMetadata = dataCollection[0]; + const chartHref = chartMetadata.href; + const url = hrefToRoutableUrl(objectGraph, chartHref); + if ((url === null || url === void 0 ? void 0 : url.length) > 0) { + const flowAction = new models.FlowAction("page"); + flowAction.pageUrl = url; + flowAction.title = name; + action = flowAction; + } + else { + continue; + } + } + else { + continue; + } + const chartClickOptions = createClickMetricsOptionsForChartOrCategory(objectGraph, (_d = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _d === void 0 ? void 0 : _d.layoutDensity, chartOrCategory, searchShelfContext); + addClickEventToAction(objectGraph, action, chartClickOptions); + if (isTitleRequired && serverData.isNullOrEmpty(name)) { + continue; + } + let chartOrCategoryModel = new models.SearchChartOrCategory(name, artwork, null, null, badge, action, (_e = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _e === void 0 ? void 0 : _e.layoutDensity, artworkSafeArea((_f = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _f === void 0 ? void 0 : _f.layoutDensity), textSafeArea((_g = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _g === void 0 ? void 0 : _g.layoutDensity)); + let chartModelMetricsOptions = createMetricsOptionsForChartOrCategory(objectGraph, chartOrCategoryModel, chartOrCategory, searchShelfContext); + const artworkOptions = { + useCase: 18 /* content.ArtworkUseCase.GroupingBrick */, + }; + const collectionIcons = groupingShelfControllerCommon.artworkForTags(objectGraph, chartOrCategory, 1060, 520, artworkOptions, chartModelMetricsOptions); + if (isSome(collectionIcons) && collectionIcons.length > 0) { + const collectionIconBackgroundColor = collectionIcons[0].backgroundColor; + if (isSome(collectionIconBackgroundColor) && (collectionIconBackgroundColor === null || collectionIconBackgroundColor === void 0 ? void 0 : collectionIconBackgroundColor.type) === "rgb") { + chartOrCategoryModel = new models.SearchChartOrCategory(name, null, collectionIcons, (_h = content.closestTagBackgroundColorForIcon(collectionIconBackgroundColor)) !== null && _h !== void 0 ? _h : undefined, badge, action, (_j = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _j === void 0 ? void 0 : _j.layoutDensity, artworkSafeArea((_k = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _k === void 0 ? void 0 : _k.layoutDensity), textSafeArea((_l = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _l === void 0 ? void 0 : _l.layoutDensity)); + chartModelMetricsOptions = createMetricsOptionsForChartOrCategory(objectGraph, chartOrCategoryModel, chartOrCategory, searchShelfContext); + } + } + addImpressionFields(objectGraph, chartOrCategoryModel, chartModelMetricsOptions); + items.push(chartOrCategoryModel); + metricsHelpersLocation.nextPosition(searchShelfContext.metricsOptions.locationTracker); + } + if (serverData.isNullOrEmpty(items)) { + return null; + } + shelf.items = items; + return shelf; +} +function artworkSafeArea(density) { + switch (density) { + // Tile + case models.GenericSearchPageShelfDisplayStyleDensity.Density1: + return models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea; + // Pill + case models.GenericSearchPageShelfDisplayStyleDensity.Density2: + return models.ChartOrCategorySafeArea.defaultPillArtworkSafeArea; + // Round + case models.GenericSearchPageShelfDisplayStyleDensity.Density3: + return undefined; + default: + return undefined; + } +} +function textSafeArea(density) { + switch (density) { + // Tile + case models.GenericSearchPageShelfDisplayStyleDensity.Density1: + return models.ChartOrCategorySafeArea.defaultTileTextSafeArea; + // Pill + case models.GenericSearchPageShelfDisplayStyleDensity.Density2: + return models.ChartOrCategorySafeArea.defaultPillTextSafeArea; + default: + return undefined; + } +} +//# sourceMappingURL=search-categories.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js new file mode 100644 index 0000000..59b2fe5 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js @@ -0,0 +1,31 @@ +/** + * Common functions for search results content. + */ +import { isDefinedNonNullNonEmpty } from "../../../foundation/json-parsing/server-data"; +import { attributeAsBooleanOrFalse, attributeAsString } from "../../../foundation/media/attributes"; +// region Editorial Content +/** + * Returns the headline / tagline for Editorial Search Results + * @param resultData Data to determine tagline for. + */ +export function editorialSearchResultTagline(objectGraph, resultData) { + // Flag to disable showing specific headings, e.g. "App of the Day" that may be unnatural in search result + const showLabelInSearch = attributeAsBooleanOrFalse(resultData, "showLabelInSearch"); + if (!showLabelInSearch) { + return null; + } + // <rdar://problem/40468686> LOC: AOTD & GOTD badges in Search result for the Editorial Item Card AOTD & GOTD stories do not show up correctly for JA-JP and TH-TH + // Always use alternative label, if one is provided. + const alternateLabel = attributeAsString(resultData, "alternateLabel"); + if (isDefinedNonNullNonEmpty(alternateLabel)) { + return alternateLabel; // No newline flattening needed for alternativeLabel + } + // Otherwise, fallback to franchise label, if any. + const label = attributeAsString(resultData, "label"); + if (isDefinedNonNullNonEmpty(label)) { + return label.replace(/\n/g, " "); + } + return null; +} +// endregion +//# sourceMappingURL=search-content-common.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js new file mode 100644 index 0000000..c5c85e7 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js @@ -0,0 +1,171 @@ +import { TodayCardDisplayStyle } from "./../../today/today-types"; +/** + * Builder for SearchLockupCollection. + */ +import { SearchLockupCollection } from "../../../api/models"; +import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../../foundation/json-parsing/server-data"; +import { attributeAsArrayOrEmpty, attributeAsString } from "../../../foundation/media/attributes"; +import { relationshipCollection } from "../../../foundation/media/relationships"; +import { createArtworkForResource } from "../../content/artwork/artwork"; +import { notesFromData } from "../../content/content"; +import { currentEditorialCollectionTreatment, } from "../../../foundation/experimentation/search-results-experiments"; +import { actionFromData, lockupsFromData } from "../../lockups/lockups"; +import { addImpressionFields, impressionOptions } from "../../metrics/helpers/impressions"; +import { popLocation, pushContentLocation } from "../../metrics/helpers/location"; +import { cardDisplayStyleFromData } from "../../today/today-card-util"; +import { editorialSearchResultTagline } from "./search-content-common"; +/** + * Whether or not a given result data should be rendered as `SearchLockupCollection` model. + * Semantically, every `collection` should be rendered as a lockup collection, but collections are currently backed by many different types of articles. + * @param resultData Data from results endpoint. + * @param searchResponseMetadata Meta blob from initial search request. + */ +export function resultDataShouldRenderLockupCollection(objectGraph, resultData, searchResponseMetadata) { + // Filter supported platforms + if (!(objectGraph.host.isiOS || objectGraph.host.isVision)) { + return false; + } + if (currentEditorialCollectionTreatment(objectGraph, searchResponseMetadata) !== + 1 /* EditorialCollectionExperimentType.Swoosh */) { + return false; + } + switch (resultData.type) { + case "groupings": + // Groupings can only be rendered if `contentIds` is present + const collectionAdamIds = attributeAsArrayOrEmpty(resultData, "contentIds"); + return isDefinedNonNullNonEmpty(collectionAdamIds); + case "rooms": + case "multirooms": + // Rooms, and multirooms always render as collection. + return true; + case "editorial-items": + // Some subtypes of editorial items render as collection. + const cardDisplayStyle = cardDisplayStyleFromData(resultData); + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.List: + case TodayCardDisplayStyle.NumberedList: + case TodayCardDisplayStyle.Grid: + case TodayCardDisplayStyle.River: + return true; + default: + return false; + } + default: + return false; + } +} +/** + * Create a `SearchLockupCollection` from search results. + * @param resultData Data from results endpoint. + * @param lockupOptions Options for lockups shared throughout search results page. + * @param metricsOptions Metrics context. + */ +export function lockupCollectionFromResultData(objectGraph, resultData, metricsOptions) { + // Text + const heading = lockupCollectionHeadingFromResultData(objectGraph, resultData); + const headingArtwork = lockupCollectionHeadingArtworkFromResultData(objectGraph, resultData); + const title = lockupCollectionTitleFromResultData(objectGraph, resultData); + // Metrics for impression + location + const lockupCollectionMetrics = impressionOptions(objectGraph, resultData, title, { + targetType: "card", + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + }); + pushContentLocation(objectGraph, lockupCollectionMetrics, title); + // Click Action ("See All") + const clickMetrics = { + actionType: "navigate", + targetType: "button", + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + id: "See All", + idType: "sequential", + }; + const seeAllAction = actionFromData(objectGraph, resultData, clickMetrics, objectGraph.host.clientIdentifier); + seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL"); + // Lockups + const listOptions = { + lockupOptions: { + metricsOptions: { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + targetType: "lockup", + }, + skipDefaultClickAction: objectGraph.client.isVision, + artworkUseCase: 8 /* ArtworkUseCase.SearchIcon */, + hideCompatibilityBadge: objectGraph.client.isVision, + }, + filter: 128 /* Filter.UnsupportedPlatform */, + }; + let lockupData = relationshipCollection(resultData, "card-contents"); + if (isNullOrEmpty(lockupData)) { + lockupData = relationshipCollection(resultData, "top-apps"); + } + const items = lockupsFromData(objectGraph, lockupData, listOptions); + popLocation(metricsOptions.locationTracker); + // Lockup Collection + const lockupCollection = new SearchLockupCollection(heading, title, items, seeAllAction, headingArtwork); + addImpressionFields(objectGraph, lockupCollection, lockupCollectionMetrics); + if (isNullOrEmpty(items)) { + return null; + } + return lockupCollection; +} +// endregion +// region Heading +/** + * Returns the title for given editorial results search results data. + * @param resultData The result data to get a title for. + */ +function lockupCollectionTitleFromResultData(objectGraph, resultData) { + const resultType = resultData.type; + switch (resultType) { + case "developers": + return attributeAsString(resultData, "name"); + default: + return notesFromData(objectGraph, resultData, "name"); + } +} +/** + * Returns the heading for given editorial results search results data. + * This builds on `editorialSearchResultTagline` with some additional search-specific logic + * @param resultData The result data to get a heading for. + */ +function lockupCollectionHeadingFromResultData(objectGraph, resultData) { + const resultType = resultData.type; + if (resultType === "developers") { + return objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_DEVELOPER_TITLE_CASE"); + } + const editorialTagline = editorialSearchResultTagline(objectGraph, resultData); + if (isDefinedNonNullNonEmpty(editorialTagline)) { + return editorialTagline; + } + if (objectGraph.client.isVision) { + return objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_COLLECTION_TITLE_CASE"); + } + else { + return objectGraph.loc.string("Search.EditorialSearchResultType.Heading.Collection"); + } +} +/** + * Returns the heading artwork to use for a given editorial search results data. + * @param resultData The result data to get heading artwork for. + */ +function lockupCollectionHeadingArtworkFromResultData(objectGraph, resultData) { + if (!objectGraph.client.isVision) { + return null; + } + const resultType = resultData.type; + let imageName; + switch (resultType) { + case "developers": + imageName = "person.crop.square"; + break; + default: + imageName = "appstore"; + break; + } + return createArtworkForResource(objectGraph, `systemimage://${imageName}`); +} +// endregion +//# sourceMappingURL=search-lockup-collection.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js new file mode 100644 index 0000000..5f363b3 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js @@ -0,0 +1,723 @@ +/** + * A builder for all SearchResultsContainers variants except for `SearchLockupCollection`. + * To be cleaned in the future. + */ +import * as validation from "@jet/environment/json/validation"; +import { isSome } from "@jet/environment/types/optional"; +import * as models from "../../../api/models"; +import { EditorialSearchResult } from "../../../api/models"; +import * as serverData from "../../../foundation/json-parsing/server-data"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import * as mediaRelationship from "../../../foundation/media/relationships"; +import * as errors from "../../../foundation/util/errors"; +import * as client from "../../../foundation/wrappers/client"; +import * as appEvents from "../../app-promotions/app-event"; +import * as appPromotionsCommon from "../../app-promotions/app-promotions-common"; +import { createArtworkForResource } from "../../content/artwork/artwork"; +import * as content from "../../content/content"; +import { editorialDisplayOptionsFromClientParams, extractEditorialClientParams, } from "../../editorial-pages/editorial-data-util"; +import * as filtering from "../../filtering"; +import * as lockups from "../../lockups/lockups"; +import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; +import * as metricsHelpersUtil from "../../metrics/helpers/util"; +import * as onDevicePersonalization from "../../personalization/on-device-personalization"; +import { defaultTodayCardConfiguration, todayCardFromData } from "../../today/today-card-util"; +import { TodayCardDisplayStyle, TodayParseContext } from "../../today/today-types"; +import * as common from "./search-content-common"; +import * as searchLockupCollection from "./search-lockup-collection"; +export function searchResultFromData(objectGraph, resultData, searchResponseMetadata, personalizationDataContainer, metricsOptions, isNetworkConstrained, searchEntity, searchExperimentsData) { + return validation.context("searchResultFromData", () => { + let searchResult = null; + const resultType = resultData.type; + const standardLockupOptions = { + metricsOptions: { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + targetType: "card", + createUniqueImpressionId: true, + }, + hideZeroRatings: true, + artworkUseCase: 8 /* content.ArtworkUseCase.SearchIcon */, + isNetworkConstrained: isNetworkConstrained, + canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mixedMediaLockup"), + clientIdentifierOverride: clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity), + isMultilineTertiaryTitleAllowed: false, + }; + const condensedBehavior = condensedBehaviorForData(objectGraph, resultData, searchExperimentsData); + switch (resultType) { + case "rooms": + case "multirooms": + case "developers": + case "editorial-items": + case "groupings": + if (resultType !== "editorial-items" && resultType !== "developers" && objectGraph.client.isVision) { + return null; + } + const todayCardConfig = defaultTodayCardConfiguration(objectGraph); + todayCardConfig.isSearchContext = true; + const todayCard = todayCardFromData(objectGraph, resultData, todayCardConfig, new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker)); + if (todayCard && todayCard.media && todayCard.media.kind === "inAppPurchase") { + if (objectGraph.client.isVision) { + return null; + } + const iapMedia = todayCard.media; + const todayCardInAppPurchaseLockup = iapMedia.lockup; + todayCardInAppPurchaseLockup.theme = "dark"; + searchResult = new models.InAppPurchaseSearchResult(todayCardInAppPurchaseLockup); + } + else if (searchLockupCollection.resultDataShouldRenderLockupCollection(objectGraph, resultData, searchResponseMetadata)) { + const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions); + if (lockupCollection) { + searchResult = lockupCollection; + } + } + else { + const editorialSearchResult = editorialSearchResultFromData(objectGraph, resultData, standardLockupOptions.metricsOptions, condensedBehavior); + if (editorialSearchResult) { + if (editorialSearchResult.title) { + editorialSearchResult.title = editorialSearchResult.title.replace(/\n/g, " "); + } + if (editorialSearchResult instanceof EditorialSearchResult && editorialSearchResult.subtitle) { + editorialSearchResult.subtitle = editorialSearchResult.subtitle.replace(/\n/g, " "); + } + searchResult = editorialSearchResult; + } + } + break; + case "in-apps": + if (objectGraph.client.isVision || objectGraph.client.isWeb) { + return null; + } + // Ensure the parent app data is available before proceeding with building the lockup. + const parentData = lockups.parentDataFromInAppData(objectGraph, resultData); + if (serverData.isNullOrEmpty(parentData)) { + return null; + } + const inAppPurchaseLockup = lockups.inAppPurchaseLockupFromData(objectGraph, resultData, standardLockupOptions); + inAppPurchaseLockup.theme = "dark"; + modifyMetadataBadgeForSearchExperiment(objectGraph, inAppPurchaseLockup, searchExperimentsData); + searchResult = new models.InAppPurchaseSearchResult(inAppPurchaseLockup); + break; + case "apps": + case "app-bundles": + default: + // There should never be an iad in the non-ad search results, so remove it here + // before creating the lockups. + delete resultData.attributes["iad"]; + if (resultType === "app-bundles") { + if (objectGraph.client.isVision) { + return null; + } + standardLockupOptions.shouldIncludeScreenshotsForChildren = + objectGraph.featureFlags.isEnabled("voyager_bundles_2025A"); + const bundleLockup = lockups.lockupFromData(objectGraph, resultData, standardLockupOptions); + bundleLockup.showMetadataInformationInLockup = true; + modifyMetadataBadgeForSearchExperiment(objectGraph, bundleLockup, searchExperimentsData); + searchResult = new models.BundleSearchResult(bundleLockup); + } + else { + const lockup = lockups.mixedMediaLockupFromData(objectGraph, resultData, standardLockupOptions, { + canPlayFullScreen: false, + playbackControls: {}, + }, searchExperimentsData); + modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentsData); + // Extract out any associated app event from meta + const appEventSearchResult = appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, metricsOptions); + if (serverData.isDefinedNonNull(appEventSearchResult)) { + searchResult = appEventSearchResult; + searchResult.condensedBehavior = "never"; + } + else { + // If no app event in meta, fallback to a regular mixed media lockup + searchResult = new models.AppSearchResult(lockup); + } + } + break; + } + if (serverData.isDefinedNonNull(searchResult) && serverData.isNull(searchResult.condensedBehavior)) { + searchResult.condensedBehavior = condensedBehavior; + } + return searchResult; + }); +} +/** + * Indicates whether a search result will display an app event + * @param objectGraph The object graph + * @param searchResultData The data for the search result + * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user + * @param appStates A mapping of adamIDs to their respective app states that is used to determine if the app has been installed by the user in the past + * @param metricsOptions Metrics options for built lockups + * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events + * @returns a boolean result + */ +export function searchResultWillUseAppEventDisplay(objectGraph, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer) { + const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData); + if (!appEventEligibleToDisplay) { + return false; + } + const { dataItems } = selectedAppEventDataItems(objectGraph, searchResultData, personalizationDataContainer); + let appEvent; + for (const appEventDataItem of dataItems) { + appEvent = sanitizedAppEvent(objectGraph, appEventDataItem, searchResultData, metricsOptions, undefined, undefined); + if (serverData.isDefinedNonNull(appEvent)) { + break; + } + } + const appIsInstalled = serverData.isDefinedNonNull(installStates) && serverData.isDefinedNonNull(installStates[searchResultData.id]) + ? installStates[searchResultData.id] + : false; + const appHasBeenInstalled = serverData.isDefinedNonNull(appStates) && serverData.isDefinedNonNull(appStates[searchResultData.id]) + ? ["downloadable"].includes(appStates[searchResultData.id]) + : false; + // App Events are only displayed in native when the user has installed the app previously and there is a valid app event + return (appIsInstalled || appHasBeenInstalled) && serverData.isDefinedNonNull(appEvent); +} +/** + * Derive the condensed behavior from a search result + * @param objectGraph The global object graph instance + * @param resultData The data blob for this specific search result + * @param pageSearchExperimentsData The page level search experiments data object + */ +function condensedBehaviorForData(objectGraph, resultData, pageSearchExperimentsData) { + var _a, _b; + /// Guard early against incompatible client devices + if (!canHaveCondensedBehaviorForClient(objectGraph)) { + return "never"; + } + const itemSearchExperimentData = resultData.meta; + const itemCondensedStyle = (_a = itemSearchExperimentData === null || itemSearchExperimentData === void 0 ? void 0 : itemSearchExperimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.condensed; + if (serverData.isDefinedNonNull(itemCondensedStyle)) { + return condensedBehaviorFromStyle(objectGraph, itemCondensedStyle); + } + const pageCondensedStyle = (_b = pageSearchExperimentsData === null || pageSearchExperimentsData === void 0 ? void 0 : pageSearchExperimentsData.displayStyle) === null || _b === void 0 ? void 0 : _b.condensed; + if (serverData.isDefinedNonNull(pageCondensedStyle)) { + return condensedBehaviorFromStyle(objectGraph, pageCondensedStyle); + } + return defaultCondensedBehaviorForClient(objectGraph); +} +/** + * Gets the condensed behavior from a given condensed style, or a default behavior based on the client type + * @param objectGraph The global object graph instance + * @param condensedStyle The condensed style on the data model + * @returns The condensed behavior for the native search result view + */ +function condensedBehaviorFromStyle(objectGraph, condensedStyle) { + switch (condensedStyle) { + case "always": + return "always"; + case "never": + return "never"; + case "when-installed": + return "whenInstalled"; + default: + return defaultCondensedBehaviorForClient(objectGraph); + } +} +/** + * Determines the default condensed behavior based for a given client type + * @param objectGraph The global object graph instance + * @param condensedStyle The condensed style on the data model + * @returns The condensed behavior for the native search result view + */ +function defaultCondensedBehaviorForClient(objectGraph) { + switch (objectGraph.client.deviceType) { + case "phone": + return "whenInstalled"; + default: + return "never"; + } +} +/** + * Determines if the current client type supports condensed behavior + * @param objectGraph The global object graph instance + * @returns Whether condensed behavior is allowed + */ +function canHaveCondensedBehaviorForClient(objectGraph) { + switch (objectGraph.client.deviceType) { + case "phone": + return true; + default: + return false; + } +} +/** + * Modifies the editor's choice badge based on whether the search experiment overrides the metadataPrecendence + * for the badge + * @param objectGraph App Store ObjectGraph + * @param lockup The lockup we need to modify for the experiment + * @param searchExperimentData The experiment data to pull the metadata info from + */ +function modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentData) { + const shouldShowEditorsChoice = metadataPrecedenceTypePreceedsType(objectGraph, searchExperimentData, "editorialBadgeInfo", "userRating"); + if (serverData.isDefinedNonNull(shouldShowEditorsChoice)) { + lockup.isEditorsChoice = lockup.isEditorsChoice && shouldShowEditorsChoice; + } +} +/** + * Determines whether the first metadata type should precede the second type for the given experiment data + * @param objectGraph App Store ObjectGraph + * @param experimentData The experiment data to pull the metadata info from + * @param firstType Does this precede the second type in the experiment order + * @param secondType Does this succeed the second type in the experiment order + * @returns whether the first type precedes the second type + */ +function metadataPrecedenceTypePreceedsType(objectGraph, experimentData, firstType, secondType) { + var _a; + if (serverData.isNull(experimentData) || !objectGraph.client.isPhone) { + return null; + } + const order = (_a = experimentData === null || experimentData === void 0 ? void 0 : experimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.metadataPrecedenceOrder; + if (!serverData.isDefinedNonNullNonEmpty(order)) { + return null; + } + const firstIndex = order.indexOf(firstType); + const secondIndex = order.indexOf(secondType); + if (firstIndex === -1 && secondIndex === -1) { + return null; + } + if (firstIndex === -1) { + return false; + } + if (secondIndex === -1) { + return true; + } + return firstIndex < secondIndex; +} +function editorialSearchResultFromData(objectGraph, resultData, metricsOptions, resultCondensedBehavior) { + return validation.context("editorialSearchResultFromData", () => { + let searchResult; + const title = mediaAttributes.attributeAsString(resultData, "name"); + const resultType = resultData.type; + switch (resultType) { + case "groupings": { + const editorialSearchResult = new models.EditorialSearchResult(title); + const collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds"); + if (serverData.isDefinedNonNullNonEmpty(collectionAdamIds)) { + editorialSearchResult.collectionAdamIds = collectionAdamIds; + } + else { + const iconArtwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), { + useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, + allowingTransparency: true, + }); + editorialSearchResult.iconArtwork = iconArtwork; + } + editorialSearchResult.type = "category"; + searchResult = editorialSearchResult; + break; + } + case "rooms": + case "multirooms": { + const editorialSearchResult = new models.EditorialSearchResult(title); + editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), { + useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, + cropCode: "sr", + }); + editorialSearchResult.collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds"); + editorialSearchResult.type = "collection"; + searchResult = editorialSearchResult; + break; + } + case "editorial-items": { + if (objectGraph.bag.searchFilterEditorialItemIds.has(resultData.id)) { + return null; + } + // Bridge objects to Today builder. + const todayParseContext = new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker); + const shouldResultBeCondensed = resultCondensedBehavior === "always"; + const editorialSearchResult = editorialSearchResultFromTodayCardData(objectGraph, resultData, todayParseContext, shouldResultBeCondensed); + searchResult = editorialSearchResult; + break; + } + case "developers": { + const editorialSearchResult = new models.EditorialSearchResult(title); + editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "editorialArtwork.bannerUber"), { + useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, + cropCode: "sr", + }); + editorialSearchResult.type = "developer"; + if (isSome(editorialSearchResult.artwork)) { + searchResult = editorialSearchResult; + } + else { + let topApps = mediaRelationship.relationshipCollection(resultData, "top-apps"); + topApps = topApps.filter((topApp) => { + return !filtering.shouldFilter(objectGraph, topApp, 76532 /* filtering.Filter.DeveloperPage */); + }); + const topAppIds = []; + const topAppArtwork = []; + topApps.forEach((app) => { + topAppIds.push(app.id); + const appIcon = content.iconFromData(objectGraph, app, { + useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, + }); + if (serverData.isDefinedNonNull(appIcon)) { + topAppArtwork.push(appIcon); + } + }); + editorialSearchResult.collectionAdamIds = topAppIds; + editorialSearchResult.collectionAppIcons = topAppArtwork; + // On visionOS, the developer result falls back to a lockup collection if no art is available. + if (objectGraph.client.isVision) { + const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions); + searchResult = lockupCollection; + } + else { + searchResult = editorialSearchResult; + } + } + break; + } + default: + break; + } + if (serverData.isNull(searchResult)) { + return null; + } + if (searchResult instanceof EditorialSearchResult) { + if (searchResult.collectionAdamIds != null && searchResult.collectionAdamIds.length) { + const lockupCount = searchResult.collectionAdamIds.length; + if (lockupCount <= 5) { + searchResult.artworkGridType = "extraLarge"; + } + else if (lockupCount <= 8) { + searchResult.artworkGridType = "large"; + } + else if (lockupCount <= 16) { + searchResult.artworkGridType = "mixed"; + } + else { + searchResult.artworkGridType = "small"; + } + } + if (objectGraph.client.isVision) { + let badgeText; + let badgeArtwork = createArtworkForResource(objectGraph, "systemimage://appstore"); + switch (searchResult.type) { + case "developer": + badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_DEVELOPER_TITLE_CASE"); + badgeArtwork = createArtworkForResource(objectGraph, "systemimage://person.crop.square"); + break; + case "category": + badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_CATEGORY_TITLE_CASE"); + break; + case "collection": + badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_COLLECTION_TITLE_CASE"); + break; + case "story": + badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_STORY_TITLE_CASE"); + break; + default: + break; + } + searchResult.badgeText = badgeText; + searchResult.badgeArtwork = badgeArtwork; + } + } + const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, resultData, searchResult.title, metricsOptions); + searchResult.clickAction = lockups.actionFromData(objectGraph, resultData, impressionOptions, null); + metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions); + return searchResult; + }); +} +function editorialSearchResultFromTodayCardData(objectGraph, cardData, todayParseContext, shouldResultBeCondensed) { + // Card configuration to use for building TodayCards that will be converted into editorial search results. + const cardConfig = defaultTodayCardConfiguration(objectGraph); + cardConfig.isSearchContext = true; + if (!objectGraph.client.isVision) { + cardConfig.prevailingCropCodes = + shouldResultBeCondensed && objectGraph.client.isPhone + ? { defaultCrop: "DMGE.AppST01" } + : { defaultCrop: "fo" }; + } + // If we are on visionOS, drop any EIs that have a not-compatible app as the primary content. + if (objectGraph.client.isVision) { + const primaryContent = content.primaryContentForData(objectGraph, cardData); + if (isSome(primaryContent)) { + const runnableOnDevice = content.runnableOnDeviceWithData(objectGraph, primaryContent, objectGraph.client.deviceType, objectGraph.appleSilicon.isSupportEnabled); + if (!runnableOnDevice) { + return null; + } + } + } + const todayCard = todayCardFromData(objectGraph, cardData, cardConfig, todayParseContext); + if (!todayCard) { + return null; + } + const editorialSearchResult = new models.EditorialSearchResult(todayCard.title); + editorialSearchResult.type = "story"; + editorialSearchResult.clickAction = todayCard.clickAction; + let collectionLockups = null; + if (todayCard.media) { + switch (todayCard.media.kind) { + case "brandedSingleApp": + const mediaSingleApp = todayCard.media; + editorialSearchResult.artwork = mediaSingleApp.artworks[0]; + if (serverData.isNull(editorialSearchResult.artwork)) { + editorialSearchResult.iconArtwork = mediaSingleApp.icon; + } + const cardDisplayStyle = mediaAttributes.attributeAsString(cardData, "cardDisplayStyle"); + switch (cardDisplayStyle) { + case TodayCardDisplayStyle.AppOfTheDay: + case TodayCardDisplayStyle.GameOfTheDay: + const relatedContentData = mediaRelationship.relationshipData(objectGraph, cardData, "card-contents"); + if (relatedContentData) { + editorialSearchResult.title = + mediaAttributes.attributeAsString(relatedContentData, "name") || + editorialSearchResult.title; + } + break; + default: + break; + } + break; + case "list": + const mediaList = todayCard.media; + collectionLockups = mediaList.lockups; + break; + case "river": + const mediaRiver = todayCard.media; + collectionLockups = mediaRiver.lockups; + break; + case "artwork": + const mediaArtwork = todayCard.media; + editorialSearchResult.artwork = mediaArtwork.artworks[0]; + break; + case "grid": + const mediaGrid = todayCard.media; + collectionLockups = mediaGrid.lockups; + break; + case "multiApp": + const multiApp = todayCard.media; + collectionLockups = multiApp.lockups; + break; + case "video": + const mediaVideo = todayCard.media; + editorialSearchResult.artwork = mediaVideo.videos[0].preview; + editorialSearchResult.video = mediaVideo.videos[0]; + if (todayCard.overlay instanceof models.TodayCardThreeLineOverlay) { + const overlay = todayCard.overlay; + editorialSearchResult.title = overlay.title; + editorialSearchResult.subtitle = overlay.description; + } + else { + editorialSearchResult.subtitle = mediaVideo.description; + } + break; + case "appEvent": + const media = todayCard.media; + editorialSearchResult.artwork = media.artworks[0]; + editorialSearchResult.appEventFormattedDates = media.formattedDates; + editorialSearchResult.subtitle = todayCard.inlineDescription; + editorialSearchResult.tintColor = media.tintColor; + editorialSearchResult.type = "appEventStory"; + if (serverData.isDefinedNonNull(todayCard.style)) { + switch (todayCard.style) { + case "light": + case "white": + editorialSearchResult.mediaOverlayStyle = "light"; + break; + case "dark": + editorialSearchResult.mediaOverlayStyle = "dark"; + break; + default: + errors.unreachable(todayCard.style); + break; + } + } + break; + default: + break; + } + } + if (todayCard.overlay) { + switch (todayCard.overlay.kind) { + case "lockup": + const cardOverlayLockup = todayCard.overlay; + if (!editorialSearchResult.artwork || objectGraph.client.isVision) { + collectionLockups = [cardOverlayLockup.lockup]; + } + break; + case "lockupList": + const cardOverlayList = todayCard.overlay; + collectionLockups = cardOverlayList.lockups; + break; + case "paragraph": + const cardOverlayParagraph = todayCard.overlay; + editorialSearchResult.subtitle = cardOverlayParagraph.paragraph.text; + break; + default: + break; + } + } + if (serverData.isDefinedNonNull(collectionLockups)) { + editorialSearchResult.collectionAdamIds = []; + editorialSearchResult.collectionAppIcons = []; + for (const lockup of collectionLockups) { + editorialSearchResult.collectionAdamIds.push(lockup.adamId); + editorialSearchResult.collectionAppIcons.push(lockup.icon); + } + if (collectionLockups.length === 1) { + editorialSearchResult.lockup = collectionLockups[0]; + } + } + const editorialClientParams = extractEditorialClientParams(objectGraph, cardData); + editorialSearchResult.editorialDisplayOptions = editorialDisplayOptionsFromClientParams(editorialClientParams); + /** + * Editorial tagline + */ + const storyTagline = common.editorialSearchResultTagline(objectGraph, cardData); + if ((storyTagline === null || storyTagline === void 0 ? void 0 : storyTagline.length) > 0 && storyTagline !== editorialSearchResult.title) { + editorialSearchResult.tagline = storyTagline; + } + const heroMedia = todayCard.heroMedia; + if (serverData.isDefinedNonNullNonEmpty(heroMedia)) { + if (serverData.isDefinedNonNullNonEmpty(heroMedia.artworks[0])) { + editorialSearchResult.artwork = heroMedia.artworks[0]; + editorialSearchResult.artwork.crop = "em"; + } + else if (serverData.isDefinedNonNullNonEmpty(heroMedia.videos[0])) { + editorialSearchResult.video = heroMedia.videos[0]; + } + } + if (editorialSearchResult.video) { + editorialSearchResult.video.canPlayFullScreen = false; + editorialSearchResult.video.playbackControls = {}; + } + if (!editorialSearchResult.collectionAdamIds && + !editorialSearchResult.artwork && + !editorialSearchResult.iconArtwork) { + return null; + } + return editorialSearchResult; +} +/// Gets the client identifier (or null) that should be used when building lockups for a given search entity. +function clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity) { + return searchEntity === "watch" ? client.watchIdentifier : null; +} +/** + * Indicates whether a search result meets basic sanity requirements to display app events. This does not include business rules for massaging a valid app event. For that, see `sanitizedAppEvent` function + * @param objectGraph The object graph + * @param searchResultData The data for the search result + * @returns a boolean result + */ +function searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData) { + if (!appPromotionsCommon.appEventsAreEnabled(objectGraph) || serverData.isNull(searchResultData.meta)) { + return false; + } + // The first organic can't use CPP data from the ad if it has an app event that will be displayed. + // In this case, it should continue to use DPP assets + const appEventDataItems = serverData.asArrayOrEmpty(searchResultData.meta, "associations.app-events.data"); + const hasInAppEvents = appEventDataItems.length > 0; + // App events can be displayed on most search result types, except for these ones + const typesIneligibleToDisplayAppEvent = [ + "rooms", + "multirooms", + "developers", + "editorial-items", + "groupings", + "in-apps", + "app-bundles", + ]; + const typeIsEligibleToDisplayAppEvent = !typesIneligibleToDisplayAppEvent.includes(searchResultData.type); + return typeIsEligibleToDisplayAppEvent && hasInAppEvents; +} +/** + * Sorts and filters app event for personalization, if applicable. May filter app events, and may select just the best one + * @param objectGraph The object graph + * @param resultData The data for the search result + * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events + * @returns an object representing the personalized app events for a user, along with the personalization result + */ +function selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer) { + const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent"); + const appEventDataItems = serverData.asArrayOrEmpty(resultData.meta, "associations.app-events.data"); + if (alwaysShowAppEvent) { + // In this scenario, there should always be only one event to choose from, + // and we don't want to do any personalization. + return { dataItems: [appEventDataItems[0]] }; + } + const personalizedDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "search", appEventDataItems, false, personalizationDataContainer, false, undefined, resultData.id); + const personalizedDataItems = personalizedDataResult.personalizedData; + if (personalizedDataItems.length <= 0) { + return { dataItems: [] }; + } + return { dataItems: personalizedDataItems, personalizationData: personalizedDataResult }; +} +/** + * Sanitizes a raw app event metadata into an AppEvent model. If the event is not hydratable or has not started, this returns null. + * @param objectGraph The object graph + * @param appEventDataItem The app event that needs to be processed + * @param resultData The data for the search result + * @param baseMetricsOptions Metrics options for built lockups + * @param offerEnvironment Offer environment for the lockup. Typically comes from lockup options + * @param offerStyle Offer style for the lockup. Typically comes from lockup options + * @returns the processed app event with all business rules applied, or null if the app event was disqualified + */ +function sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, offerEnvironment, offerStyle) { + const metricsOptions = { + ...baseMetricsOptions, + targetType: "eventModule", + }; + const appEventOrDate = appEvents.appEventOrPromotionStartDateFromData(objectGraph, appEventDataItem, resultData, false, true, offerEnvironment, offerStyle, false, metricsOptions, false, true, null, false, false); + // Ignore a future AppEvent promotionStartDate here. + if (serverData.isNull(appEventOrDate) || appEventOrDate instanceof Date) { + return null; + } + else { + return appEventOrDate; + } +} +/** + * Creates an app event search result from the data and associated lockup, if an app event exists in the metadata + * @param resultData The data blob + * @param lockup The associated mixed media lockup + * @param standardLockupOptions: The standard lockup options for search results + * @param personalizationDataContainer The data container to use for personalizing the selected app event + * @param baseMetricsOptions Base metrics options for this result + * @returns {AppEventSearchResult} The app event search result, or null + */ +function appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, baseMetricsOptions) { + return validation.context("appEventSearchResultFromData", () => { + const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, resultData); + if (!appEventEligibleToDisplay) { + return null; + } + const { dataItems, personalizationData } = selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer); + let appEvent; + let parentAppData; + for (const appEventDataItem of dataItems) { + const appEventOrNull = sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, standardLockupOptions.offerEnvironment, standardLockupOptions.offerStyle); + if (serverData.isDefinedNonNull(appEventOrNull)) { + appEvent = appEventOrNull; + parentAppData = resultData !== null && resultData !== void 0 ? resultData : mediaRelationship.relationshipData(objectGraph, appEventDataItem, "app"); + break; + } + } + if (serverData.isNull(appEvent)) { + return null; + } + const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent"); + const searchResult = new models.AppEventSearchResult(); + searchResult.lockup = lockup; + searchResult.appEvent = appEvent; + searchResult.alwaysShowAppEvent = alwaysShowAppEvent; + searchResult.clickAction = lockup.clickAction; + const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(null, personalizationData === null || personalizationData === void 0 ? void 0 : personalizationData.processingType, null); + const impressionOptions = { + ...baseMetricsOptions, + id: appEvent.appEventId, + kind: "inAppEvent", + targetType: "eventModule", + title: appEvent.title, + softwareType: null, + recoMetricsData: recoMetricsData, + }; + if (serverData.isDefinedNonNull(parentAppData)) { + impressionOptions.relatedSubjectIds = [parentAppData.id]; + } + metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions); + return searchResult; + }); +} +//# sourceMappingURL=search-results.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js new file mode 100644 index 0000000..03df66d --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js @@ -0,0 +1,53 @@ +import * as models from "../../../api/models"; +import * as mediaAttributes from "../../../foundation/media/attributes"; +import { asInterface } from "../../../foundation/json-parsing/server-data"; +import { createMetricsOptionsForGenericSearchPageShelf } from "../../metrics/helpers/search/search-shelves"; +import { isNothing } from "@jet/environment"; +// The types of search pages shelves can render in +export var SearchPageType; +(function (SearchPageType) { + SearchPageType[SearchPageType["Landing"] = 0] = "Landing"; + SearchPageType[SearchPageType["Results"] = 1] = "Results"; + SearchPageType[SearchPageType["ChartsAndCategories"] = 2] = "ChartsAndCategories"; + SearchPageType[SearchPageType["Focus"] = 3] = "Focus"; +})(SearchPageType || (SearchPageType = {})); +/** + * Collections the shelf's attributes + * @param objectGraph The App Store Object Graph + * @param data The shelf data object + * @returns The attributes for the shelf + */ +export function shelfAttributesFromData(objectGraph, data, shelfKind = undefined, pageKind = models.SearchPageKind.Default) { + var _a, _b, _c; + const title = (_a = mediaAttributes.attributeAsString(data, "title")) !== null && _a !== void 0 ? _a : undefined; + let displayStyle = (_b = mediaAttributes.attributeAsInterface(data, "displayStyle")) !== null && _b !== void 0 ? _b : undefined; + /// If we are on the see-all page, we currently don't get back a displayStyle object, so we need to manually provide one + /// for the tile brick type as see-all is hard-coded for that density everywhere else (natively) + if (isNothing(displayStyle) && pageKind === models.SearchPageKind.CategoriesAndCharts) { + displayStyle = { + layoutDensity: models.GenericSearchPageShelfDisplayStyleDensity.Density1, + layout: undefined, + layoutSize: undefined, + }; + } + const itemDisplayStyleAttributes = mediaAttributes.attributeAsDictionary(data, "itemDisplayStyle"); + const itemDisplayStyle = asInterface(itemDisplayStyleAttributes); + const hasSeeAll = mediaAttributes.attributeAsBooleanOrFalse(data, "hasSeeAll"); + const displayCount = (_c = mediaAttributes.attributeAsNumber(data, "displayCount")) !== null && _c !== void 0 ? _c : undefined; + const seeAllURL = hasSeeAll ? data.href : undefined; + return new models.SearchShelfAttributes(data.id, title, displayStyle, displayCount, hasSeeAll, seeAllURL, itemDisplayStyle, shelfKind); +} +/** + * Creates a base shelf context for the search shelf + * @param objectGraph The App Store Object Graph + * @param data The shelf data object + * @param shelfAttributes The shelf's attributes + * @param searchPageContext The context for the page containing the shelf + * @returns A standard shelf context for the shelf + */ +export function baseShelfContext(objectGraph, data, shelfAttributes, searchPageContext) { + return { + metricsOptions: createMetricsOptionsForGenericSearchPageShelf(objectGraph, data, shelfAttributes, searchPageContext), + }; +} +//# sourceMappingURL=search-shelves.js.map
\ No newline at end of file |
