/** * Common for top-charts. * Incrementally migrate `top-charts-controller` builder logic here. */ import { isSome, isNothing } from "@jet/environment/types/optional"; import * as categories from "../common/categories"; import { isDefinedNonNullNonEmpty } from "../foundation/json-parsing/server-data"; import { attributeAsString } from "../foundation/media/attributes"; import { fetchData } from "../foundation/media/network"; import { Parameters } from "../foundation/network/url-constants"; import { URL } from "../foundation/network/urls"; import * as client from "../foundation/wrappers/client"; import { mediaApiChartRequestForGenre } from "./builders/url-mapping"; // region API - URLs /** * Given a chart type, lookup the most appropriate chart to show next to it using * fallback mapping logic, e.g. `top-paid` for `top-free` and visa versa. * * MAINTAINERS NOTE: This is typically driven by Editorial's programming but this can be used as a fallback. * * @param objectGraph The App Store object graph. * @param chartType The type of chart to lookup a sibling for, e.g. `top-free`. * @returns The sibling of the provided chart type, e.g. `top-paid`. */ export function lookupFallbackSiblingChart(objectGraph, chartType) { // Vision currently doesn't support sibling charts - don't attempt to find one. if (isNothing(chartType) || objectGraph.client.isVision) { return undefined; } switch (chartType) { case "top-free" /* TopChartType.TopFree */: return "top-paid" /* TopChartType.TopPaid */; case "top-paid" /* TopChartType.TopPaid */: return "top-free" /* TopChartType.TopFree */; /* ** MAINTAINERS NOTE ** We don't want to show "Top Apps" / "Paid Apps" for extensions, trending, or emerging, so don't map these together until we can confirm the desired UX we want for this. case TopChartType.TopFreeSafariExtensions: return TopChartType.TopPaidSafariExtensions; case TopChartType.TopPaidSafariExtensions: return TopChartType.TopFreeSafariExtensions; case TopChartType.TopFreeTrending: return TopChartType.TopPaidTrending; case TopChartType.TopPaidTrending: return TopChartType.TopFreeTrending; case TopChartType.TopFreeEmerging: return TopChartType.TopPaidEmerging; case TopChartType.TopPaidEmerging: return TopChartType.TopFreeEmerging; */ default: return undefined; } } /** * Creates a charts parameter value for an invidual chart shelf's see all flow action URL, which may show multiple charts. * * ** MAINTAINERS NOTE ** * This function guarantees the selected chart is always included in charts. * This helps make the user experience more resilient to either unexpected editorial programming or client JS programmer error. * For example, if provided chart set is missing the selected chart or is missing any siblings, we lookup hardcoded fallback * mappings, e.g. `top-paid` for `top-free`. * * @param objectGraph The App Store object graph. * @param selectedChartType The selected chart, e.g. `top-free`. * @param chartSet The collection fo charts to be shown as sibligs, e.g. `top-free` and `top-paid` in a segmented control * @returns Value for charts URL query paramater. */ function makeChartsParameterValue(objectGraph, selectedChartType, chartSet) { var _a; const hasMultipleCharts = (_a = (chartSet === null || chartSet === void 0 ? void 0 : chartSet.editorialCharts.length) > 1) !== null && _a !== void 0 ? _a : false; const charts = chartSet === null || chartSet === void 0 ? void 0 : chartSet.editorialCharts.map((chart) => chart.type.toString()).join(","); // use editorially programmed chart set if defined with multiple charts, including the selection. if (isSome(charts) && charts.includes(selectedChartType) && hasMultipleCharts) { return charts; } // otherwise, lookup hardcoded fallback sibling if available, e.g. `top-paid` for `top-free`. const siblingChartType = lookupFallbackSiblingChart(objectGraph, selectedChartType); if (siblingChartType === undefined) { return selectedChartType; // no sibling found, show just the single selected chart } return [selectedChartType, siblingChartType].join(","); // show harcoded pair of charts } /** * Construct the URL for a single chart shelf "See All", that might be part of an editorial chart set. * @param objectGraph The App Store object graph. * @param chartData The media API data for the chart shelf, e.g. `top-paid`. * @param chartSet The set of charts shown when tapping "See All", e.g. `top-free` and `top-paid`. */ export function makeSeeAllUrlFromMediaApiData(objectGraph, chartData, chartSet) { const baseUrl = attributeAsString(chartData, "chartHref"); const chartSeeAllUrl = new URL(baseUrl); /** * The selected chart to show, e.g. `top-free`. */ const selectedChart = attributeAsString(chartData, "chart"); if (isDefinedNonNullNonEmpty(selectedChart)) { chartSeeAllUrl.param(Parameters.chart, selectedChart); } /** * The collection of charts to show, e.g. `top-free` and `top-paid`. * This is needed since a chart sets are vended down as a single fcKind item with children that is divided into multiple shelves. * Tapping "See All" on a chart shelf, should show a top charts page with all `charts` provided and `chart` selected. */ const charts = makeChartsParameterValue(objectGraph, selectedChart, chartSet); if (isDefinedNonNullNonEmpty(charts)) { chartSeeAllUrl.param(Parameters.charts, charts); } return chartSeeAllUrl.toString(); } /** * Returns whether or not given chart page URL charts parameter is for safari extensions. * Charts for safari extensions must be handled as a special case since they don't have an associated specific genre. * * @param url The URL to check if it is Safari Extension charts. */ function isSafariExtensionCharts(charts) { return (isDefinedNonNullNonEmpty(charts) && (charts.indexOf("top-free-safari-extensions" /* TopChartType.TopFreeSafariExtensions */) !== -1 || charts.indexOf("top-paid-safari-extensions" /* TopChartType.TopPaidSafariExtensions */) !== -1)); } // endregion API // region MAPI Requests /** * Fetch the top-level structural data for top charts. * * @param objectGraph The App Store object graph. * @param genreId The genre to fetch charts for, e.g. Apps or Games. * @param charts The specific charts in this genre. */ export async function fetchTopChartsData(objectGraph, genreId, charts, ages = null) { // Request const topChartsRequest = mediaApiChartRequestForGenre(objectGraph, genreId, charts, ages); // Pagination + Sparse limits topChartsRequest.withLimit(200); if (objectGraph.client.isMac || objectGraph.client.isWeb) { topChartsRequest.withSparseLimit(25); } topChartsRequest.enablingFeature("newChartsElements"); return await fetchData(objectGraph, topChartsRequest); } /** * Fetch the categories data promise accompanying given `chartUrl`. * * @param objectGraph Object graph * @param charts The charts to fetch categories data for, e.g. `top-free,top-paid` * @param genreId The genre for chart to fetch categories data for, e.g. Apps or Games */ export async function fetchCategoriesDataForPlatform(objectGraph, charts, genreId) { let categoriesRequestPromise; /** * No categories filter on: * - All tvOS charts * - All Messages AppStore charts * - Safari Extension Charts (Genre-less Genre) */ if (objectGraph.client.isTV || objectGraph.host.clientIdentifier === client.messagesIdentifier || isSafariExtensionCharts(charts)) { categoriesRequestPromise = Promise.resolve({}); } else { const categoriesRequest = categories.createRequest(objectGraph, genreId); categoriesRequestPromise = fetchData(objectGraph, categoriesRequest).catch(() => undefined); } return await categoriesRequestPromise; } // endregion // region Page Title /** * Return the top charts page title to use for a collection of top chart segments. * * @param objectGraph Object graph * @param segments The segments to derive page title for */ export function topChartsPageTitleForSegments(objectGraph, segments) { const everySegmentIsTrending = segments.every((seg) => isTrendingChartType(seg.chart)); if (everySegmentIsTrending) { return objectGraph.loc.string("PAGE_TITLE_TRENDING_CHARTS"); } const everySegmentIsEmerging = segments.every((seg) => isEmergingChartType(seg.chart)); if (everySegmentIsEmerging) { return objectGraph.loc.string("PAGE_TITLE_EMERGING_CHARTS"); } return objectGraph.loc.string("PAGE_TITLE_TOP_CHARTS"); } // endregion // region Top Charts Type /// Whether or not given chart type is trending chart type function isTrendingChartType(chart) { switch (chart) { case "top-trending-free" /* TopChartType.TopFreeTrending */: case "top-trending-paid" /* TopChartType.TopPaidTrending */: return true; default: return false; } } /// Whether or not given chart type is emerging chart type function isEmergingChartType(chart) { switch (chart) { case "top-emerging-free" /* TopChartType.TopFreeEmerging */: case "top-emerging-paid" /* TopChartType.TopPaidEmerging */: return true; default: return false; } } // endregion //# sourceMappingURL=top-charts-common.js.map