1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
|
import { isSome } from "@jet/environment";
import * as validation from "@jet/environment/json/validation";
import * as models 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 artworkBuilder from "../content/artwork/artwork";
import * as metricsHelpersClicks from "../metrics/helpers/clicks";
import * as types from "./grouping-types";
import { GroupingAppEventShelfController } from "./shelf-controllers/grouping-app-event-shelf-controller";
import { GroupingArcadeFooterShelfController } from "./shelf-controllers/grouping-arcade-footer-shelf-controller";
import { GroupingBrickShelfController } from "./shelf-controllers/grouping-brick-shelf-controller";
import { GroupingRibbonBarShelfController } from "./shelf-controllers/grouping-ribbon-bar-shelf-controller";
import { GroupingCategoryShelfController } from "./shelf-controllers/grouping-category-shelf-controller";
import { GroupingEditorialCardShelfController } from "./shelf-controllers/grouping-editorial-card-shelf-controller";
import { GroupingEditorialStoryCardShelfController } from "./shelf-controllers/grouping-editorial-story-card-shelf-controller";
import { GroupingGameCenterActivityFeedController } from "./shelf-controllers/grouping-game-center-activity-feed-shelf-controller";
import { GroupingGameCenterContinuePlayingShelfController } from "./shelf-controllers/grouping-game-center-continue-playing-shelf-controller";
import { GroupingGameCenterPopularWithYourFriendsController } from "./shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller";
import { GroupingGameCenterReengagementShelfController } from "./shelf-controllers/grouping-game-center-reengagement-shelf-controller";
import { GroupingGameCenterSuggestedFriendsController } from "./shelf-controllers/grouping-game-center-suggested-friends-shelf-controller";
import { GroupingHeroCarouselShelfController } from "./shelf-controllers/grouping-hero-carousel-shelf-controller";
import { GroupingHorizontalCardShelfController } from "./shelf-controllers/grouping-horizontal-card-shelf-controller";
import { GroupingLargeBreakoutShelfController } from "./shelf-controllers/grouping-large-breakout-shelf-controller";
import { GroupingLinkShelfController } from "./shelf-controllers/grouping-link-shelf-controller";
import { GroupingLockupShelfController } from "./shelf-controllers/grouping-lockup-shelf-controller";
import { GroupingPersonalizedLockupShelfController } from "./shelf-controllers/grouping-personalized-lockup-shelf-controller";
import { createShelfAccessibilityMetadata, } from "./shelf-controllers/grouping-shelf-controller";
import * as groupingShelfControllerCommon from "./shelf-controllers/grouping-shelf-controller-common";
import { GroupingSmallBreakoutShelfController } from "./shelf-controllers/grouping-small-breakout-shelf-controller";
import { ArcadeDownloadPackShelfController } from "./shelf-controllers/arcade-download-pack-shelf-controller";
import { flowActionForAccountURL } from "../account/account-links-regex-parser";
import { GroupingMediaPageHeaderShelfController } from "./shelf-controllers/grouping-tags-header-shelf-controller";
import { applySearchAdMissedOpportunityToShelvesIfNeeded } from "../ads/ad-common";
import { GroupingTagBrickShelfController } from "./shelf-controllers/grouping-tag-brick-shelf-controller";
// region Constants
/// The controllers that know how to render specific shelves
const shelfControllers = [
new GroupingAppEventShelfController(),
new GroupingArcadeFooterShelfController(),
new GroupingBrickShelfController(),
new GroupingRibbonBarShelfController(),
new GroupingCategoryShelfController(),
new GroupingEditorialCardShelfController(),
new GroupingEditorialStoryCardShelfController(),
new GroupingGameCenterContinuePlayingShelfController(),
new GroupingGameCenterPopularWithYourFriendsController(),
new GroupingGameCenterReengagementShelfController(),
new GroupingGameCenterSuggestedFriendsController(),
new GroupingGameCenterActivityFeedController(),
new GroupingHeroCarouselShelfController(),
new GroupingHorizontalCardShelfController(),
new GroupingLargeBreakoutShelfController(),
new GroupingLinkShelfController(),
new GroupingLockupShelfController(),
new GroupingPersonalizedLockupShelfController(),
new GroupingSmallBreakoutShelfController(),
new ArcadeDownloadPackShelfController(),
new GroupingMediaPageHeaderShelfController(),
new GroupingTagBrickShelfController(),
];
/**
* For a given flattened grouping go through and use our controllers to render the associated shelves, this should be used
* for the initial page load. The second fetches for these shelves are handled by the routing system and the routed
* shelf controller. This method will add all the shelves to the current groupingParseContext.
*
* @param objectGraph The App Store dependency graph
* @param flattenedGrouping The original grouping page response data from MAPI flattened into the list of featured content
* @param groupingParseContext The current parse context for this rendering of the grouping.
* @protected
*/
export function insertInitialShelvesIntoGroupingParseContext(objectGraph, flattenedGrouping, groupingParseContext) {
validation.catchingContext(`parseGrouping`, () => {
for (const mediaApiData of flattenedGrouping.data) {
const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
if (types.isTopChart(featuredContentId)) {
const editorialId = serverData.asString(mediaApiData, "id");
groupingParseContext.chartSet = lookupChartSetForEditorialId(editorialId, flattenedGrouping.editorialChartSets);
}
else {
groupingParseContext.chartSet = null;
}
const baseRequirements = groupingShelfControllerCommon.createBaseShelfRequirements(objectGraph, mediaApiData, groupingParseContext);
const token = baseRequirements.shelfToken;
const baseMetricsOptions = baseRequirements.metricsOptions;
let shelf;
for (const shelfController of shelfControllers) {
if (shelfController.supports(objectGraph, mediaApiData, featuredContentId)) {
shelf = validation.catchingContext(`parseGroupingShelf`, () => {
return shelfController.createShelf(objectGraph, mediaApiData, groupingParseContext, token, baseMetricsOptions);
});
break;
}
}
if (serverData.isDefinedNonNull(shelf)) {
groupingParseContext.shelves.push(shelf);
applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, groupingParseContext.shelves, "searchLanding", baseMetricsOptions.id, groupingParseContext.metricsPageInformation);
}
}
});
}
/**
* Parse a single editorially programmed chart from media API data.
* @returns The chart model or null.
*/
function makeEditorialChartFromMediaApiData(chartData) {
const fcKind = mediaAttributes.attributeAsNumber(chartData, "editorialElementKind");
if (!types.isTopChart(fcKind)) {
return null;
}
return {
id: serverData.asString(chartData, "id"),
href: mediaAttributes.attributeAsString(chartData, "chartHref"),
name: mediaAttributes.attributeAsString(chartData, "name"),
type: mediaAttributes.attributeAsString(chartData, "chart"),
};
}
/**
* Parse a collection of charts, programmed by editorial, from media api data.
* @returns The chart set model, or null.
*/
function makeEditorialChartSetFromMediaApiData(chartSetData) {
const fcKind = mediaAttributes.attributeAsNumber(chartSetData, "editorialElementKind");
if (fcKind !== 424 /* types.FeaturedContentID.AppStore_ChartSet */) {
return null;
}
return {
id: serverData.asString(chartSetData, "id"),
name: mediaAttributes.attributeAsString(chartSetData, "name"),
editorialCharts: mediaRelationship
.relationshipCollection(chartSetData, "children")
.map((chartData) => makeEditorialChartFromMediaApiData(chartData))
.filter((chart) => isSome(chart)),
};
}
/**
* Given a list of chart sets, find the chart set that has the provided editorial identifier.
*
* @param editorialId The chart or chart set identifier from editorial.
* @param chartSets The list of all chart sets from the grouping.
* @returns The charts associated with the editorial item from the grouping.
*/
function lookupChartSetForEditorialId(editorialId, editorialChartSets) {
for (const chartSet of editorialChartSets) {
if (chartSet.id === editorialId) {
return chartSet; // editorialId is for a chart set itself
}
for (const chart of chartSet.editorialCharts) {
if (chart.id === editorialId) {
return chartSet; // / editorialId is for a specific chart from the chart set.
}
}
}
return null;
}
/**
* Take the media API data for a single grouping, and flatten that tree structure into a single list of media api data objects.
*
* @param mediaApiGroupingData The original media API data for a single grouping
*/
export function flattenMediaApiGroupingData(objectGraph, mediaApiGroupingData) {
const groupingData = [];
const chartSets = [];
function addMediaApiDataToGroupingData(mediaApiData) {
const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
validation.catchingContext(`flattenGroupingTree.addMediaApiDataToGroupingData: ${featuredContentId}`, () => {
const groupingTabData = mediaRelationship.relationshipData(objectGraph, mediaApiData, "tabs");
if (serverData.isDefinedNonNull(groupingTabData)) {
addMediaApiDataToGroupingData(groupingTabData);
}
else if (types.shouldContinueToWalkChildren(featuredContentId)) {
const featuredContentChildren = mediaRelationship.relationshipCollection(mediaApiData, "children");
for (const featuredContentChild of featuredContentChildren) {
addMediaApiDataToGroupingData(featuredContentChild);
}
}
else if (featuredContentId === 424 /* types.FeaturedContentID.AppStore_ChartSet */) {
// Keep track of chartSets programmed by editorial, e.g. "Top Charts" includes "Top Free Apps" and "Top Paid Apps".
const editorialChartSet = makeEditorialChartSetFromMediaApiData(mediaApiData);
if (isSome(editorialChartSet)) {
chartSets.push(editorialChartSet);
}
// Add charts from the chartSet, as defined by editorial, to grouping page data.
const chartSetChildren = mediaRelationship.relationshipCollection(mediaApiData, "children");
for (const chartSetChild of chartSetChildren) {
addMediaApiDataToGroupingData(chartSetChild);
}
}
else {
groupingData.push(mediaApiData);
}
});
}
addMediaApiDataToGroupingData(mediaApiGroupingData);
return {
data: groupingData,
editorialChartSets: chartSets,
originalGroupingData: mediaApiGroupingData,
};
}
// endregion
// region Shelves
/**
* Create a shelf for footer buttons, e.g "Send Gift" and "Redeem".
*
* @return Shelf configured to display `FooterButtons`s.
*/
export function shelfForFooterButtons(objectGraph, metricsPageInformation, metricsLocationTracker) {
var _a, _b, _c, _d, _e, _f;
if (objectGraph.user && objectGraph.user.isManagedAppleID) {
return null;
}
// Watch App Store does not support titled button stacks,
// and has different footer buttons.
if (objectGraph.client.isWatch) {
const shelf = new models.Shelf("action");
const items = [];
const accountFlowAction = new models.FlowAction("account");
accountFlowAction.title = objectGraph.loc.string("ACCOUNT", "Account");
accountFlowAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://person.crop.circle");
const accountFlowClickOptions = {
targetType: "button",
id: "account",
pageInformation: metricsPageInformation,
locationTracker: metricsLocationTracker,
};
metricsHelpersClicks.addClickEventToAction(objectGraph, accountFlowAction, accountFlowClickOptions);
items.push(accountFlowAction);
shelf.items = items;
shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf);
return shelf;
}
else {
const items = [];
const redeemTitle = objectGraph.loc.string("FOOTER_REDEEM", "Redeem");
const redeemActionUrl = objectGraph.client.isVision
? objectGraph.bag.redeemCodeLanding
: objectGraph.bag.redeemUrl;
const redeemFlowAction = flowActionForAccountURL(objectGraph, redeemActionUrl);
const redeemButton = new models.TitledButton(redeemTitle, redeemFlowAction);
redeemButton.id = "redeem";
items.push(redeemButton);
if (objectGraph.bag.isMonetaryGiftingEnabled) {
const giftTitle = objectGraph.loc.string("FOOTER_SEND_GIFT", "Send Gift");
const giftActionUrl = "gift";
const giftFlowAction = flowActionForAccountURL(objectGraph, giftActionUrl);
const giftButton = new models.TitledButton(giftTitle, giftFlowAction);
giftButton.id = "gift";
items.push(giftButton);
}
const topUpUrl = objectGraph.bag.accountTopUpURL;
if (isSome(topUpUrl)) {
const topUpTitle = (_a = objectGraph.bag.accountTopUpTitle) !== null && _a !== void 0 ? _a : objectGraph.loc.string("FOOTER_ADD_MONEY");
const topUpFlowAction = flowActionForAccountURL(objectGraph, topUpUrl);
const topUpButton = new models.TitledButton(topUpTitle, topUpFlowAction);
topUpButton.id = "topUp";
items.push(topUpButton);
}
let shelf;
if (objectGraph.client.isVision) {
shelf = new models.Shelf("titledButton");
shelf.items = items;
}
else {
shelf = new models.Shelf("titledButtonStack");
const stack = new models.TitledButtonStack(items);
// On compact width, we want to place a line break after each button.
stack.compactLineBreaks = stack.buttons.map((value, index) => index);
shelf.items = [stack];
const accessibilityLabel = objectGraph.loc.string("Shelves.Accessibility.Label");
const roleDescription = objectGraph.loc.string("Shelves.Accessibility.RoleDescription");
shelf.accessibilityMetadata = {
label: (_d = (_b = shelf.title) !== null && _b !== void 0 ? _b : (_c = shelf.header) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : accessibilityLabel,
roleDescription: roleDescription.replace("%d", `${(_f = (_e = stack.buttons) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0}`),
};
}
return shelf;
}
}
/**
* Create a T&C shelf that on click opens an external url.
*
* @param objectGraph
* @param destinationUrl Url to open when terms and conditions is tapped.
* @param hasSeparator Whether the footnote has a separator above it.
* @return Shelf configured to display Terms and Conditions.
*/
export function shelfForTermsAndConditions(objectGraph, destinationUrl, hasSeparator = true) {
const urlAction = new models.ExternalUrlAction(destinationUrl);
const termsAndConditionTitle = objectGraph.loc.string("TermsAndConditions.Title");
const footnote = new models.Footnote(termsAndConditionTitle);
footnote.clickAction = urlAction;
footnote.presentationStyle = ["hasChevron", "textLightensOnHighlight"];
if (hasSeparator) {
footnote.presentationStyle.push("hasSeparator");
}
const shelf = new models.Shelf("footnote");
shelf.items = [footnote];
if (objectGraph.bag.emailSupportLinkURL) {
const emailSupportAction = new models.ExternalUrlAction(objectGraph.bag.emailSupportLinkURL);
const emailSupport = new models.Footnote("Email Support");
emailSupport.clickAction = emailSupportAction;
emailSupport.presentationStyle = ["hasChevron", "textLightensOnHighlight"];
if (hasSeparator) {
emailSupport.presentationStyle.push("hasSeparator");
}
shelf.items.push(emailSupport);
}
return shelf;
}
export function shelfForUnifiedMessage(objectGraph, placement, context, deliveryMethod) {
const shelf = new models.Shelf("unifiedMessage");
shelf.id = placement;
shelf.items = [new models.UnifiedMessage(placement, context, deliveryMethod)];
shelf.isHidden = true;
return shelf;
}
// endregion
//# sourceMappingURL=grouping-common.js.map
|