diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/search/search.js | |
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search/search.js')
| -rw-r--r-- | node_modules/@jet-app/app-store/tmp/src/common/search/search.js | 1024 |
1 files changed, 1024 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search.js new file mode 100644 index 0000000..aec35e0 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search.js @@ -0,0 +1,1024 @@ +// +// search.ts +// AppStoreKit +// +// Created by Kevin MacWhinnie on 8/15/16. +// Copyright (c) 2016 Apple Inc. All rights reserved. +// +import { isNothing, isSome } from "@jet/environment"; +import * as validation from "@jet/environment/json/validation"; +import { FetchTimingMetricsBuilder } from "@jet/environment/metrics/fetch-timing-metrics-builder"; +import { PageInvocationPoint } from "@jet/environment/types/metrics"; +import * as models from "../../api/models"; +import { SearchResultsLearnMoreNotice } from "../../api/models"; +import * as serverData from "../../foundation/json-parsing/server-data"; +import * as mediaDataStructure from "../../foundation/media/data-structure"; +import * as contentArtwork from "../content/artwork/artwork"; +import * as metricsBuilder from "../metrics/builder"; +import * as metricsHelpersClicks from "../metrics/helpers/clicks"; +import * as metricsHelpersImpressions from "../metrics/helpers/impressions"; +import * as metricsHelpersLocation from "../metrics/helpers/location"; +import * as metricsHelpersMisc from "../metrics/helpers/misc"; +import * as metricsHelpersPage from "../metrics/helpers/page"; +import * as guidedSearch from "./guided-search/guided-search"; +import { addGuidedSearchParentImpressionMetrics, addSearchResultParentImpressionMetrics, } from "./guided-search/guided-search-metrics"; +import * as searchAdsODML from "./search-ads-odml"; +import * as searchCommon from "./search-common"; +import { createDefaultSelectedFacetOptions, createSearchFacets, createSearchPageFacets } from "./search-facets"; +import * as searchResultsFetching from "./search-results-fetching"; +import { createSearchResultsLearnMoreNoticeLinkableText } from "./search-results-learn-more-notice"; +import * as searchResultsPipeline from "./search-results-pipeline"; +import * as searchSpellCorrection from "./search-spell-correction"; +import * as searchToken from "./search-token"; +// MARK: - Search Hints +/** + * Convert the response from the search hints endpoint into a model object. + * @param {String} prefixTerm The term the search hints are for (i.e. searchPrefix). + * @param {String} searchUrl The Url if we were to search for this term right away + * @param {*} response The API response. + * @return {SearchHintSet} The search hint set containing the search hint array + */ +export function searchHintsFromApiResponse(objectGraph, prefixTerm, hintsContainer) { + return validation.context("searchHintsFromApiResponse", () => { + var _a, _b, _c, _d; + const metricsOptions = { + targetType: "listItem", + pageInformation: metricsHelpersPage.pageInformationForSearchHintsPage(objectGraph, prefixTerm, hintsContainer.hintsRequestUrl, hintsContainer.dataSetId), + locationTracker: metricsHelpersLocation.newLocationTracker(), + }; + // Build user hint that matches what user typed. Appears in first position. + let userTypedHintAction = null; + if ((_a = hintsContainer.userTypedTerm) === null || _a === void 0 ? void 0 : _a.length) { + userTypedHintAction = new models.SearchAction(hintsContainer.userTypedTerm, hintsContainer.userTypedTerm, null, "userTypedHint"); + userTypedHintAction.spellCheckEnabled = true; + userTypedHintAction.prefixTerm = prefixTerm; + metricsHelpersImpressions.addImpressionMetricsToHintsSearchAction(objectGraph, userTypedHintAction, metricsOptions); + metricsHelpersClicks.addEventsToSearchAction(objectGraph, userTypedHintAction, metricsOptions.targetType, metricsOptions.locationTracker, metricsOptions.pageInformation); + metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); + } + // Build standard hints. + const searchHintActions = (_c = (_b = hintsContainer.rawHints) === null || _b === void 0 ? void 0 : _b.map((rawHint) => { + return searchHintAction(objectGraph, rawHint, prefixTerm, metricsOptions); + })) !== null && _c !== void 0 ? _c : []; + // Prepend user hint if any. + if (userTypedHintAction != null) { + searchHintActions.unshift(userTypedHintAction); + } + const hintSet = new models.SearchHintSet(searchHintActions, (_d = hintsContainer.ghostHintsTerm) !== null && _d !== void 0 ? _d : null); + /** + * Send `input` Search Events when search hints returns. + * For SSS, this is the granularity we agreed on, instead of sending it in native per keystroke. + * Unlike standard page metrics, we only: + * - Fire a 'input' search event + * - Setup pageFields for page fields generator. + */ + const searchEvent = metricsBuilder.createMetricsSearchData(objectGraph, prefixTerm, "key", "input", hintsContainer.hintsRequestUrl, { ...metricsHelpersMisc.fieldsFromPageInformation(metricsOptions.pageInformation) }); + hintSet.pageMetrics.pageFields = metricsHelpersMisc.fieldsFromPageInformation(metricsOptions.pageInformation); + hintSet.pageMetrics.addData(searchEvent, [PageInvocationPoint.pageEnter]); + hintSet.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", metricsOptions.pageInformation, metricsOptions.locationTracker, prefixTerm); + hintSet.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", metricsOptions.pageInformation, metricsOptions.locationTracker, prefixTerm); + return hintSet; + }); +} +/** + * Creates a search hint action from the search hint data + * @param objectGraph The App Store object graph + * @param hintData The hint object data + * @param prefixTerm The hint prefix term + * @param metricsOptions The metrics options to use for click and impressions metrics for this hint + * @returns A search hint action for the hint data + */ +function searchHintAction(objectGraph, hintData, prefixTerm, metricsOptions) { + var _a, _b, _c, _d, _e; + const searchEntity = (_a = searchEntityFromHintData(hintData)) !== null && _a !== void 0 ? _a : undefined; + const searchAction = new models.SearchAction((_b = hintData.displayTerm) !== null && _b !== void 0 ? _b : "", (_c = hintData.searchTerm) !== null && _c !== void 0 ? _c : "", null, "hints", searchEntity, hintData.source); + searchAction.artwork = contentArtwork.createArtworkForSystemImage(objectGraph, (_d = models.searchEntitySystemImage(searchEntity)) !== null && _d !== void 0 ? _d : "magnifyingglass"); + searchAction.spellCheckEnabled = true; + searchAction.prefixTerm = prefixTerm; + metricsHelpersImpressions.addImpressionMetricsToHintsSearchAction(objectGraph, searchAction, metricsOptions); + metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, metricsOptions.targetType, metricsOptions.locationTracker, (_e = metricsOptions.pageInformation) !== null && _e !== void 0 ? _e : undefined); + metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); + return searchAction; +} +/** + * + * @param objectGraph The App Store object graph + * @param entity The entity value string from the hint object + * @param context The context value string from the hint object + * @returns The correct entity for the hint based on legacy entity types + * + * Entities: + * "arcade" ==> "apps" with context "arcade" + "developer" ==> "developers" + "story" ==> "editorial-items" + "watch" ==> "apps" with context "watch" + */ +function searchEntityFromHintData(rawHint) { + const hintEntity = rawHint.entity; + switch (hintEntity) { + case "apps": + return rawHint.context; + case "developers": + return "developer"; + case "editorial-items": + return "story"; + default: + return null; + } +} +// MARK: - Trending Searches +/** + * Convert the response from the trending searches endpoint into a model object. + * @param {*} response The API response. + * @return {TrendingSearch} A trending searches model object. + */ +export function trendingSearchesFromApiResponse(objectGraph, response) { + return validation.context("trendingSearchesFromApiResponse", () => { + const locationTracker = metricsHelpersLocation.newLocationTracker(); + const searches = serverData.asArrayOrEmpty(response, "trendingSearches").map((rawSearch) => { + const term = serverData.asString(rawSearch, "label"); + const searchAction = new models.SearchAction(term, term, serverData.asString(rawSearch, "url"), "trending"); + metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", locationTracker); + metricsHelpersLocation.nextPosition(locationTracker); + return searchAction; + }); + const title = searches.length > 0 ? serverData.asString(response, "header.label") : null; + const trendingSearches = new models.TrendingSearches(title, searches); + switch (objectGraph.client.deviceType) { + case "pad": + trendingSearches.maxNumberOfSearches = 10; + break; + case "phone": + trendingSearches.maxNumberOfSearches = 7; + break; + default: + break; + } + return trendingSearches; + }); +} +// MARK: - Search Results +/** + * Gets the shelf ID for search results, on a specfic segment if applicable + * @param segmentTitle The title of the segment this shelf is on, if using segmented search + * @returns The id for the search results shelf + * @note This needs to include the segment title for segment search results since the contents on + * each segment would otherwise have the same shelf id and therefore would lead to the content never changing + */ +function getSearchResultsShelfId(segmentTitle) { + if (isSome(segmentTitle) && segmentTitle.length !== 0) { + return `SearchResults.${segmentTitle}.shelfId`; + } + else { + return "SearchResults.shelfId"; + } +} +/** + * Gets metrics pageid to use for the search results page. + * @param segmentType The segment this page is displaying if any + * @returns The id for the search results page + */ +function getSearchResultsPageId(segmentType) { + switch (segmentType) { + case models.SegmentedSearchResultsPageSegmentType.iOS: + return "ios"; + case models.SegmentedSearchResultsPageSegmentType.visionOS: + return "visionos"; + default: + return "SearchTopResults"; + } +} +/** + * Creates a new empty `SearchResults` object. + * @return {SearchResults} An empty search results model object. + */ +export function emptyResults(objectGraph, requestFacets) { + const searchResults = new models.SearchResults(); + if (serverData.isDefinedNonNull(requestFacets)) { + searchResults.facets = createSearchFacets(objectGraph, requestFacets); + searchResults.pageFacets = createSearchPageFacets(objectGraph); + searchResults.selectedFacetOptions = createDefaultSelectedFacetOptions(objectGraph); + } + searchResults.results = []; + return searchResults; +} +/** + * Creates a new empty `SearchResultsPage` object. + * @return {SearchResults} An empty search results model object. + */ +export function emptyResultsPage(objectGraph, requestFacets) { + const searchResultsPage = new models.SearchResultsPage([]); + if (serverData.isDefinedNonNull(requestFacets)) { + searchResultsPage.facets = createSearchFacets(objectGraph, requestFacets); + searchResultsPage.pageFacets = createSearchPageFacets(objectGraph); + searchResultsPage.selectedFacetOptions = createDefaultSelectedFacetOptions(objectGraph); + } + return searchResultsPage; +} +/** + * A container like class to manage a search results page segment and surrounding context + */ +class SegmentedSearchResultsPageSegmentContext { +} +/** + * Builds `BaseSearchPage` from data. + */ +async function baseSearchPageFromResponse(objectGraph, combinedSearchData) { + return await validation.context("searchResultsFromResponse", async () => { + var _a; + const fetchTimingMetricsBuilder = (_a = objectGraph.fetchTimingMetricsBuilder) !== null && _a !== void 0 ? _a : new FetchTimingMetricsBuilder(); + const page = await fetchTimingMetricsBuilder.measureModelConstructionAsync(async () => { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v; + const response = combinedSearchData.catalogResponse; + const requestMetadata = combinedSearchData.requestMetadata; + const searchRequestUrl = requestMetadata.searchRequestUrl; + const sponsoredSearchRequestData = combinedSearchData.sponsoredSearchRequestData; + const guidedSearchData = response.results.guidedSearch; + // Term Context + const termContext = searchCommon.createTermContextForSpellcheckedSequentialResponse(objectGraph, requestMetadata.requestDescriptor, response); + // Metrics + const wasOdmlSuccessful = searchAdsODML.wasODMLSuccessful(objectGraph, combinedSearchData.sponsoredSearchAdvertData); + const metricsOptions = { + locationTracker: metricsHelpersLocation.newLocationTracker(), + pageInformation: metricsHelpersPage.pageInformationForSearchPage(objectGraph, requestMetadata.requestDescriptor, response, termContext, searchRequestUrl, getSearchResultsPageId(), sponsoredSearchRequestData, wasOdmlSuccessful, guidedSearchData), + createUniqueImpressionId: true, + }; + // Root container + const isShelfBasedSearch = objectGraph.featureFlags.isEnabled("shelves_2_0_search") || + objectGraph.client.isiOS || + objectGraph.client.isTV || + objectGraph.client.isVision || + objectGraph.client.isWeb; + const baseSearchPage = isShelfBasedSearch + ? new models.SearchResultsPage() + : new models.SearchResults(); + // Guided Search Experimentation (pinned vs mid-scroll) + const guidedSearchPosition = searchResultsPipeline.guidedSearchPositionFromSearchResponseMeta(response.meta, objectGraph); + if (isNothing(guidedSearchPosition)) { + /** + * Guided Search + * - note: This is important to occur before `createSearchResults` since its impression index should be lower than the container for search results. + */ + addModelsForGuidedSearch(objectGraph, baseSearchPage, requestMetadata, guidedSearchData === null || guidedSearchData === void 0 ? void 0 : guidedSearchData.facets, metricsOptions); + } + const shelfMetricsOptions = { + id: "search-results", + kind: null, + softwareType: null, + targetType: "SearchResults", + title: "Search Results", + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + idType: "relationship", + }; + let resultsShelf; + if (isShelfBasedSearch) { + resultsShelf = new models.Shelf("searchResult"); + resultsShelf.id = getSearchResultsShelfId(); + resultsShelf.isHorizontal = false; + if (objectGraph.client.isWeb) { + resultsShelf.title = objectGraph.loc + .string("Search.ResultsTitle") + .replace("@@search_term@@", termContext.term); + } + // We need to create and apply impressions fields to the shelf before creating the search results, + // so that the shelf is sitting in the right location relative to its children from an impressions perspective. + metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions); + metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results"); + } + let advertData = dataForAdvertsFromCombinedData(objectGraph, combinedSearchData); + const appliedPolicy = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.appliedPolicy; + if (isSome(appliedPolicy)) { + // If there was an applied policy, suppress the ad. + advertData = []; + if (appliedPolicy === "ageRestricted") { + (_b = metricsOptions.pageInformation) === null || _b === void 0 ? void 0 : _b.iAdInfo.setMissedOpportunity(objectGraph, "ODP_NOAD", "searchResults"); + } + } + const unsafeSearch = (_f = (_e = (_d = (_c = response.meta) === null || _c === void 0 ? void 0 : _c.results) === null || _d === void 0 ? void 0 : _d.search) === null || _e === void 0 ? void 0 : _e.searchSafety) !== null && _f !== void 0 ? _f : false; + const missedOpportunityReason = ((_k = (_j = (_h = (_g = response.meta) === null || _g === void 0 ? void 0 : _g.results) === null || _h === void 0 ? void 0 : _h.search) === null || _j === void 0 ? void 0 : _j.reason) === null || _k === void 0 ? void 0 : _k.kind) === "no-results" ? "NLS_NORESULTS" : "NLS_NOAD"; + if (unsafeSearch) { + // If the search was deemed unsafe, suppress Ad and record the missed opportunity. + advertData = []; + (_l = metricsOptions.pageInformation) === null || _l === void 0 ? void 0 : _l.iAdInfo.setMissedOpportunity(objectGraph, missedOpportunityReason, "searchResults"); + } + /** + * Advert + Search Results + */ + const builderResults = await searchResultsPipeline.createSearchResults(objectGraph, requestMetadata, response.meta, metricsOptions, dataForSearchResultsFromCombinedData(objectGraph, combinedSearchData), advertData, guidedSearchData === null || guidedSearchData === void 0 ? void 0 : guidedSearchData.facets, installedStatesForAdvertsData(combinedSearchData), appStatesForAdvertsData(combinedSearchData)); + if (unsafeSearch && builderResults.builtSearchResults.length !== 0) { + const searchAdOpportunity = builderResults.builtSearchResults[0].lockup + .searchAdOpportunity; + searchAdOpportunity === null || searchAdOpportunity === void 0 ? void 0 : searchAdOpportunity.setMissedOpportunityReason(missedOpportunityReason); + searchAdOpportunity === null || searchAdOpportunity === void 0 ? void 0 : searchAdOpportunity.setTemplateType("APPLOCKUP"); + } + // Add result shelves to page. + if (isShelfBasedSearch && resultsShelf) { + const searchResultsPage = baseSearchPage; + resultsShelf.items = builderResults.builtSearchResults; + searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics; + searchResultsPage.shelves.push(resultsShelf); + // Display the context message for the results (or lack thereof). + const contextCard = createSearchResultsContextCard(response.results.queryContext, objectGraph); + const resultsReason = (_p = (_o = (_m = response.meta) === null || _m === void 0 ? void 0 : _m.results) === null || _o === void 0 ? void 0 : _o.search) === null || _p === void 0 ? void 0 : _p.reason; + if ((resultsReason === null || resultsReason === void 0 ? void 0 : resultsReason.kind) === "no-results") { + searchResultsPage.unavailableReason = { + title: objectGraph.loc.stringWithFallback("Search.Results.Empty.Title", "No results"), + message: resultsReason.text, + action: actionFromSearchResultsLinks(resultsReason.links), + contextCard: contextCard, + }; + } + else if (contextCard) { + const paragraphShelf = new models.Shelf("searchResultsContextCard"); + paragraphShelf.items = [contextCard]; + const contextCardPosition = (_t = (_s = (_r = (_q = response.meta) === null || _q === void 0 ? void 0 : _q.displayStyle) === null || _r === void 0 ? void 0 : _r.queryContext) === null || _s === void 0 ? void 0 : _s.position) !== null && _t !== void 0 ? _t : 0; + if (contextCardPosition > 0) { + const postCardShelfContents = resultsShelf.items.splice(contextCardPosition); + const postCardResulsShelf = { + ...resultsShelf, + id: "searchResults2", + items: postCardShelfContents, + isValid: resultsShelf.isValid, + }; + searchResultsPage.shelves.push(paragraphShelf); + searchResultsPage.shelves.push(postCardResulsShelf); + } + else { + searchResultsPage.shelves.unshift(paragraphShelf); + } + } + // Remove unsafe searches from recents if no results were provided. + if (unsafeSearch && builderResults.builtSearchResults.length === 0) { + (_v = (_u = objectGraph.onDeviceSearchHistoryManager).removeRecentSearchTerm) === null || _v === void 0 ? void 0 : _v.call(_u, termContext.term); + } + } + else { + const searchResults = baseSearchPage; + searchResults.results = builderResults.builtSearchResults; + addSearchResultParentImpressionMetrics(objectGraph, searchResults, metricsOptions); + } + /** + * Next Page + */ + if (builderResults.deferredSearchResults.length > 0) { + baseSearchPage.nextPage = searchToken.createSearchToken(objectGraph, builderResults.deferredSearchResults, requestMetadata, response.meta, metricsOptions); + } + if (isShelfBasedSearch) { + metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker); + } + /** + * Spell Correction Message + */ + baseSearchPage.message = searchSpellCorrection.spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions); + /** + * Search Entity + */ + const searchEntity = requestMetadata.requestDescriptor.searchEntity; + const searchEntitySpecified = !serverData.isNullOrEmpty(searchEntity); + if (!searchEntitySpecified) { + baseSearchPage.facets = createSearchFacets(objectGraph, requestMetadata.requestDescriptor.facets, combinedSearchData.categoriesFilterData); + baseSearchPage.pageFacets = createSearchPageFacets(objectGraph, combinedSearchData.categoriesFilterData); + baseSearchPage.selectedFacetOptions = serverData.isDefinedNonNullNonEmpty(combinedSearchData.requestMetadata.requestDescriptor.selectedFacetOptions) + ? combinedSearchData.requestMetadata.requestDescriptor.selectedFacetOptions + : createDefaultSelectedFacetOptions(objectGraph); + } + // Attach search term context + baseSearchPage.searchTermContext = termContext; + // Enable autoplay search results on all clients except tv. + baseSearchPage.isAutoPlayEnabled = objectGraph.client.deviceType !== "tv"; + baseSearchPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone; + // Search Transparency + baseSearchPage.transparencyLink = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions); + metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, baseSearchPage, metricsOptions.pageInformation); + baseSearchPage.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", metricsOptions.pageInformation, metricsOptions.locationTracker, termContext.term); + baseSearchPage.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", metricsOptions.pageInformation, metricsOptions.locationTracker, termContext.term); + return baseSearchPage; + }); + return page; + }); +} +// Creates context card model for search results from media API data. +export function createSearchResultsContextCard(queryContext, objectGraph) { + return validation.context("createSearchResultsContextCard", () => { + var _a, _b; + if (isNothing(queryContext) || !["iOS", "macOS"].includes(objectGraph.host.platform)) { + return undefined; // no server data or platform unsupported + } + const linkActions = (_a = queryContext.links) === null || _a === void 0 ? void 0 : _a.map(actionFromSearchResultsLink); + const linkedSubstrings = linkActions === null || linkActions === void 0 ? void 0 : linkActions.reduce((map, linkAction) => { + var _a; + if ((_a = linkAction.title) === null || _a === void 0 ? void 0 : _a.length) { + if (objectGraph.host.isMac) { + linkAction.title += "\u00a0\u2197"; + } + map[linkAction.title] = linkAction; + } + return map; + }, {}); + let rawText = queryContext.text; + if ((linkActions === null || linkActions === void 0 ? void 0 : linkActions.length) === 1 && ((_b = linkActions[0].title) === null || _b === void 0 ? void 0 : _b.length)) { + // Add single link to end of text, separated by a space. + rawText += " " + linkActions[0].title; + } + else if (linkActions && linkActions.length > 1) { + // Add multiple lines below end of text, separated by a new line. + if (rawText.length > 0) { + rawText += "\n"; + } + rawText += linkActions + .map((action) => action.title) + .filter((title) => title === null || title === void 0 ? void 0 : title.length) + .join("\n"); + } + const styledText = new models.StyledText(rawText); + const linkableText = new models.LinkableText(styledText, linkedSubstrings); + const contextCard = new models.SearchResultsContextCard(linkableText); + return contextCard; + }); +} +/// Creates and returns the first url action from the provided link data included in the search results response. +function actionFromSearchResultsLinks(linksData) { + return validation.context("actionFromSearchResultsLinks", () => { + const bestLinkData = linksData === null || linksData === void 0 ? void 0 : linksData.find((linkData) => linkData.url.length > 0); + return bestLinkData ? actionFromSearchResultsLink(bestLinkData) : undefined; + }); +} +/// Creates and returns an action from the provided link data included in the search results response. +function actionFromSearchResultsLink(linkData) { + return validation.context("actionFromSearchResultsLink", () => { + var _a; + const linkAction = new models.ExternalUrlAction(linkData.url, false); + linkAction.title = (_a = linkData.label) === null || _a === void 0 ? void 0 : _a.replace(" ", "\u00a0"); // Use non-breaking spaces for link labels. + linkAction.artwork = new models.Artwork("systemimage://arrow.up.forward", 0, 0, []); + return linkAction; + }); +} +function installedStatesForAdvertsData(combinedSearchData) { + var _a, _b; + return (_b = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.installedStates) !== null && _b !== void 0 ? _b : {}; +} +function appStatesForAdvertsData(combinedSearchData) { + var _a, _b; + return (_b = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.appStates) !== null && _b !== void 0 ? _b : {}; +} +export async function searchResultsFromResponse(objectGraph, combinedSearchData) { + return await baseSearchPageFromResponse(objectGraph, combinedSearchData); +} +export async function searchResultsPageFromResponse(objectGraph, combinedSearchData) { + return await baseSearchPageFromResponse(objectGraph, combinedSearchData); +} +/** + * Creates a new empty `SegmentedSearchResultsPage` object. + * @return An empty segmented search results model object. + */ +export function emptySegmentedResultsPage(objectGraph) { + return new models.SegmentedSearchResultsPage(); +} +/** + * Creates the segmented search results page form the response + * @param objectGraph The app store object graph + * @param combinedSearchData The combined data for the segmented search results response + * @returns A promise of the segmented search results page + */ +export async function segmentedSearchResultsPageFromResponse(objectGraph, combinedSearchData) { + return await validation.context("segmentedSearchResultsPageFromResponse", async () => { + const response = combinedSearchData.catalogResponse; + const requestMetadata = combinedSearchData.requestMetadata; + if (isSome(response.results.search.groups)) { + const fetchPages = response.results.search.groups.map(async (group) => { + const segmentResponse = await baseSegmentFromGroupResponse(objectGraph, combinedSearchData, response, group, requestMetadata); + return segmentResponse; + }); + return await Promise.all(fetchPages).then((pageContexts) => { + const page = new models.SegmentedSearchResultsPage(); + if (serverData.isNullOrEmpty(pageContexts)) { + return page; + } + const segments = pageContexts.map((context) => { + return context.segment; + }); + page.segments = segments; + const initialSegmentId = response.results.search.autoSelectedGroupId || segments[0].groupId; + page.selectedSegmentId = initialSegmentId; + addSegmentChangeActionsToPages(objectGraph, pageContexts, initialSegmentId); + return page; + }); + } + else { + return new models.SegmentedSearchResultsPage(); + } + }); +} +/** + * Adds all segment change actions to each search result page segment + * @param objectGraph The App Store object graph + * @param contexts The segment contexts for the page + * @param initialSegmentId The initial segment Id. + */ +function addSegmentChangeActionsToPages(objectGraph, contexts, initialSegmentId) { + const firstNonEmptySegmentContext = contexts.find((context) => { + const hasNonEmptyPage = context.segment.page.shelves.some((shelf) => { + const isNotEmpty = shelf.items.length > 0; + return isNotEmpty; + }); + return hasNonEmptyPage; + }); + const firstNonEmptySegmentId = firstNonEmptySegmentContext === null || firstNonEmptySegmentContext === void 0 ? void 0 : firstNonEmptySegmentContext.segment.groupId; + const segments = contexts.map((context) => { + return context.segment; + }); + const nativeSegmentGroupId = segmentGroupIdForPlatform(objectGraph, segments); + // Check if the native segment has content. + const nativeSegmentIsNonEmpty = segments.some((segment) => { + if (segment.groupId !== nativeSegmentGroupId) { + return false; + } + return segment.page.shelves.some((shelf) => { + return shelf.items.length > 0; + }); + }); + contexts.forEach((context) => { + context.segment.emptySegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.EmptyResults, context.segment.page.id, context.metricsOptions, firstNonEmptySegmentId, context.segment.groupId); + // Only add the non-native segment change action if the native segment has content, and + // the initial segment wasn't the native segment. + if (nativeSegmentIsNonEmpty && initialSegmentId !== nativeSegmentGroupId) { + context.segment.nonNativeSegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.NonNative, context.segment.page.id, context.metricsOptions, nativeSegmentGroupId, context.segment.groupId); + } + context.segment.segmentPickerSegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.Picker, context.segment.page.id, context.metricsOptions, context.segment.groupId); + }); +} +/** + * + * @param objectGraph The app store object graph + * @param combinedSearchData The combined search results which contains the response for segmented results + * @param groupResponse The response for the segmented results + * @param searchData The data for a single search results segment + * @param requestMetadata The metadata from the search request + * @returns A promise of a segmented search results page segment + */ +async function baseSegmentFromGroupResponse(objectGraph, combinedSearchData, groupResponse, searchData, requestMetadata) { + return await validation.context("baseSearchPageFromGroupResponse", async () => { + const searchRequestUrl = requestMetadata.searchRequestUrl; + const segmentType = segmentTypeFromGroupId(searchData.groupId); + // Term Context + const termContext = searchCommon.createTermContextForSpellcheckedGroupedResponse(objectGraph, requestMetadata.requestDescriptor, groupResponse); + // Metrics + const metricsOptions = { + locationTracker: metricsHelpersLocation.newLocationTracker(), + pageInformation: metricsHelpersPage.pageInformationForSearchPage(objectGraph, requestMetadata.requestDescriptor, groupResponse, termContext, searchRequestUrl, getSearchResultsPageId(segmentType), null, false, null), + createUniqueImpressionId: true, + }; + const shelfMetricsOptions = { + id: "search-results", + kind: null, + softwareType: null, + targetType: "SearchResults", + title: "Search Results", + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + idType: "relationship", + }; + // Root container + const searchResultsPage = new models.SearchResultsPage(); + const searchSegment = new models.SegmentedSearchResultsPageSegment(); + const resultsShelf = new models.Shelf("searchResult"); + resultsShelf.isHorizontal = false; + // We need to create and apply impressions fields to the shelf before creating the search results, + // so that the shelf is sitting in the right location relative to its children from an impressions perspective. + metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions); + metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results"); + const resultsData = mediaDataStructure.dataCollectionFromDataContainer(searchData); + const builderResults = await searchResultsPipeline.createSearchResults(objectGraph, requestMetadata, groupResponse.meta, metricsOptions, resultsData); + const learnMoreNoticeLinkableText = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions); + const shelves = searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf); + searchResultsPage.shelves.push(...shelves); + searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics; + if (builderResults.deferredSearchResults.length > 0) { + searchResultsPage.nextPage = searchToken.createSearchToken(objectGraph, builderResults.deferredSearchResults, requestMetadata, groupResponse.meta, metricsOptions); + } + metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker); + /** + * Spell Correction Message + */ + searchResultsPage.message = searchSpellCorrection.spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions); + // Enable autoplay search results on all clients except tv. + searchResultsPage.isAutoPlayEnabled = objectGraph.client.deviceType !== "tv"; + searchResultsPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone; + // Search Transparency + searchResultsPage.transparencyLink = learnMoreNoticeLinkableText; + metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, searchResultsPage, metricsOptions.pageInformation); + searchSegment.groupId = searchData.groupId; + resultsShelf.id = getSearchResultsShelfId(searchSegment.groupId); + searchSegment.page = searchResultsPage; + searchSegment.title = segmentTitleForSegmentType(objectGraph, false, segmentType); + return { + segment: searchSegment, + metricsOptions: metricsOptions, + }; + }); +} +/** + * Get an array of shelves from the search builder results. + * This is used to decide where to insert the search results learn more notice across + * initial and paginated page builds. + * + * This is currently only used on visionOS search paths, but has the appropriate checks to ensure + * it doesn't run on unsupported platforms, so could be used in all search code paths. + * @param objectGraph The Object Graph. + * @param learnMoreNoticeLinkableText The linkable text to display as part of the learn more notice. + * @param builderResults The results from `createSearchResults`. + * @param resultsShelf The initial shelf created to hold the results. This is passed in + * because in many cases a reference to this is required later when building out the page. + * @param startingContentOffsetWithinResults The starting content offset before the newly built + * content is added. For an initial page build, this will be zero, and for paginated pages this + * is stored on the token. + * @returns An array of shelves for a search results page, with the learn more notice inserted + * if required. + */ +function searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf, startingContentOffsetWithinResults = 0) { + let learnMoreNoticeIndex = searchResultsLearnMoreNoticeIndex(objectGraph); + const builtResultsCount = builderResults.builtSearchResults.length; + const hasDeferredResults = builderResults.deferredSearchResults.length > 0; + // The total built results for the page so far. This is the initial page, plus any paginated content. + const totalBuiltResults = startingContentOffsetWithinResults + builtResultsCount; + // There are a number of conditions we don't want to insert the learn more notice: + // 1. No index at which to insert it - this generally means the platform doesn't support this method of insertion. + // 2. No linkable text object - the bag key wasn't present. + // 3. The starting offset is greater than or equal to the insertion index - we can assume we've already shown the + // notice in a previous page build. + // 4. We have deferred results and the total built results count is less than the threshold - we'll insert the + // notice in a subsequent fetch. + // 5. We have no built results and no deferred results, ie. the page will be empty. + // If we fail any of these conditions, we will just attach all the items into the single shelf and return that. + if (learnMoreNoticeIndex === undefined || + learnMoreNoticeLinkableText === undefined || + startingContentOffsetWithinResults >= learnMoreNoticeIndex || + (hasDeferredResults && totalBuiltResults < learnMoreNoticeIndex) || + (totalBuiltResults === 0 && !hasDeferredResults)) { + resultsShelf.items = builderResults.builtSearchResults; + return [resultsShelf]; + } + // Adjust the index by the starting offset, to ensure we take any items built in the initial fetch into account. + learnMoreNoticeIndex = learnMoreNoticeIndex - startingContentOffsetWithinResults; + // We have satisfied the insertion conditions for the learn more notice - we need to split the shelf in two to incorporate + // the learn more notice as its own shelf in between. This is due to layout restrictions on visionOS where we cannot + // accommodate a full width item in a grid of two items per row. + resultsShelf.items = builderResults.builtSearchResults.slice(0, learnMoreNoticeIndex); + const learnMoreNoticeShelf = new models.Shelf("searchResultsLearnMoreNotice"); + learnMoreNoticeShelf.isHorizontal = false; + const searchResult = new SearchResultsLearnMoreNotice(learnMoreNoticeLinkableText); + learnMoreNoticeShelf.items = [searchResult]; + const shelves = [resultsShelf, learnMoreNoticeShelf]; + const secondResultsShelfItems = builderResults.builtSearchResults.slice(learnMoreNoticeIndex); + if (secondResultsShelfItems.length > 0) { + const secondResultsShelf = new models.Shelf("searchResult"); + // Give the second results shelf the same impression metrics. This will result in each shelf contributing to the same impression metrics, + // the only difference is we will see two "viewedInfo" entries for each distinct shelf within the top level impressions item. + secondResultsShelf.impressionMetrics = resultsShelf.impressionMetrics; + secondResultsShelf.isHorizontal = false; + secondResultsShelf.items = secondResultsShelfItems; + shelves.push(secondResultsShelf); + } + return shelves; +} +/** + * Get the index for where the Search Results Learn More Notice should be inserted. + * Returns undefined if either: + * - the editorial ID is not available in the bag, or + * - the platform does not support this style of presentation. + * @param objectGraph The Object Graph + */ +function searchResultsLearnMoreNoticeIndex(objectGraph) { + const editorialItemId = objectGraph.bag.searchResultsLearnMoreEditorialId; + if (isNothing(editorialItemId) || (editorialItemId === null || editorialItemId === void 0 ? void 0 : editorialItemId.length) === 0) { + return undefined; + } + if (objectGraph.client.isVision) { + return 6; + } + return undefined; +} +export async function paginatedSearchResultsWithToken(objectGraph, token) { + return await validation.context("paginatedSearchResultsWithToken", async () => { + const nextItemsToFetch = searchToken.getNextItemsToFetch(objectGraph, token); + const advancedToken = searchToken.advanceSearchTokenResults(objectGraph, token); + if (nextItemsToFetch.length === 0) { + return await Promise.resolve(emptyResults(objectGraph)); + } + return await searchResultsFetching + .fetchSearchResultItems(objectGraph, nextItemsToFetch) + .then(async (dataContainer) => { + const resultsDatum = mediaDataStructure.dataCollectionFromDataContainer(dataContainer); + return await searchResultsPipeline + .createSearchResults(objectGraph, token.requestMetadata, token.responseMetadata, token.metricsOptions, resultsDatum) + .then((builderResults) => { + const searchResults = new models.SearchResults(); + searchResults.results = builderResults.builtSearchResults; + searchResults.nextPage = advancedToken; + return searchResults; + }); + }); + }); +} +export async function paginatedSearchResultsPageWithToken(objectGraph, token) { + return await validation.context("paginatedSearchResultsPageWithToken", async () => { + const nextItemsToFetch = searchToken.getNextItemsToFetch(objectGraph, token); + const advancedToken = searchToken.advanceSearchTokenResults(objectGraph, token); + if (nextItemsToFetch.length === 0) { + return await Promise.resolve(emptyResultsPage(objectGraph)); + } + return await searchResultsFetching + .fetchSearchResultItems(objectGraph, nextItemsToFetch) + .then(async (dataContainer) => { + const resultsDatum = mediaDataStructure.dataCollectionFromDataContainer(dataContainer); + const shelfMetricsOptions = { + id: "search-results", + kind: null, + softwareType: null, + targetType: "SearchResults", + title: "Search Results", + pageInformation: token.metricsOptions.pageInformation, + locationTracker: token.metricsOptions.locationTracker, + idType: "relationship", + }; + // Set up the "new" shelf + const resultsShelf = new models.Shelf("searchResult"); + resultsShelf.id = getSearchResultsShelfId(); + resultsShelf.isHorizontal = false; + // Shelf Impressions: Add impression fields. + // This shelf, and the associated metrics data isn't really used - it's basically just a vehicle for the new items to be + // merged into the old page/shelf. Even so, we create and apply impressions metrics correctly before setting the content + // location and current position so it looks correct. + metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions); + // Set the content location to the "search-results" shelf. + metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results"); + // Update position to content offset within the same search shelf prior to building the new results. + metricsHelpersLocation.setCurrentPosition(shelfMetricsOptions.locationTracker, token.contentOffsetWithinResultsShelf); + return await searchResultsPipeline + .createSearchResults(objectGraph, token.requestMetadata, token.responseMetadata, token.metricsOptions, resultsDatum) + .then((builderResults) => { + const learnMoreNoticeLinkableText = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, shelfMetricsOptions); + const shelves = searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf, token.contentOffsetWithinResultsShelf); + const searchResultsPage = new models.SearchResultsPage(shelves); + // Ensure we increment the offset for any future paginated results. + if (serverData.isDefinedNonNull(advancedToken)) { + advancedToken.contentOffsetWithinResultsShelf = metricsHelpersLocation.currentPosition(shelfMetricsOptions.locationTracker); + searchResultsPage.nextPage = advancedToken; + } + searchResultsPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone; + searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics; + metricsHelpersLocation.popLocation(token.metricsOptions.locationTracker); + searchResultsPage.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", token.metricsOptions.pageInformation, token.metricsOptions.locationTracker, token.requestMetadata.requestDescriptor.term); + searchResultsPage.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", token.metricsOptions.pageInformation, token.metricsOptions.locationTracker, token.requestMetadata.requestDescriptor.term); + return searchResultsPage; + }); + }); + }); +} +// region Extracting Data +/** + * Returns the array of data objects to build search results with. + */ +function dataForSearchResultsFromCombinedData(objectGraph, combinedSearchData) { + return mediaDataStructure.dataCollectionFromDataContainer(combinedSearchData.catalogResponse.results.search); +} +/** + * Returns the array of data objects to build search adverts with. + */ +function dataForAdvertsFromCombinedData(objectGraph, combinedSearchData) { + const rawAdvertsData = mediaDataStructure.dataCollectionFromDataContainer(combinedSearchData.catalogResponse.results["ads-result"]); + return searchAdsODML.applyNativeAdvertData(objectGraph, rawAdvertsData, combinedSearchData.sponsoredSearchAdvertData); +} +// endregion +// region Guided Search +/** + * Add models for Guided Search into `SearchResults` model, specifically: + * - `GuidedSearchToken`s + * - `GuidedSearchQuery`s + * - Metrics Container for tokens. + */ +function addModelsForGuidedSearch(objectGraph, searchResultsPage, requestMetadata, facetData, metricsOptions) { + if (!objectGraph.host.isiOS) { + return; + } + const request = requestMetadata.requestDescriptor; + metricsHelpersLocation.pushBasicLocation(objectGraph, { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + targetType: "SearchRevisions", + }, ""); + const tokens = []; + // Tokens from facet data, if any + if (serverData.isDefinedNonNullNonEmpty(facetData)) { + for (const data of facetData) { + const token = guidedSearch.createGuidedSearchToken(objectGraph, "toggle", request, data, metricsOptions); + if (token) { + tokens.push(token); + metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); + } + } + } + // Token from selected entity hint, iff there aren't tokens already + if (serverData.isNullOrEmpty(tokens) && requestMetadata.requestDescriptor.searchEntity) { + const entityClearingToken = guidedSearch.createGuidedSearchTokenClearingEntityFilter(objectGraph, requestMetadata.requestDescriptor, metricsOptions); + tokens.push(entityClearingToken); + metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); + } + const queries = guidedSearch.createGuidedSearchQueries(objectGraph, requestMetadata.requestDescriptor, facetData); + metricsHelpersLocation.popLocation(metricsOptions.locationTracker); + if (serverData.isDefinedNonNullNonEmpty(tokens)) { + searchResultsPage.guidedSearchTokens = tokens; + searchResultsPage.guidedSearchQueries = queries; + addGuidedSearchParentImpressionMetrics(objectGraph, searchResultsPage, metricsOptions); + metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); // increment **after** assigning guided token parent impression. + } +} +/// The contextual reason for the segment change +var SegmentChangeReason; +(function (SegmentChangeReason) { + SegmentChangeReason[SegmentChangeReason["EmptyResults"] = 0] = "EmptyResults"; + SegmentChangeReason[SegmentChangeReason["Picker"] = 1] = "Picker"; + SegmentChangeReason[SegmentChangeReason["NonNative"] = 2] = "NonNative"; +})(SegmentChangeReason || (SegmentChangeReason = {})); +/** + * Creates a segment change action for moving from one segment to another for a specific reason + * @param objectGraph The App Store object graph + * @param reason The reason why the segment is changing + * @param pageId The search results page id + * @param metricsOptions The metrics options for the segment this action will attach to + * @param switchingToGroupId The groupId of the segment we are switching to + * @param switchingFromGroupId The groupId of the segment we are switching from (the current segment) + * @returns A segment change action for the specific context and locations + */ +function segmentChangeAction(objectGraph, reason, pageId, metricsOptions, switchingToGroupId, switchingFromGroupId) { + const includeAppsSuffix = reason !== SegmentChangeReason.Picker; + const switchingToSegmentTitle = segmentTitleForSegmentType(objectGraph, includeAppsSuffix, segmentTypeFromGroupId(switchingToGroupId)); + let action; + switch (reason) { + case SegmentChangeReason.EmptyResults: + if (isNothing(switchingToSegmentTitle) || switchingFromGroupId === switchingToGroupId) { + return undefined; + } + metricsHelpersLocation.pushBasicLocation(objectGraph, { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + targetType: "SearchResults", + }, "emptyResultsSegmentSwitch"); + const emptyResultsTitle = objectGraph.loc + .string("SEARCH_RESULTS_SWITCH_TO_OTHER_RESULTS") + .replace("{platformApps}", switchingToSegmentTitle); + action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle, new models.StyledText(emptyResultsTitle)); + metricsHelpersClicks.addClickEventToSearchPageSegmentChangeAction(objectGraph, action, "button", metricsOptions.locationTracker); + metricsHelpersLocation.popLocation(metricsOptions.locationTracker); + break; + case SegmentChangeReason.Picker: + if (isNothing(switchingToSegmentTitle)) { + return undefined; + } + metricsHelpersLocation.pushBasicLocation(objectGraph, { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + targetType: "SearchResults", + }, "searchResultsSegmentSwitch"); + action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle); + metricsHelpersClicks.addClickEventToSearchPageSegmentChangeAction(objectGraph, action, "button", metricsOptions.locationTracker); + metricsHelpersLocation.popLocation(metricsOptions.locationTracker); + break; + case SegmentChangeReason.NonNative: + const currentSegmentType = segmentTypeFromGroupId(switchingFromGroupId); + if (currentSegmentType === segmentTypeForPlatform(objectGraph)) { + return undefined; + } + const switchingFromSegmentTitle = segmentTitleForSegmentType(objectGraph, true, segmentTypeFromGroupId(switchingFromGroupId)); + const nonNativeTitle = objectGraph.loc + .string("Search.Results.ShowingNonNativeResults") + .replace("@@current_platform_apps@@", switchingFromSegmentTitle) + .replace("@@native_platform_apps@@", switchingToSegmentTitle); + metricsHelpersLocation.pushBasicLocation(objectGraph, { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + targetType: "SearchResults", + }, "nonNativeResultsSegmentSwitch"); + action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle, new models.StyledText(nonNativeTitle, "text/x-apple-as3-nqml")); + metricsHelpersLocation.popLocation(metricsOptions.locationTracker); + break; + default: + break; + } + const clickActionOptions = { + actionType: "navigate", + id: pageId, + targetType: "button", + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + }; + metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickActionOptions); + return action; +} +/** + * Gets the segment type for the given group id + * @param groupId The group id of the segment + * @returns The segment type for the group id + */ +function segmentTypeFromGroupId(groupId) { + if (isSome(groupId)) { + switch (groupId) { + case "iOS": + case "ios": + return models.SegmentedSearchResultsPageSegmentType.iOS; + case "xrOS": + case "xros": + return models.SegmentedSearchResultsPageSegmentType.visionOS; + default: + return undefined; + } + } + return undefined; +} +/** + * Gets the segment type for the current platform + * @param objectGraph The App Store object graph + * @returns The segment type for the current platform + */ +function segmentTypeForPlatform(objectGraph) { + switch (objectGraph.client.deviceType) { + case "vision": + return models.SegmentedSearchResultsPageSegmentType.visionOS; + case "pad": + case "phone": + return models.SegmentedSearchResultsPageSegmentType.iOS; + default: + return undefined; + } +} +/** + * Gets the segment title for the given segment type + * @param objectGraph The App Store object graph + * @param includeAppsSuffix Whether to include the word "Apps" at the end of the title + * @param segmentType The segment type we want the title of + * @returns The segment title for the segment type + */ +function segmentTitleForSegmentType(objectGraph, includeAppsSuffix, segmentType) { + switch (segmentType) { + case models.SegmentedSearchResultsPageSegmentType.visionOS: + return includeAppsSuffix + ? objectGraph.loc.string("SEARCH_RESULTS_VISION_APPS_TITLE") + : objectGraph.loc.string("SEARCH_RESULTS_VISION_TITLE"); + case models.SegmentedSearchResultsPageSegmentType.iOS: + return includeAppsSuffix + ? objectGraph.loc.string("SEARCH_RESULTS_IPHONE_IPAD_APPS_TITLE") + : objectGraph.loc.string("SEARCH_RESULTS_IPHONE_IPAD_TITLE"); + default: + return undefined; + } +} +/** + * Gets the group id for the current platform + * @param objectGraph The App Store object graph + * @param segments The segments on the search results page + * @returns The group id for the current platform + */ +function segmentGroupIdForPlatform(objectGraph, segments) { + const nativeSegmentType = segmentTypeForPlatform(objectGraph); + const nativeSegment = segments.find((segment) => { + return segmentTypeFromGroupId(segment.groupId) === nativeSegmentType; + }); + return nativeSegment === null || nativeSegment === void 0 ? void 0 : nativeSegment.groupId; +} +/** + * + * @param objectGraph The App Store Object Graph + * @param searchClearActionType The way the user cleared the search (x in search field or cancel button in toolbar) + * @param pageInformation The metrics page information + * @param locationTracker The metrics location tracker + * @param searchTerm The current search term + * @returns An action to trigger when the search is cancelled or cleared + */ +export function createSearchCancelledOrClearedAction(objectGraph, searchClearActionType, pageInformation, locationTracker, searchTerm) { + const action = new models.BlankAction(); + let type; + let targetId; + switch (searchClearActionType) { + case "cancel": + type = "dismiss"; + targetId = "cancel"; + break; + case "clear": + type = "delete"; + targetId = "clear"; + break; + default: + break; + } + metricsHelpersClicks.addClickEventToSearchCancelOrDismissAction(objectGraph, action, { + targetType: "button", + id: targetId, + idType: undefined, + actionType: type, + pageInformation: pageInformation, + locationTracker: locationTracker, + }, "button", searchTerm); + return action; +} +//# sourceMappingURL=search.js.map
\ No newline at end of file |
